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