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