QGIS API Documentation 4.3.0-Master (eb72899e5dc)
Loading...
Searching...
No Matches
qgslayoutitemslistview.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgslayoutitemslistview.cpp
3 --------------------------
4 Date : October 2017
5 Copyright : (C) 2017 Nyall Dawson
6 Email : nyall dot dawson at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
17
18#include "qgslayout.h"
20#include "qgslayoutitemgroup.h"
21#include "qgslayoutmodel.h"
22#include "qgslayoutview.h"
23
24#include <QHeaderView>
25#include <QMenu>
26#include <QMouseEvent>
27
28#include "moc_qgslayoutitemslistview.cpp"
29
31 : QSortFilterProxyModel( parent )
32 , mModel( model )
33{
34 setSourceModel( mModel );
35}
36
38{
39 return mModel->itemFromIndex( mapToSource( index ) );
40}
41
42QModelIndex QgsLayoutItemsListViewModel::indexForItem( QgsLayoutItem *item, const int column ) const
43{
44 return mapFromSource( mModel->indexForItem( item, column ) );
45}
46
47void QgsLayoutItemsListViewModel::setSelected( const QModelIndex &index )
48{
49 mModel->setSelected( mapToSource( index ) );
50}
51
52bool QgsLayoutItemsListViewModel::filterAcceptsRow( int sourceRow, const QModelIndex & ) const
53{
54 if ( sourceRow == 0 )
55 return false; // hide empty null item row
56 return true;
57}
58
59QVariant QgsLayoutItemsListViewModel::data( const QModelIndex &index, int role ) const
60{
61 if ( !index.isValid() )
62 return QVariant();
63
64 QgsLayoutItem *item = itemFromIndex( index );
65 if ( !item )
66 {
67 return QVariant();
68 }
69
70 if ( role == Qt::FontRole )
71 {
72 if ( index.column() == QgsLayoutModel::ItemId && item->isSelected() )
73 {
74 //draw name of selected items in bold
75 QFont boldFont;
76 boldFont.setBold( true );
77 return boldFont;
78 }
79 }
80
81 return QSortFilterProxyModel::data( index, role );
82}
83
84
85//
86// QgsLayoutItemsListView
87//
88
90 : QTreeView( parent )
91 , mDesigner( designer )
92{
93 setColumnWidth( 0, 30 );
94 setColumnWidth( 1, 30 );
95 setDragEnabled( true );
96 setAcceptDrops( true );
97 setDropIndicatorShown( true );
98 setDragDropMode( QAbstractItemView::InternalMove );
99 setContextMenuPolicy( Qt::CustomContextMenu );
100 setIndentation( 0 );
101
102 // Allow multi selection from the list view
103 setSelectionMode( QAbstractItemView::ExtendedSelection );
104 setSelectionBehavior( QAbstractItemView::SelectRows );
105
106 connect( this, &QWidget::customContextMenuRequested, this, &QgsLayoutItemsListView::showContextMenu );
107 if ( mDesigner )
108 {
109 setLayoutView( mDesigner->view() );
110 }
111}
112
114{
115 mLayout = layout;
116 mModel = new QgsLayoutItemsListViewModel( layout->itemsModel(), this );
117 setModel( mModel );
118
119 header()->setSectionResizeMode( 0, QHeaderView::Fixed );
120 header()->setSectionResizeMode( 1, QHeaderView::Fixed );
121 setColumnWidth( 0, Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'x' ) * 4 );
122 setColumnWidth( 1, Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'x' ) * 4 );
123 header()->setSectionsMovable( false );
124
125 connect( selectionModel(), &QItemSelectionModel::selectionChanged, this, &QgsLayoutItemsListView::updateSelection );
126}
127
129{
130 if ( event->key() == Qt::Key_Space )
131 {
132 const auto constSelectedIndexes = selectionModel()->selectedIndexes();
133 if ( !constSelectedIndexes.isEmpty() )
134 {
135 const bool isFirstItemVisible = mModel->itemFromIndex( constSelectedIndexes[0] )->isVisible();
136
137 for ( const QModelIndex &index : constSelectedIndexes )
138 {
139 if ( QgsLayoutItem *item = mModel->itemFromIndex( index ) )
140 {
141 item->setVisibility( !isFirstItemVisible );
142 }
143 }
144 //return here, because otherwise it will just invert visibility for each item instead setting all the same
145 return;
146 }
147 }
148
149 QTreeView::keyPressEvent( event );
150}
151
152void QgsLayoutItemsListView::setLayoutView( QgsLayoutView *view )
153{
154 connect( view, &QgsLayoutView::itemFocused, this, &QgsLayoutItemsListView::onItemFocused );
155}
156
157void QgsLayoutItemsListView::updateSelection( const QItemSelection &selected, const QItemSelection &deselected )
158{
159 QSet< QgsLayoutItem * > selectedItems;
160 QSet< QgsLayoutItem * > deselectedItems;
161 // this slot will be called whenever the selected **indexes** change in the view. While logically
162 // that sounds like it will only happen when the actual **selection** changes, it's also trigerred when
163 // the indices which correspond to those selected items is changed. And this happens on Qt6 when the proxy model's
164 // source model emits dataChanged.
165 // To avoid an endless loop here we resolve the actual selected/deselected layout items from their indices, and
166 // abort out early if the actual selected items aren't changed.
167 for ( const QModelIndex &index : selected.indexes() )
168 {
169 if ( QgsLayoutItem *item = mModel->itemFromIndex( index ) )
170 {
171 selectedItems << item;
172 }
173 }
174 for ( const QModelIndex &index : deselected.indexes() )
175 {
176 if ( QgsLayoutItem *item = mModel->itemFromIndex( index ) )
177 {
178 deselectedItems << item;
179 }
180 }
181 if ( selectedItems == deselectedItems )
182 {
183 return; // nothing changed
184 }
185
186 // Do nothing if we are currently updating the selection
187 // because user has selected/deselected some items in the
188 // graphics view
189 if ( !mModel || mUpdatingFromView )
190 return;
191
192 // Set the updating flag
193 mUpdatingSelection = true;
194
195 // Deselect all items from the layout (prevent firing signals, selection will be changed)
196 whileBlocking( mLayout )->deselectAll();
197
198 bool itemSelected = false;
199
200 // Check if the clicked item was selected or deselected
201 if ( selectionModel()->isSelected( selectionModel()->currentIndex() ) )
202 {
203 // It it was selected, set it as the layout's selected item;
204 // This will show the item properties
205 QgsLayoutItem *currentItem = mModel->itemFromIndex( selectionModel()->currentIndex() );
206 mLayout->setSelectedItem( currentItem );
207 itemSelected = true;
208 }
209 for ( QgsLayoutItem *item : selectedItems )
210 {
211 if ( !itemSelected )
212 {
213 // If clicked item was actually deselected, set the first selected item in the list
214 // as the layout's selected item
215 mLayout->setSelectedItem( item );
216 itemSelected = true;
217 }
218 else
219 {
220 item->setSelected( true );
221 }
222
223 // find top level group this item is contained within, and mark the group as selected
224 QgsLayoutItemGroup *group = item->parentGroup();
225 while ( group && group->parentGroup() )
226 {
227 group = group->parentGroup();
228 }
229 if ( group && group != item )
230 group->setSelected( true );
231 }
232 // Reset the updating flag
233 mUpdatingSelection = false;
234}
235
236void QgsLayoutItemsListView::onItemFocused( QgsLayoutItem *focusedItem )
237{
238 // Do nothing if we are in the middle of selecting items in the layoutView
239 if ( !mModel || mUpdatingSelection )
240 return;
241
242 // Set the updating flag
243 mUpdatingFromView = true;
244
245 // Deselect all items in list
246 clearSelection();
247
248 // Set the current index to the focused item
249 QModelIndex index = mModel->indexForItem( focusedItem );
250 if ( index.isValid() )
251 {
252 setCurrentIndex( index );
253 }
254
255 // Select rows in the item list for every selected items in the graphics view
256 const QList<QgsLayoutItem *> selectedItems = mLayout->selectedLayoutItems();
257 for ( QgsLayoutItem *item : selectedItems )
258 {
259 const QModelIndex firstCol = mModel->indexForItem( item );
260 if ( firstCol.isValid() )
261 {
262 // Select the whole row
263 QItemSelection selection;
264 selection.select( firstCol, firstCol.siblingAtColumn( mModel->columnCount( firstCol.parent() ) - 1 ) );
265 selectionModel()->select( selection, QItemSelectionModel::Select );
266 }
267 }
268 // Reset the updating flag
269 mUpdatingFromView = false;
270}
271
272void QgsLayoutItemsListView::showContextMenu( QPoint point )
273{
274 if ( !mModel || !mDesigner )
275 return;
276 const QModelIndex index = indexAt( point );
277 QgsLayoutItem *item = mModel->itemFromIndex( index );
278 if ( !item )
279 return;
280
281 QMenu *menu = new QMenu( this );
282
283 QAction *copyAction = new QAction( tr( "Copy Item" ), menu );
284 connect( copyAction, &QAction::triggered, this, [this, item]() { mDesigner->view()->copyItems( QList<QgsLayoutItem *>() << item, QgsLayoutView::ClipboardCopy ); } );
285 menu->addAction( copyAction );
286 QAction *deleteAction = new QAction( tr( "Delete Item" ), menu );
287 connect( deleteAction, &QAction::triggered, this, [this, item]() { mDesigner->view()->deleteItems( QList<QgsLayoutItem *>() << item ); } );
288 menu->addAction( deleteAction );
289 menu->addSeparator();
290
291 QAction *itemPropertiesAction = new QAction( tr( "Item Properties…" ), menu );
292 connect( itemPropertiesAction, &QAction::triggered, this, [this, item]() { mDesigner->showItemOptions( item, true ); } );
293 menu->addAction( itemPropertiesAction );
294
295 menu->popup( mapToGlobal( point ) );
296}
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition qgis.h:7320
A common interface for layout designer dialogs and widgets.
Base class for graphical items within a QgsLayout.
QgsLayoutItemGroup * parentGroup() const
Returns the item's parent group, if the item is part of a QgsLayoutItemGroup group.
virtual void setSelected(bool selected)
Sets whether the item should be selected.
Model for the layout items list view.
QgsLayoutItem * itemFromIndex(const QModelIndex &index) const
Returns the layout item listed at the specified index.
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override
QgsLayoutItemsListViewModel(QgsLayoutModel *model, QObject *parent)
constructor
void setSelected(const QModelIndex &index)
Sets the selected index.
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
QModelIndex indexForItem(QgsLayoutItem *item, const int column=0) const
Returns the model index matching the specified layout item.
void setCurrentLayout(QgsLayout *layout)
Sets the current layout.
QgsLayoutItemsListView(QWidget *parent, QgsLayoutDesignerInterface *designer)
Constructor for QgsLayoutItemsListView.
void keyPressEvent(QKeyEvent *event) override
A model for items attached to a layout.
A graphical widget to display and interact with QgsLayouts.
void itemFocused(QgsLayoutItem *item)
Emitted when an item is "focused" in the view, i.e.
@ ClipboardCopy
Copy items.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition qgslayout.h:51
QgsLayoutModel * itemsModel()
Returns the items model attached to the layout.
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition qgis.h:7619