QGIS API Documentation  3.16.0-Hannover (43b64b13f3)
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 
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 : 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 #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 : qgis::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 : qgis::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 }
QgsLayoutItem::parentGroup
QgsLayoutItemGroup * parentGroup() const
Returns the item's parent group, if the item is part of a QgsLayoutItemGroup group.
Definition: qgslayoutitem.cpp:230
QgsLayoutItem::id
QString id() const
Returns the item's ID name.
Definition: qgslayoutitem.h:357
qgslayoutitemgroup.h
QgsLayoutProxyModel::allowEmptyItem
bool allowEmptyItem() const
Returns true if the model includes the empty item choice.
Definition: qgslayoutmodel.cpp:998
QgsLayoutProxyModel::lessThan
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override
Definition: qgslayoutmodel.cpp:961
QgsLayoutModel::rowCount
int rowCount(const QModelIndex &parent=QModelIndex()) const override
Definition: qgslayoutmodel.cpp:98
QgsLayoutModel::dropMimeData
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override
Definition: qgslayoutmodel.cpp:308
QgsLayoutProxyModel::setFilterType
void setFilterType(QgsLayoutItemRegistry::ItemType filter)
Sets the item type filter.
Definition: qgslayoutmodel.cpp:1014
QgsApplication::getThemeIcon
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
Definition: qgsapplication.cpp:626
QgsLayoutItemRegistry::ItemType
ItemType
Item types.
Definition: qgslayoutitemregistry.h:307
QgsLayout::setSelectedItem
void setSelectedItem(QgsLayoutItem *item)
Clears any selected items and sets item as the current selection.
Definition: qgslayout.cpp:159
QgsLayout::itemByUuid
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
QgsLayoutProxyModel::itemFromSourceIndex
QgsLayoutItem * itemFromSourceIndex(const QModelIndex &sourceIndex) const
Returns the QgsLayoutItem corresponding to an index from the source QgsLayoutModel model.
Definition: qgslayoutmodel.cpp:982
QgsLayoutItem::icon
virtual QIcon icon() const
Returns the item's icon.
Definition: qgslayoutitem.h:334
QgsLayoutModel::LockStatus
@ LockStatus
Item lock status checkbox.
Definition: qgslayoutmodel.h:62
QgsLayoutModel::removeRows
bool removeRows(int row, int count, const QModelIndex &parent=QModelIndex()) override
Definition: qgslayoutmodel.cpp:413
QgsLayoutModel::supportedDropActions
Qt::DropActions supportedDropActions() const override
Definition: qgslayoutmodel.cpp:266
QgsLayoutItem::setSelected
virtual void setSelected(bool selected)
Sets whether the item should be selected.
Definition: qgslayoutitem.cpp:160
QgsLayoutItem::type
int type() const override
Returns a unique graphics item type identifier.
Definition: qgslayoutitem.cpp:124
qgsapplication.h
QgsLayoutModel::Visibility
@ Visibility
Item visibility checkbox.
Definition: qgslayoutmodel.h:61
QgsLayoutModel::flags
Qt::ItemFlags flags(const QModelIndex &index) const override
Definition: qgslayoutmodel.cpp:875
QgsLayoutModel::index
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
Definition: qgslayoutmodel.cpp:51
QgsLayoutItem::setLocked
void setLocked(bool locked)
Sets whether the item is locked, preventing mouse interactions with the item.
Definition: qgslayoutitem.cpp:200
QgsLayoutModel::indexForItem
QModelIndex indexForItem(QgsLayoutItem *item, int column=0)
Returns the QModelIndex corresponding to a QgsLayoutItem item and column, if possible.
Definition: qgslayoutmodel.cpp:903
QgsLayoutItem::setVisibility
virtual void setVisibility(bool visible)
Sets whether the item is visible.
Definition: qgslayoutitem.cpp:170
QgsLayoutModel::ItemId
@ ItemId
Item ID.
Definition: qgslayoutmodel.h:63
QgsLayoutItem
Base class for graphical items within a QgsLayout.
Definition: qgslayoutitem.h:113
qgslayout.h
QgsLayoutItemGroup
A container for grouping several QgsLayoutItems.
Definition: qgslayoutitemgroup.h:29
QgsLayoutModel::QgsLayoutModel
QgsLayoutModel(QgsLayout *layout, QObject *parent=nullptr)
Constructor for a QgsLayoutModel attached to the specified layout.
Definition: qgslayoutmodel.cpp:32
QgsLayoutItem::setId
virtual void setId(const QString &id)
Set the item's id name.
Definition: qgslayoutitem.cpp:134
QgsLayoutItem::itemFlags
virtual Flags itemFlags() const
Returns the item's flags, which indicate how the item behaves.
Definition: qgslayoutitem.cpp:129
QgsLayoutItem::uuid
virtual QString uuid() const
Returns the item identification string.
Definition: qgslayoutitem.h:343
QgsLayoutModel::mimeTypes
QStringList mimeTypes() const override
Definition: qgslayoutmodel.cpp:271
QgsLayoutProxyModel::QgsLayoutProxyModel
QgsLayoutProxyModel(QgsLayout *layout, QObject *parent=nullptr)
Constructor for QgsLayoutProxyModelm, attached to the specified layout.
Definition: qgslayoutmodel.cpp:948
QgsLayoutModel::setData
bool setData(const QModelIndex &index, const QVariant &value, int role) override
Definition: qgslayoutmodel.cpp:196
QgsLayout
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:50
QgsLayoutModel::mimeData
QMimeData * mimeData(const QModelIndexList &indexes) const override
Definition: qgslayoutmodel.cpp:278
QgsLayout::updateZValues
void updateZValues(bool addUndoCommands=true)
Resets the z-values of items based on their position in the internal z order list.
Definition: qgslayout.cpp:918
QgsLayoutItem::displayName
virtual QString displayName() const
Gets item display name.
Definition: qgslayoutitem.cpp:107
zOrderDescending
bool zOrderDescending(QgsLayoutItem *item1, QgsLayoutItem *item2)
Definition: qgslayoutmodel.cpp:303
QgsLayoutProxyModel::itemFlags
QgsLayoutItem::Flags itemFlags() const
Returns the layout item flags used for filtering the available items.
Definition: qgslayoutmodel.cpp:1009
QgsLayoutModel::data
QVariant data(const QModelIndex &index, int role) const override
Definition: qgslayoutmodel.cpp:125
QgsLayoutProxyModel::setExceptedItemList
void setExceptedItemList(const QList< QgsLayoutItem * > &items)
Sets a list of specific items to exclude from the model.
Definition: qgslayoutmodel.cpp:1020
QgsLayoutProxyModel::filterAcceptsRow
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override
Definition: qgslayoutmodel.cpp:1029
QgsLayoutItemRegistry
Registry of available layout item types.
Definition: qgslayoutitemregistry.h:300
qgslogger.h
QgsLayoutProxyModel::setItemFlags
void setItemFlags(QgsLayoutItem::Flags flags)
Sets layout item flags to use for filtering the available items.
Definition: qgslayoutmodel.cpp:1003
QgsLayoutItemRegistry::LayoutItem
@ LayoutItem
Base class for items.
Definition: qgslayoutitemregistry.h:308
QgsLayoutModel::itemFromIndex
QgsLayoutItem * itemFromIndex(const QModelIndex &index) const
Returns the QgsLayoutItem corresponding to a QModelIndex index, if possible.
Definition: qgslayoutmodel.cpp:39
QgsLayoutProxyModel::setAllowEmptyItem
void setAllowEmptyItem(bool allowEmpty)
Sets whether an optional empty layout item is present in the model.
Definition: qgslayoutmodel.cpp:992
qgslayoutmodel.h
QgsLayoutModel::columnCount
int columnCount(const QModelIndex &parent=QModelIndex()) const override
Definition: qgslayoutmodel.cpp:119
QgsLayout::itemsModel
QgsLayoutModel * itemsModel()
Returns the items model attached to the layout.
Definition: qgslayout.cpp:137
QgsLayoutItem::isLocked
bool isLocked() const
Returns true if the item is locked, and cannot be interacted with using the mouse.
Definition: qgslayoutitem.h:400
QgsLayoutItemRegistry::LayoutPage
@ LayoutPage
Page items.
Definition: qgslayoutitemregistry.h:316
QgsLayoutModel::headerData
QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const override
Definition: qgslayoutmodel.cpp:230
QgsLayoutModel::parent
QModelIndex parent(const QModelIndex &index) const override
Definition: qgslayoutmodel.cpp:90