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
QgsLayoutModel(QgsLayout * layout,QObject * parent)32 QgsLayoutModel::QgsLayoutModel( QgsLayout *layout, QObject *parent )
33 : QAbstractItemModel( parent )
34 , mLayout( layout )
35 {
36
37 }
38
itemFromIndex(const QModelIndex & index) const39 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
index(int row,int column,const QModelIndex & parent) const51 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
refreshItemsInScene()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
parent(const QModelIndex & index) const90 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
rowCount(const QModelIndex & parent) const98 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
columnCount(const QModelIndex & parent) const119 int QgsLayoutModel::columnCount( const QModelIndex &parent ) const
120 {
121 Q_UNUSED( parent )
122 return 3;
123 }
124
data(const QModelIndex & index,int role) const125 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
setData(const QModelIndex & index,const QVariant & value,int role=Qt::EditRole)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
headerData(int section,Qt::Orientation orientation,int role) const230 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
supportedDropActions() const266 Qt::DropActions QgsLayoutModel::supportedDropActions() const
267 {
268 return Qt::MoveAction;
269 }
270
mimeTypes() const271 QStringList QgsLayoutModel::mimeTypes() const
272 {
273 QStringList types;
274 types << QStringLiteral( "application/x-vnd.qgis.qgis.composeritemid" );
275 return types;
276 }
277
mimeData(const QModelIndexList & indexes) const278 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
zOrderDescending(QgsLayoutItem * item1,QgsLayoutItem * item2)303 bool zOrderDescending( QgsLayoutItem *item1, QgsLayoutItem *item2 )
304 {
305 return item1->zValue() > item2->zValue();
306 }
307
dropMimeData(const QMimeData * data,Qt::DropAction action,int row,int column,const QModelIndex & parent)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
removeRows(int row,int count,const QModelIndex & parent)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
430 ///@cond PRIVATE
clear()431 void QgsLayoutModel::clear()
432 {
433 //totally reset model
434 beginResetModel();
435 mItemZList.clear();
436 refreshItemsInScene();
437 endResetModel();
438 }
439
zOrderListSize() const440 int QgsLayoutModel::zOrderListSize() const
441 {
442 return mItemZList.size();
443 }
444
rebuildZList()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 }
466 ///@endcond
467
rebuildSceneItemList()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 }
506 ///@cond PRIVATE
addItemAtTop(QgsLayoutItem * item)507 void QgsLayoutModel::addItemAtTop( QgsLayoutItem *item )
508 {
509 mItemZList.push_front( item );
510 refreshItemsInScene();
511 item->setZValue( mItemZList.size() );
512 }
513
removeItem(QgsLayoutItem * item)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
setItemRemoved(QgsLayoutItem * item)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
updateItemDisplayName(QgsLayoutItem * item)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
updateItemLockStatus(QgsLayoutItem * item)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
updateItemVisibility(QgsLayoutItem * item)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
updateItemSelectStatus(QgsLayoutItem * item)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
reorderItemUp(QgsLayoutItem * item)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
reorderItemDown(QgsLayoutItem * item)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
reorderItemToTop(QgsLayoutItem * item)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
reorderItemToBottom(QgsLayoutItem * item)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
findItemAbove(QgsLayoutItem * item) const828 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
findItemBelow(QgsLayoutItem * item) const849 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
zOrderList()868 QList<QgsLayoutItem *> &QgsLayoutModel::zOrderList()
869 {
870 return mItemZList;
871 }
872
873 ///@endcond
874
flags(const QModelIndex & index) const875 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
indexForItem(QgsLayoutItem * item,const int column)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
920 ///@cond PRIVATE
setSelected(const QModelIndex & index)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 }
942 ///@endcond
943
944 //
945 // QgsLayoutProxyModel
946 //
947
QgsLayoutProxyModel(QgsLayout * layout,QObject * parent)948 QgsLayoutProxyModel::QgsLayoutProxyModel( QgsLayout *layout, QObject *parent )
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
lessThan(const QModelIndex & left,const QModelIndex & right) const961 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
itemFromSourceIndex(const QModelIndex & sourceIndex) const982 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
setAllowEmptyItem(bool allowEmpty)992 void QgsLayoutProxyModel::setAllowEmptyItem( bool allowEmpty )
993 {
994 mAllowEmpty = allowEmpty;
995 invalidateFilter();
996 }
997
allowEmptyItem() const998 bool QgsLayoutProxyModel::allowEmptyItem() const
999 {
1000 return mAllowEmpty;
1001 }
1002
setItemFlags(QgsLayoutItem::Flags flags)1003 void QgsLayoutProxyModel::setItemFlags( QgsLayoutItem::Flags flags )
1004 {
1005 mItemFlags = flags;
1006 invalidateFilter();
1007 }
1008
itemFlags() const1009 QgsLayoutItem::Flags QgsLayoutProxyModel::itemFlags() const
1010 {
1011 return mItemFlags;
1012 }
1013
setFilterType(QgsLayoutItemRegistry::ItemType filter)1014 void QgsLayoutProxyModel::setFilterType( QgsLayoutItemRegistry::ItemType filter )
1015 {
1016 mItemTypeFilter = filter;
1017 invalidate();
1018 }
1019
setExceptedItemList(const QList<QgsLayoutItem * > & items)1020 void QgsLayoutProxyModel::setExceptedItemList( const QList< QgsLayoutItem *> &items )
1021 {
1022 if ( mExceptedList == items )
1023 return;
1024
1025 mExceptedList = items;
1026 invalidateFilter();
1027 }
1028
filterAcceptsRow(int sourceRow,const QModelIndex & sourceParent) const1029 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 }
1053