QGIS API Documentation 3.43.0-Master (32433f7016e)
qgslayoutlegendwidget.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgslayoutlegendwidget.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
19#include "moc_qgslayoutlegendwidget.cpp"
20#include "qgslayoutitemlegend.h"
22#include "qgslayoutitemwidget.h"
23#include "qgslayoutitemmap.h"
24#include "qgsguiutils.h"
26
27#include "qgsapplication.h"
28#include "qgslayertree.h"
29#include "qgslayertreeutils.h"
30#include "qgslayertreemodel.h"
32#include "qgslegendrenderer.h"
33#include "qgsmapcanvas.h"
34#include "qgsmaplayerlegend.h"
35#include "qgsrenderer.h"
36#include "qgsvectorlayer.h"
37#include "qgslayoutatlas.h"
38#include "qgslayoutitemlegend.h"
43#include "qgssymbol.h"
44#include "qgslayoutundostack.h"
45#include "qgsexpressionfinder.h"
47#include "qgspainting.h"
48
49#include <QMenu>
50#include <QMessageBox>
51#include <QInputDialog>
52#include <QActionGroup>
53
55
56namespace
57{
58 int _unfilteredLegendNodeIndex( QgsLayerTreeModelLegendNode *legendNode )
59 {
60 return legendNode->model()->layerOriginalLegendNodes( legendNode->layerNode() ).indexOf( legendNode );
61 }
62
63 int _originalLegendNodeIndex( QgsLayerTreeModelLegendNode *legendNode )
64 {
65 // figure out index of the legend node as it comes out of the map layer legend.
66 // first legend nodes may be reordered, output of that is available in layerOriginalLegendNodes().
67 // next the nodes may be further filtered (by scale, map content etc).
68 // so here we go in reverse order: 1. find index before filtering, 2. find index before reorder
69 int unfilteredNodeIndex = _unfilteredLegendNodeIndex( legendNode );
71 return ( unfilteredNodeIndex >= 0 && unfilteredNodeIndex < order.count() ? order[unfilteredNodeIndex] : -1 );
72 }
73
74 void _moveLegendNode( QgsLayerTreeLayer *nodeLayer, int legendNodeIndex, int destLegendNodeIndex )
75 {
76 QList<int> order = QgsMapLayerLegendUtils::legendNodeOrder( nodeLayer );
77 const int offset = destLegendNodeIndex - legendNodeIndex;
78
79 if ( legendNodeIndex < 0 || legendNodeIndex >= order.count() )
80 return;
81 if ( legendNodeIndex + offset < 0 || legendNodeIndex + offset >= order.count() )
82 return;
83
84 int id = order.takeAt( legendNodeIndex );
85 order.insert( legendNodeIndex + offset, id );
86
88 }
89} //namespace
90
91QgsLayoutLegendWidget::QgsLayoutLegendWidget( QgsLayoutItemLegend *legend, QgsMapCanvas *mapCanvas )
92 : QgsLayoutItemBaseWidget( nullptr, legend )
93 , mLegend( legend )
94 , mMapCanvas( mapCanvas )
95{
96 Q_ASSERT( mLegend );
97
98 setupUi( this );
99 connect( mWrapCharLineEdit, &QLineEdit::textChanged, this, &QgsLayoutLegendWidget::mWrapCharLineEdit_textChanged );
100 connect( mTitleLineEdit, &QLineEdit::textChanged, this, &QgsLayoutLegendWidget::mTitleLineEdit_textChanged );
101 connect( mTitleAlignCombo, &QgsAlignmentComboBox::changed, this, &QgsLayoutLegendWidget::titleAlignmentChanged );
102 connect( mGroupAlignCombo, &QgsAlignmentComboBox::changed, this, &QgsLayoutLegendWidget::groupAlignmentChanged );
103 connect( mSubgroupAlignCombo, &QgsAlignmentComboBox::changed, this, &QgsLayoutLegendWidget::subgroupAlignmentChanged );
104 connect( mItemAlignCombo, &QgsAlignmentComboBox::changed, this, &QgsLayoutLegendWidget::itemAlignmentChanged );
105 connect( mColumnCountSpinBox, static_cast<void ( QSpinBox::* )( int )>( &QSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mColumnCountSpinBox_valueChanged );
106 connect( mSplitLayerCheckBox, &QCheckBox::toggled, this, &QgsLayoutLegendWidget::mSplitLayerCheckBox_toggled );
107 connect( mEqualColumnWidthCheckBox, &QCheckBox::toggled, this, &QgsLayoutLegendWidget::mEqualColumnWidthCheckBox_toggled );
108 connect( mSymbolWidthSpinBox, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mSymbolWidthSpinBox_valueChanged );
109 connect( mSymbolHeightSpinBox, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mSymbolHeightSpinBox_valueChanged );
110 connect( mMaxSymbolSizeSpinBox, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mMaxSymbolSizeSpinBox_valueChanged );
111 connect( mMinSymbolSizeSpinBox, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mMinSymbolSizeSpinBox_valueChanged );
112 connect( mWmsLegendWidthSpinBox, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mWmsLegendWidthSpinBox_valueChanged );
113 connect( mWmsLegendHeightSpinBox, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mWmsLegendHeightSpinBox_valueChanged );
114 connect( mTitleSpaceBottomSpinBox, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mTitleSpaceBottomSpinBox_valueChanged );
115 connect( mGroupSpaceSpinBox, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mGroupSpaceSpinBox_valueChanged );
116 connect( mGroupIndentSpinBox, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mGroupIndentSpinBox_valueChanged );
117 connect( mSubgroupIndentSpinBox, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mSubgroupIndentSpinBox_valueChanged );
118 connect( mSpaceBelowGroupHeadingSpinBox, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::spaceBelowGroupHeadingChanged );
119 connect( mGroupSideSpinBox, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::spaceGroupSideChanged );
120 connect( mLayerSpaceSpinBox, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mLayerSpaceSpinBox_valueChanged );
121 connect( mSpaceBelowSubgroupHeadingSpinBox, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::spaceBelowSubGroupHeadingChanged );
122 connect( mSubgroupSideSpinBox, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::spaceSubGroupSideChanged );
123 connect( mSymbolSpaceSpinBox, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mSymbolSpaceSpinBox_valueChanged );
124 connect( mSymbolSideSpaceSpinBox, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::spaceSymbolSideChanged );
125 connect( mIconLabelSpaceSpinBox, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mIconLabelSpaceSpinBox_valueChanged );
126 connect( mBoxSpaceSpinBox, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mBoxSpaceSpinBox_valueChanged );
127 connect( mColumnSpaceSpinBox, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mColumnSpaceSpinBox_valueChanged );
128 connect( mMaxWidthSpinBox, qOverload< double >( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::maxWidthChanged );
129 connect( mCheckBoxAutoUpdate, &QCheckBox::stateChanged, this, [=]( int state ) { mCheckBoxAutoUpdate_stateChanged( state ); } );
130 connect( mCheckboxResizeContents, &QCheckBox::toggled, this, &QgsLayoutLegendWidget::mCheckboxResizeContents_toggled );
131 connect( mRasterStrokeGroupBox, &QgsCollapsibleGroupBoxBasic::toggled, this, &QgsLayoutLegendWidget::mRasterStrokeGroupBox_toggled );
132 connect( mRasterStrokeWidthSpinBox, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mRasterStrokeWidthSpinBox_valueChanged );
133 connect( mRasterStrokeColorButton, &QgsColorButton::colorChanged, this, &QgsLayoutLegendWidget::mRasterStrokeColorButton_colorChanged );
134 connect( mExpandAllToolButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::expandLegendTree );
135 connect( mCollapseAllToolButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::collapseLegendTree );
136 connect( mMoveDownToolButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::mMoveDownToolButton_clicked );
137 connect( mMoveUpToolButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::mMoveUpToolButton_clicked );
138 connect( mRemoveToolButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::mRemoveToolButton_clicked );
139 connect( mAddToolButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::mAddToolButton_clicked );
140 connect( mEditPushButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::mEditPushButton_clicked );
141 connect( mCountToolButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::mCountToolButton_clicked );
142 connect( mExpressionFilterButton, &QgsLegendFilterButton::toggled, this, &QgsLayoutLegendWidget::mExpressionFilterButton_toggled );
143 connect( mLayerExpressionButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::mLayerExpressionButton_clicked );
144 connect( mFilterByMapCheckBox, &QCheckBox::toggled, this, &QgsLayoutLegendWidget::mFilterByMapCheckBox_toggled );
145 connect( mUpdateAllPushButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::mUpdateAllPushButton_clicked );
146 connect( mAddGroupToolButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::mAddGroupToolButton_clicked );
147 connect( mFilterLegendByAtlasCheckBox, &QCheckBox::toggled, this, &QgsLayoutLegendWidget::mFilterLegendByAtlasCheckBox_toggled );
148 connect( mItemTreeView, &QgsLayerTreeView::doubleClicked, this, &QgsLayoutLegendWidget::mItemTreeView_doubleClicked );
149
150 connect( mFilterByMapCheckBox, &QCheckBox::toggled, mButtonLinkedMaps, &QWidget::setEnabled );
151 mButtonLinkedMaps->setEnabled( false );
152 connect( mButtonLinkedMaps, &QToolButton::clicked, this, [=] {
153 mMapFilteringWidget = new QgsLayoutLegendMapFilteringWidget( mLegend );
154 openPanel( mMapFilteringWidget );
155 } );
156
157 setPanelTitle( tr( "Legend Properties" ) );
158
159 mTitleFontButton->setMode( QgsFontButton::ModeTextRenderer );
160 mGroupFontButton->setMode( QgsFontButton::ModeTextRenderer );
161 mLayerFontButton->setMode( QgsFontButton::ModeTextRenderer );
162 mItemFontButton->setMode( QgsFontButton::ModeTextRenderer );
163
164 mTitleAlignCombo->setAvailableAlignments( Qt::AlignLeft | Qt::AlignHCenter | Qt::AlignRight );
165 mGroupAlignCombo->setAvailableAlignments( Qt::AlignLeft | Qt::AlignHCenter | Qt::AlignRight );
166 mSubgroupAlignCombo->setAvailableAlignments( Qt::AlignLeft | Qt::AlignHCenter | Qt::AlignRight );
167 mItemAlignCombo->setAvailableAlignments( Qt::AlignLeft | Qt::AlignHCenter | Qt::AlignRight );
168
169 mArrangementCombo->setAvailableAlignments( Qt::AlignLeft | Qt::AlignRight );
170 connect( mArrangementCombo, &QgsAlignmentComboBox::changed, this, &QgsLayoutLegendWidget::arrangementChanged );
171 mArrangementCombo->customizeAlignmentDisplay( Qt::AlignLeft, tr( "Symbols on Left" ), QgsApplication::getThemeIcon( QStringLiteral( "/mIconArrangeSymbolsLeft.svg" ) ) );
172 mArrangementCombo->customizeAlignmentDisplay( Qt::AlignRight, tr( "Symbols on Right" ), QgsApplication::getThemeIcon( QStringLiteral( "/mIconArrangeSymbolsRight.svg" ) ) );
173
174 mSpaceBelowGroupHeadingSpinBox->setClearValue( 0 );
175 mGroupSideSpinBox->setClearValue( 0 );
176 mSpaceBelowSubgroupHeadingSpinBox->setClearValue( 0 );
177 mSubgroupSideSpinBox->setClearValue( 0 );
178 mSymbolSideSpaceSpinBox->setClearValue( 0 );
179
180 mMaxWidthSpinBox->setShowClearButton( true );
181 mMaxWidthSpinBox->setClearValue( 0, tr( "Disabled" ) );
182
183 // setup icons
184 mAddToolButton->setIcon( QIcon( QgsApplication::iconPath( "symbologyAdd.svg" ) ) );
185 mEditPushButton->setIcon( QIcon( QgsApplication::iconPath( "symbologyEdit.svg" ) ) );
186 mRemoveToolButton->setIcon( QIcon( QgsApplication::iconPath( "symbologyRemove.svg" ) ) );
187 mMoveUpToolButton->setIcon( QIcon( QgsApplication::iconPath( "mActionArrowUp.svg" ) ) );
188 mMoveDownToolButton->setIcon( QIcon( QgsApplication::iconPath( "mActionArrowDown.svg" ) ) );
189 mCountToolButton->setIcon( QIcon( QgsApplication::iconPath( "mActionSum.svg" ) ) );
190 mLayerExpressionButton->setIcon( QIcon( QgsApplication::iconPath( "mIconExpression.svg" ) ) );
191 mExpandAllToolButton->setIcon( QIcon( QgsApplication::iconPath( "mActionExpandTree.svg" ) ) );
192 mCollapseAllToolButton->setIcon( QIcon( QgsApplication::iconPath( "mActionCollapseTree.svg" ) ) );
193
194 mMoveDownToolButton->setIconSize( QgsGuiUtils::iconSize( true ) );
195 mMoveUpToolButton->setIconSize( QgsGuiUtils::iconSize( true ) );
196 mAddGroupToolButton->setIconSize( QgsGuiUtils::iconSize( true ) );
197 mAddToolButton->setIconSize( QgsGuiUtils::iconSize( true ) );
198 mRemoveToolButton->setIconSize( QgsGuiUtils::iconSize( true ) );
199 mEditPushButton->setIconSize( QgsGuiUtils::iconSize( true ) );
200 mCountToolButton->setIconSize( QgsGuiUtils::iconSize( true ) );
201 mExpressionFilterButton->setIconSize( QgsGuiUtils::iconSize( true ) );
202 mLayerExpressionButton->setIconSize( QgsGuiUtils::iconSize( true ) );
203 mExpandAllToolButton->setIconSize( QgsGuiUtils::iconSize( true ) );
204 mCollapseAllToolButton->setIconSize( QgsGuiUtils::iconSize( true ) );
205
206 mRasterStrokeColorButton->setColorDialogTitle( tr( "Select Stroke Color" ) );
207 mRasterStrokeColorButton->setAllowOpacity( true );
208 mRasterStrokeColorButton->setContext( QStringLiteral( "composer " ) );
209
210 mMapComboBox->setCurrentLayout( legend->layout() );
211 mMapComboBox->setItemType( QgsLayoutItemRegistry::LayoutMap );
212 connect( mMapComboBox, &QgsLayoutItemComboBox::itemChanged, this, &QgsLayoutLegendWidget::composerMapChanged );
213
214 //add widget for general composer item properties
215 mItemPropertiesWidget = new QgsLayoutItemPropertiesWidget( this, legend );
216 mainLayout->addWidget( mItemPropertiesWidget );
217
218 mItemTreeView->setHeaderHidden( true );
219
220 mItemTreeView->setModel( legend->model() );
221 mItemTreeView->setMenuProvider( new QgsLayoutLegendMenuProvider( mItemTreeView, this ) );
222 setLegendMapViewData();
223 connect( legend, &QgsLayoutObject::changed, this, &QgsLayoutLegendWidget::setGuiElements );
224
225 // connect atlas state to the filter legend by atlas checkbox
226 if ( layoutAtlas() )
227 {
228 connect( layoutAtlas(), &QgsLayoutAtlas::toggled, this, &QgsLayoutLegendWidget::updateFilterLegendByAtlasButton );
229 }
230 connect( &legend->layout()->reportContext(), &QgsLayoutReportContext::layerChanged, this, &QgsLayoutLegendWidget::updateFilterLegendByAtlasButton );
231
232 mTitleFontButton->registerExpressionContextGenerator( this );
233 mGroupFontButton->registerExpressionContextGenerator( this );
234 mLayerFontButton->registerExpressionContextGenerator( this );
235 mItemFontButton->registerExpressionContextGenerator( this );
236 mExpressionFilterButton->registerExpressionContextGenerator( this );
237
238 mTitleFontButton->setLayer( coverageLayer() );
239 mGroupFontButton->setLayer( coverageLayer() );
240 mLayerFontButton->setLayer( coverageLayer() );
241 mItemFontButton->setLayer( coverageLayer() );
242 if ( mLegend->layout() )
243 {
244 connect( &mLegend->layout()->reportContext(), &QgsLayoutReportContext::layerChanged, mTitleFontButton, &QgsFontButton::setLayer );
245 connect( &mLegend->layout()->reportContext(), &QgsLayoutReportContext::layerChanged, mGroupFontButton, &QgsFontButton::setLayer );
246 connect( &mLegend->layout()->reportContext(), &QgsLayoutReportContext::layerChanged, mLayerFontButton, &QgsFontButton::setLayer );
247 connect( &mLegend->layout()->reportContext(), &QgsLayoutReportContext::layerChanged, mItemFontButton, &QgsFontButton::setLayer );
248 }
249
250 registerDataDefinedButton( mLegendTitleDDBtn, QgsLayoutObject::DataDefinedProperty::LegendTitle );
251 registerDataDefinedButton( mColumnsDDBtn, QgsLayoutObject::DataDefinedProperty::LegendColumnCount );
252 registerDataDefinedButton( mLegendAutoWrapWidthDDBtn, QgsLayoutObject::DataDefinedProperty::LegendAutoWrapWidth );
253
254 setGuiElements();
255
256 connect( mItemTreeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsLayoutLegendWidget::selectedChanged );
257 connect( mTitleFontButton, &QgsFontButton::changed, this, &QgsLayoutLegendWidget::titleFontChanged );
258 connect( mGroupFontButton, &QgsFontButton::changed, this, &QgsLayoutLegendWidget::groupFontChanged );
259 connect( mLayerFontButton, &QgsFontButton::changed, this, &QgsLayoutLegendWidget::layerFontChanged );
260 connect( mItemFontButton, &QgsFontButton::changed, this, &QgsLayoutLegendWidget::itemFontChanged );
261}
262
263void QgsLayoutLegendWidget::setMasterLayout( QgsMasterLayoutInterface *masterLayout )
264{
265 if ( mItemPropertiesWidget )
266 mItemPropertiesWidget->setMasterLayout( masterLayout );
267}
268
269void QgsLayoutLegendWidget::setDesignerInterface( QgsLayoutDesignerInterface *iface )
270{
272 mTitleFontButton->setMessageBar( iface->messageBar() );
273 mGroupFontButton->setMessageBar( iface->messageBar() );
274 mLayerFontButton->setMessageBar( iface->messageBar() );
275 mItemFontButton->setMessageBar( iface->messageBar() );
276}
277
278void QgsLayoutLegendWidget::setGuiElements()
279{
280 if ( !mLegend )
281 {
282 return;
283 }
284
285 blockAllSignals( true );
286 mTitleLineEdit->setText( mLegend->title() );
287 whileBlocking( mTitleAlignCombo )->setCurrentAlignment( mLegend->titleAlignment() );
288 whileBlocking( mGroupAlignCombo )->setCurrentAlignment( mLegend->style( Qgis::LegendComponent::Group ).alignment() );
289 whileBlocking( mSubgroupAlignCombo )->setCurrentAlignment( mLegend->style( Qgis::LegendComponent::Subgroup ).alignment() );
290 whileBlocking( mItemAlignCombo )->setCurrentAlignment( mLegend->style( Qgis::LegendComponent::SymbolLabel ).alignment() );
291 whileBlocking( mArrangementCombo )->setCurrentAlignment( mLegend->symbolAlignment() );
292 mFilterByMapCheckBox->setChecked( mLegend->legendFilterByMapEnabled() );
293 mButtonLinkedMaps->setEnabled( mLegend->legendFilterByMapEnabled() );
294 mColumnCountSpinBox->setValue( mLegend->columnCount() );
295 mSplitLayerCheckBox->setChecked( mLegend->splitLayer() );
296 mEqualColumnWidthCheckBox->setChecked( mLegend->equalColumnWidth() );
297 mSymbolWidthSpinBox->setValue( mLegend->symbolWidth() );
298 mSymbolHeightSpinBox->setValue( mLegend->symbolHeight() );
299 mMaxSymbolSizeSpinBox->setValue( mLegend->maximumSymbolSize() );
300 mMinSymbolSizeSpinBox->setValue( mLegend->minimumSymbolSize() );
301 mWmsLegendWidthSpinBox->setValue( mLegend->wmsLegendWidth() );
302 mWmsLegendHeightSpinBox->setValue( mLegend->wmsLegendHeight() );
303 mTitleSpaceBottomSpinBox->setValue( mLegend->style( Qgis::LegendComponent::Title ).margin( QgsLegendStyle::Bottom ) );
304 mGroupSpaceSpinBox->setValue( mLegend->style( Qgis::LegendComponent::Group ).margin( QgsLegendStyle::Top ) );
305 mGroupIndentSpinBox->setValue( mLegend->style( Qgis::LegendComponent::Group ).indent() );
306 mSubgroupIndentSpinBox->setValue( mLegend->style( Qgis::LegendComponent::Subgroup ).indent() );
307 mGroupSideSpinBox->setValue( mLegend->style( Qgis::LegendComponent::Group ).margin( QgsLegendStyle::Left ) );
308 mSpaceBelowGroupHeadingSpinBox->setValue( mLegend->style( Qgis::LegendComponent::Group ).margin( QgsLegendStyle::Bottom ) );
309 mLayerSpaceSpinBox->setValue( mLegend->style( Qgis::LegendComponent::Subgroup ).margin( QgsLegendStyle::Top ) );
310 mSpaceBelowSubgroupHeadingSpinBox->setValue( mLegend->style( Qgis::LegendComponent::Subgroup ).margin( QgsLegendStyle::Bottom ) );
311 mSubgroupSideSpinBox->setValue( mLegend->style( Qgis::LegendComponent::Subgroup ).margin( QgsLegendStyle::Left ) );
312 // We keep Symbol and SymbolLabel Top in sync for now
313 mSymbolSpaceSpinBox->setValue( mLegend->style( Qgis::LegendComponent::Symbol ).margin( QgsLegendStyle::Top ) );
314 mIconLabelSpaceSpinBox->setValue( mLegend->style( Qgis::LegendComponent::SymbolLabel ).margin( QgsLegendStyle::Left ) );
315 mSymbolSideSpaceSpinBox->setValue( mLegend->style( Qgis::LegendComponent::Symbol ).margin( QgsLegendStyle::Left ) );
316 mBoxSpaceSpinBox->setValue( mLegend->boxSpace() );
317 mColumnSpaceSpinBox->setValue( mLegend->columnSpace() );
318
319 mMaxWidthSpinBox->setValue( mLegend->autoWrapLinesAfter() );
320
321 mRasterStrokeGroupBox->setChecked( mLegend->drawRasterStroke() );
322 mRasterStrokeWidthSpinBox->setValue( mLegend->rasterStrokeWidth() );
323 mRasterStrokeColorButton->setColor( mLegend->rasterStrokeColor() );
324
325 mCheckBoxAutoUpdate->setChecked( mLegend->autoUpdateModel() );
326
327 mCheckboxResizeContents->setChecked( mLegend->resizeToContents() );
328 mFilterLegendByAtlasCheckBox->setChecked( mLegend->legendFilterOutAtlas() );
329 mWrapCharLineEdit->setText( mLegend->wrapString() );
330
331 QgsLayoutItemMap *map = mLegend->linkedMap();
332 mMapComboBox->setItem( map );
333 mTitleFontButton->setTextFormat( mLegend->style( Qgis::LegendComponent::Title ).textFormat() );
334 mGroupFontButton->setTextFormat( mLegend->style( Qgis::LegendComponent::Group ).textFormat() );
335 mLayerFontButton->setTextFormat( mLegend->style( Qgis::LegendComponent::Subgroup ).textFormat() );
336 mItemFontButton->setTextFormat( mLegend->style( Qgis::LegendComponent::SymbolLabel ).textFormat() );
337
338 blockAllSignals( false );
339
340 mCheckBoxAutoUpdate_stateChanged( mLegend->autoUpdateModel() ? Qt::Checked : Qt::Unchecked, false );
341 updateDataDefinedButton( mLegendTitleDDBtn );
342 updateDataDefinedButton( mColumnsDDBtn );
343}
344
345void QgsLayoutLegendWidget::mWrapCharLineEdit_textChanged( const QString &text )
346{
347 if ( mLegend )
348 {
349 mLegend->beginCommand( tr( "Change Legend Wrap" ) );
350 mLegend->setWrapString( text );
351 mLegend->adjustBoxSize();
352 mLegend->update();
353 mLegend->endCommand();
354 }
355}
356
357void QgsLayoutLegendWidget::mTitleLineEdit_textChanged( const QString &text )
358{
359 if ( mLegend )
360 {
361 mLegend->beginCommand( tr( "Change Legend Title" ), QgsLayoutItem::UndoLegendText );
362 mLegend->setTitle( text );
363 mLegend->adjustBoxSize();
364 mLegend->update();
365 mLegend->endCommand();
366 }
367}
368
369void QgsLayoutLegendWidget::titleAlignmentChanged()
370{
371 if ( mLegend )
372 {
373 Qt::AlignmentFlag alignment = static_cast<Qt::AlignmentFlag>( static_cast<int>( mTitleAlignCombo->currentAlignment() & Qt::AlignHorizontal_Mask ) );
374 mLegend->beginCommand( tr( "Change Title Alignment" ) );
375 mLegend->setTitleAlignment( alignment );
376 mLegend->update();
377 mLegend->endCommand();
378 }
379}
380
381void QgsLayoutLegendWidget::groupAlignmentChanged()
382{
383 if ( mLegend )
384 {
385 mLegend->beginCommand( tr( "Change Group Alignment" ) );
386 mLegend->rstyle( Qgis::LegendComponent::Group ).setAlignment( mGroupAlignCombo->currentAlignment() );
387 mLegend->update();
388 mLegend->endCommand();
389 }
390}
391
392void QgsLayoutLegendWidget::subgroupAlignmentChanged()
393{
394 if ( mLegend )
395 {
396 mLegend->beginCommand( tr( "Change Subgroup Alignment" ) );
397 mLegend->rstyle( Qgis::LegendComponent::Subgroup ).setAlignment( mSubgroupAlignCombo->currentAlignment() );
398 mLegend->update();
399 mLegend->endCommand();
400 }
401}
402
403void QgsLayoutLegendWidget::itemAlignmentChanged()
404{
405 if ( mLegend )
406 {
407 mLegend->beginCommand( tr( "Change Item Alignment" ) );
408 mLegend->rstyle( Qgis::LegendComponent::SymbolLabel ).setAlignment( mItemAlignCombo->currentAlignment() );
409 mLegend->update();
410 mLegend->endCommand();
411 }
412}
413
414void QgsLayoutLegendWidget::arrangementChanged()
415{
416 if ( mLegend )
417 {
418 Qt::AlignmentFlag alignment = static_cast<Qt::AlignmentFlag>( static_cast<int>( mArrangementCombo->currentAlignment() & Qt::AlignHorizontal_Mask ) );
419 mLegend->beginCommand( tr( "Change Legend Arrangement" ) );
420 mLegend->setSymbolAlignment( alignment );
421 mLegend->update();
422 mLegend->endCommand();
423 }
424}
425
426void QgsLayoutLegendWidget::mColumnCountSpinBox_valueChanged( int c )
427{
428 if ( mLegend )
429 {
430 mLegend->beginCommand( tr( "Change Column Count" ), QgsLayoutItem::UndoLegendColumnCount );
431 mLegend->setColumnCount( c );
432 mLegend->adjustBoxSize();
433 mLegend->update();
434 mLegend->endCommand();
435 }
436 mSplitLayerCheckBox->setEnabled( c > 1 );
437 mEqualColumnWidthCheckBox->setEnabled( c > 1 );
438}
439
440void QgsLayoutLegendWidget::mSplitLayerCheckBox_toggled( bool checked )
441{
442 if ( mLegend )
443 {
444 mLegend->beginCommand( tr( "Split Legend Layers" ) );
445 mLegend->setSplitLayer( checked );
446 mLegend->adjustBoxSize();
447 mLegend->update();
448 mLegend->endCommand();
449 }
450}
451
452void QgsLayoutLegendWidget::mEqualColumnWidthCheckBox_toggled( bool checked )
453{
454 if ( mLegend )
455 {
456 mLegend->beginCommand( tr( "Legend Column Width" ) );
457 mLegend->setEqualColumnWidth( checked );
458 mLegend->adjustBoxSize();
459 mLegend->update();
460 mLegend->endCommand();
461 }
462}
463
464void QgsLayoutLegendWidget::mSymbolWidthSpinBox_valueChanged( double d )
465{
466 if ( mLegend )
467 {
468 mLegend->beginCommand( tr( "Resize Symbol Width" ), QgsLayoutItem::UndoLegendSymbolWidth );
469 mLegend->setSymbolWidth( d );
470 mLegend->adjustBoxSize();
471 mLegend->update();
472 mLegend->endCommand();
473 }
474}
475
476void QgsLayoutLegendWidget::mMaxSymbolSizeSpinBox_valueChanged( double d )
477{
478 if ( mLegend )
479 {
480 mLegend->beginCommand( tr( "Change Legend Maximum Symbol Size" ), QgsLayoutItem::UndoLegendMaxSymbolSize );
481 mLegend->setMaximumSymbolSize( d );
482 mLegend->adjustBoxSize();
483 mLegend->update();
484 mLegend->endCommand();
485 }
486}
487
488void QgsLayoutLegendWidget::mMinSymbolSizeSpinBox_valueChanged( double d )
489{
490 if ( mLegend )
491 {
492 mLegend->beginCommand( tr( "Change Legend Minimum Symbol Size" ), QgsLayoutItem::UndoLegendMinSymbolSize );
493 mLegend->setMinimumSymbolSize( d );
494 mLegend->adjustBoxSize();
495 mLegend->update();
496 mLegend->endCommand();
497 }
498}
499
500void QgsLayoutLegendWidget::mSymbolHeightSpinBox_valueChanged( double d )
501{
502 if ( mLegend )
503 {
504 mLegend->beginCommand( tr( "Resize Symbol Height" ), QgsLayoutItem::UndoLegendSymbolHeight );
505 mLegend->setSymbolHeight( d );
506 mLegend->adjustBoxSize();
507 mLegend->update();
508 mLegend->endCommand();
509 }
510}
511
512void QgsLayoutLegendWidget::mWmsLegendWidthSpinBox_valueChanged( double d )
513{
514 if ( mLegend )
515 {
516 mLegend->beginCommand( tr( "Resize WMS Width" ), QgsLayoutItem::UndoLegendWmsLegendWidth );
517 mLegend->setWmsLegendWidth( d );
518 mLegend->adjustBoxSize();
519 mLegend->update();
520 mLegend->endCommand();
521 }
522}
523
524void QgsLayoutLegendWidget::mWmsLegendHeightSpinBox_valueChanged( double d )
525{
526 if ( mLegend )
527 {
528 mLegend->beginCommand( tr( "Resize WMS Height" ), QgsLayoutItem::UndoLegendWmsLegendHeight );
529 mLegend->setWmsLegendHeight( d );
530 mLegend->adjustBoxSize();
531 mLegend->update();
532 mLegend->endCommand();
533 }
534}
535
536void QgsLayoutLegendWidget::mTitleSpaceBottomSpinBox_valueChanged( double d )
537{
538 if ( mLegend )
539 {
540 mLegend->beginCommand( tr( "Change Title Space" ), QgsLayoutItem::UndoLegendTitleSpaceBottom );
541 mLegend->rstyle( Qgis::LegendComponent::Title ).setMargin( QgsLegendStyle::Bottom, d );
542 mLegend->adjustBoxSize();
543 mLegend->update();
544 mLegend->endCommand();
545 }
546}
547
548void QgsLayoutLegendWidget::mGroupSpaceSpinBox_valueChanged( double d )
549{
550 if ( mLegend )
551 {
552 mLegend->beginCommand( tr( "Change Group Space" ), QgsLayoutItem::UndoLegendGroupSpace );
553 mLegend->rstyle( Qgis::LegendComponent::Group ).setMargin( QgsLegendStyle::Top, d );
554 mLegend->adjustBoxSize();
555 mLegend->update();
556 mLegend->endCommand();
557 }
558}
559
560void QgsLayoutLegendWidget::mGroupIndentSpinBox_valueChanged( double d )
561{
562 if ( mLegend )
563 {
564 mLegend->beginCommand( tr( "Change Group Indent" ), QgsLayoutItem::UndoLegendGroupIndent );
565 mLegend->rstyle( Qgis::LegendComponent::Group ).setIndent( d );
566 mLegend->adjustBoxSize();
567 mLegend->update();
568 mLegend->endCommand();
569 }
570}
571
572void QgsLayoutLegendWidget::mSubgroupIndentSpinBox_valueChanged( double d )
573{
574 if ( mLegend )
575 {
576 mLegend->beginCommand( tr( "Change Subgroup Indent" ), QgsLayoutItem::UndoLegendSubgroupIndent );
577 mLegend->rstyle( Qgis::LegendComponent::Subgroup ).setIndent( d );
578 mLegend->adjustBoxSize();
579 mLegend->update();
580 mLegend->endCommand();
581 }
582}
583
584void QgsLayoutLegendWidget::spaceBelowGroupHeadingChanged( double space )
585{
586 if ( mLegend )
587 {
588 mLegend->beginCommand( tr( "Change Group Space" ), QgsLayoutItem::UndoLegendGroupSpace );
589 mLegend->rstyle( Qgis::LegendComponent::Group ).setMargin( QgsLegendStyle::Bottom, space );
590 mLegend->adjustBoxSize();
591 mLegend->update();
592 mLegend->endCommand();
593 }
594}
595
596void QgsLayoutLegendWidget::spaceGroupSideChanged( double space )
597{
598 if ( mLegend )
599 {
600 mLegend->beginCommand( tr( "Change Side of Group Space" ), QgsLayoutItem::UndoLegendGroupSpace );
601 mLegend->rstyle( Qgis::LegendComponent::Group ).setMargin( QgsLegendStyle::Left, space );
602 mLegend->adjustBoxSize();
603 mLegend->update();
604 mLegend->endCommand();
605 }
606}
607
608void QgsLayoutLegendWidget::spaceSubGroupSideChanged( double space )
609{
610 if ( mLegend )
611 {
612 mLegend->beginCommand( tr( "Change Side of Subgroup Space" ), QgsLayoutItem::UndoLegendLayerSpace );
613 mLegend->rstyle( Qgis::LegendComponent::Subgroup ).setMargin( QgsLegendStyle::Left, space );
614 mLegend->adjustBoxSize();
615 mLegend->update();
616 mLegend->endCommand();
617 }
618}
619
620void QgsLayoutLegendWidget::spaceSymbolSideChanged( double space )
621{
622 if ( mLegend )
623 {
624 mLegend->beginCommand( tr( "Change Side of Symbol Space" ), QgsLayoutItem::UndoLegendSymbolSpace );
625 mLegend->rstyle( Qgis::LegendComponent::Symbol ).setMargin( QgsLegendStyle::Left, space );
626 mLegend->adjustBoxSize();
627 mLegend->update();
628 mLegend->endCommand();
629 }
630}
631
632void QgsLayoutLegendWidget::mLayerSpaceSpinBox_valueChanged( double d )
633{
634 if ( mLegend )
635 {
636 mLegend->beginCommand( tr( "Change Subgroup Space" ), QgsLayoutItem::UndoLegendLayerSpace );
637 mLegend->rstyle( Qgis::LegendComponent::Subgroup ).setMargin( QgsLegendStyle::Top, d );
638 mLegend->adjustBoxSize();
639 mLegend->update();
640 mLegend->endCommand();
641 }
642}
643
644void QgsLayoutLegendWidget::mSymbolSpaceSpinBox_valueChanged( double d )
645{
646 if ( mLegend )
647 {
648 mLegend->beginCommand( tr( "Change Symbol Space" ), QgsLayoutItem::UndoLegendSymbolSpace );
649 // We keep Symbol and SymbolLabel Top in sync for now
650 mLegend->rstyle( Qgis::LegendComponent::Symbol ).setMargin( QgsLegendStyle::Top, d );
651 mLegend->rstyle( Qgis::LegendComponent::SymbolLabel ).setMargin( QgsLegendStyle::Top, d );
652 mLegend->adjustBoxSize();
653 mLegend->update();
654 mLegend->endCommand();
655 }
656}
657
658void QgsLayoutLegendWidget::mIconLabelSpaceSpinBox_valueChanged( double d )
659{
660 if ( mLegend )
661 {
662 mLegend->beginCommand( tr( "Change Label Space" ), QgsLayoutItem::UndoLegendIconSymbolSpace );
663 mLegend->rstyle( Qgis::LegendComponent::SymbolLabel ).setMargin( QgsLegendStyle::Left, d );
664 mLegend->adjustBoxSize();
665 mLegend->update();
666 mLegend->endCommand();
667 }
668}
669
670void QgsLayoutLegendWidget::titleFontChanged()
671{
672 if ( mLegend )
673 {
674 mLegend->beginCommand( tr( "Change Title Font" ), QgsLayoutItem::UndoLegendTitleFont );
675 mLegend->rstyle( Qgis::LegendComponent::Title ).setTextFormat( mTitleFontButton->textFormat() );
676 mLegend->adjustBoxSize();
677 mLegend->update();
678 mLegend->endCommand();
679 }
680}
681
682void QgsLayoutLegendWidget::groupFontChanged()
683{
684 if ( mLegend )
685 {
686 mLegend->beginCommand( tr( "Change Group Font" ), QgsLayoutItem::UndoLegendGroupFont );
687 mLegend->rstyle( Qgis::LegendComponent::Group ).setTextFormat( mGroupFontButton->textFormat() );
688 mLegend->adjustBoxSize();
689 mLegend->update();
690 mLegend->endCommand();
691 }
692}
693
694void QgsLayoutLegendWidget::layerFontChanged()
695{
696 if ( mLegend )
697 {
698 mLegend->beginCommand( tr( "Change Layer Font" ), QgsLayoutItem::UndoLegendLayerFont );
699 mLegend->rstyle( Qgis::LegendComponent::Subgroup ).setTextFormat( mLayerFontButton->textFormat() );
700 mLegend->adjustBoxSize();
701 mLegend->update();
702 mLegend->endCommand();
703 }
704}
705
706void QgsLayoutLegendWidget::itemFontChanged()
707{
708 if ( mLegend )
709 {
710 mLegend->beginCommand( tr( "Change Item Font" ), QgsLayoutItem::UndoLegendItemFont );
711 mLegend->rstyle( Qgis::LegendComponent::SymbolLabel ).setTextFormat( mItemFontButton->textFormat() );
712 mLegend->adjustBoxSize();
713 mLegend->update();
714 mLegend->endCommand();
715 }
716}
717
718void QgsLayoutLegendWidget::spaceBelowSubGroupHeadingChanged( double space )
719{
720 if ( mLegend )
721 {
722 mLegend->beginCommand( tr( "Change Subgroup Space" ), QgsLayoutItem::UndoLegendLayerSpace );
723 mLegend->rstyle( Qgis::LegendComponent::Subgroup ).setMargin( QgsLegendStyle::Bottom, space );
724 mLegend->adjustBoxSize();
725 mLegend->update();
726 mLegend->endCommand();
727 }
728}
729
730void QgsLayoutLegendWidget::mBoxSpaceSpinBox_valueChanged( double d )
731{
732 if ( mLegend )
733 {
734 mLegend->beginCommand( tr( "Change Box Space" ), QgsLayoutItem::UndoLegendBoxSpace );
735 mLegend->setBoxSpace( d );
736 mLegend->adjustBoxSize();
737 mLegend->update();
738 mLegend->endCommand();
739 }
740}
741
742void QgsLayoutLegendWidget::mColumnSpaceSpinBox_valueChanged( double d )
743{
744 if ( mLegend )
745 {
746 mLegend->beginCommand( tr( "Change Column Space" ), QgsLayoutItem::UndoLegendColumnSpace );
747 mLegend->setColumnSpace( d );
748 mLegend->adjustBoxSize();
749 mLegend->update();
750 mLegend->endCommand();
751 }
752}
753
754void QgsLayoutLegendWidget::maxWidthChanged( double width )
755{
756 if ( mLegend )
757 {
758 mLegend->beginCommand( tr( "Change Legend Wrapping" ), QgsLayoutItem::UndoLegendAutoWrapAfter );
759 mLegend->setAutoWrapLinesAfter( width );
760 mLegend->adjustBoxSize();
761 mLegend->update();
762 mLegend->endCommand();
763 }
764}
765
766void QgsLayoutLegendWidget::mMoveDownToolButton_clicked()
767{
768 if ( !mLegend )
769 {
770 return;
771 }
772
773 const QModelIndex index = mItemTreeView->selectionModel()->currentIndex();
774 const QModelIndex sourceIndex = mItemTreeView->proxyModel()->mapToSource( index );
775 const QModelIndex parentIndex = sourceIndex.parent();
776 if ( !sourceIndex.isValid() || sourceIndex.row() == mItemTreeView->layerTreeModel()->rowCount( parentIndex ) - 1 )
777 return;
778
779 QgsLayerTreeNode *node = mItemTreeView->index2node( index );
780 QgsLayerTreeModelLegendNode *legendNode = mItemTreeView->index2legendNode( index );
781 if ( !node && !legendNode )
782 return;
783
784 mLegend->beginCommand( tr( "Moved Legend Item Down" ) );
785
786 if ( node )
787 {
788 QgsLayerTreeGroup *parentGroup = QgsLayerTree::toGroup( node->parent() );
789 parentGroup->insertChildNode( sourceIndex.row() + 2, node->clone() );
790 parentGroup->removeChildNode( node );
791 }
792 else // legend node
793 {
794 // get the next index, the one that will have to be above our index,
795 const QModelIndex nextIndex = index.siblingAtRow( index.row() + 1 );
796 QgsLayerTreeModelLegendNode *nextLegendNode = nextIndex.isValid() ? mItemTreeView->index2legendNode( nextIndex ) : nullptr;
797 if ( nextLegendNode )
798 {
799 _moveLegendNode( legendNode->layerNode(), _unfilteredLegendNodeIndex( legendNode ), _unfilteredLegendNodeIndex( nextLegendNode ) );
800 mItemTreeView->layerTreeModel()->refreshLayerLegend( legendNode->layerNode() );
801 }
802 }
803
804 mItemTreeView->setCurrentIndex( mItemTreeView->proxyModel()->mapFromSource( mItemTreeView->layerTreeModel()->index( sourceIndex.row() + 1, 0, parentIndex ) ) );
805
806 mLegend->update();
807 mLegend->endCommand();
808}
809
810void QgsLayoutLegendWidget::mMoveUpToolButton_clicked()
811{
812 if ( !mLegend )
813 {
814 return;
815 }
816
817 const QModelIndex index = mItemTreeView->selectionModel()->currentIndex();
818 const QModelIndex sourceIndex = mItemTreeView->proxyModel()->mapToSource( index );
819 const QModelIndex parentIndex = sourceIndex.parent();
820 if ( !sourceIndex.isValid() || sourceIndex.row() == 0 )
821 return;
822
823 QgsLayerTreeNode *node = mItemTreeView->index2node( index );
824 QgsLayerTreeModelLegendNode *legendNode = mItemTreeView->index2legendNode( index );
825 if ( !node && !legendNode )
826 return;
827
828 mLegend->beginCommand( tr( "Move Legend Item Up" ) );
829
830 if ( node )
831 {
832 QgsLayerTreeGroup *parentGroup = QgsLayerTree::toGroup( node->parent() );
833 parentGroup->insertChildNode( sourceIndex.row() - 1, node->clone() );
834 parentGroup->removeChildNode( node );
835 }
836 else // legend node
837 {
838 // get the previous index, the one that will have to be below our index,
839 const QModelIndex prevIndex = index.siblingAtRow( index.row() - 1 );
840 QgsLayerTreeModelLegendNode *prevLegendNode = prevIndex.isValid() ? mItemTreeView->index2legendNode( prevIndex ) : nullptr;
841 if ( prevLegendNode )
842 {
843 _moveLegendNode( legendNode->layerNode(), _unfilteredLegendNodeIndex( legendNode ), _unfilteredLegendNodeIndex( prevLegendNode ) );
844 mItemTreeView->layerTreeModel()->refreshLayerLegend( legendNode->layerNode() );
845 }
846 }
847
848 mItemTreeView->setCurrentIndex( mItemTreeView->proxyModel()->mapFromSource( mItemTreeView->layerTreeModel()->index( sourceIndex.row() - 1, 0, parentIndex ) ) );
849
850 mLegend->update();
851 mLegend->endCommand();
852}
853
854void QgsLayoutLegendWidget::expandLegendTree()
855{
856 mItemTreeView->expandAll();
857}
858
859void QgsLayoutLegendWidget::collapseLegendTree()
860{
861 mItemTreeView->collapseAll();
862}
863
864void QgsLayoutLegendWidget::mCheckBoxAutoUpdate_stateChanged( int state, bool userTriggered )
865{
866 if ( userTriggered )
867 {
868 mLegend->beginCommand( tr( "Change Auto Update" ) );
869
870 mLegend->setAutoUpdateModel( state == Qt::Checked );
871 mLegend->update();
872 mLegend->endCommand();
873 }
874
875 // do not allow editing of model if auto update is on - we would modify project's layer tree
876 QList<QWidget *> widgets;
877 widgets << mMoveDownToolButton << mMoveUpToolButton << mRemoveToolButton << mAddToolButton
878 << mEditPushButton << mCountToolButton << mUpdateAllPushButton << mAddGroupToolButton
879 << mExpressionFilterButton << mCollapseAllToolButton << mExpandAllToolButton;
880 for ( QWidget *w : std::as_const( widgets ) )
881 w->setEnabled( state != Qt::Checked );
882
883 if ( state == Qt::Unchecked )
884 {
885 // update widgets state based on current selection
886 selectedChanged( QModelIndex(), QModelIndex() );
887 mItemTreeView->proxyModel()->setShowPrivateLayers( true );
888 }
889 else
890 {
891 mItemTreeView->proxyModel()->setShowPrivateLayers( false );
892 }
893}
894
895void QgsLayoutLegendWidget::composerMapChanged( QgsLayoutItem *item )
896{
897 if ( !mLegend )
898 {
899 return;
900 }
901
902 QgsLayout *layout = mLegend->layout();
903 if ( !layout )
904 {
905 return;
906 }
907
908 QgsLayoutItemMap *map = qobject_cast<QgsLayoutItemMap *>( item );
909 if ( map )
910 {
911 mLegend->beginCommand( tr( "Change Legend Map" ) );
912 mLegend->setLinkedMap( map );
913 mLegend->update();
914 mLegend->endCommand();
915
916 setLegendMapViewData();
917 }
918}
919
920void QgsLayoutLegendWidget::mCheckboxResizeContents_toggled( bool checked )
921{
922 if ( !mLegend )
923 {
924 return;
925 }
926
927 mLegend->beginCommand( tr( "Resize Legend to Contents" ) );
928 mLegend->setResizeToContents( checked );
929 if ( checked )
930 mLegend->adjustBoxSize();
931 mLegend->update();
932 mLegend->endCommand();
933}
934
935void QgsLayoutLegendWidget::mRasterStrokeGroupBox_toggled( bool state )
936{
937 if ( !mLegend )
938 {
939 return;
940 }
941
942 mLegend->beginCommand( tr( "Change Legend Borders" ) );
943 mLegend->setDrawRasterStroke( state );
944 mLegend->adjustBoxSize();
945 mLegend->update();
946 mLegend->endCommand();
947}
948
949void QgsLayoutLegendWidget::mRasterStrokeWidthSpinBox_valueChanged( double d )
950{
951 if ( !mLegend )
952 {
953 return;
954 }
955
956 mLegend->beginCommand( tr( "Resize Legend Borders" ), QgsLayoutItem::UndoLegendRasterStrokeWidth );
957 mLegend->setRasterStrokeWidth( d );
958 mLegend->adjustBoxSize();
959 mLegend->update();
960 mLegend->endCommand();
961}
962
963void QgsLayoutLegendWidget::mRasterStrokeColorButton_colorChanged( const QColor &newColor )
964{
965 if ( !mLegend )
966 {
967 return;
968 }
969
970 mLegend->beginCommand( tr( "Change Legend Border Color" ), QgsLayoutItem::UndoLegendRasterStrokeColor );
971 mLegend->setRasterStrokeColor( newColor );
972 mLegend->update();
973 mLegend->endCommand();
974}
975
976void QgsLayoutLegendWidget::mAddToolButton_clicked()
977{
978 if ( !mLegend )
979 {
980 return;
981 }
982
983 QList<QgsMapLayer *> visibleLayers;
984 if ( mLegend->linkedMap() )
985 {
986 visibleLayers = mLegend->linkedMap()->layersToRender();
987 }
988 if ( visibleLayers.isEmpty() )
989 {
990 // just use current canvas layers as visible layers
991 visibleLayers = mMapCanvas->layers( true );
992 }
993
994 QgsLayoutLegendLayersDialog addDialog( this );
995 addDialog.setVisibleLayers( visibleLayers );
996 if ( addDialog.exec() == QDialog::Accepted )
997 {
998 const QList<QgsMapLayer *> layers = addDialog.selectedLayers();
999 if ( !layers.empty() )
1000 {
1001 mLegend->beginCommand( tr( "Add Legend Item(s)" ) );
1002 for ( QgsMapLayer *layer : layers )
1003 {
1004 mLegend->model()->rootGroup()->addLayer( layer );
1005 }
1006 mLegend->updateLegend();
1007 mLegend->update();
1008 mLegend->endCommand();
1009 }
1010 }
1011}
1012
1013void QgsLayoutLegendWidget::mRemoveToolButton_clicked()
1014{
1015 if ( !mLegend )
1016 {
1017 return;
1018 }
1019
1020 QItemSelectionModel *selectionModel = mItemTreeView->selectionModel();
1021 if ( !selectionModel )
1022 {
1023 return;
1024 }
1025
1026 mLegend->beginCommand( tr( "Remove Legend Item" ) );
1027
1028 QList<QPersistentModelIndex> proxyIndexes;
1029 const QModelIndexList viewSelection = selectionModel->selectedIndexes();
1030 for ( const QModelIndex &index : viewSelection )
1031 proxyIndexes << index;
1032
1033 // first try to remove legend nodes
1034 QHash<QgsLayerTreeLayer *, QList<int>> nodesWithRemoval;
1035 for ( const QPersistentModelIndex &proxyIndex : std::as_const( proxyIndexes ) )
1036 {
1037 if ( QgsLayerTreeModelLegendNode *legendNode = mItemTreeView->index2legendNode( proxyIndex ) )
1038 {
1039 QgsLayerTreeLayer *nodeLayer = legendNode->layerNode();
1040 nodesWithRemoval[nodeLayer].append( _unfilteredLegendNodeIndex( legendNode ) );
1041 }
1042 }
1043 for ( auto it = nodesWithRemoval.constBegin(); it != nodesWithRemoval.constEnd(); ++it )
1044 {
1045 QList<int> toDelete = it.value();
1046 std::sort( toDelete.begin(), toDelete.end(), std::greater<int>() );
1047 QList<int> order = QgsMapLayerLegendUtils::legendNodeOrder( it.key() );
1048
1049 for ( int i : std::as_const( toDelete ) )
1050 {
1051 if ( i >= 0 && i < order.count() )
1052 order.removeAt( i );
1053 }
1054
1056 mItemTreeView->layerTreeModel()->refreshLayerLegend( it.key() );
1057 }
1058
1059 // then remove layer tree nodes
1060 for ( const QPersistentModelIndex &proxyIndex : std::as_const( proxyIndexes ) )
1061 {
1062 if ( proxyIndex.isValid() && mItemTreeView->index2node( proxyIndex ) )
1063 {
1064 const QModelIndex sourceIndex = mItemTreeView->proxyModel()->mapToSource( proxyIndex );
1065 mLegend->model()->removeRow( sourceIndex.row(), sourceIndex.parent() );
1066 }
1067 }
1068
1069 mLegend->updateLegend();
1070 mLegend->update();
1071 mLegend->endCommand();
1072}
1073
1074void QgsLayoutLegendWidget::mEditPushButton_clicked()
1075{
1076 if ( !mLegend )
1077 {
1078 return;
1079 }
1080
1081 QModelIndex idx = mItemTreeView->selectionModel()->currentIndex();
1082 mItemTreeView_doubleClicked( idx );
1083}
1084
1085void QgsLayoutLegendWidget::resetLayerNodeToDefaults()
1086{
1087 if ( !mLegend )
1088 {
1089 return;
1090 }
1091
1092 //get current item
1093 QModelIndex currentIndex = mItemTreeView->currentIndex();
1094 if ( !currentIndex.isValid() )
1095 {
1096 return;
1097 }
1098
1099 QgsLayerTreeLayer *nodeLayer = nullptr;
1100 if ( QgsLayerTreeNode *node = mItemTreeView->index2node( currentIndex ) )
1101 {
1102 if ( QgsLayerTree::isLayer( node ) )
1103 nodeLayer = QgsLayerTree::toLayer( node );
1104 }
1105 if ( QgsLayerTreeModelLegendNode *legendNode = mItemTreeView->index2legendNode( currentIndex ) )
1106 {
1107 nodeLayer = legendNode->layerNode();
1108 }
1109
1110 if ( !nodeLayer )
1111 return;
1112
1113 mLegend->beginCommand( tr( "Update Legend" ) );
1114
1115 const auto constCustomProperties = nodeLayer->customProperties();
1116 for ( const QString &key : constCustomProperties )
1117 {
1118 if ( key.startsWith( QLatin1String( "legend/" ) ) )
1119 nodeLayer->removeCustomProperty( key );
1120 }
1121
1122 nodeLayer->setPatchShape( QgsLegendPatchShape() );
1123 nodeLayer->setPatchSize( QSizeF() );
1124
1125 mItemTreeView->layerTreeModel()->refreshLayerLegend( nodeLayer );
1126
1127 mLegend->updateLegend();
1128 mLegend->update();
1129 mLegend->endCommand();
1130}
1131
1132void QgsLayoutLegendWidget::mCountToolButton_clicked( bool checked )
1133{
1134 if ( !mLegend )
1135 {
1136 return;
1137 }
1138
1139 const QList<QModelIndex> selectedIndexes = mItemTreeView->selectionModel()->selectedIndexes();
1140 if ( selectedIndexes.empty() )
1141 return;
1142
1143 mLegend->beginCommand( tr( "Update Legend" ) );
1144 for ( const QModelIndex &index : selectedIndexes )
1145 {
1146 QgsLayerTreeNode *currentNode = mItemTreeView->index2node( index );
1147 if ( !QgsLayerTree::isLayer( currentNode ) )
1148 continue;
1149
1150 currentNode->setCustomProperty( QStringLiteral( "showFeatureCount" ), checked ? 1 : 0 );
1151 }
1152 mLegend->updateFilterByMap();
1153 mLegend->adjustBoxSize();
1154 mLegend->endCommand();
1155}
1156
1157void QgsLayoutLegendWidget::mFilterByMapCheckBox_toggled( bool checked )
1158{
1159 mLegend->beginCommand( tr( "Update Legend" ) );
1160 mLegend->setLegendFilterByMapEnabled( checked );
1161 mLegend->adjustBoxSize();
1162 mLegend->update();
1163 mLegend->endCommand();
1164}
1165
1166void QgsLayoutLegendWidget::mExpressionFilterButton_toggled( bool checked )
1167{
1168 if ( !mLegend )
1169 {
1170 return;
1171 }
1172
1173 //get current item
1174 QModelIndex currentIndex = mItemTreeView->currentIndex();
1175 if ( !currentIndex.isValid() )
1176 {
1177 return;
1178 }
1179
1180 QgsLayerTreeNode *currentNode = mItemTreeView->currentNode();
1181 if ( !QgsLayerTree::isLayer( currentNode ) )
1182 return;
1183
1184 QgsLayerTreeUtils::setLegendFilterByExpression( *qobject_cast<QgsLayerTreeLayer *>( currentNode ), mExpressionFilterButton->expressionText(), checked );
1185
1186 mLegend->beginCommand( tr( "Update Legend" ) );
1187 mLegend->updateFilterByMap();
1188 mLegend->adjustBoxSize();
1189 mLegend->endCommand();
1190}
1191
1192void QgsLayoutLegendWidget::mLayerExpressionButton_clicked()
1193{
1194 if ( !mLegend )
1195 {
1196 return;
1197 }
1198
1199 QModelIndex currentIndex = mItemTreeView->currentIndex();
1200 if ( !currentIndex.isValid() )
1201 return;
1202
1203 QgsLayerTreeNode *currentNode = mItemTreeView->currentNode();
1204 if ( !QgsLayerTree::isLayer( currentNode ) )
1205 return;
1206
1207 QgsLayerTreeLayer *layerNode = qobject_cast<QgsLayerTreeLayer *>( currentNode );
1208 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layerNode->layer() );
1209
1210 if ( !vl )
1211 return;
1212
1213 QString currentExpression;
1214 if ( layerNode->labelExpression().isEmpty() )
1215 currentExpression = QStringLiteral( "@symbol_label" );
1216 else
1217 currentExpression = layerNode->labelExpression();
1218 QgsExpressionContext legendContext = mLegend->createExpressionContext();
1219 legendContext.appendScope( vl->createExpressionContextScope() );
1220
1221 QStringList highlighted;
1222 if ( QgsLegendModel *model = mLegend->model() )
1223 {
1224 const QList<QgsLayerTreeModelLegendNode *> legendNodes = model->layerLegendNodes( layerNode, false );
1225 if ( !legendNodes.isEmpty() )
1226 {
1227 if ( QgsSymbolLegendNode *symbolNode = qobject_cast<QgsSymbolLegendNode *>( legendNodes.first() ) )
1228 {
1229 legendContext.appendScope( symbolNode->createSymbolScope() );
1230 highlighted << QStringLiteral( "symbol_label" ) << QStringLiteral( "symbol_id" ) << QStringLiteral( "symbol_count" );
1231 }
1232 }
1233 }
1234
1235 legendContext.setHighlightedVariables( highlighted );
1236
1237 // Passing the vector layer to expression dialog exposes the fields, but we still want generic
1238 // layer variables
1240 limitedLayerScope->setFields( QgsFields() );
1241 legendContext.appendScope( limitedLayerScope );
1242
1243 QgsExpressionBuilderDialog expressiondialog( nullptr, currentExpression, nullptr, QStringLiteral( "generic" ), legendContext );
1244 if ( expressiondialog.exec() )
1245 {
1246 layerNode->setLabelExpression( expressiondialog.expressionText() );
1247 mItemTreeView->layerTreeModel()->refreshLayerLegend( layerNode );
1248 }
1249
1250 mLegend->beginCommand( tr( "Update Legend" ) );
1251 mLegend->refresh();
1252 mLegend->adjustBoxSize();
1253 mLegend->endCommand();
1254}
1255
1256void QgsLayoutLegendWidget::mUpdateAllPushButton_clicked()
1257{
1258 updateLegend();
1259}
1260
1261void QgsLayoutLegendWidget::mAddGroupToolButton_clicked()
1262{
1263 if ( mLegend )
1264 {
1265 mLegend->beginCommand( tr( "Add Legend Group" ) );
1266 mLegend->model()->rootGroup()->addGroup( tr( "Group" ) );
1267 mLegend->updateLegend();
1268 mLegend->update();
1269 mLegend->endCommand();
1270 }
1271}
1272
1273void QgsLayoutLegendWidget::mFilterLegendByAtlasCheckBox_toggled( bool toggled )
1274{
1275 Q_UNUSED( toggled )
1276 if ( mLegend )
1277 {
1278 mLegend->setLegendFilterOutAtlas( toggled );
1279 // force update of legend when in preview mode
1280 mLegend->refresh();
1281 }
1282}
1283
1284void QgsLayoutLegendWidget::updateLegend()
1285{
1286 if ( mLegend )
1287 {
1288 mLegend->beginCommand( tr( "Update Legend" ) );
1289
1290 // this will reset the model completely, losing any changes
1291 mLegend->setAutoUpdateModel( true );
1292 mLegend->setAutoUpdateModel( false );
1293 mLegend->update();
1294 mLegend->endCommand();
1295 }
1296}
1297
1298void QgsLayoutLegendWidget::setReportTypeString( const QString &string )
1299{
1300 mFilterLegendByAtlasCheckBox->setText( tr( "Only show items inside current %1 feature" ).arg( string ) );
1301 mFilterLegendByAtlasCheckBox->setToolTip( tr( "Filter out legend elements that lie outside the current %1 feature." ).arg( string ) );
1302}
1303
1304QgsExpressionContext QgsLayoutLegendWidget::createExpressionContext() const
1305{
1306 QgsExpressionContext context = mLegend->createExpressionContext();
1307 return context;
1308}
1309
1310bool QgsLayoutLegendWidget::setNewItem( QgsLayoutItem *item )
1311{
1313 return false;
1314
1315 if ( mLegend )
1316 {
1317 disconnect( mLegend, &QgsLayoutObject::changed, this, &QgsLayoutLegendWidget::setGuiElements );
1318 }
1319
1320 mLegend = qobject_cast<QgsLayoutItemLegend *>( item );
1321 mItemPropertiesWidget->setItem( mLegend );
1322
1323 if ( mMapFilteringWidget )
1324 mMapFilteringWidget->setItem( mLegend );
1325
1326 if ( mLegend )
1327 {
1328 mItemTreeView->setModel( mLegend->model() );
1329 connect( mLegend, &QgsLayoutObject::changed, this, &QgsLayoutLegendWidget::setGuiElements );
1330 }
1331
1332 setGuiElements();
1333
1334 return true;
1335}
1336
1337void QgsLayoutLegendWidget::blockAllSignals( bool b )
1338{
1339 mTitleLineEdit->blockSignals( b );
1340 mTitleAlignCombo->blockSignals( b );
1341 mItemTreeView->blockSignals( b );
1342 mCheckBoxAutoUpdate->blockSignals( b );
1343 mMapComboBox->blockSignals( b );
1344 mFilterByMapCheckBox->blockSignals( b );
1345 mColumnCountSpinBox->blockSignals( b );
1346 mSplitLayerCheckBox->blockSignals( b );
1347 mEqualColumnWidthCheckBox->blockSignals( b );
1348 mSymbolWidthSpinBox->blockSignals( b );
1349 mSymbolHeightSpinBox->blockSignals( b );
1350 mMaxSymbolSizeSpinBox->blockSignals( b );
1351 mMinSymbolSizeSpinBox->blockSignals( b );
1352 mGroupSpaceSpinBox->blockSignals( b );
1353 mGroupIndentSpinBox->blockSignals( b );
1354 mSubgroupIndentSpinBox->blockSignals( b );
1355 mSpaceBelowGroupHeadingSpinBox->blockSignals( b );
1356 mGroupSideSpinBox->blockSignals( b );
1357 mSpaceBelowSubgroupHeadingSpinBox->blockSignals( b );
1358 mSubgroupSideSpinBox->blockSignals( b );
1359 mLayerSpaceSpinBox->blockSignals( b );
1360 mSymbolSpaceSpinBox->blockSignals( b );
1361 mSymbolSideSpaceSpinBox->blockSignals( b );
1362 mIconLabelSpaceSpinBox->blockSignals( b );
1363 mBoxSpaceSpinBox->blockSignals( b );
1364 mColumnSpaceSpinBox->blockSignals( b );
1365 mRasterStrokeGroupBox->blockSignals( b );
1366 mRasterStrokeColorButton->blockSignals( b );
1367 mRasterStrokeWidthSpinBox->blockSignals( b );
1368 mWmsLegendWidthSpinBox->blockSignals( b );
1369 mWmsLegendHeightSpinBox->blockSignals( b );
1370 mCheckboxResizeContents->blockSignals( b );
1371 mTitleSpaceBottomSpinBox->blockSignals( b );
1372 mFilterLegendByAtlasCheckBox->blockSignals( b );
1373 mTitleFontButton->blockSignals( b );
1374 mGroupFontButton->blockSignals( b );
1375 mLayerFontButton->blockSignals( b );
1376 mItemFontButton->blockSignals( b );
1377 mWrapCharLineEdit->blockSignals( b );
1378 mMaxWidthSpinBox->blockSignals( b );
1379}
1380
1381void QgsLayoutLegendWidget::selectedChanged( const QModelIndex &current, const QModelIndex &previous )
1382{
1383 Q_UNUSED( current )
1384 Q_UNUSED( previous )
1385
1386 mLayerExpressionButton->setEnabled( false );
1387
1388 if ( mLegend && mLegend->autoUpdateModel() )
1389 {
1390 QgsLayerTreeNode *currentNode = mItemTreeView->currentNode();
1391 if ( !QgsLayerTree::isLayer( currentNode ) )
1392 return;
1393
1394 QgsLayerTreeLayer *currentLayerNode = QgsLayerTree::toLayer( currentNode );
1395 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( currentLayerNode->layer() );
1396 if ( !vl )
1397 return;
1398
1399 mLayerExpressionButton->setEnabled( true );
1400 return;
1401 }
1402
1403 mCountToolButton->setChecked( false );
1404 mCountToolButton->setEnabled( false );
1405
1406
1407 mExpressionFilterButton->blockSignals( true );
1408 mExpressionFilterButton->setChecked( false );
1409 mExpressionFilterButton->setEnabled( false );
1410 mExpressionFilterButton->blockSignals( false );
1411
1412 QgsLayerTreeNode *currentNode = mItemTreeView->currentNode();
1413 if ( !QgsLayerTree::isLayer( currentNode ) )
1414 return;
1415
1416 QgsLayerTreeLayer *currentLayerNode = QgsLayerTree::toLayer( currentNode );
1417 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( currentLayerNode->layer() );
1418 if ( !vl )
1419 return;
1420
1421 mCountToolButton->setChecked( currentNode->customProperty( QStringLiteral( "showFeatureCount" ), 0 ).toInt() );
1422 mCountToolButton->setEnabled( true );
1423 mLayerExpressionButton->setEnabled( true );
1424
1425 bool exprEnabled;
1426 QString expr = QgsLayerTreeUtils::legendFilterByExpression( *qobject_cast<QgsLayerTreeLayer *>( currentNode ), &exprEnabled );
1427 mExpressionFilterButton->blockSignals( true );
1428 mExpressionFilterButton->setExpressionText( expr );
1429 mExpressionFilterButton->setVectorLayer( vl );
1430 mExpressionFilterButton->setEnabled( true );
1431 mExpressionFilterButton->setChecked( exprEnabled );
1432 mExpressionFilterButton->blockSignals( false );
1433}
1434
1435void QgsLayoutLegendWidget::setCurrentNodeStyleFromAction()
1436{
1437 QAction *a = qobject_cast<QAction *>( sender() );
1438 if ( !a || !mItemTreeView->currentNode() )
1439 return;
1440
1441 QgsLegendRenderer::setNodeLegendStyle( mItemTreeView->currentNode(), static_cast<Qgis::LegendComponent>( a->data().toInt() ) );
1442 mLegend->update();
1443}
1444
1445void QgsLayoutLegendWidget::setLegendMapViewData()
1446{
1447 if ( mLegend->linkedMap() )
1448 {
1449 const int dpi = QgsPainting::qtDefaultDpiX();
1451 measurementConverter.setDpi( dpi );
1452 double mapWidth = measurementConverter.convert( mLegend->linkedMap()->sizeWithUnits(), Qgis::LayoutUnit::Pixels ).width();
1453 double mapHeight = measurementConverter.convert( mLegend->linkedMap()->sizeWithUnits(), Qgis::LayoutUnit::Pixels ).height();
1454 double mapUnitsPerPixelX = mLegend->linkedMap()->extent().width() / mapWidth;
1455 double mapUnitsPerPixelY = mLegend->linkedMap()->extent().height() / mapHeight;
1456 mLegend->model()->setLegendMapViewData( ( mapUnitsPerPixelX > mapUnitsPerPixelY ? mapUnitsPerPixelX : mapUnitsPerPixelY ), dpi, mLegend->linkedMap()->scale() );
1457 }
1458}
1459
1460void QgsLayoutLegendWidget::updateFilterLegendByAtlasButton()
1461{
1462 if ( QgsLayoutAtlas *atlas = layoutAtlas() )
1463 {
1464 mFilterLegendByAtlasCheckBox->setEnabled( atlas->enabled() && mLegend->layout()->reportContext().layer() && mLegend->layout()->reportContext().layer()->geometryType() == Qgis::GeometryType::Polygon );
1465 }
1466}
1467
1468void QgsLayoutLegendWidget::mItemTreeView_doubleClicked( const QModelIndex &idx )
1469{
1470 if ( !mLegend || !idx.isValid() )
1471 {
1472 return;
1473 }
1474
1475 if ( mLegend->autoUpdateModel() )
1476 return;
1477
1478 QgsLayerTreeNode *currentNode = mItemTreeView->index2node( idx );
1479 QgsLayerTreeModelLegendNode *legendNode = mItemTreeView->index2legendNode( idx );
1480
1481 int originalIndex = -1;
1482 if ( legendNode )
1483 {
1484 originalIndex = _originalLegendNodeIndex( legendNode );
1485 currentNode = legendNode->layerNode();
1486 }
1487
1488 QgsLayoutLegendNodeWidget *widget = new QgsLayoutLegendNodeWidget( mLegend, currentNode, legendNode, originalIndex );
1489 openPanel( widget );
1490}
1491
1492
1493//
1494// QgsComposerLegendMenuProvider
1495//
1496
1497QgsLayoutLegendMenuProvider::QgsLayoutLegendMenuProvider( QgsLayerTreeView *view, QgsLayoutLegendWidget *w )
1498 : mView( view )
1499 , mWidget( w )
1500{}
1501
1502QMenu *QgsLayoutLegendMenuProvider::createContextMenu()
1503{
1504 if ( !mView->currentNode() )
1505 return nullptr;
1506
1507 if ( mWidget->legend()->autoUpdateModel() )
1508 return nullptr; // no editing allowed
1509
1510 QMenu *menu = new QMenu();
1511
1512 if ( QgsLayerTree::isLayer( mView->currentNode() ) )
1513 {
1514 menu->addAction( QObject::tr( "Reset to Defaults" ), mWidget, &QgsLayoutLegendWidget::resetLayerNodeToDefaults );
1515 menu->addSeparator();
1516 }
1517
1518 Qgis::LegendComponent currentStyle = QgsLegendRenderer::nodeLegendStyle( mView->currentNode(), mView->layerTreeModel() );
1519
1520 QActionGroup *styleGroup = new QActionGroup { mWidget };
1521 styleGroup->setExclusive( true );
1522
1523 QList<Qgis::LegendComponent> lst;
1525 for ( Qgis::LegendComponent style : std::as_const( lst ) )
1526 {
1527 QAction *action = menu->addAction( QgsLegendStyle::styleLabel( style ), mWidget, &QgsLayoutLegendWidget::setCurrentNodeStyleFromAction );
1528 action->setActionGroup( styleGroup );
1529 action->setCheckable( true );
1530 action->setChecked( currentStyle == style );
1531 action->setData( static_cast<int>( style ) );
1532 }
1533
1534 return menu;
1535}
1536
1537//
1538// QgsLayoutLegendNodeWidget
1539//
1540QgsLayoutLegendNodeWidget::QgsLayoutLegendNodeWidget( QgsLayoutItemLegend *legend, QgsLayerTreeNode *node, QgsLayerTreeModelLegendNode *legendNode, int originalLegendNodeIndex, QWidget *parent )
1541 : QgsPanelWidget( parent )
1542 , mLegend( legend )
1543 , mNode( node )
1544 , mLayer( qobject_cast<QgsLayerTreeLayer *>( node ) )
1545 , mLegendNode( legendNode )
1546 , mOriginalLegendNodeIndex( originalLegendNodeIndex )
1547{
1548 setupUi( this );
1549 setPanelTitle( tr( "Legend Item Properties" ) );
1550
1551 // auto close panel if layer removed
1552 connect( node, &QObject::destroyed, this, &QgsPanelWidget::acceptPanel );
1553
1554 mColumnSplitBehaviorComboBox->addItem( tr( "Follow Legend Default" ), QgsLayerTreeLayer::UseDefaultLegendSetting );
1555 mColumnSplitBehaviorComboBox->addItem( tr( "Allow Splitting Over Columns" ), QgsLayerTreeLayer::AllowSplittingLegendNodesOverMultipleColumns );
1556 mColumnSplitBehaviorComboBox->addItem( tr( "Prevent Splitting Over Columns" ), QgsLayerTreeLayer::PreventSplittingLegendNodesOverMultipleColumns );
1557
1558 QString currentLabel;
1559 if ( mLegendNode )
1560 {
1561 currentLabel = mLegendNode->data( Qt::EditRole ).toString();
1562 mColumnBreakBeforeCheckBox->setChecked( mLegendNode->columnBreak() );
1563 }
1564 else if ( mLayer )
1565 {
1566 currentLabel = mLayer->name();
1567 QVariant v = mLayer->customProperty( QStringLiteral( "legend/title-label" ) );
1568 if ( !QgsVariantUtils::isNull( v ) )
1569 currentLabel = v.toString();
1570 mColumnBreakBeforeCheckBox->setChecked( mLayer->customProperty( QStringLiteral( "legend/column-break" ) ).toInt() );
1571
1572 mColumnSplitBehaviorComboBox->setCurrentIndex( mColumnSplitBehaviorComboBox->findData( mLayer->legendSplitBehavior() ) );
1573 }
1574 else
1575 {
1576 currentLabel = QgsLayerTree::toGroup( mNode )->name();
1577 mColumnBreakBeforeCheckBox->setChecked( mNode->customProperty( QStringLiteral( "legend/column-break" ) ).toInt() );
1578 }
1579
1580 mWidthSpinBox->setClearValue( 0, tr( "Default" ) );
1581 mHeightSpinBox->setClearValue( 0, tr( "Default" ) );
1582 mWidthSpinBox->setVisible( mLegendNode || mLayer );
1583 mHeightSpinBox->setVisible( mLegendNode || mLayer );
1584 mPatchGroup->setVisible( mLegendNode || mLayer );
1585 mPatchWidthLabel->setVisible( mLegendNode || mLayer );
1586 mPatchHeightLabel->setVisible( mLegendNode || mLayer );
1587 mCustomSymbolCheckBox->setVisible( mLegendNode || mLegend->model()->legendNodeEmbeddedInParent( mLayer ) );
1588 mColumnSplitLabel->setVisible( mLayer && !mLegendNode );
1589 mColumnSplitBehaviorComboBox->setVisible( mLayer && !mLegendNode );
1590 if ( mLegendNode )
1591 {
1592 mWidthSpinBox->setValue( mLegendNode->userPatchSize().width() );
1593 mHeightSpinBox->setValue( mLegendNode->userPatchSize().height() );
1594 }
1595 else if ( mLayer )
1596 {
1597 mWidthSpinBox->setValue( mLayer->patchSize().width() );
1598 mHeightSpinBox->setValue( mLayer->patchSize().height() );
1599 }
1600
1601 mCustomSymbolCheckBox->setChecked( false );
1602
1603 QgsLegendPatchShape patchShape;
1604 if ( QgsSymbolLegendNode *symbolLegendNode = dynamic_cast<QgsSymbolLegendNode *>( mLegendNode ) )
1605 {
1606 patchShape = symbolLegendNode->patchShape();
1607
1608 std::unique_ptr<QgsSymbol> customSymbol( symbolLegendNode->customSymbol() ? symbolLegendNode->customSymbol()->clone() : nullptr );
1609 mCustomSymbolCheckBox->setChecked( customSymbol.get() );
1610 if ( customSymbol )
1611 {
1612 mPatchShapeButton->setPreviewSymbol( customSymbol->clone() );
1613 mCustomSymbolButton->setSymbolType( customSymbol->type() );
1614 mCustomSymbolButton->setSymbol( customSymbol.release() );
1615 }
1616 else if ( symbolLegendNode->symbol() )
1617 {
1618 mPatchShapeButton->setPreviewSymbol( symbolLegendNode->symbol()->clone() );
1619 mCustomSymbolButton->setSymbolType( symbolLegendNode->symbol()->type() );
1620 mCustomSymbolButton->setSymbol( symbolLegendNode->symbol()->clone() );
1621 }
1622 }
1623 else if ( !mLegendNode && mLayer )
1624 {
1625 patchShape = mLayer->patchShape();
1626 if ( QgsSymbolLegendNode *symbolLegendNode = dynamic_cast<QgsSymbolLegendNode *>( mLegend->model()->legendNodeEmbeddedInParent( mLayer ) ) )
1627 {
1628 if ( QgsSymbol *customSymbol = symbolLegendNode->customSymbol() )
1629 {
1630 mCustomSymbolCheckBox->setChecked( true );
1631 mPatchShapeButton->setPreviewSymbol( customSymbol->clone() );
1632 mCustomSymbolButton->setSymbolType( customSymbol->type() );
1633 mCustomSymbolButton->setSymbol( customSymbol->clone() );
1634 }
1635 else
1636 {
1637 mPatchShapeButton->setPreviewSymbol( symbolLegendNode->symbol()->clone() );
1638 mCustomSymbolButton->setSymbolType( symbolLegendNode->symbol()->type() );
1639 mCustomSymbolButton->setSymbol( symbolLegendNode->symbol()->clone() );
1640 }
1641 }
1642 }
1643
1644 if ( mLayer && mLayer->layer() && mLayer->layer()->type() == Qgis::LayerType::Vector )
1645 {
1646 switch ( qobject_cast<QgsVectorLayer *>( mLayer->layer() )->geometryType() )
1647 {
1649 mPatchShapeButton->setSymbolType( Qgis::SymbolType::Fill );
1650 break;
1651
1653 mPatchShapeButton->setSymbolType( Qgis::SymbolType::Line );
1654 break;
1655
1657 mPatchShapeButton->setSymbolType( Qgis::SymbolType::Marker );
1658 break;
1659
1660 default:
1661 mPatchShapeLabel->hide();
1662 mPatchShapeButton->hide();
1663 break;
1664 }
1665 if ( !patchShape.isNull() )
1666 mPatchShapeButton->setShape( patchShape );
1667 }
1668 else if ( QgsSymbolLegendNode *symbolLegendNode = dynamic_cast<QgsSymbolLegendNode *>( mLegendNode ) )
1669 {
1670 if ( symbolLegendNode->symbol() )
1671 {
1672 mPatchShapeButton->setSymbolType( symbolLegendNode->symbol()->type() );
1673 }
1674 else
1675 {
1676 mPatchShapeLabel->hide();
1677 mPatchShapeButton->hide();
1678 }
1679 }
1680 else
1681 {
1682 mPatchShapeLabel->hide();
1683 mPatchShapeButton->hide();
1684 }
1685
1686 if ( QgsColorRampLegendNode *colorRampNode = dynamic_cast<QgsColorRampLegendNode *>( mLegendNode ) )
1687 {
1688 mLabelGroup->hide();
1689 mColorRampLegendWidget->setSettings( colorRampNode->settings() );
1690 }
1691 else
1692 {
1693 mColorRampLegendWidget->hide();
1694 }
1695
1696 if ( mLegendNode )
1697 {
1698 switch ( static_cast<QgsLayerTreeModelLegendNode::NodeTypes>( mLegendNode->data( static_cast<int>( QgsLayerTreeModelLegendNode::CustomRole::NodeType ) ).toInt() ) )
1699 {
1706 mCustomSymbolCheckBox->hide();
1707 break;
1708
1711 break;
1712 }
1713 }
1714
1715 mLabelEdit->setPlainText( currentLabel );
1716 connect( mLabelEdit, &QPlainTextEdit::textChanged, this, &QgsLayoutLegendNodeWidget::labelChanged );
1717 connect( mPatchShapeButton, &QgsLegendPatchShapeButton::changed, this, &QgsLayoutLegendNodeWidget::patchChanged );
1718 connect( mInsertExpressionButton, &QPushButton::clicked, this, &QgsLayoutLegendNodeWidget::insertExpression );
1719
1720 connect( mWidthSpinBox, qOverload<double>( &QgsDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendNodeWidget::sizeChanged );
1721 connect( mHeightSpinBox, qOverload<double>( &QgsDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendNodeWidget::sizeChanged );
1722
1723 connect( mCustomSymbolCheckBox, &QGroupBox::toggled, this, &QgsLayoutLegendNodeWidget::customSymbolChanged );
1724 connect( mCustomSymbolButton, &QgsSymbolButton::changed, this, &QgsLayoutLegendNodeWidget::customSymbolChanged );
1725
1726 connect( mColumnBreakBeforeCheckBox, &QCheckBox::toggled, this, &QgsLayoutLegendNodeWidget::columnBreakToggled );
1727
1728 connect( mColumnSplitBehaviorComboBox, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsLayoutLegendNodeWidget::columnSplitChanged );
1729
1730 connect( mColorRampLegendWidget, &QgsColorRampLegendNodeWidget::widgetChanged, this, &QgsLayoutLegendNodeWidget::colorRampLegendChanged );
1731
1732 connectChildPanel( mColorRampLegendWidget );
1733}
1734
1735void QgsLayoutLegendNodeWidget::setDockMode( bool dockMode )
1736{
1737 mColorRampLegendWidget->setDockMode( dockMode );
1738 QgsPanelWidget::setDockMode( dockMode );
1739}
1740
1741void QgsLayoutLegendNodeWidget::labelChanged()
1742{
1743 mLegend->beginCommand( tr( "Edit Legend Item" ), QgsLayoutItem::UndoLegendText );
1744
1745 const QString label = mLabelEdit->toPlainText();
1746 if ( QgsLayerTree::isGroup( mNode ) )
1747 {
1748 QgsLayerTree::toGroup( mNode )->setName( label );
1749 }
1750 else if ( mLegendNode )
1751 {
1752 QgsMapLayerLegendUtils::setLegendNodeUserLabel( mLayer, mOriginalLegendNodeIndex, label );
1753 mLegend->model()->refreshLayerLegend( mLayer );
1754 }
1755 else if ( mLayer )
1756 {
1757 mLayer->setCustomProperty( QStringLiteral( "legend/title-label" ), label );
1758
1759 // force update of label of the legend node with embedded icon (a bit clumsy i know)
1760 if ( QgsLayerTreeModelLegendNode *embeddedNode = mLegend->model()->legendNodeEmbeddedInParent( mLayer ) )
1761 embeddedNode->setUserLabel( QString() );
1762 }
1763
1764 mLegend->adjustBoxSize();
1765 mLegend->invalidateCache();
1766 mLegend->update();
1767 mLegend->endCommand();
1768}
1769
1770void QgsLayoutLegendNodeWidget::patchChanged()
1771{
1772 mLegend->beginCommand( tr( "Edit Legend Item" ) );
1773
1774 QgsLegendPatchShape shape = mPatchShapeButton->shape();
1775 if ( mLegendNode )
1776 {
1777 QgsMapLayerLegendUtils::setLegendNodePatchShape( mLayer, mOriginalLegendNodeIndex, shape );
1778 mLegend->model()->refreshLayerLegend( mLayer );
1779 }
1780 else if ( mLayer )
1781 {
1782 mLayer->setPatchShape( shape );
1783 const QList<QgsLayerTreeModelLegendNode *> layerLegendNodes = mLegend->model()->layerLegendNodes( mLayer, false );
1784 for ( QgsLayerTreeModelLegendNode *node : layerLegendNodes )
1785 {
1786 QgsMapLayerLegendUtils::setLegendNodePatchShape( mLayer, _originalLegendNodeIndex( node ), shape );
1787 }
1788 mLegend->model()->refreshLayerLegend( mLayer );
1789 }
1790
1791 mLegend->adjustBoxSize();
1792 mLegend->update();
1793 mLegend->endCommand();
1794}
1795
1796void QgsLayoutLegendNodeWidget::insertExpression()
1797{
1798 if ( !mLegend )
1799 return;
1800
1801 QString expression = QgsExpressionFinder::findAndSelectActiveExpression( mLabelEdit );
1802
1803 // use the atlas coverage layer, if any
1804 QgsVectorLayer *layer = mLegend->layout() ? mLegend->layout()->reportContext().layer() : nullptr;
1805
1806 QgsExpressionContext context = mLegend->createExpressionContext();
1807
1808 if ( mLayer && mLayer->layer() )
1809 {
1810 context.appendScope( QgsExpressionContextUtils::layerScope( mLayer->layer() ) );
1811 }
1812
1813 context.setHighlightedVariables( QStringList() << QStringLiteral( "legend_title" ) << QStringLiteral( "legend_column_count" ) << QStringLiteral( "legend_split_layers" ) << QStringLiteral( "legend_wrap_string" ) << QStringLiteral( "legend_filter_by_map" ) << QStringLiteral( "legend_filter_out_atlas" ) );
1814
1815 QgsExpressionBuilderDialog exprDlg( layer, expression, this, QStringLiteral( "generic" ), context );
1816
1817 exprDlg.setWindowTitle( tr( "Insert Expression" ) );
1818 if ( exprDlg.exec() == QDialog::Accepted )
1819 {
1820 expression = exprDlg.expressionText();
1821 if ( !expression.isEmpty() )
1822 {
1823 mLegend->beginCommand( tr( "Insert expression" ) );
1824 mLabelEdit->insertPlainText( "[%" + expression.trimmed() + "%]" );
1825 mLegend->endCommand();
1826 }
1827 }
1828}
1829
1830void QgsLayoutLegendNodeWidget::sizeChanged( double )
1831{
1832 mLegend->beginCommand( tr( "Edit Legend Item" ) );
1833 const QSizeF size = QSizeF( mWidthSpinBox->value(), mHeightSpinBox->value() );
1834
1835 if ( mLegendNode )
1836 {
1837 QgsMapLayerLegendUtils::setLegendNodeSymbolSize( mLayer, mOriginalLegendNodeIndex, size );
1838 mLegend->model()->refreshLayerLegend( mLayer );
1839 }
1840 else if ( mLayer )
1841 {
1842 mLayer->setPatchSize( size );
1843 const QList<QgsLayerTreeModelLegendNode *> layerLegendNodes = mLegend->model()->layerLegendNodes( mLayer, false );
1844 for ( QgsLayerTreeModelLegendNode *node : layerLegendNodes )
1845 {
1846 QgsMapLayerLegendUtils::setLegendNodeSymbolSize( mLayer, _originalLegendNodeIndex( node ), size );
1847 }
1848 mLegend->model()->refreshLayerLegend( mLayer );
1849 }
1850
1851 mLegend->adjustBoxSize();
1852 mLegend->update();
1853 mLegend->endCommand();
1854}
1855
1856void QgsLayoutLegendNodeWidget::customSymbolChanged()
1857{
1858 mLegend->beginCommand( tr( "Edit Legend Item" ) );
1859
1860 if ( mCustomSymbolCheckBox->isChecked() )
1861 {
1862 if ( mLegendNode )
1863 {
1864 QgsMapLayerLegendUtils::setLegendNodeCustomSymbol( mLayer, mOriginalLegendNodeIndex, mCustomSymbolButton->symbol() );
1865 mLegend->model()->refreshLayerLegend( mLayer );
1866 }
1867 else if ( mLayer )
1868 {
1869 const QList<QgsLayerTreeModelLegendNode *> layerLegendNodes = mLegend->model()->layerLegendNodes( mLayer, false );
1870 for ( QgsLayerTreeModelLegendNode *node : layerLegendNodes )
1871 {
1872 QgsMapLayerLegendUtils::setLegendNodeCustomSymbol( mLayer, _originalLegendNodeIndex( node ), mCustomSymbolButton->symbol() );
1873 }
1874 mLegend->model()->refreshLayerLegend( mLayer );
1875 }
1876 }
1877 else
1878 {
1879 if ( mLegendNode )
1880 {
1881 QgsMapLayerLegendUtils::setLegendNodeCustomSymbol( mLayer, mOriginalLegendNodeIndex, nullptr );
1882 mLegend->model()->refreshLayerLegend( mLayer );
1883 }
1884 else if ( mLayer )
1885 {
1886 const QList<QgsLayerTreeModelLegendNode *> layerLegendNodes = mLegend->model()->layerLegendNodes( mLayer, false );
1887 for ( QgsLayerTreeModelLegendNode *node : layerLegendNodes )
1888 {
1889 QgsMapLayerLegendUtils::setLegendNodeCustomSymbol( mLayer, _originalLegendNodeIndex( node ), nullptr );
1890 }
1891 mLegend->model()->refreshLayerLegend( mLayer );
1892 }
1893 }
1894
1895 mLegend->adjustBoxSize();
1896 mLegend->update();
1897 mLegend->endCommand();
1898}
1899
1900void QgsLayoutLegendNodeWidget::colorRampLegendChanged()
1901{
1902 mLegend->beginCommand( tr( "Edit Legend Item" ) );
1903
1904 QgsColorRampLegendNodeSettings settings = mColorRampLegendWidget->settings();
1905 QgsMapLayerLegendUtils::setLegendNodeColorRampSettings( mLayer, mOriginalLegendNodeIndex, &settings );
1906 mLegend->model()->refreshLayerLegend( mLayer );
1907
1908 mLegend->adjustBoxSize();
1909 mLegend->update();
1910 mLegend->endCommand();
1911}
1912
1913void QgsLayoutLegendNodeWidget::columnBreakToggled( bool checked )
1914{
1915 mLegend->beginCommand( tr( "Edit Legend Columns" ) );
1916
1917 if ( mLegendNode )
1918 {
1919 QgsMapLayerLegendUtils::setLegendNodeColumnBreak( mLayer, mOriginalLegendNodeIndex, checked );
1920 mLegend->model()->refreshLayerLegend( mLayer );
1921 }
1922 else if ( mLayer )
1923 {
1924 mLayer->setCustomProperty( QStringLiteral( "legend/column-break" ), QString( checked ? '1' : '0' ) );
1925 }
1926 else if ( mNode )
1927 {
1928 mNode->setCustomProperty( QStringLiteral( "legend/column-break" ), QString( checked ? '1' : '0' ) );
1929 }
1930
1931 mLegend->adjustBoxSize();
1932 mLegend->update();
1933 mLegend->endCommand();
1934}
1935
1936void QgsLayoutLegendNodeWidget::columnSplitChanged()
1937{
1938 mLegend->beginCommand( tr( "Edit Legend Columns" ) );
1939
1940 if ( mLayer && !mLegendNode )
1941 {
1942 mLayer->setLegendSplitBehavior( static_cast<QgsLayerTreeLayer::LegendNodesSplitBehavior>( mColumnSplitBehaviorComboBox->currentData().toInt() ) );
1943 }
1944
1945 mLegend->adjustBoxSize();
1946 mLegend->update();
1947 mLegend->endCommand();
1948}
1949
1950//
1951// QgsLayoutLegendMapFilteringWidget
1952//
1953
1954QgsLayoutLegendMapFilteringWidget::QgsLayoutLegendMapFilteringWidget( QgsLayoutItemLegend *legend )
1955 : QgsLayoutItemBaseWidget( nullptr, legend )
1956 , mLegendItem( legend )
1957{
1958 setupUi( this );
1959 setPanelTitle( tr( "Legend Filtering" ) );
1960
1961 setNewItem( legend );
1962}
1963
1964bool QgsLayoutLegendMapFilteringWidget::setNewItem( QgsLayoutItem *item )
1965{
1967 return false;
1968
1969 if ( mLegendItem )
1970 {
1971 disconnect( mLegendItem, &QgsLayoutObject::changed, this, &QgsLayoutLegendMapFilteringWidget::updateGuiElements );
1972 }
1973
1974 mLegendItem = qobject_cast<QgsLayoutItemLegend *>( item );
1975
1976 if ( mLegendItem )
1977 {
1978 connect( mLegendItem, &QgsLayoutObject::changed, this, &QgsLayoutLegendMapFilteringWidget::updateGuiElements );
1979 }
1980
1981 updateGuiElements();
1982
1983 return true;
1984}
1985
1986void QgsLayoutLegendMapFilteringWidget::updateGuiElements()
1987{
1988 if ( mBlockUpdates )
1989 return;
1990
1991 mBlockUpdates = true;
1992
1993 if ( mFilterMapItemsListView->model() )
1994 {
1995 QAbstractItemModel *oldModel = mFilterMapItemsListView->model();
1996 mFilterMapItemsListView->setModel( nullptr );
1997 oldModel->deleteLater();
1998 }
1999
2000 QgsLayoutLegendMapFilteringModel *model = new QgsLayoutLegendMapFilteringModel( mLegendItem, mLegendItem->layout()->itemsModel(), mFilterMapItemsListView );
2001 mFilterMapItemsListView->setModel( model );
2002
2003 mBlockUpdates = false;
2004}
2005
2006//
2007// QgsLayoutLegendMapFilteringModel
2008//
2009
2010QgsLayoutLegendMapFilteringModel::QgsLayoutLegendMapFilteringModel( QgsLayoutItemLegend *legend, QgsLayoutModel *layoutModel, QObject *parent )
2011 : QSortFilterProxyModel( parent )
2012 , mLayoutModel( layoutModel )
2013 , mLegendItem( legend )
2014{
2015 setSourceModel( layoutModel );
2016}
2017
2018int QgsLayoutLegendMapFilteringModel::columnCount( const QModelIndex & ) const
2019{
2020 return 1;
2021}
2022
2023QVariant QgsLayoutLegendMapFilteringModel::data( const QModelIndex &i, int role ) const
2024{
2025 if ( !i.isValid() )
2026 return QVariant();
2027
2028 if ( i.column() != 0 )
2029 return QVariant();
2030
2031 const QModelIndex sourceIndex = mapToSource( index( i.row(), QgsLayoutModel::ItemId, i.parent() ) );
2032
2033 QgsLayoutItemMap *mapItem = qobject_cast<QgsLayoutItemMap *>( mLayoutModel->itemFromIndex( mapToSource( i ) ) );
2034 if ( !mapItem )
2035 {
2036 return QVariant();
2037 }
2038
2039 switch ( role )
2040 {
2041 case Qt::CheckStateRole:
2042 switch ( i.column() )
2043 {
2044 case 0:
2045 {
2046 if ( !mLegendItem )
2047 return Qt::Unchecked;
2048
2049 return mLegendItem->filterByMapItems().contains( mapItem ) ? Qt::Checked : Qt::Unchecked;
2050 }
2051
2052 default:
2053 return QVariant();
2054 }
2055
2056 default:
2057 return mLayoutModel->data( sourceIndex, role );
2058 }
2059}
2060
2061bool QgsLayoutLegendMapFilteringModel::setData( const QModelIndex &index, const QVariant &value, int role )
2062{
2063 Q_UNUSED( role )
2064
2065 if ( !index.isValid() )
2066 return false;
2067
2068 QgsLayoutItemMap *mapItem = qobject_cast<QgsLayoutItemMap *>( mLayoutModel->itemFromIndex( mapToSource( index ) ) );
2069 if ( !mapItem || !mLegendItem )
2070 {
2071 return false;
2072 }
2073
2074 mLegendItem->layout()->undoStack()->beginCommand( mLegendItem, tr( "Change Legend Linked Maps" ) );
2075
2076 QList<QgsLayoutItemMap *> linkedMaps = mLegendItem->filterByMapItems();
2077 if ( value.toBool() )
2078 {
2079 if ( !linkedMaps.contains( mapItem ) )
2080 {
2081 linkedMaps.append( mapItem );
2082 mLegendItem->setFilterByMapItems( linkedMaps );
2083 }
2084 }
2085 else
2086 {
2087 linkedMaps.removeAll( mapItem );
2088 mLegendItem->setFilterByMapItems( linkedMaps );
2089 }
2090 emit dataChanged( index, index, QVector<int>() << role );
2091
2092 mLegendItem->layout()->undoStack()->endCommand();
2093 mLegendItem->invalidateCache();
2094
2095 return true;
2096}
2097
2098Qt::ItemFlags QgsLayoutLegendMapFilteringModel::flags( const QModelIndex &index ) const
2099{
2100 Qt::ItemFlags flags = QSortFilterProxyModel::flags( index );
2101
2102 if ( !index.isValid() )
2103 {
2104 return flags;
2105 }
2106
2107 QgsLayoutItemMap *mapItem = qobject_cast<QgsLayoutItemMap *>( mLayoutModel->itemFromIndex( mapToSource( index ) ) );
2108 const bool isMainLinkedMapItem = mLegendItem ? ( mLegendItem->linkedMap() == mapItem ) : false;
2109
2110 // the main linked map item will always be considered checked in this panel.
2111 // otherwise we have a potentially confusing user situation where they have selected a main linked map for their legend
2112 // and enabled the filter by map option, but the filtering isn't applying to that main linked map (ie. things don't work
2113 // as they did in < 3.32)
2114 if ( !isMainLinkedMapItem )
2115 {
2116 flags |= Qt::ItemIsEnabled;
2117 }
2118 else
2119 {
2120 flags &= ~Qt::ItemIsEnabled;
2121 }
2122
2123 switch ( index.column() )
2124 {
2125 case 0:
2126 if ( !isMainLinkedMapItem )
2127 return flags | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable;
2128 else
2129 return flags | Qt::ItemIsSelectable;
2130
2131 default:
2132 return flags | Qt::ItemIsSelectable;
2133 }
2134}
2135
2136bool QgsLayoutLegendMapFilteringModel::filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const
2137{
2138 QgsLayoutItem *item = mLayoutModel->itemFromIndex( mLayoutModel->index( source_row, 0, source_parent ) );
2139 if ( !item || item->type() != QgsLayoutItemRegistry::ItemType::LayoutMap )
2140 {
2141 return false;
2142 }
2143
2144 return true;
2145}
2146
2147
LegendComponent
Component of legends which can be styled.
Definition qgis.h:4406
@ Symbol
Symbol icon (excluding label)
@ Group
Legend group title.
@ Hidden
Special style, item is hidden including margins around.
@ Subgroup
Legend subgroup title.
@ Title
Legend title.
@ SymbolLabel
Symbol label (excluding icon)
@ Polygon
Polygons.
@ Vector
Vector layer.
@ Marker
Marker symbol.
@ Line
Line symbol.
@ Fill
Fill symbol.
void changed()
Emitted when the alignment is changed.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QString iconPath(const QString &iconFile)
Returns path to the desired icon file.
void colorChanged(const QColor &color)
Emitted whenever a new color is set for the button.
Settings for a color ramp legend node.
A legend node which renders a color ramp.
A generic dialog for building expression strings.
Single scope for storing variables and functions for use within a QgsExpressionContext.
void setFields(const QgsFields &fields)
Convenience function for setting a fields for the scope.
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
void setHighlightedVariables(const QStringList &variableNames)
Sets the list of variable names within the context intended to be highlighted to the user.
static QString findAndSelectActiveExpression(QgsCodeEditor *editor, const QString &pattern=QString())
Find the expression under the cursor in the given editor and select it.
Container of fields for a vector layer.
Definition qgsfields.h:46
@ ModeTextRenderer
Configure font settings for use with QgsTextRenderer.
void setLayer(QgsVectorLayer *layer)
Sets a layer to associate with the widget.
void changed()
Emitted when the widget's text format settings are changed.
Layer tree group node serves as a container for layers and further groups.
void insertChildNode(int index, QgsLayerTreeNode *node)
Insert existing node at specified position.
void setName(const QString &n) override
Sets the group's name.
void removeChildNode(QgsLayerTreeNode *node)
Remove a child node from this group.
QString name() const override
Returns the group's name.
Layer tree node points to a map layer.
void setPatchSize(QSizeF size)
Sets the user (overridden) size for the legend node.
QString labelExpression() const
Returns the expression member of the LayerTreeNode.
LegendNodesSplitBehavior
Legend node column split behavior.
@ AllowSplittingLegendNodesOverMultipleColumns
Allow splitting node's legend nodes across multiple columns.
@ PreventSplittingLegendNodesOverMultipleColumns
Prevent splitting node's legend nodes across multiple columns.
@ UseDefaultLegendSetting
Inherit default legend column splitting setting.
void setLabelExpression(const QString &expression)
set the expression to evaluate
void setPatchShape(const QgsLegendPatchShape &shape)
Sets the symbol patch shape to use when rendering the legend node symbol.
QgsMapLayer * layer() const
Returns the map layer associated with this node.
An abstract interface for legend items returned from QgsMapLayerLegend implementation.
@ SimpleLegend
Simple label with icon legend node type.
@ RasterSymbolLegend
Raster symbol legend node type.
@ ImageLegend
Raster image legend node type.
@ DataDefinedSizeLegend
Marker symbol legend node type.
@ EmbeddedWidget
Embedded widget placeholder node type.
@ SymbolLegend
Vector symbol legend node type.
@ NodeType
Type of node. Added in 3.16.
QgsLayerTreeModel * model() const
Returns pointer to model owning this legend node.
QgsLayerTreeLayer * layerNode() const
Returns pointer to the parent layer node.
QgsLayerTreeModelLegendNode * legendNodeEmbeddedInParent(QgsLayerTreeLayer *nodeLayer) const
Returns legend node that may be embedded in parent (i.e.
QList< QgsLayerTreeModelLegendNode * > layerOriginalLegendNodes(QgsLayerTreeLayer *nodeLayer)
Returns original (unfiltered) list of legend nodes attached to a particular layer node.
Base class for nodes in a layer tree.
void setCustomProperty(const QString &key, const QVariant &value)
Sets a custom property for the node. Properties are stored in a map and saved in project file.
void removeCustomProperty(const QString &key)
Remove a custom property from layer. Properties are stored in a map and saved in project file.
QVariant customProperty(const QString &key, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer. Properties are stored in a map and saved in project file.
QgsLayerTreeNode * parent()
Gets pointer to the parent. If parent is nullptr, the node is a root node.
QStringList customProperties() const
Returns list of keys stored in custom properties.
virtual QgsLayerTreeNode * clone() const =0
Create a copy of the node. Returns new instance.
static void setLegendFilterByExpression(QgsLayerTreeLayer &layer, const QString &expr, bool enabled=true)
Sets the expression filter of a legend layer.
static QString legendFilterByExpression(const QgsLayerTreeLayer &layer, bool *enabled=nullptr)
Returns the expression filter of a legend layer.
Extends QTreeView and provides additional functionality when working with a layer tree.
static QgsLayerTreeLayer * toLayer(QgsLayerTreeNode *node)
Cast node to a layer.
static bool isLayer(const QgsLayerTreeNode *node)
Check whether the node is a valid layer node.
static bool isGroup(QgsLayerTreeNode *node)
Check whether the node is a valid group node.
static QgsLayerTreeGroup * toGroup(QgsLayerTreeNode *node)
Cast node to a group.
Used to render QgsLayout as an atlas, by iterating over the features from an associated vector layer.
void toggled(bool enabled)
Emitted when atlas is enabled or disabled.
A common interface for layout designer dialogs and widgets.
virtual QgsMessageBar * messageBar()=0
Returns the designer's message bar.
A base class for property widgets for layout items.
virtual void setDesignerInterface(QgsLayoutDesignerInterface *iface)
Sets the the layout designer interface in which the widget is being shown.
void itemChanged(QgsLayoutItem *item)
Emitted whenever the currently selected item changes.
A layout item subclass for map legends.
QgsLegendModel * model()
Returns the legend model.
Layout graphical items for displaying a map.
A widget for controlling the common properties of layout items (e.g.
Base class for graphical items within a QgsLayout.
@ UndoLegendMinSymbolSize
Legend minimum symbol size.
@ UndoLegendTitleSpaceBottom
Legend title space.
@ UndoLegendIconSymbolSpace
Legend icon symbol space.
@ UndoLegendLayerFont
Legend layer font.
@ UndoLegendBoxSpace
Legend box space.
@ UndoLegendRasterStrokeColor
Legend raster stroke color.
@ UndoLegendTitleFont
Legend title font.
@ UndoLegendItemFont
Legend item font.
@ UndoLegendWmsLegendWidth
Legend WMS width.
@ UndoLegendLayerSpace
Legend layer spacing.
@ UndoLegendColumnSpace
Legend column space.
@ UndoLegendMaxSymbolSize
Legend maximum symbol size.
@ UndoLegendAutoWrapAfter
Legend auto wrap lines after distance.
@ UndoLegendSymbolSpace
Legend symbol spacing.
@ UndoLegendSymbolWidth
Legend symbol width.
@ UndoLegendColumnCount
Legend column count.
@ UndoLegendSymbolHeight
Legend symbol height.
@ UndoLegendGroupSpace
Legend group spacing.
@ UndoLegendSubgroupIndent
Legend subgroup indent.
@ UndoLegendRasterStrokeWidth
Legend raster stroke width.
@ UndoLegendGroupIndent
Legend group indent.
@ UndoLegendText
Legend text.
@ UndoLegendWmsLegendHeight
Legend WMS height.
@ UndoLegendGroupFont
Legend group font.
int type() const override
Returns a unique graphics item type identifier.
A dialog to add new layers to the legend.
Provides a method of converting QgsLayoutMeasurements from one unit to another.
QgsLayoutMeasurement convert(QgsLayoutMeasurement measurement, Qgis::LayoutUnit targetUnits) const
Converts a measurement from one unit to another.
void setDpi(const double dpi)
Sets the dots per inch (dpi) for the measurement converter.
A model for items attached to a layout.
const QgsLayout * layout() const
Returns the layout the object is attached to.
void changed()
Emitted when the object's properties change.
@ LegendAutoWrapWidth
Legend text automatic wrapping width.
@ LegendColumnCount
Legend column count.
void layerChanged(QgsVectorLayer *layer)
Emitted when the context's layer is changed.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition qgslayout.h:49
QgsLayoutReportContext & reportContext()
Returns a reference to the layout's report context, which stores information relating to the current ...
Item model implementation based on layer tree model for layout legend.
void changed()
Emitted when the shape's settings are changed.
Represents a patch shape for use in map legends.
void setSymbolType(Qgis::SymbolType type)
Sets the symbol type associated with this patch.
bool isNull() const
Returns true if the patch shape is a null QgsLegendPatchShape, which indicates that the default legen...
static void setNodeLegendStyle(QgsLayerTreeNode *node, Qgis::LegendComponent style)
Sets the style of a node.
static Qgis::LegendComponent nodeLegendStyle(QgsLayerTreeNode *node, QgsLayerTreeModel *model)
Returns the style for the given node, within the specified model.
@ Left
Left side.
@ Bottom
Bottom side.
static QString styleLabel(Qgis::LegendComponent s)
Returns a translated string representing a style component, for use in UI.
Map canvas is a class for displaying all GIS data types on a canvas.
static void setLegendNodeOrder(QgsLayerTreeLayer *nodeLayer, const QList< int > &order)
static void setLegendNodeColorRampSettings(QgsLayerTreeLayer *nodeLayer, int originalIndex, const QgsColorRampLegendNodeSettings *settings)
Sets a custom legend color ramp settings for the legend node belonging to nodeLayer at the specified ...
static void setLegendNodeCustomSymbol(QgsLayerTreeLayer *nodeLayer, int originalIndex, const QgsSymbol *symbol)
Sets a custom legend symbol for the legend node belonging to nodeLayer at the specified originalIndex...
static void setLegendNodeUserLabel(QgsLayerTreeLayer *nodeLayer, int originalIndex, const QString &newLabel)
static void setLegendNodeSymbolSize(QgsLayerTreeLayer *nodeLayer, int originalIndex, QSizeF size)
Sets the legend symbol size for the legend node belonging to nodeLayer at the specified originalIndex...
static void setLegendNodeColumnBreak(QgsLayerTreeLayer *nodeLayer, int originalIndex, bool columnBreakBeforeNode)
Sets whether a forced column break should occur before the node.
static void setLegendNodePatchShape(QgsLayerTreeLayer *nodeLayer, int originalIndex, const QgsLegendPatchShape &shape)
Sets the legend patch shape for the legend node belonging to nodeLayer at the specified originalIndex...
static QList< int > legendNodeOrder(QgsLayerTreeLayer *nodeLayer)
Base class for all map layer types.
Definition qgsmaplayer.h:77
Interface for master layout type objects, such as print layouts and reports.
static int qtDefaultDpiX()
Returns the default Qt horizontal DPI.
Base class for any widget that can be shown as an inline panel.
void widgetChanged()
Emitted when the widget state changes.
void acceptPanel()
Accept the panel.
virtual void setDockMode(bool dockMode)
Set the widget in dock mode which tells the widget to emit panel widgets and not open dialogs.
void changed()
Emitted when the symbol's settings are changed.
Implementation of legend node interface for displaying preview of vector symbols and their labels and...
Abstract base class for all rendered symbols.
Definition qgssymbol.h:231
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
Represents a vector layer which manages a vector based dataset.
QgsExpressionContextScope * createExpressionContextScope() const FINAL
This method needs to be reimplemented in all classes which implement this interface and return an exp...
QSize iconSize(bool dockableToolbar)
Returns the user-preferred size of a window's toolbar icons.
QgsLayerTreeModelLegendNode * legendNode(const QString &rule, QgsLayerTreeModel &model)
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition qgis.h:6190