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