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