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