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