QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
qgslayoutmodel.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgslayoutmodel.cpp
3  ------------------
4  begin : October 2017
5  copyright : (C) 2017 by Nyall Dawson
6  email : nyall dot dawson at gmail dot com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "qgslayoutmodel.h"
19 #include "qgslayout.h"
20 #include "qgsapplication.h"
21 #include "qgslogger.h"
22 #include "qgslayoutitemgroup.h"
23 #include <QApplication>
24 #include <QGraphicsItem>
25 #include <QDomDocument>
26 #include <QDomElement>
27 #include <QMimeData>
28 #include <QSettings>
29 #include <QMessageBox>
30 #include <QIcon>
31 
32 QgsLayoutModel::QgsLayoutModel( QgsLayout *layout, QObject *parent )
33  : QAbstractItemModel( parent )
34  , mLayout( layout )
35 {
36 
37 }
38 
40 {
41  //try to return the QgsLayoutItem corresponding to a QModelIndex
42  if ( !index.isValid() || index.row() == 0 )
43  {
44  return nullptr;
45  }
46 
47  QgsLayoutItem *item = static_cast<QgsLayoutItem *>( index.internalPointer() );
48  return item;
49 }
50 
51 QModelIndex QgsLayoutModel::index( int row, int column,
52  const QModelIndex &parent ) const
53 {
54  if ( column < 0 || column >= columnCount() )
55  {
56  //column out of bounds
57  return QModelIndex();
58  }
59 
60  if ( !parent.isValid() && row == 0 )
61  {
62  return createIndex( row, column, nullptr );
63  }
64  else if ( !parent.isValid() && row >= 1 && row < mItemsInScene.size() + 1 )
65  {
66  //return an index for the layout item at this position
67  return createIndex( row, column, mItemsInScene.at( row - 1 ) );
68  }
69 
70  //only top level supported for now
71  return QModelIndex();
72 }
73 
74 void QgsLayoutModel::refreshItemsInScene()
75 {
76  mItemsInScene.clear();
77 
78  const QList< QGraphicsItem * > items = mLayout->items();
79  //filter paper items from list
80  //TODO - correctly handle grouped item z order placement
81  for ( QgsLayoutItem *item : qgis::as_const( mItemZList ) )
82  {
83  if ( item->type() != QgsLayoutItemRegistry::LayoutPage && items.contains( item ) )
84  {
85  mItemsInScene.push_back( item );
86  }
87  }
88 }
89 
90 QModelIndex QgsLayoutModel::parent( const QModelIndex &index ) const
91 {
92  Q_UNUSED( index )
93 
94  //all items are top level for now
95  return QModelIndex();
96 }
97 
98 int QgsLayoutModel::rowCount( const QModelIndex &parent ) const
99 {
100  if ( !parent.isValid() )
101  {
102  return mItemsInScene.size() + 1;
103  }
104 
105  QGraphicsItem *parentItem = itemFromIndex( parent );
106 
107  if ( !parentItem )
108  {
109  return mItemsInScene.size() + 1;
110  }
111  else
112  {
113  //no children for now
114  return 0;
115  }
116 }
117 
118 int QgsLayoutModel::columnCount( const QModelIndex &parent ) const
119 {
120  Q_UNUSED( parent )
121  return 3;
122 }
123 
124 QVariant QgsLayoutModel::data( const QModelIndex &index, int role ) const
125 {
126  if ( !index.isValid() )
127  return QVariant();
128 
129  QgsLayoutItem *item = itemFromIndex( index );
130  if ( !item )
131  {
132  return QVariant();
133  }
134 
135  switch ( role )
136  {
137  case Qt::DisplayRole:
138  if ( index.column() == ItemId )
139  {
140  return item->displayName();
141  }
142  else
143  {
144  return QVariant();
145  }
146 
147  case Qt::DecorationRole:
148  if ( index.column() == ItemId )
149  {
150  return item->icon();
151  }
152  else
153  {
154  return QVariant();
155  }
156 
157  case Qt::EditRole:
158  if ( index.column() == ItemId )
159  {
160  return item->id();
161  }
162  else
163  {
164  return QVariant();
165  }
166 
167  case Qt::UserRole:
168  //store item uuid in userrole so we can later get the QModelIndex for a specific item
169  return item->uuid();
170  case Qt::UserRole+1:
171  //user role stores reference in column object
172  return qVariantFromValue( qobject_cast<QObject *>( item ) );
173 
174  case Qt::TextAlignmentRole:
175  return Qt::AlignLeft & Qt::AlignVCenter;
176 
177  case Qt::CheckStateRole:
178  switch ( index.column() )
179  {
180  case Visibility:
181  //column 0 is visibility of item
182  return item->isVisible() ? Qt::Checked : Qt::Unchecked;
183  case LockStatus:
184  //column 1 is locked state of item
185  return item->isLocked() ? Qt::Checked : Qt::Unchecked;
186  default:
187  return QVariant();
188  }
189 
190  default:
191  return QVariant();
192  }
193 }
194 
195 bool QgsLayoutModel::setData( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole )
196 {
197  Q_UNUSED( role )
198 
199  if ( !index.isValid() )
200  return false;
201 
202  QgsLayoutItem *item = itemFromIndex( index );
203  if ( !item )
204  {
205  return false;
206  }
207 
208  switch ( index.column() )
209  {
210  case Visibility:
211  //first column is item visibility
212  item->setVisibility( value.toBool() );
213  return true;
214 
215  case LockStatus:
216  //second column is item lock state
217  item->setLocked( value.toBool() );
218  return true;
219 
220  case ItemId:
221  //last column is item id
222  item->setId( value.toString() );
223  return true;
224  }
225 
226  return false;
227 }
228 
229 QVariant QgsLayoutModel::headerData( int section, Qt::Orientation orientation, int role ) const
230 {
231  switch ( role )
232  {
233  case Qt::DisplayRole:
234  {
235  if ( section == ItemId )
236  {
237  return tr( "Item" );
238  }
239  return QVariant();
240  }
241 
242  case Qt::DecorationRole:
243  {
244  if ( section == Visibility )
245  {
246  return qVariantFromValue( QgsApplication::getThemeIcon( QStringLiteral( "/mActionShowAllLayersGray.svg" ) ) );
247  }
248  else if ( section == LockStatus )
249  {
250  return qVariantFromValue( QgsApplication::getThemeIcon( QStringLiteral( "/lockedGray.svg" ) ) );
251  }
252 
253  return QVariant();
254  }
255 
256  case Qt::TextAlignmentRole:
257  return Qt::AlignLeft & Qt::AlignVCenter;
258 
259  default:
260  return QAbstractItemModel::headerData( section, orientation, role );
261  }
262 
263 }
264 
266 {
267  return Qt::MoveAction;
268 }
269 
270 QStringList QgsLayoutModel::mimeTypes() const
271 {
272  QStringList types;
273  types << QStringLiteral( "application/x-vnd.qgis.qgis.composeritemid" );
274  return types;
275 }
276 
277 QMimeData *QgsLayoutModel::mimeData( const QModelIndexList &indexes ) const
278 {
279  QMimeData *mimeData = new QMimeData();
280  QByteArray encodedData;
281 
282  QDataStream stream( &encodedData, QIODevice::WriteOnly );
283 
284  for ( const QModelIndex &index : indexes )
285  {
286  if ( index.isValid() && index.column() == ItemId )
287  {
288  QgsLayoutItem *item = itemFromIndex( index );
289  if ( !item )
290  {
291  continue;
292  }
293  QString text = item->uuid();
294  stream << text;
295  }
296  }
297 
298  mimeData->setData( QStringLiteral( "application/x-vnd.qgis.qgis.composeritemid" ), encodedData );
299  return mimeData;
300 }
301 
303 {
304  return item1->zValue() > item2->zValue();
305 }
306 
307 bool QgsLayoutModel::dropMimeData( const QMimeData *data,
308  Qt::DropAction action, int row, int column, const QModelIndex &parent )
309 {
310  if ( column != ItemId )
311  {
312  return false;
313  }
314 
315  if ( action == Qt::IgnoreAction )
316  {
317  return true;
318  }
319 
320  if ( !data->hasFormat( QStringLiteral( "application/x-vnd.qgis.qgis.composeritemid" ) ) )
321  {
322  return false;
323  }
324 
325  if ( parent.isValid() )
326  {
327  return false;
328  }
329 
330  int beginRow = row != -1 ? row : rowCount( QModelIndex() );
331 
332  QByteArray encodedData = data->data( QStringLiteral( "application/x-vnd.qgis.qgis.composeritemid" ) );
333  QDataStream stream( &encodedData, QIODevice::ReadOnly );
334  QList<QgsLayoutItem *> droppedItems;
335  int rows = 0;
336 
337  while ( !stream.atEnd() )
338  {
339  QString text;
340  stream >> text;
341  QgsLayoutItem *item = mLayout->itemByUuid( text );
342  if ( item )
343  {
344  droppedItems << item;
345  ++rows;
346  }
347  }
348 
349  if ( droppedItems.empty() )
350  {
351  //no dropped items
352  return false;
353  }
354 
355  //move dropped items
356 
357  //first sort them by z-order
358  std::sort( droppedItems.begin(), droppedItems.end(), zOrderDescending );
359 
360  //calculate position in z order list to drop items at
361  int destPos = 0;
362  if ( beginRow < rowCount() )
363  {
364  QgsLayoutItem *itemBefore = mItemsInScene.at( beginRow );
365  destPos = mItemZList.indexOf( itemBefore );
366  }
367  else
368  {
369  //place items at end
370  destPos = mItemZList.size();
371  }
372 
373  //calculate position to insert moved rows to
374  int insertPos = destPos;
375  for ( QgsLayoutItem *item : qgis::as_const( droppedItems ) )
376  {
377  int listPos = mItemZList.indexOf( item );
378  if ( listPos == -1 )
379  {
380  //should be impossible
381  continue;
382  }
383 
384  if ( listPos < destPos )
385  {
386  insertPos--;
387  }
388  }
389 
390  //remove rows from list
391  auto itemIt = droppedItems.begin();
392  for ( ; itemIt != droppedItems.end(); ++itemIt )
393  {
394  mItemZList.removeOne( *itemIt );
395  }
396 
397  //insert items
398  itemIt = droppedItems.begin();
399  for ( ; itemIt != droppedItems.end(); ++itemIt )
400  {
401  mItemZList.insert( insertPos, *itemIt );
402  insertPos++;
403  }
404 
405  rebuildSceneItemList();
406 
407  mLayout->updateZValues( true );
408 
409  return true;
410 }
411 
412 bool QgsLayoutModel::removeRows( int row, int count, const QModelIndex &parent )
413 {
414  Q_UNUSED( count )
415  if ( parent.isValid() )
416  {
417  return false;
418  }
419 
420  if ( row >= rowCount() )
421  {
422  return false;
423  }
424 
425  //do nothing - moves are handled by the dropMimeData method
426  return true;
427 }
428 
430 void QgsLayoutModel::clear()
431 {
432  //totally reset model
433  beginResetModel();
434  mItemZList.clear();
435  refreshItemsInScene();
436  endResetModel();
437 }
438 
439 int QgsLayoutModel::zOrderListSize() const
440 {
441  return mItemZList.size();
442 }
443 
444 void QgsLayoutModel::rebuildZList()
445 {
446  QList<QgsLayoutItem *> sortedList;
447  //rebuild the item z order list based on the current zValues of items in the scene
448 
449  //get items in descending zValue order
450  const QList<QGraphicsItem *> itemList = mLayout->items( Qt::DescendingOrder );
451  for ( QGraphicsItem *item : itemList )
452  {
453  if ( QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item ) )
454  {
455  if ( layoutItem->type() != QgsLayoutItemRegistry::LayoutPage )
456  {
457  sortedList.append( layoutItem );
458  }
459  }
460  }
461 
462  mItemZList = sortedList;
463  rebuildSceneItemList();
464 }
466 
467 void QgsLayoutModel::rebuildSceneItemList()
468 {
469  //step through the z list and rebuild the items in scene list,
470  //emitting signals as required
471  int row = 0;
472  const QList< QGraphicsItem * > items = mLayout->items();
473  for ( QgsLayoutItem *item : qgis::as_const( mItemZList ) )
474  {
475  if ( item->type() == QgsLayoutItemRegistry::LayoutPage || !items.contains( item ) )
476  {
477  //item not in scene, skip it
478  continue;
479  }
480 
481  int sceneListPos = mItemsInScene.indexOf( item );
482  if ( sceneListPos == row )
483  {
484  //already in list in correct position, nothing to do
485 
486  }
487  else if ( sceneListPos != -1 )
488  {
489  //in list, but in wrong spot
490  beginMoveRows( QModelIndex(), sceneListPos + 1, sceneListPos + 1, QModelIndex(), row + 1 );
491  mItemsInScene.removeAt( sceneListPos );
492  mItemsInScene.insert( row, item );
493  endMoveRows();
494  }
495  else
496  {
497  //needs to be inserted into list
498  beginInsertRows( QModelIndex(), row + 1, row + 1 );
499  mItemsInScene.insert( row, item );
500  endInsertRows();
501  }
502  row++;
503  }
504 }
506 void QgsLayoutModel::addItemAtTop( QgsLayoutItem *item )
507 {
508  mItemZList.push_front( item );
509  refreshItemsInScene();
510  item->setZValue( mItemZList.size() );
511 }
512 
513 void QgsLayoutModel::removeItem( QgsLayoutItem *item )
514 {
515  if ( !item )
516  {
517  //nothing to do
518  return;
519  }
520 
521  int pos = mItemZList.indexOf( item );
522  if ( pos == -1 )
523  {
524  //item not in z list, nothing to do
525  return;
526  }
527 
528  //need to get QModelIndex of item
529  QModelIndex itemIndex = indexForItem( item );
530  if ( !itemIndex.isValid() )
531  {
532  //removing an item not in the scene (e.g., deleted item)
533  //we need to remove it from the list, but don't need to call
534  //beginRemoveRows or endRemoveRows since the item was not used by the model
535  mItemZList.removeAt( pos );
536  refreshItemsInScene();
537  return;
538  }
539 
540  //remove item from model
541  int row = itemIndex.row();
542  beginRemoveRows( QModelIndex(), row, row );
543  mItemZList.removeAt( pos );
544  refreshItemsInScene();
545  endRemoveRows();
546 }
547 
548 void QgsLayoutModel::setItemRemoved( QgsLayoutItem *item )
549 {
550  if ( !item )
551  {
552  //nothing to do
553  return;
554  }
555 
556  int pos = mItemZList.indexOf( item );
557  if ( pos == -1 )
558  {
559  //item not in z list, nothing to do
560  return;
561  }
562 
563  //need to get QModelIndex of item
564  QModelIndex itemIndex = indexForItem( item );
565  if ( !itemIndex.isValid() )
566  {
567  return;
568  }
569 
570  //removing item
571  int row = itemIndex.row();
572  beginRemoveRows( QModelIndex(), row, row );
573  mLayout->removeItem( item );
574  refreshItemsInScene();
575  endRemoveRows();
576 }
577 
578 void QgsLayoutModel::updateItemDisplayName( QgsLayoutItem *item )
579 {
580  if ( !item )
581  {
582  //nothing to do
583  return;
584  }
585 
586  //need to get QModelIndex of item
587  QModelIndex itemIndex = indexForItem( item, ItemId );
588  if ( !itemIndex.isValid() )
589  {
590  return;
591  }
592 
593  //emit signal for item id change
594  emit dataChanged( itemIndex, itemIndex );
595 }
596 
597 void QgsLayoutModel::updateItemLockStatus( QgsLayoutItem *item )
598 {
599  if ( !item )
600  {
601  //nothing to do
602  return;
603  }
604 
605  //need to get QModelIndex of item
606  QModelIndex itemIndex = indexForItem( item, LockStatus );
607  if ( !itemIndex.isValid() )
608  {
609  return;
610  }
611 
612  //emit signal for item lock status change
613  emit dataChanged( itemIndex, itemIndex );
614 }
615 
616 void QgsLayoutModel::updateItemVisibility( QgsLayoutItem *item )
617 {
618  if ( !item )
619  {
620  //nothing to do
621  return;
622  }
623 
624  //need to get QModelIndex of item
625  QModelIndex itemIndex = indexForItem( item, Visibility );
626  if ( !itemIndex.isValid() )
627  {
628  return;
629  }
630 
631  //emit signal for item visibility change
632  emit dataChanged( itemIndex, itemIndex );
633 }
634 
635 void QgsLayoutModel::updateItemSelectStatus( QgsLayoutItem *item )
636 {
637  if ( !item )
638  {
639  //nothing to do
640  return;
641  }
642 
643  //need to get QModelIndex of item
644  QModelIndex itemIndex = indexForItem( item, ItemId );
645  if ( !itemIndex.isValid() )
646  {
647  return;
648  }
649 
650  //emit signal for item visibility change
651  emit dataChanged( itemIndex, itemIndex );
652 }
653 
654 bool QgsLayoutModel::reorderItemUp( QgsLayoutItem *item )
655 {
656  if ( !item )
657  {
658  return false;
659  }
660 
661  if ( mItemsInScene.at( 0 ) == item )
662  {
663  //item is already topmost item present in scene, nothing to do
664  return false;
665  }
666 
667  //move item in z list
668  QMutableListIterator<QgsLayoutItem *> it( mItemZList );
669  if ( ! it.findNext( item ) )
670  {
671  //can't find item in z list, nothing to do
672  return false;
673  }
674 
675  const QList< QGraphicsItem * > sceneItems = mLayout->items();
676 
677  it.remove();
678  while ( it.hasPrevious() )
679  {
680  //search through item z list to find previous item which is present in the scene
681  it.previous();
682  if ( it.value() && sceneItems.contains( it.value() ) )
683  {
684  break;
685  }
686  }
687  it.insert( item );
688 
689  //also move item in scene items z list and notify of model changes
690  QModelIndex itemIndex = indexForItem( item );
691  if ( !itemIndex.isValid() )
692  {
693  return true;
694  }
695 
696  //move item up in scene list
697  int row = itemIndex.row();
698  beginMoveRows( QModelIndex(), row, row, QModelIndex(), row - 1 );
699  refreshItemsInScene();
700  endMoveRows();
701  return true;
702 }
703 
704 bool QgsLayoutModel::reorderItemDown( QgsLayoutItem *item )
705 {
706  if ( !item )
707  {
708  return false;
709  }
710 
711  if ( mItemsInScene.last() == item )
712  {
713  //item is already lowest item present in scene, nothing to do
714  return false;
715  }
716 
717  //move item in z list
718  QMutableListIterator<QgsLayoutItem *> it( mItemZList );
719  if ( ! it.findNext( item ) )
720  {
721  //can't find item in z list, nothing to do
722  return false;
723  }
724 
725  const QList< QGraphicsItem * > sceneItems = mLayout->items();
726  it.remove();
727  while ( it.hasNext() )
728  {
729  //search through item z list to find next item which is present in the scene
730  //(deleted items still exist in the z list so that they can be restored to their correct stacking order,
731  //but since they are not in the scene they should be ignored here)
732  it.next();
733  if ( it.value() && sceneItems.contains( it.value() ) )
734  {
735  break;
736  }
737  }
738  it.insert( item );
739 
740  //also move item in scene items z list and notify of model changes
741  QModelIndex itemIndex = indexForItem( item );
742  if ( !itemIndex.isValid() )
743  {
744  return true;
745  }
746 
747  //move item down in scene list
748  int row = itemIndex.row();
749  beginMoveRows( QModelIndex(), row, row, QModelIndex(), row + 2 );
750  refreshItemsInScene();
751  endMoveRows();
752  return true;
753 }
754 
755 bool QgsLayoutModel::reorderItemToTop( QgsLayoutItem *item )
756 {
757  if ( !item || !mItemsInScene.contains( item ) )
758  {
759  return false;
760  }
761 
762  if ( mItemsInScene.at( 0 ) == item )
763  {
764  //item is already topmost item present in scene, nothing to do
765  return false;
766  }
767 
768  //move item in z list
769  QMutableListIterator<QgsLayoutItem *> it( mItemZList );
770  if ( it.findNext( item ) )
771  {
772  it.remove();
773  }
774  mItemZList.push_front( item );
775 
776  //also move item in scene items z list and notify of model changes
777  QModelIndex itemIndex = indexForItem( item );
778  if ( !itemIndex.isValid() )
779  {
780  return true;
781  }
782 
783  //move item to top
784  int row = itemIndex.row();
785  beginMoveRows( QModelIndex(), row, row, QModelIndex(), 1 );
786  refreshItemsInScene();
787  endMoveRows();
788  return true;
789 }
790 
791 bool QgsLayoutModel::reorderItemToBottom( QgsLayoutItem *item )
792 {
793  if ( !item || !mItemsInScene.contains( item ) )
794  {
795  return false;
796  }
797 
798  if ( mItemsInScene.last() == item )
799  {
800  //item is already lowest item present in scene, nothing to do
801  return false;
802  }
803 
804  //move item in z list
805  QMutableListIterator<QgsLayoutItem *> it( mItemZList );
806  if ( it.findNext( item ) )
807  {
808  it.remove();
809  }
810  mItemZList.push_back( item );
811 
812  //also move item in scene items z list and notify of model changes
813  QModelIndex itemIndex = indexForItem( item );
814  if ( !itemIndex.isValid() )
815  {
816  return true;
817  }
818 
819  //move item to bottom
820  int row = itemIndex.row();
821  beginMoveRows( QModelIndex(), row, row, QModelIndex(), rowCount() );
822  refreshItemsInScene();
823  endMoveRows();
824  return true;
825 }
826 
827 QgsLayoutItem *QgsLayoutModel::findItemAbove( QgsLayoutItem *item ) const
828 {
829  //search item z list for selected item
830  QListIterator<QgsLayoutItem *> it( mItemZList );
831  it.toBack();
832  if ( it.findPrevious( item ) )
833  {
834  //move position to before selected item
835  while ( it.hasPrevious() )
836  {
837  //now find previous item, since list is sorted from lowest->highest items
838  if ( it.hasPrevious() && !it.peekPrevious()->isGroupMember() )
839  {
840  return it.previous();
841  }
842  it.previous();
843  }
844  }
845  return nullptr;
846 }
847 
848 QgsLayoutItem *QgsLayoutModel::findItemBelow( QgsLayoutItem *item ) const
849 {
850  //search item z list for selected item
851  QListIterator<QgsLayoutItem *> it( mItemZList );
852  if ( it.findNext( item ) )
853  {
854  //return next item (list is sorted from lowest->highest items)
855  while ( it.hasNext() )
856  {
857  if ( !it.peekNext()->isGroupMember() )
858  {
859  return it.next();
860  }
861  it.next();
862  }
863  }
864  return nullptr;
865 }
866 
867 QList<QgsLayoutItem *> &QgsLayoutModel::zOrderList()
868 {
869  return mItemZList;
870 }
871 
873 
874 Qt::ItemFlags QgsLayoutModel::flags( const QModelIndex &index ) const
875 {
876  Qt::ItemFlags flags = QAbstractItemModel::flags( index );
877 
878  if ( ! index.isValid() )
879  {
880  return flags | Qt::ItemIsDropEnabled;
881  }
882 
883  if ( index.row() == 0 )
884  {
885  return flags | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
886  }
887  else
888  {
889  switch ( index.column() )
890  {
891  case Visibility:
892  case LockStatus:
893  return flags | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
894  case ItemId:
895  return flags | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
896  default:
897  return flags | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
898  }
899  }
900 }
901 
902 QModelIndex QgsLayoutModel::indexForItem( QgsLayoutItem *item, const int column )
903 {
904  if ( !item )
905  {
906  return QModelIndex();
907  }
908 
909  int row = mItemsInScene.indexOf( item );
910  if ( row == -1 )
911  {
912  //not found
913  return QModelIndex();
914  }
915 
916  return index( row + 1, column );
917 }
918 
920 void QgsLayoutModel::setSelected( const QModelIndex &index )
921 {
922  QgsLayoutItem *item = itemFromIndex( index );
923  if ( !item )
924  {
925  return;
926  }
927 
928  // find top level group this item is contained within, and mark the group as selected
929  QgsLayoutItemGroup *group = item->parentGroup();
930  while ( group && group->parentGroup() )
931  {
932  group = group->parentGroup();
933  }
934 
935  // but the actual main selected item is the item itself (allows editing of item properties)
936  mLayout->setSelectedItem( item );
937 
938  if ( group && group != item )
939  group->setSelected( true );
940 }
942 
943 //
944 // QgsLayoutProxyModel
945 //
946 
948  : QSortFilterProxyModel( parent )
949  , mLayout( layout )
950  , mItemTypeFilter( QgsLayoutItemRegistry::LayoutItem )
951 {
952  if ( mLayout )
953  setSourceModel( mLayout->itemsModel() );
954 
955  setDynamicSortFilter( true );
956  setSortLocaleAware( true );
957  sort( QgsLayoutModel::ItemId );
958 }
959 
960 bool QgsLayoutProxyModel::lessThan( const QModelIndex &left, const QModelIndex &right ) const
961 {
962  const QString leftText = sourceModel()->data( left, Qt::DisplayRole ).toString();
963  const QString rightText = sourceModel()->data( right, Qt::DisplayRole ).toString();
964  if ( leftText.isEmpty() )
965  return true;
966  if ( rightText.isEmpty() )
967  return false;
968 
969  //sort by item id
970  const QgsLayoutItem *item1 = itemFromSourceIndex( left );
971  const QgsLayoutItem *item2 = itemFromSourceIndex( right );
972  if ( !item1 )
973  return false;
974 
975  if ( !item2 )
976  return true;
977 
978  return QString::localeAwareCompare( item1->displayName(), item2->displayName() ) < 0;
979 }
980 
981 QgsLayoutItem *QgsLayoutProxyModel::itemFromSourceIndex( const QModelIndex &sourceIndex ) const
982 {
983  if ( !mLayout )
984  return nullptr;
985 
986  //get column corresponding to an index from the source model
987  QVariant itemAsVariant = sourceModel()->data( sourceIndex, Qt::UserRole + 1 );
988  return qobject_cast<QgsLayoutItem *>( itemAsVariant.value<QObject *>() );
989 }
990 
992 {
993  mAllowEmpty = allowEmpty;
994  invalidateFilter();
995 }
996 
998 {
999  return mAllowEmpty;
1000 }
1001 
1003 {
1004  mItemTypeFilter = filter;
1005  invalidate();
1006 }
1007 
1008 void QgsLayoutProxyModel::setExceptedItemList( const QList< QgsLayoutItem *> &items )
1009 {
1010  if ( mExceptedList == items )
1011  return;
1012 
1013  mExceptedList = items;
1014  invalidateFilter();
1015 }
1016 
1017 bool QgsLayoutProxyModel::filterAcceptsRow( int sourceRow, const QModelIndex &sourceParent ) const
1018 {
1019  //get QgsComposerItem corresponding to row
1020  QModelIndex index = sourceModel()->index( sourceRow, 0, sourceParent );
1021  QgsLayoutItem *item = itemFromSourceIndex( index );
1022 
1023  if ( !item )
1024  return mAllowEmpty;
1025 
1026  // specific exceptions
1027  if ( mExceptedList.contains( item ) )
1028  return false;
1029 
1030  // filter by type
1031  if ( mItemTypeFilter != QgsLayoutItemRegistry::LayoutItem && item->type() != mItemTypeFilter )
1032  return false;
1033 
1034  return true;
1035 }
1036 
QgsLayoutProxyModel(QgsLayout *layout, QObject *parent=nullptr)
Constructor for QgsLayoutProxyModelm, attached to the specified layout.
virtual QIcon icon() const
Returns the item&#39;s icon.
bool setData(const QModelIndex &index, const QVariant &value, int role) override
void setSelectedItem(QgsLayoutItem *item)
Clears any selected items and sets item as the current selection.
Definition: qgslayout.cpp:158
bool removeRows(int row, int count, const QModelIndex &parent=QModelIndex()) override
QgsLayoutItem * itemFromIndex(const QModelIndex &index) const
Returns the QgsLayoutItem corresponding to a QModelIndex index, if possible.
Base class for graphical items within a QgsLayout.
int type() const override
Returns a unique graphics item type identifier.
QVariant data(const QModelIndex &index, int role) const override
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override
QgsLayoutItem * itemByUuid(const QString &uuid, bool includeTemplateUuids=false) const
Returns the layout item with matching uuid unique identifier, or nullptr if a matching item could not...
Definition: qgslayout.cpp:237
int columnCount(const QModelIndex &parent=QModelIndex()) const override
A container for grouping several QgsLayoutItems.
QModelIndex indexForItem(QgsLayoutItem *item, int column=0)
Returns the QModelIndex corresponding to a QgsLayoutItem item and column, if possible.
Qt::ItemFlags flags(const QModelIndex &index) const override
void setAllowEmptyItem(bool allowEmpty)
Sets whether an optional empty layout item is present in the model.
int rowCount(const QModelIndex &parent=QModelIndex()) const override
virtual void setSelected(bool selected)
Sets whether the item should be selected.
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
Qt::DropActions supportedDropActions() const override
void setExceptedItemList(const QList< QgsLayoutItem * > &items)
Sets a list of specific items to exclude from the model.
QMimeData * mimeData(const QModelIndexList &indexes) const override
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override
void updateZValues(bool addUndoCommands=true)
Resets the z-values of items based on their position in the internal z order list.
Definition: qgslayout.cpp:917
virtual void setId(const QString &id)
Set the item&#39;s id name.
QStringList mimeTypes() const override
void setLocked(bool locked)
Sets whether the item is locked, preventing mouse interactions with the item.
Item visibility checkbox.
bool zOrderDescending(QgsLayoutItem *item1, QgsLayoutItem *item2)
QString id() const
Returns the item&#39;s ID name.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:49
void setFilterType(QgsLayoutItemRegistry::ItemType filter)
Sets the item type filter.
virtual void setVisibility(bool visible)
Sets whether the item is visible.
Item lock status checkbox.
QgsLayoutItem * itemFromSourceIndex(const QModelIndex &sourceIndex) const
Returns the QgsLayoutItem corresponding to an index from the source QgsLayoutModel model...
QgsLayoutModel * itemsModel()
Returns the items model attached to the layout.
Definition: qgslayout.cpp:136
virtual QString displayName() const
Gets item display name.
QModelIndex parent(const QModelIndex &index) const override
QgsLayoutModel(QgsLayout *layout, QObject *parent=nullptr)
Constructor for a QgsLayoutModel attached to the specified layout.
Registry of available layout item types.
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override
virtual QString uuid() const
Returns the item identification string.
bool isLocked() const
Returns true if the item is locked, and cannot be interacted with using the mouse.
QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const override
bool allowEmptyItem() const
Returns true if the model includes the empty item choice.
QgsLayoutItemGroup * parentGroup() const
Returns the item&#39;s parent group, if the item is part of a QgsLayoutItemGroup group.
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override