QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
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
916 << mMoveDownToolButton
917 << mMoveUpToolButton
918 << mRemoveToolButton
919 << mAddToolButton
920 << mEditPushButton
921 << mCountToolButton
922 << mResetLayersButton
923 << mAddGroupToolButton
924 << mExpressionFilterButton
925 << mCollapseAllToolButton
926 << mExpandAllToolButton;
927 for ( QWidget *w : std::as_const( widgets ) )
928 w->setEnabled( allowEdits );
929
930 if ( allowEdits )
931 {
932 // update widgets state based on current selection
933 selectedChanged( QModelIndex(), QModelIndex() );
934 mItemTreeView->proxyModel()->setShowPrivateLayers( true );
935 }
936 else
937 {
938 mItemTreeView->proxyModel()->setShowPrivateLayers( false );
939 }
940}
941
942void QgsLayoutLegendWidget::composerMapChanged( QgsLayoutItem *item )
943{
944 if ( !mLegend )
945 {
946 return;
947 }
948
949 QgsLayout *layout = mLegend->layout();
950 if ( !layout )
951 {
952 return;
953 }
954
955 QgsLayoutItemMap *map = qobject_cast<QgsLayoutItemMap *>( item );
956 if ( map )
957 {
958 mLegend->beginCommand( tr( "Change Legend Map" ) );
959 mLegend->setLinkedMap( map );
960 mLegend->update();
961 mLegend->endCommand();
962
963 setLegendMapViewData();
964 }
965}
966
967void QgsLayoutLegendWidget::mCheckboxResizeContents_toggled( bool checked )
968{
969 if ( !mLegend )
970 {
971 return;
972 }
973
974 mLegend->beginCommand( tr( "Resize Legend to Contents" ) );
975 mLegend->setResizeToContents( checked );
976 if ( checked )
977 mLegend->adjustBoxSize();
978 mLegend->update();
979 mLegend->endCommand();
980}
981
982void QgsLayoutLegendWidget::mRasterStrokeGroupBox_toggled( bool state )
983{
984 if ( !mLegend )
985 {
986 return;
987 }
988
989 mLegend->beginCommand( tr( "Change Legend Borders" ) );
990 mLegend->setDrawRasterStroke( state );
991 mLegend->adjustBoxSize();
992 mLegend->update();
993 mLegend->endCommand();
994}
995
996void QgsLayoutLegendWidget::mRasterStrokeWidthSpinBox_valueChanged( double d )
997{
998 if ( !mLegend )
999 {
1000 return;
1001 }
1002
1003 mLegend->beginCommand( tr( "Resize Legend Borders" ), QgsLayoutItem::UndoLegendRasterStrokeWidth );
1004 mLegend->setRasterStrokeWidth( d );
1005 mLegend->adjustBoxSize();
1006 mLegend->update();
1007 mLegend->endCommand();
1008}
1009
1010void QgsLayoutLegendWidget::mRasterStrokeColorButton_colorChanged( const QColor &newColor )
1011{
1012 if ( !mLegend )
1013 {
1014 return;
1015 }
1016
1017 mLegend->beginCommand( tr( "Change Legend Border Color" ), QgsLayoutItem::UndoLegendRasterStrokeColor );
1018 mLegend->setRasterStrokeColor( newColor );
1019 mLegend->update();
1020 mLegend->endCommand();
1021}
1022
1023void QgsLayoutLegendWidget::mAddToolButton_clicked()
1024{
1025 if ( !mLegend )
1026 {
1027 return;
1028 }
1029
1030 QList<QgsMapLayer *> visibleLayers;
1031 if ( mLegend->linkedMap() )
1032 {
1033 visibleLayers = mLegend->linkedMap()->layersToRender();
1034 }
1035 if ( visibleLayers.isEmpty() )
1036 {
1037 // just use current canvas layers as visible layers
1038 visibleLayers = mMapCanvas->layers( true );
1039 }
1040
1041 QgsLayoutLegendLayersDialog addDialog( this );
1042 addDialog.setVisibleLayers( visibleLayers );
1043 if ( addDialog.exec() == QDialog::Accepted )
1044 {
1045 const QList<QgsMapLayer *> layers = addDialog.selectedLayers();
1046 if ( !layers.empty() )
1047 {
1048 mLegend->beginCommand( tr( "Add Legend Item(s)" ) );
1049 for ( QgsMapLayer *layer : layers )
1050 {
1051 mLegend->model()->rootGroup()->addLayer( layer );
1052 }
1053 mLegend->updateLegend();
1054 mLegend->update();
1055 mLegend->endCommand();
1056 }
1057 }
1058}
1059
1060void QgsLayoutLegendWidget::mRemoveToolButton_clicked()
1061{
1062 if ( !mLegend )
1063 {
1064 return;
1065 }
1066
1067 QItemSelectionModel *selectionModel = mItemTreeView->selectionModel();
1068 if ( !selectionModel )
1069 {
1070 return;
1071 }
1072
1073 mLegend->beginCommand( tr( "Remove Legend Item" ) );
1074
1075 QList<QPersistentModelIndex> proxyIndexes;
1076 const QModelIndexList viewSelection = selectionModel->selectedIndexes();
1077 for ( const QModelIndex &index : viewSelection )
1078 proxyIndexes << index;
1079
1080 // first try to remove legend nodes
1081 QHash<QgsLayerTreeLayer *, QList<int>> nodesWithRemoval;
1082 for ( const QPersistentModelIndex &proxyIndex : std::as_const( proxyIndexes ) )
1083 {
1084 if ( QgsLayerTreeModelLegendNode *legendNode = mItemTreeView->index2legendNode( proxyIndex ) )
1085 {
1086 QgsLayerTreeLayer *nodeLayer = legendNode->layerNode();
1087 nodesWithRemoval[nodeLayer].append( _unfilteredLegendNodeIndex( legendNode ) );
1088 }
1089 }
1090 for ( auto it = nodesWithRemoval.constBegin(); it != nodesWithRemoval.constEnd(); ++it )
1091 {
1092 QList<int> toDelete = it.value();
1093 std::sort( toDelete.begin(), toDelete.end(), std::greater<int>() );
1094 QList<int> order = QgsMapLayerLegendUtils::legendNodeOrder( it.key() );
1095
1096 for ( int i : std::as_const( toDelete ) )
1097 {
1098 if ( i >= 0 && i < order.count() )
1099 order.removeAt( i );
1100 }
1101
1103 mItemTreeView->layerTreeModel()->refreshLayerLegend( it.key() );
1104 }
1105
1106 // then remove layer tree nodes
1107 for ( const QPersistentModelIndex &proxyIndex : std::as_const( proxyIndexes ) )
1108 {
1109 if ( proxyIndex.isValid() && mItemTreeView->index2node( proxyIndex ) )
1110 {
1111 const QModelIndex sourceIndex = mItemTreeView->proxyModel()->mapToSource( proxyIndex );
1112 mLegend->model()->removeRow( sourceIndex.row(), sourceIndex.parent() );
1113 }
1114 }
1115
1116 mLegend->updateLegend();
1117 mLegend->update();
1118 mLegend->endCommand();
1119}
1120
1121void QgsLayoutLegendWidget::mEditPushButton_clicked()
1122{
1123 if ( !mLegend )
1124 {
1125 return;
1126 }
1127
1128 QModelIndex idx = mItemTreeView->selectionModel()->currentIndex();
1129 mItemTreeView_doubleClicked( idx );
1130}
1131
1132void QgsLayoutLegendWidget::resetLayerNodeToDefaults()
1133{
1134 if ( !mLegend )
1135 {
1136 return;
1137 }
1138
1139 //get current item
1140 QModelIndex currentIndex = mItemTreeView->currentIndex();
1141 if ( !currentIndex.isValid() )
1142 {
1143 return;
1144 }
1145
1146 QgsLayerTreeLayer *nodeLayer = nullptr;
1147 if ( QgsLayerTreeNode *node = mItemTreeView->index2node( currentIndex ) )
1148 {
1149 if ( QgsLayerTree::isLayer( node ) )
1150 nodeLayer = QgsLayerTree::toLayer( node );
1151 }
1152 if ( QgsLayerTreeModelLegendNode *legendNode = mItemTreeView->index2legendNode( currentIndex ) )
1153 {
1154 nodeLayer = legendNode->layerNode();
1155 }
1156
1157 if ( !nodeLayer )
1158 return;
1159
1160 mLegend->beginCommand( tr( "Update Legend" ) );
1161
1162 const auto constCustomProperties = nodeLayer->customProperties();
1163 for ( const QString &key : constCustomProperties )
1164 {
1165 if ( key.startsWith( "legend/"_L1 ) )
1166 nodeLayer->removeCustomProperty( key );
1167 }
1168
1169 nodeLayer->setPatchShape( QgsLegendPatchShape() );
1170 nodeLayer->setPatchSize( QSizeF() );
1171
1172 mItemTreeView->layerTreeModel()->refreshLayerLegend( nodeLayer );
1173
1174 mLegend->updateLegend();
1175 mLegend->update();
1176 mLegend->endCommand();
1177}
1178
1179void QgsLayoutLegendWidget::mCountToolButton_clicked( bool checked )
1180{
1181 if ( !mLegend )
1182 {
1183 return;
1184 }
1185
1186 const QList<QModelIndex> selectedIndexes = mItemTreeView->selectionModel()->selectedIndexes();
1187 if ( selectedIndexes.empty() )
1188 return;
1189
1190 mLegend->beginCommand( tr( "Update Legend" ) );
1191 for ( const QModelIndex &index : selectedIndexes )
1192 {
1193 QgsLayerTreeNode *currentNode = mItemTreeView->index2node( index );
1194 if ( !QgsLayerTree::isLayer( currentNode ) )
1195 continue;
1196
1197 currentNode->setCustomProperty( u"showFeatureCount"_s, checked ? 1 : 0 );
1198 }
1199 mLegend->updateFilterByMap();
1200 mLegend->adjustBoxSize();
1201 mLegend->endCommand();
1202}
1203
1204void QgsLayoutLegendWidget::mFilterByMapCheckBox_toggled( bool checked )
1205{
1206 mLegend->beginCommand( tr( "Update Legend" ) );
1207 mLegend->setLegendFilterByMapEnabled( checked );
1208 mLegend->adjustBoxSize();
1209 mLegend->update();
1210 mLegend->endCommand();
1211}
1212
1213void QgsLayoutLegendWidget::resetLayers( Qgis::LegendSyncMode mode )
1214{
1215 if ( !mLegend )
1216 return;
1217
1218 mLegend->beginCommand( tr( "Update Legend" ) );
1219 mLegend->resetManualLayers( mode );
1220 mLegend->update();
1221 mLegend->endCommand();
1222}
1223
1224void QgsLayoutLegendWidget::mExpressionFilterButton_toggled( bool checked )
1225{
1226 if ( !mLegend )
1227 {
1228 return;
1229 }
1230
1231 //get current item
1232 QModelIndex currentIndex = mItemTreeView->currentIndex();
1233 if ( !currentIndex.isValid() )
1234 {
1235 return;
1236 }
1237
1238 QgsLayerTreeNode *currentNode = mItemTreeView->currentNode();
1239 if ( !QgsLayerTree::isLayer( currentNode ) )
1240 return;
1241
1242 QgsLayerTreeUtils::setLegendFilterByExpression( *qobject_cast<QgsLayerTreeLayer *>( currentNode ), mExpressionFilterButton->expressionText(), checked );
1243
1244 mLegend->beginCommand( tr( "Update Legend" ) );
1245 mLegend->updateFilterByMap();
1246 mLegend->adjustBoxSize();
1247 mLegend->endCommand();
1248}
1249
1250void QgsLayoutLegendWidget::mLayerExpressionButton_clicked()
1251{
1252 if ( !mLegend )
1253 {
1254 return;
1255 }
1256
1257 QModelIndex currentIndex = mItemTreeView->currentIndex();
1258 if ( !currentIndex.isValid() )
1259 return;
1260
1261 QgsLayerTreeNode *currentNode = mItemTreeView->currentNode();
1262 if ( !QgsLayerTree::isLayer( currentNode ) )
1263 return;
1264
1265 QgsLayerTreeLayer *layerNode = qobject_cast<QgsLayerTreeLayer *>( currentNode );
1266 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layerNode->layer() );
1267
1268 if ( !vl )
1269 return;
1270
1271 QString currentExpression;
1272 if ( layerNode->labelExpression().isEmpty() )
1273 currentExpression = u"@symbol_label"_s;
1274 else
1275 currentExpression = layerNode->labelExpression();
1276 QgsExpressionContext legendContext = mLegend->createExpressionContext();
1277 legendContext.appendScope( vl->createExpressionContextScope() );
1278
1279 QStringList highlighted;
1280 if ( QgsLegendModel *model = mLegend->model() )
1281 {
1282 const QList<QgsLayerTreeModelLegendNode *> legendNodes = model->layerLegendNodes( layerNode, false );
1283 if ( !legendNodes.isEmpty() )
1284 {
1285 if ( QgsSymbolLegendNode *symbolNode = qobject_cast<QgsSymbolLegendNode *>( legendNodes.first() ) )
1286 {
1287 legendContext.appendScope( symbolNode->createSymbolScope() );
1288 highlighted << u"symbol_label"_s << u"symbol_id"_s << u"symbol_count"_s;
1289 }
1290 }
1291 }
1292
1293 legendContext.setHighlightedVariables( highlighted );
1294
1295 // Passing the vector layer to expression dialog exposes the fields, but we still want generic
1296 // layer variables
1298 limitedLayerScope->setFields( QgsFields() );
1299 legendContext.appendScope( limitedLayerScope );
1300
1301 QgsExpressionBuilderDialog expressiondialog( nullptr, currentExpression, nullptr, u"generic"_s, legendContext );
1302 if ( expressiondialog.exec() )
1303 {
1304 layerNode->setLabelExpression( expressiondialog.expressionText() );
1305 mItemTreeView->layerTreeModel()->refreshLayerLegend( layerNode );
1306 }
1307
1308 mLegend->beginCommand( tr( "Update Legend" ) );
1309 mLegend->refresh();
1310 mLegend->adjustBoxSize();
1311 mLegend->endCommand();
1312}
1313
1314void QgsLayoutLegendWidget::mAddGroupToolButton_clicked()
1315{
1316 if ( mLegend )
1317 {
1318 mLegend->beginCommand( tr( "Add Legend Group" ) );
1319 mLegend->model()->rootGroup()->addGroup( tr( "Group" ) );
1320 mLegend->updateLegend();
1321 mLegend->update();
1322 mLegend->endCommand();
1323 }
1324}
1325
1326void QgsLayoutLegendWidget::mFilterLegendByAtlasCheckBox_toggled( bool toggled )
1327{
1328 Q_UNUSED( toggled )
1329 if ( mLegend )
1330 {
1331 mLegend->setLegendFilterOutAtlas( toggled );
1332 // force update of legend when in preview mode
1333 mLegend->refresh();
1334 }
1335}
1336
1337void QgsLayoutLegendWidget::setReportTypeString( const QString &string )
1338{
1339 mFilterLegendByAtlasCheckBox->setText( tr( "Only show items inside current %1 feature" ).arg( string ) );
1340 mFilterLegendByAtlasCheckBox->setToolTip( tr( "Filter out legend elements that lie outside the current %1 feature." ).arg( string ) );
1341}
1342
1343QgsExpressionContext QgsLayoutLegendWidget::createExpressionContext() const
1344{
1345 QgsExpressionContext context = mLegend->createExpressionContext();
1346 return context;
1347}
1348
1349bool QgsLayoutLegendWidget::setNewItem( QgsLayoutItem *item )
1350{
1352 return false;
1353
1354 if ( mLegend )
1355 {
1356 disconnect( mLegend, &QgsLayoutObject::changed, this, &QgsLayoutLegendWidget::setGuiElements );
1357 }
1358
1359 mLegend = qobject_cast<QgsLayoutItemLegend *>( item );
1360 mItemPropertiesWidget->setItem( mLegend );
1361
1362 if ( mMapFilteringWidget )
1363 mMapFilteringWidget->setItem( mLegend );
1364
1365 if ( mLegend )
1366 {
1367 mLegendProxyModel = new QgsLegendLayerTreeProxyModel( mLegend, this );
1368
1369 mItemTreeView->setModel( mLegend->model(), mLegendProxyModel );
1370
1371 connect( mLegend, &QgsLayoutObject::changed, this, &QgsLayoutLegendWidget::setGuiElements );
1372 }
1373
1374 setGuiElements();
1375
1376 return true;
1377}
1378
1379void QgsLayoutLegendWidget::blockAllSignals( bool b )
1380{
1381 mTitleLineEdit->blockSignals( b );
1382 mTitleAlignCombo->blockSignals( b );
1383 mItemTreeView->blockSignals( b );
1384 mSyncModeCombo->blockSignals( b );
1385 mMapComboBox->blockSignals( b );
1386 mFilterByMapCheckBox->blockSignals( b );
1387 mColumnCountSpinBox->blockSignals( b );
1388 mSplitLayerCheckBox->blockSignals( b );
1389 mEqualColumnWidthCheckBox->blockSignals( b );
1390 mSymbolWidthSpinBox->blockSignals( b );
1391 mSymbolHeightSpinBox->blockSignals( b );
1392 mMaxSymbolSizeSpinBox->blockSignals( b );
1393 mMinSymbolSizeSpinBox->blockSignals( b );
1394 mGroupSpaceSpinBox->blockSignals( b );
1395 mGroupIndentSpinBox->blockSignals( b );
1396 mSubgroupIndentSpinBox->blockSignals( b );
1397 mSpaceBelowGroupHeadingSpinBox->blockSignals( b );
1398 mGroupSideSpinBox->blockSignals( b );
1399 mSpaceBelowSubgroupHeadingSpinBox->blockSignals( b );
1400 mSubgroupSideSpinBox->blockSignals( b );
1401 mLayerSpaceSpinBox->blockSignals( b );
1402 mSymbolSpaceSpinBox->blockSignals( b );
1403 mSymbolSideSpaceSpinBox->blockSignals( b );
1404 mIconLabelSpaceSpinBox->blockSignals( b );
1405 mBoxSpaceSpinBox->blockSignals( b );
1406 mColumnSpaceSpinBox->blockSignals( b );
1407 mRasterStrokeGroupBox->blockSignals( b );
1408 mRasterStrokeColorButton->blockSignals( b );
1409 mRasterStrokeWidthSpinBox->blockSignals( b );
1410 mWmsLegendWidthSpinBox->blockSignals( b );
1411 mWmsLegendHeightSpinBox->blockSignals( b );
1412 mCheckboxResizeContents->blockSignals( b );
1413 mTitleSpaceBottomSpinBox->blockSignals( b );
1414 mFilterLegendByAtlasCheckBox->blockSignals( b );
1415 mTitleFontButton->blockSignals( b );
1416 mGroupFontButton->blockSignals( b );
1417 mLayerFontButton->blockSignals( b );
1418 mItemFontButton->blockSignals( b );
1419 mWrapCharLineEdit->blockSignals( b );
1420 mMaxWidthSpinBox->blockSignals( b );
1421}
1422
1423void QgsLayoutLegendWidget::selectedChanged( const QModelIndex &current, const QModelIndex &previous )
1424{
1425 Q_UNUSED( current )
1426 Q_UNUSED( previous )
1427
1428 mLayerExpressionButton->setEnabled( false );
1429
1430 if ( mLegend && mLegend->syncMode() != Qgis::LegendSyncMode::Manual )
1431 {
1432 QgsLayerTreeNode *currentNode = mItemTreeView->currentNode();
1433 if ( !QgsLayerTree::isLayer( currentNode ) )
1434 return;
1435
1436 QgsLayerTreeLayer *currentLayerNode = QgsLayerTree::toLayer( currentNode );
1437 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( currentLayerNode->layer() );
1438 if ( !vl )
1439 return;
1440
1441 mLayerExpressionButton->setEnabled( true );
1442 return;
1443 }
1444
1445 mCountToolButton->setChecked( false );
1446 mCountToolButton->setEnabled( false );
1447
1448
1449 mExpressionFilterButton->blockSignals( true );
1450 mExpressionFilterButton->setChecked( false );
1451 mExpressionFilterButton->setEnabled( false );
1452 mExpressionFilterButton->blockSignals( false );
1453
1454 QgsLayerTreeNode *currentNode = mItemTreeView->currentNode();
1455 if ( !QgsLayerTree::isLayer( currentNode ) )
1456 return;
1457
1458 QgsLayerTreeLayer *currentLayerNode = QgsLayerTree::toLayer( currentNode );
1459 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( currentLayerNode->layer() );
1460 if ( !vl )
1461 return;
1462
1463 mCountToolButton->setChecked( currentNode->customProperty( u"showFeatureCount"_s, 0 ).toInt() );
1464 mCountToolButton->setEnabled( true );
1465 mLayerExpressionButton->setEnabled( true );
1466
1467 bool exprEnabled;
1468 QString expr = QgsLayerTreeUtils::legendFilterByExpression( *qobject_cast<QgsLayerTreeLayer *>( currentNode ), &exprEnabled );
1469 mExpressionFilterButton->blockSignals( true );
1470 mExpressionFilterButton->setExpressionText( expr );
1471 mExpressionFilterButton->setVectorLayer( vl );
1472 mExpressionFilterButton->setEnabled( true );
1473 mExpressionFilterButton->setChecked( exprEnabled );
1474 mExpressionFilterButton->blockSignals( false );
1475}
1476
1477void QgsLayoutLegendWidget::setCurrentNodeStyleFromAction()
1478{
1479 QAction *a = qobject_cast<QAction *>( sender() );
1480 if ( !a || !mItemTreeView->currentNode() )
1481 return;
1482
1483 QgsLegendRenderer::setNodeLegendStyle( mItemTreeView->currentNode(), static_cast<Qgis::LegendComponent>( a->data().toInt() ) );
1484 mLegend->update();
1485}
1486
1487void QgsLayoutLegendWidget::setLegendMapViewData()
1488{
1489 if ( mLegend->linkedMap() )
1490 {
1491 const int dpi = QgsPainting::qtDefaultDpiX();
1493 measurementConverter.setDpi( dpi );
1494 double mapWidth = measurementConverter.convert( mLegend->linkedMap()->sizeWithUnits(), Qgis::LayoutUnit::Pixels ).width();
1495 double mapHeight = measurementConverter.convert( mLegend->linkedMap()->sizeWithUnits(), Qgis::LayoutUnit::Pixels ).height();
1496 double mapUnitsPerPixelX = mLegend->linkedMap()->extent().width() / mapWidth;
1497 double mapUnitsPerPixelY = mLegend->linkedMap()->extent().height() / mapHeight;
1498 mLegend->model()->setLegendMapViewData( ( mapUnitsPerPixelX > mapUnitsPerPixelY ? mapUnitsPerPixelX : mapUnitsPerPixelY ), dpi, mLegend->linkedMap()->scale() );
1499 }
1500}
1501
1502void QgsLayoutLegendWidget::updateFilterLegendByAtlasButton()
1503{
1504 if ( QgsLayoutAtlas *atlas = layoutAtlas() )
1505 {
1506 mFilterLegendByAtlasCheckBox->setEnabled( atlas->enabled() && mLegend->layout()->reportContext().layer() && mLegend->layout()->reportContext().layer()->geometryType() == Qgis::GeometryType::Polygon );
1507 }
1508}
1509
1510void QgsLayoutLegendWidget::mItemTreeView_doubleClicked( const QModelIndex &idx )
1511{
1512 if ( !mLegend || !idx.isValid() )
1513 {
1514 return;
1515 }
1516
1517 if ( mLegend->syncMode() != Qgis::LegendSyncMode::Manual )
1518 return;
1519
1520 QgsLayerTreeNode *currentNode = mItemTreeView->index2node( idx );
1521 QgsLayerTreeModelLegendNode *legendNode = mItemTreeView->index2legendNode( idx );
1522
1523 int originalIndex = -1;
1524 if ( legendNode )
1525 {
1526 originalIndex = _originalLegendNodeIndex( legendNode );
1527 currentNode = legendNode->layerNode();
1528 }
1529
1530 QgsLayoutLegendNodeWidget *widget = new QgsLayoutLegendNodeWidget( mLegend, currentNode, legendNode, originalIndex );
1531 openPanel( widget );
1532}
1533
1534
1535//
1536// QgsComposerLegendMenuProvider
1537//
1538
1539QgsLayoutLegendMenuProvider::QgsLayoutLegendMenuProvider( QgsLayerTreeView *view, QgsLayoutLegendWidget *w )
1540 : mView( view )
1541 , mWidget( w )
1542{}
1543
1544QMenu *QgsLayoutLegendMenuProvider::createContextMenu()
1545{
1546 if ( !mView->currentNode() )
1547 return nullptr;
1548
1549 if ( mWidget->legend()->syncMode() != Qgis::LegendSyncMode::Manual )
1550 return nullptr; // no editing allowed
1551
1552 QMenu *menu = new QMenu();
1553
1554 if ( QgsLayerTree::isLayer( mView->currentNode() ) )
1555 {
1556 menu->addAction( QObject::tr( "Reset to Defaults" ), mWidget, &QgsLayoutLegendWidget::resetLayerNodeToDefaults );
1557 menu->addSeparator();
1558 }
1559
1560 Qgis::LegendComponent currentStyle = QgsLegendRenderer::nodeLegendStyle( mView->currentNode(), mView->layerTreeModel() );
1561
1562 QActionGroup *styleGroup = new QActionGroup { mWidget };
1563 styleGroup->setExclusive( true );
1564
1565 QList<Qgis::LegendComponent> lst;
1567 for ( Qgis::LegendComponent style : std::as_const( lst ) )
1568 {
1569 QAction *action = menu->addAction( QgsLegendStyle::styleLabel( style ), mWidget, &QgsLayoutLegendWidget::setCurrentNodeStyleFromAction );
1570 action->setActionGroup( styleGroup );
1571 action->setCheckable( true );
1572 action->setChecked( currentStyle == style );
1573 action->setData( static_cast<int>( style ) );
1574 }
1575
1576 return menu;
1577}
1578
1579//
1580// QgsLayoutLegendNodeWidget
1581//
1582QgsLayoutLegendNodeWidget::QgsLayoutLegendNodeWidget( QgsLayoutItemLegend *legend, QgsLayerTreeNode *node, QgsLayerTreeModelLegendNode *legendNode, int originalLegendNodeIndex, QWidget *parent )
1583 : QgsPanelWidget( parent )
1584 , mLegend( legend )
1585 , mNode( node )
1586 , mLayer( qobject_cast<QgsLayerTreeLayer *>( node ) )
1587 , mLegendNode( legendNode )
1588 , mOriginalLegendNodeIndex( originalLegendNodeIndex )
1589{
1590 setupUi( this );
1591 setPanelTitle( tr( "Legend Item Properties" ) );
1592
1593 // auto close panel if layer removed
1594 connect( node, &QObject::destroyed, this, &QgsPanelWidget::acceptPanel );
1595
1596 mColumnSplitBehaviorComboBox->addItem( tr( "Follow Legend Default" ), QgsLayerTreeLayer::UseDefaultLegendSetting );
1597 mColumnSplitBehaviorComboBox->addItem( tr( "Allow Splitting Over Columns" ), QgsLayerTreeLayer::AllowSplittingLegendNodesOverMultipleColumns );
1598 mColumnSplitBehaviorComboBox->addItem( tr( "Prevent Splitting Over Columns" ), QgsLayerTreeLayer::PreventSplittingLegendNodesOverMultipleColumns );
1599
1600 QString currentLabel;
1601 if ( mLegendNode )
1602 {
1603 currentLabel = mLegendNode->data( Qt::EditRole ).toString();
1604 mColumnBreakBeforeCheckBox->setChecked( mLegendNode->columnBreak() );
1605 }
1606 else if ( mLayer )
1607 {
1608 currentLabel = mLayer->name();
1609 QVariant v = mLayer->customProperty( u"legend/title-label"_s );
1610 if ( !QgsVariantUtils::isNull( v ) )
1611 currentLabel = v.toString();
1612 mColumnBreakBeforeCheckBox->setChecked( mLayer->customProperty( u"legend/column-break"_s ).toInt() );
1613
1614 mColumnSplitBehaviorComboBox->setCurrentIndex( mColumnSplitBehaviorComboBox->findData( mLayer->legendSplitBehavior() ) );
1615 }
1616 else
1617 {
1618 currentLabel = QgsLayerTree::toGroup( mNode )->name();
1619 mColumnBreakBeforeCheckBox->setChecked( mNode->customProperty( u"legend/column-break"_s ).toInt() );
1620 }
1621
1622 mWidthSpinBox->setClearValue( 0, tr( "Default" ) );
1623 mHeightSpinBox->setClearValue( 0, tr( "Default" ) );
1624 mWidthSpinBox->setVisible( mLegendNode || mLayer );
1625 mHeightSpinBox->setVisible( mLegendNode || mLayer );
1626 mPatchGroup->setVisible( mLegendNode || mLayer );
1627 mPatchWidthLabel->setVisible( mLegendNode || mLayer );
1628 mPatchHeightLabel->setVisible( mLegendNode || mLayer );
1629 mCustomSymbolCheckBox->setVisible( mLegendNode || mLegend->model()->legendNodeEmbeddedInParent( mLayer ) );
1630 mColumnSplitLabel->setVisible( mLayer && !mLegendNode );
1631 mColumnSplitBehaviorComboBox->setVisible( mLayer && !mLegendNode );
1632 if ( mLegendNode )
1633 {
1634 mWidthSpinBox->setValue( mLegendNode->userPatchSize().width() );
1635 mHeightSpinBox->setValue( mLegendNode->userPatchSize().height() );
1636 }
1637 else if ( mLayer )
1638 {
1639 mWidthSpinBox->setValue( mLayer->patchSize().width() );
1640 mHeightSpinBox->setValue( mLayer->patchSize().height() );
1641 }
1642
1643 mCustomSymbolCheckBox->setChecked( false );
1644
1645 QgsLegendPatchShape patchShape;
1646 if ( QgsSymbolLegendNode *symbolLegendNode = dynamic_cast<QgsSymbolLegendNode *>( mLegendNode ) )
1647 {
1648 patchShape = symbolLegendNode->patchShape();
1649
1650 std::unique_ptr<QgsSymbol> customSymbol( symbolLegendNode->customSymbol() ? symbolLegendNode->customSymbol()->clone() : nullptr );
1651 mCustomSymbolCheckBox->setChecked( customSymbol.get() );
1652 if ( customSymbol )
1653 {
1654 mPatchShapeButton->setPreviewSymbol( customSymbol->clone() );
1655 mCustomSymbolButton->setSymbolType( customSymbol->type() );
1656 mCustomSymbolButton->setSymbol( customSymbol.release() );
1657 }
1658 else if ( symbolLegendNode->symbol() )
1659 {
1660 mPatchShapeButton->setPreviewSymbol( symbolLegendNode->symbol()->clone() );
1661 mCustomSymbolButton->setSymbolType( symbolLegendNode->symbol()->type() );
1662 mCustomSymbolButton->setSymbol( symbolLegendNode->symbol()->clone() );
1663 }
1664 }
1665 else if ( !mLegendNode && mLayer )
1666 {
1667 patchShape = mLayer->patchShape();
1668 if ( QgsSymbolLegendNode *symbolLegendNode = dynamic_cast<QgsSymbolLegendNode *>( mLegend->model()->legendNodeEmbeddedInParent( mLayer ) ) )
1669 {
1670 if ( QgsSymbol *customSymbol = symbolLegendNode->customSymbol() )
1671 {
1672 mCustomSymbolCheckBox->setChecked( true );
1673 mPatchShapeButton->setPreviewSymbol( customSymbol->clone() );
1674 mCustomSymbolButton->setSymbolType( customSymbol->type() );
1675 mCustomSymbolButton->setSymbol( customSymbol->clone() );
1676 }
1677 else
1678 {
1679 mPatchShapeButton->setPreviewSymbol( symbolLegendNode->symbol()->clone() );
1680 mCustomSymbolButton->setSymbolType( symbolLegendNode->symbol()->type() );
1681 mCustomSymbolButton->setSymbol( symbolLegendNode->symbol()->clone() );
1682 }
1683 }
1684 }
1685
1686 if ( mLayer && mLayer->layer() && mLayer->layer()->type() == Qgis::LayerType::Vector )
1687 {
1688 switch ( qobject_cast<QgsVectorLayer *>( mLayer->layer() )->geometryType() )
1689 {
1691 mPatchShapeButton->setSymbolType( Qgis::SymbolType::Fill );
1692 break;
1693
1695 mPatchShapeButton->setSymbolType( Qgis::SymbolType::Line );
1696 break;
1697
1699 mPatchShapeButton->setSymbolType( Qgis::SymbolType::Marker );
1700 break;
1701
1702 default:
1703 mPatchShapeLabel->hide();
1704 mPatchShapeButton->hide();
1705 break;
1706 }
1707 if ( !patchShape.isNull() )
1708 mPatchShapeButton->setShape( patchShape );
1709 }
1710 else if ( QgsSymbolLegendNode *symbolLegendNode = dynamic_cast<QgsSymbolLegendNode *>( mLegendNode ) )
1711 {
1712 if ( symbolLegendNode->symbol() )
1713 {
1714 mPatchShapeButton->setSymbolType( symbolLegendNode->symbol()->type() );
1715 }
1716 else
1717 {
1718 mPatchShapeLabel->hide();
1719 mPatchShapeButton->hide();
1720 }
1721 }
1722 else
1723 {
1724 mPatchShapeLabel->hide();
1725 mPatchShapeButton->hide();
1726 }
1727
1728 if ( QgsColorRampLegendNode *colorRampNode = dynamic_cast<QgsColorRampLegendNode *>( mLegendNode ) )
1729 {
1730 mLabelGroup->hide();
1731 mColorRampLegendWidget->setSettings( colorRampNode->settings() );
1732 }
1733 else
1734 {
1735 mColorRampLegendWidget->hide();
1736 }
1737
1738 if ( mLegendNode )
1739 {
1740 switch ( static_cast<QgsLayerTreeModelLegendNode::NodeTypes>( mLegendNode->data( static_cast<int>( QgsLayerTreeModelLegendNode::CustomRole::NodeType ) ).toInt() ) )
1741 {
1748 mCustomSymbolCheckBox->hide();
1749 break;
1750
1753 break;
1754 }
1755 }
1756
1757 mLabelEdit->setPlainText( currentLabel );
1758 connect( mLabelEdit, &QPlainTextEdit::textChanged, this, &QgsLayoutLegendNodeWidget::labelChanged );
1759 connect( mPatchShapeButton, &QgsLegendPatchShapeButton::changed, this, &QgsLayoutLegendNodeWidget::patchChanged );
1760 connect( mInsertExpressionButton, &QPushButton::clicked, this, &QgsLayoutLegendNodeWidget::insertExpression );
1761
1762 connect( mWidthSpinBox, qOverload<double>( &QgsDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendNodeWidget::sizeChanged );
1763 connect( mHeightSpinBox, qOverload<double>( &QgsDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendNodeWidget::sizeChanged );
1764
1765 connect( mCustomSymbolCheckBox, &QGroupBox::toggled, this, &QgsLayoutLegendNodeWidget::customSymbolChanged );
1766 connect( mCustomSymbolButton, &QgsSymbolButton::changed, this, &QgsLayoutLegendNodeWidget::customSymbolChanged );
1767
1768 connect( mColumnBreakBeforeCheckBox, &QCheckBox::toggled, this, &QgsLayoutLegendNodeWidget::columnBreakToggled );
1769
1770 connect( mColumnSplitBehaviorComboBox, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsLayoutLegendNodeWidget::columnSplitChanged );
1771
1772 connect( mColorRampLegendWidget, &QgsColorRampLegendNodeWidget::widgetChanged, this, &QgsLayoutLegendNodeWidget::colorRampLegendChanged );
1773
1774 connectChildPanel( mColorRampLegendWidget );
1775}
1776
1777void QgsLayoutLegendNodeWidget::setDockMode( bool dockMode )
1778{
1779 mColorRampLegendWidget->setDockMode( dockMode );
1780 QgsPanelWidget::setDockMode( dockMode );
1781}
1782
1783void QgsLayoutLegendNodeWidget::labelChanged()
1784{
1785 mLegend->beginCommand( tr( "Edit Legend Item" ), QgsLayoutItem::UndoLegendText );
1786
1787 const QString label = mLabelEdit->toPlainText();
1788 if ( QgsLayerTree::isGroup( mNode ) )
1789 {
1790 QgsLayerTree::toGroup( mNode )->setName( label );
1791 }
1792 else if ( mLegendNode )
1793 {
1794 QgsMapLayerLegendUtils::setLegendNodeUserLabel( mLayer, mOriginalLegendNodeIndex, label );
1795 mLegend->model()->refreshLayerLegend( mLayer );
1796 }
1797 else if ( mLayer )
1798 {
1799 mLayer->setCustomProperty( u"legend/title-label"_s, label );
1800
1801 // force update of label of the legend node with embedded icon (a bit clumsy i know)
1802 if ( QgsLayerTreeModelLegendNode *embeddedNode = mLegend->model()->legendNodeEmbeddedInParent( mLayer ) )
1803 embeddedNode->setUserLabel( QString() );
1804 }
1805
1806 mLegend->adjustBoxSize();
1807 mLegend->invalidateCache();
1808 mLegend->update();
1809 mLegend->endCommand();
1810}
1811
1812void QgsLayoutLegendNodeWidget::patchChanged()
1813{
1814 mLegend->beginCommand( tr( "Edit Legend Item" ) );
1815
1816 QgsLegendPatchShape shape = mPatchShapeButton->shape();
1817 if ( mLegendNode )
1818 {
1819 QgsMapLayerLegendUtils::setLegendNodePatchShape( mLayer, mOriginalLegendNodeIndex, shape );
1820 mLegend->model()->refreshLayerLegend( mLayer );
1821 }
1822 else if ( mLayer )
1823 {
1824 mLayer->setPatchShape( shape );
1825 const QList<QgsLayerTreeModelLegendNode *> layerLegendNodes = mLegend->model()->layerLegendNodes( mLayer, false );
1826 for ( QgsLayerTreeModelLegendNode *node : layerLegendNodes )
1827 {
1828 QgsMapLayerLegendUtils::setLegendNodePatchShape( mLayer, _originalLegendNodeIndex( node ), shape );
1829 }
1830 mLegend->model()->refreshLayerLegend( mLayer );
1831 }
1832
1833 mLegend->adjustBoxSize();
1834 mLegend->update();
1835 mLegend->endCommand();
1836}
1837
1838void QgsLayoutLegendNodeWidget::insertExpression()
1839{
1840 if ( !mLegend )
1841 return;
1842
1843 QString expression = QgsExpressionFinder::findAndSelectActiveExpression( mLabelEdit );
1844
1845 // use the atlas coverage layer, if any
1846 QgsVectorLayer *layer = mLegend->layout() ? mLegend->layout()->reportContext().layer() : nullptr;
1847
1848 QgsExpressionContext context = mLegend->createExpressionContext();
1849
1850 if ( mLayer && mLayer->layer() )
1851 {
1852 context.appendScope( QgsExpressionContextUtils::layerScope( mLayer->layer() ) );
1853 }
1854
1856 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
1857 );
1858
1859 QgsExpressionBuilderDialog exprDlg( layer, expression, this, u"generic"_s, context );
1860
1861 exprDlg.setWindowTitle( tr( "Insert Expression" ) );
1862 if ( exprDlg.exec() == QDialog::Accepted )
1863 {
1864 expression = exprDlg.expressionText();
1865 if ( !expression.isEmpty() )
1866 {
1867 mLegend->beginCommand( tr( "Insert expression" ) );
1868 mLabelEdit->insertPlainText( "[%" + expression.trimmed() + "%]" );
1869 mLegend->endCommand();
1870 }
1871 }
1872}
1873
1874void QgsLayoutLegendNodeWidget::sizeChanged( double )
1875{
1876 mLegend->beginCommand( tr( "Edit Legend Item" ) );
1877 const QSizeF size = QSizeF( mWidthSpinBox->value(), mHeightSpinBox->value() );
1878
1879 if ( mLegendNode )
1880 {
1881 QgsMapLayerLegendUtils::setLegendNodeSymbolSize( mLayer, mOriginalLegendNodeIndex, size );
1882 mLegend->model()->refreshLayerLegend( mLayer );
1883 }
1884 else if ( mLayer )
1885 {
1886 mLayer->setPatchSize( size );
1887 const QList<QgsLayerTreeModelLegendNode *> layerLegendNodes = mLegend->model()->layerLegendNodes( mLayer, false );
1888 for ( QgsLayerTreeModelLegendNode *node : layerLegendNodes )
1889 {
1890 QgsMapLayerLegendUtils::setLegendNodeSymbolSize( mLayer, _originalLegendNodeIndex( node ), size );
1891 }
1892 mLegend->model()->refreshLayerLegend( mLayer );
1893 }
1894
1895 mLegend->adjustBoxSize();
1896 mLegend->update();
1897 mLegend->endCommand();
1898}
1899
1900void QgsLayoutLegendNodeWidget::customSymbolChanged()
1901{
1902 mLegend->beginCommand( tr( "Edit Legend Item" ) );
1903
1904 if ( mCustomSymbolCheckBox->isChecked() )
1905 {
1906 if ( mLegendNode )
1907 {
1908 QgsMapLayerLegendUtils::setLegendNodeCustomSymbol( mLayer, mOriginalLegendNodeIndex, mCustomSymbolButton->symbol() );
1909 mLegend->model()->refreshLayerLegend( mLayer );
1910 }
1911 else if ( mLayer )
1912 {
1913 const QList<QgsLayerTreeModelLegendNode *> layerLegendNodes = mLegend->model()->layerLegendNodes( mLayer, false );
1914 for ( QgsLayerTreeModelLegendNode *node : layerLegendNodes )
1915 {
1916 QgsMapLayerLegendUtils::setLegendNodeCustomSymbol( mLayer, _originalLegendNodeIndex( node ), mCustomSymbolButton->symbol() );
1917 }
1918 mLegend->model()->refreshLayerLegend( mLayer );
1919 }
1920 }
1921 else
1922 {
1923 if ( mLegendNode )
1924 {
1925 QgsMapLayerLegendUtils::setLegendNodeCustomSymbol( mLayer, mOriginalLegendNodeIndex, nullptr );
1926 mLegend->model()->refreshLayerLegend( mLayer );
1927 }
1928 else if ( mLayer )
1929 {
1930 const QList<QgsLayerTreeModelLegendNode *> layerLegendNodes = mLegend->model()->layerLegendNodes( mLayer, false );
1931 for ( QgsLayerTreeModelLegendNode *node : layerLegendNodes )
1932 {
1933 QgsMapLayerLegendUtils::setLegendNodeCustomSymbol( mLayer, _originalLegendNodeIndex( node ), nullptr );
1934 }
1935 mLegend->model()->refreshLayerLegend( mLayer );
1936 }
1937 }
1938
1939 mLegend->adjustBoxSize();
1940 mLegend->update();
1941 mLegend->endCommand();
1942}
1943
1944void QgsLayoutLegendNodeWidget::colorRampLegendChanged()
1945{
1946 mLegend->beginCommand( tr( "Edit Legend Item" ) );
1947
1948 QgsColorRampLegendNodeSettings settings = mColorRampLegendWidget->settings();
1949 QgsMapLayerLegendUtils::setLegendNodeColorRampSettings( mLayer, mOriginalLegendNodeIndex, &settings );
1950 mLegend->model()->refreshLayerLegend( mLayer );
1951
1952 mLegend->adjustBoxSize();
1953 mLegend->update();
1954 mLegend->endCommand();
1955}
1956
1957void QgsLayoutLegendNodeWidget::columnBreakToggled( bool checked )
1958{
1959 mLegend->beginCommand( tr( "Edit Legend Columns" ) );
1960
1961 if ( mLegendNode )
1962 {
1963 QgsMapLayerLegendUtils::setLegendNodeColumnBreak( mLayer, mOriginalLegendNodeIndex, checked );
1964 mLegend->model()->refreshLayerLegend( mLayer );
1965 }
1966 else if ( mLayer )
1967 {
1968 mLayer->setCustomProperty( u"legend/column-break"_s, QString( checked ? '1' : '0' ) );
1969 }
1970 else if ( mNode )
1971 {
1972 mNode->setCustomProperty( u"legend/column-break"_s, QString( checked ? '1' : '0' ) );
1973 }
1974
1975 mLegend->adjustBoxSize();
1976 mLegend->update();
1977 mLegend->endCommand();
1978}
1979
1980void QgsLayoutLegendNodeWidget::columnSplitChanged()
1981{
1982 mLegend->beginCommand( tr( "Edit Legend Columns" ) );
1983
1984 if ( mLayer && !mLegendNode )
1985 {
1986 mLayer->setLegendSplitBehavior( static_cast<QgsLayerTreeLayer::LegendNodesSplitBehavior>( mColumnSplitBehaviorComboBox->currentData().toInt() ) );
1987 }
1988
1989 mLegend->adjustBoxSize();
1990 mLegend->update();
1991 mLegend->endCommand();
1992}
1993
1994//
1995// QgsLayoutLegendMapFilteringWidget
1996//
1997
1998QgsLayoutLegendMapFilteringWidget::QgsLayoutLegendMapFilteringWidget( QgsLayoutItemLegend *legend )
1999 : QgsLayoutItemBaseWidget( nullptr, legend )
2000 , mLegendItem( legend )
2001{
2002 setupUi( this );
2003 setPanelTitle( tr( "Legend Filtering" ) );
2004
2005 setNewItem( legend );
2006}
2007
2008bool QgsLayoutLegendMapFilteringWidget::setNewItem( QgsLayoutItem *item )
2009{
2011 return false;
2012
2013 if ( mLegendItem )
2014 {
2015 disconnect( mLegendItem, &QgsLayoutObject::changed, this, &QgsLayoutLegendMapFilteringWidget::updateGuiElements );
2016 }
2017
2018 mLegendItem = qobject_cast<QgsLayoutItemLegend *>( item );
2019
2020 if ( mLegendItem )
2021 {
2022 connect( mLegendItem, &QgsLayoutObject::changed, this, &QgsLayoutLegendMapFilteringWidget::updateGuiElements );
2023 }
2024
2025 updateGuiElements();
2026
2027 return true;
2028}
2029
2030void QgsLayoutLegendMapFilteringWidget::updateGuiElements()
2031{
2032 if ( mBlockUpdates )
2033 return;
2034
2035 mBlockUpdates = true;
2036
2037 if ( mFilterMapItemsListView->model() )
2038 {
2039 QAbstractItemModel *oldModel = mFilterMapItemsListView->model();
2040 mFilterMapItemsListView->setModel( nullptr );
2041 oldModel->deleteLater();
2042 }
2043
2044 QgsLayoutLegendMapFilteringModel *model = new QgsLayoutLegendMapFilteringModel( mLegendItem, mLegendItem->layout()->itemsModel(), mFilterMapItemsListView );
2045 mFilterMapItemsListView->setModel( model );
2046
2047 mBlockUpdates = false;
2048}
2049
2050//
2051// QgsLayoutLegendMapFilteringModel
2052//
2053
2054QgsLayoutLegendMapFilteringModel::QgsLayoutLegendMapFilteringModel( QgsLayoutItemLegend *legend, QgsLayoutModel *layoutModel, QObject *parent )
2055 : QSortFilterProxyModel( parent )
2056 , mLayoutModel( layoutModel )
2057 , mLegendItem( legend )
2058{
2059 setSourceModel( layoutModel );
2060}
2061
2062int QgsLayoutLegendMapFilteringModel::columnCount( const QModelIndex & ) const
2063{
2064 return 1;
2065}
2066
2067QVariant QgsLayoutLegendMapFilteringModel::data( const QModelIndex &i, int role ) const
2068{
2069 if ( !i.isValid() )
2070 return QVariant();
2071
2072 if ( i.column() != 0 )
2073 return QVariant();
2074
2075 const QModelIndex sourceIndex = mapToSource( index( i.row(), QgsLayoutModel::ItemId, i.parent() ) );
2076
2077 QgsLayoutItemMap *mapItem = qobject_cast<QgsLayoutItemMap *>( mLayoutModel->itemFromIndex( mapToSource( i ) ) );
2078 if ( !mapItem )
2079 {
2080 return QVariant();
2081 }
2082
2083 switch ( role )
2084 {
2085 case Qt::CheckStateRole:
2086 switch ( i.column() )
2087 {
2088 case 0:
2089 {
2090 if ( !mLegendItem )
2091 return Qt::Unchecked;
2092
2093 return mLegendItem->filterByMapItems().contains( mapItem ) ? Qt::Checked : Qt::Unchecked;
2094 }
2095
2096 default:
2097 return QVariant();
2098 }
2099
2100 default:
2101 return mLayoutModel->data( sourceIndex, role );
2102 }
2103}
2104
2105bool QgsLayoutLegendMapFilteringModel::setData( const QModelIndex &index, const QVariant &value, int role )
2106{
2107 Q_UNUSED( role )
2108
2109 if ( !index.isValid() )
2110 return false;
2111
2112 QgsLayoutItemMap *mapItem = qobject_cast<QgsLayoutItemMap *>( mLayoutModel->itemFromIndex( mapToSource( index ) ) );
2113 if ( !mapItem || !mLegendItem )
2114 {
2115 return false;
2116 }
2117
2118 mLegendItem->layout()->undoStack()->beginCommand( mLegendItem, tr( "Change Legend Linked Maps" ) );
2119
2120 QList<QgsLayoutItemMap *> linkedMaps = mLegendItem->filterByMapItems();
2121 if ( value.toBool() )
2122 {
2123 if ( !linkedMaps.contains( mapItem ) )
2124 {
2125 linkedMaps.append( mapItem );
2126 mLegendItem->setFilterByMapItems( linkedMaps );
2127 }
2128 }
2129 else
2130 {
2131 linkedMaps.removeAll( mapItem );
2132 mLegendItem->setFilterByMapItems( linkedMaps );
2133 }
2134 emit dataChanged( index, index, QVector<int>() << role );
2135
2136 mLegendItem->layout()->undoStack()->endCommand();
2137 mLegendItem->invalidateCache();
2138
2139 return true;
2140}
2141
2142Qt::ItemFlags QgsLayoutLegendMapFilteringModel::flags( const QModelIndex &index ) const
2143{
2144 Qt::ItemFlags flags = QSortFilterProxyModel::flags( index );
2145
2146 if ( !index.isValid() )
2147 {
2148 return flags;
2149 }
2150
2151 QgsLayoutItemMap *mapItem = qobject_cast<QgsLayoutItemMap *>( mLayoutModel->itemFromIndex( mapToSource( index ) ) );
2152 const bool isMainLinkedMapItem = mLegendItem ? ( mLegendItem->linkedMap() == mapItem ) : false;
2153
2154 // the main linked map item will always be considered checked in this panel.
2155 // otherwise we have a potentially confusing user situation where they have selected a main linked map for their legend
2156 // and enabled the filter by map option, but the filtering isn't applying to that main linked map (ie. things don't work
2157 // as they did in < 3.32)
2158 if ( !isMainLinkedMapItem )
2159 {
2160 flags |= Qt::ItemIsEnabled;
2161 }
2162 else
2163 {
2164 flags &= ~Qt::ItemIsEnabled;
2165 }
2166
2167 switch ( index.column() )
2168 {
2169 case 0:
2170 if ( !isMainLinkedMapItem )
2171 return flags | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable;
2172 else
2173 return flags | Qt::ItemIsSelectable;
2174
2175 default:
2176 return flags | Qt::ItemIsSelectable;
2177 }
2178}
2179
2180bool QgsLayoutLegendMapFilteringModel::filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const
2181{
2182 QgsLayoutItem *item = mLayoutModel->itemFromIndex( mLayoutModel->index( source_row, 0, source_parent ) );
2183 if ( !item || item->type() != QgsLayoutItemRegistry::ItemType::LayoutMap )
2184 {
2185 return false;
2186 }
2187
2188 return true;
2189}
2190
2191//
2192// QgsLegendLayerTreeProxyModel
2193//
2194
2195QgsLegendLayerTreeProxyModel::QgsLegendLayerTreeProxyModel( QgsLayoutItemLegend *legend, QObject *parent )
2196 : QgsLayerTreeProxyModel( legend->model(), parent )
2197{
2198 setSyncMode( legend->syncMode() );
2199}
2200
2201void QgsLegendLayerTreeProxyModel::setSyncMode( Qgis::LegendSyncMode mode )
2202{
2203 mSyncMode = mode;
2204
2205 // only show private layers when in manual mode
2206 setShowPrivateLayers( mSyncMode == Qgis::LegendSyncMode::Manual );
2207
2208 invalidateFilter();
2209}
2210
2211bool QgsLegendLayerTreeProxyModel::nodeShown( QgsLayerTreeNode *node ) const
2212{
2213 if ( !QgsLayerTreeProxyModel::nodeShown( node ) )
2214 return false;
2215
2216 if ( node && node->nodeType() == QgsLayerTreeNode::NodeLayer )
2217 {
2218 if ( QgsMapLayer *layer = QgsLayerTree::toLayer( node )->layer() )
2219 {
2220 if ( QgsMapLayerLegend *layerLegend = layer->legend(); layerLegend && layerLegend->flags().testFlag( Qgis::MapLayerLegendFlag::ExcludeByDefault ) )
2221 {
2222 switch ( mSyncMode )
2223 {
2226 return false;
2227
2229 break;
2230 }
2231 }
2232 }
2233 }
2234
2235 return true;
2236}
2237
2238
@ Pixels
Pixels.
Definition qgis.h:5368
LegendComponent
Component of legends which can be styled.
Definition qgis.h:4719
@ Symbol
Symbol icon (excluding label).
Definition qgis.h:4725
@ Group
Legend group title.
Definition qgis.h:4723
@ Hidden
Special style, item is hidden including margins around.
Definition qgis.h:4721
@ Subgroup
Legend subgroup title.
Definition qgis.h:4724
@ Title
Legend title.
Definition qgis.h:4722
@ SymbolLabel
Symbol label (excluding icon).
Definition qgis.h:4726
LegendSyncMode
Legend synchronization mode.
Definition qgis.h:4737
@ VisibleLayers
Synchronize to map layers. The legend will include layers which are included in the linked map only.
Definition qgis.h:4739
@ AllProjectLayers
Synchronize to all project layers.
Definition qgis.h:4738
@ Manual
No automatic synchronization of legend layers. The legend will be manually populated.
Definition qgis.h:4740
@ Point
Points.
Definition qgis.h:380
@ Line
Lines.
Definition qgis.h:381
@ Polygon
Polygons.
Definition qgis.h:382
@ ExcludeByDefault
If set, the layer should not be included in legends by default, and must be manually added by a user.
Definition qgis.h:4699
@ Vector
Vector layer.
Definition qgis.h:207
@ Marker
Marker symbol.
Definition qgis.h:637
@ Line
Line symbol.
Definition qgis.h:638
@ Fill
Fill symbol.
Definition qgis.h:639
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:227
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:6880