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 
18 #include "qgslayoutlegendwidget.h"
19 #include "qgslayoutitemlegend.h"
20 #include "qgslayoutlegendlayersdialog.h"
21 #include "qgslayoutitemwidget.h"
22 #include "qgslayoutitemmap.h"
23 #include "qgslayout.h"
24 #include "qgsguiutils.h"
25 
26 #include "qgsapplication.h"
27 #include "qgslayertree.h"
28 #include "qgslayertreeutils.h"
29 #include "qgslayertreemodel.h"
30 #include "qgslayertreemodellegendnode.h"
31 #include "qgslegendrenderer.h"
32 #include "qgsmapcanvas.h"
33 #include "qgsmaplayerlegend.h"
34 #include "qgsproject.h"
35 #include "qgsrenderer.h"
36 #include "qgsvectorlayer.h"
37 #include "qgslayoutatlas.h"
38 #include "qgslayoutitemlegend.h"
39 #include "qgslayoutmeasurementconverter.h"
40 #include "qgsunittypes.h"
41 #include "qgsexpressionbuilderdialog.h"
42 #include "qgsexpressioncontextutils.h"
43 #include "qgslegendpatchshapewidget.h"
44 
45 #include <QMenu>
46 #include <QMessageBox>
47 #include <QInputDialog>
48 
49 ///@cond PRIVATE
50 
51 Q_GUI_EXPORT extern int qt_defaultDpiX();
52 
_unfilteredLegendNodeIndex(QgsLayerTreeModelLegendNode * legendNode)53 static int _unfilteredLegendNodeIndex( QgsLayerTreeModelLegendNode *legendNode )
54 {
55   return legendNode->model()->layerOriginalLegendNodes( legendNode->layerNode() ).indexOf( legendNode );
56 }
57 
_originalLegendNodeIndex(QgsLayerTreeModelLegendNode * legendNode)58 static int _originalLegendNodeIndex( QgsLayerTreeModelLegendNode *legendNode )
59 {
60   // figure out index of the legend node as it comes out of the map layer legend.
61   // first legend nodes may be reordered, output of that is available in layerOriginalLegendNodes().
62   // next the nodes may be further filtered (by scale, map content etc).
63   // so here we go in reverse order: 1. find index before filtering, 2. find index before reorder
64   int unfilteredNodeIndex = _unfilteredLegendNodeIndex( legendNode );
65   QList<int> order = QgsMapLayerLegendUtils::legendNodeOrder( legendNode->layerNode() );
66   return ( unfilteredNodeIndex >= 0 && unfilteredNodeIndex < order.count() ? order[unfilteredNodeIndex] : -1 );
67 }
68 
69 
QgsLayoutLegendWidget(QgsLayoutItemLegend * legend,QgsMapCanvas * mapCanvas)70 QgsLayoutLegendWidget::QgsLayoutLegendWidget( QgsLayoutItemLegend *legend, QgsMapCanvas *mapCanvas )
71   : QgsLayoutItemBaseWidget( nullptr, legend )
72   , mLegend( legend )
73   , mMapCanvas( mapCanvas )
74 {
75   Q_ASSERT( mLegend );
76 
77   setupUi( this );
78   connect( mWrapCharLineEdit, &QLineEdit::textChanged, this, &QgsLayoutLegendWidget::mWrapCharLineEdit_textChanged );
79   connect( mTitleLineEdit, &QLineEdit::textChanged, this, &QgsLayoutLegendWidget::mTitleLineEdit_textChanged );
80   connect( mTitleAlignCombo, &QgsAlignmentComboBox::changed, this, &QgsLayoutLegendWidget::titleAlignmentChanged );
81   connect( mGroupAlignCombo, &QgsAlignmentComboBox::changed, this, &QgsLayoutLegendWidget::groupAlignmentChanged );
82   connect( mSubgroupAlignCombo, &QgsAlignmentComboBox::changed, this, &QgsLayoutLegendWidget::subgroupAlignmentChanged );
83   connect( mItemAlignCombo, &QgsAlignmentComboBox::changed, this, &QgsLayoutLegendWidget::itemAlignmentChanged );
84   connect( mColumnCountSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mColumnCountSpinBox_valueChanged );
85   connect( mSplitLayerCheckBox, &QCheckBox::toggled, this, &QgsLayoutLegendWidget::mSplitLayerCheckBox_toggled );
86   connect( mEqualColumnWidthCheckBox, &QCheckBox::toggled, this, &QgsLayoutLegendWidget::mEqualColumnWidthCheckBox_toggled );
87   connect( mSymbolWidthSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mSymbolWidthSpinBox_valueChanged );
88   connect( mSymbolHeightSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mSymbolHeightSpinBox_valueChanged );
89   connect( mMaxSymbolSizeSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mMaxSymbolSizeSpinBox_valueChanged );
90   connect( mMinSymbolSizeSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mMinSymbolSizeSpinBox_valueChanged );
91   connect( mWmsLegendWidthSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mWmsLegendWidthSpinBox_valueChanged );
92   connect( mWmsLegendHeightSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mWmsLegendHeightSpinBox_valueChanged );
93   connect( mTitleSpaceBottomSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mTitleSpaceBottomSpinBox_valueChanged );
94   connect( mGroupSpaceSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mGroupSpaceSpinBox_valueChanged );
95   connect( mSpaceBelowGroupHeadingSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::spaceBelowGroupHeadingChanged );
96   connect( mGroupSideSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::spaceGroupSideChanged );
97   connect( mLayerSpaceSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mLayerSpaceSpinBox_valueChanged );
98   connect( mSpaceBelowSubgroupHeadingSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::spaceBelowSubGroupHeadingChanged );
99   connect( mSubgroupSideSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::spaceSubGroupSideChanged );
100   connect( mSymbolSpaceSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mSymbolSpaceSpinBox_valueChanged );
101   connect( mSymbolSideSpaceSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::spaceSymbolSideChanged );
102   connect( mIconLabelSpaceSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mIconLabelSpaceSpinBox_valueChanged );
103   connect( mFontColorButton, &QgsColorButton::colorChanged, this, &QgsLayoutLegendWidget::mFontColorButton_colorChanged );
104   connect( mBoxSpaceSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mBoxSpaceSpinBox_valueChanged );
105   connect( mColumnSpaceSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mColumnSpaceSpinBox_valueChanged );
106   connect( mLineSpacingSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mLineSpacingSpinBox_valueChanged );
107   connect( mCheckBoxAutoUpdate, &QCheckBox::stateChanged, this, [ = ]( int state ) { mCheckBoxAutoUpdate_stateChanged( state ); } );
108   connect( mCheckboxResizeContents, &QCheckBox::toggled, this, &QgsLayoutLegendWidget::mCheckboxResizeContents_toggled );
109   connect( mRasterStrokeGroupBox, &QgsCollapsibleGroupBoxBasic::toggled, this, &QgsLayoutLegendWidget::mRasterStrokeGroupBox_toggled );
110   connect( mRasterStrokeWidthSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendWidget::mRasterStrokeWidthSpinBox_valueChanged );
111   connect( mRasterStrokeColorButton, &QgsColorButton::colorChanged, this, &QgsLayoutLegendWidget::mRasterStrokeColorButton_colorChanged );
112   connect( mMoveDownToolButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::mMoveDownToolButton_clicked );
113   connect( mMoveUpToolButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::mMoveUpToolButton_clicked );
114   connect( mRemoveToolButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::mRemoveToolButton_clicked );
115   connect( mAddToolButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::mAddToolButton_clicked );
116   connect( mEditPushButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::mEditPushButton_clicked );
117   connect( mCountToolButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::mCountToolButton_clicked );
118   connect( mExpressionFilterButton, &QgsLegendFilterButton::toggled, this, &QgsLayoutLegendWidget::mExpressionFilterButton_toggled );
119   connect( mLayerExpressionButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::mLayerExpressionButton_clicked );
120   connect( mFilterByMapCheckBox, &QCheckBox::toggled, this, &QgsLayoutLegendWidget::mFilterByMapCheckBox_toggled );
121   connect( mUpdateAllPushButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::mUpdateAllPushButton_clicked );
122   connect( mAddGroupToolButton, &QToolButton::clicked, this, &QgsLayoutLegendWidget::mAddGroupToolButton_clicked );
123   connect( mFilterLegendByAtlasCheckBox, &QCheckBox::toggled, this, &QgsLayoutLegendWidget::mFilterLegendByAtlasCheckBox_toggled );
124   connect( mItemTreeView, &QgsLayerTreeView::doubleClicked, this, &QgsLayoutLegendWidget::mItemTreeView_doubleClicked );
125   setPanelTitle( tr( "Legend Properties" ) );
126 
127   mTitleFontButton->setMode( QgsFontButton::ModeQFont );
128   mGroupFontButton->setMode( QgsFontButton::ModeQFont );
129   mLayerFontButton->setMode( QgsFontButton::ModeQFont );
130   mItemFontButton->setMode( QgsFontButton::ModeQFont );
131 
132   mTitleAlignCombo->setAvailableAlignments( Qt::AlignLeft | Qt::AlignHCenter | Qt::AlignRight );
133   mGroupAlignCombo->setAvailableAlignments( Qt::AlignLeft | Qt::AlignHCenter | Qt::AlignRight );
134   mSubgroupAlignCombo->setAvailableAlignments( Qt::AlignLeft | Qt::AlignHCenter | Qt::AlignRight );
135   mItemAlignCombo->setAvailableAlignments( Qt::AlignLeft | Qt::AlignHCenter | Qt::AlignRight );
136 
137   mArrangementCombo->setAvailableAlignments( Qt::AlignLeft | Qt::AlignRight );
138   connect( mArrangementCombo, &QgsAlignmentComboBox::changed, this, &QgsLayoutLegendWidget::arrangementChanged );
139   mArrangementCombo->customizeAlignmentDisplay( Qt::AlignLeft, tr( "Symbols on Left" ), QgsApplication::getThemeIcon( QStringLiteral( "/mIconArrangeSymbolsLeft.svg" ) ) );
140   mArrangementCombo->customizeAlignmentDisplay( Qt::AlignRight, tr( "Symbols on Right" ), QgsApplication::getThemeIcon( QStringLiteral( "/mIconArrangeSymbolsRight.svg" ) ) );
141 
142   mSpaceBelowGroupHeadingSpinBox->setClearValue( 0 );
143   mGroupSideSpinBox->setClearValue( 0 );
144   mSpaceBelowSubgroupHeadingSpinBox->setClearValue( 0 );
145   mSubgroupSideSpinBox->setClearValue( 0 );
146   mSymbolSideSpaceSpinBox->setClearValue( 0 );
147 
148   // setup icons
149   mAddToolButton->setIcon( QIcon( QgsApplication::iconPath( "symbologyAdd.svg" ) ) );
150   mEditPushButton->setIcon( QIcon( QgsApplication::iconPath( "symbologyEdit.svg" ) ) );
151   mRemoveToolButton->setIcon( QIcon( QgsApplication::iconPath( "symbologyRemove.svg" ) ) );
152   mMoveUpToolButton->setIcon( QIcon( QgsApplication::iconPath( "mActionArrowUp.svg" ) ) );
153   mMoveDownToolButton->setIcon( QIcon( QgsApplication::iconPath( "mActionArrowDown.svg" ) ) );
154   mCountToolButton->setIcon( QIcon( QgsApplication::iconPath( "mActionSum.svg" ) ) );
155   mLayerExpressionButton->setIcon( QIcon( QgsApplication::iconPath( "mIconExpression.svg" ) ) );
156 
157   mMoveDownToolButton->setIconSize( QgsGuiUtils::iconSize( true ) );
158   mMoveUpToolButton->setIconSize( QgsGuiUtils::iconSize( true ) );
159   mAddGroupToolButton->setIconSize( QgsGuiUtils::iconSize( true ) );
160   mAddToolButton->setIconSize( QgsGuiUtils::iconSize( true ) );
161   mRemoveToolButton->setIconSize( QgsGuiUtils::iconSize( true ) );
162   mEditPushButton->setIconSize( QgsGuiUtils::iconSize( true ) );
163   mCountToolButton->setIconSize( QgsGuiUtils::iconSize( true ) );
164   mExpressionFilterButton->setIconSize( QgsGuiUtils::iconSize( true ) );
165   mLayerExpressionButton->setIconSize( QgsGuiUtils::iconSize( true ) );
166 
167   mFontColorButton->setColorDialogTitle( tr( "Select Font Color" ) );
168   mFontColorButton->setContext( QStringLiteral( "composer" ) );
169 
170   mRasterStrokeColorButton->setColorDialogTitle( tr( "Select Stroke Color" ) );
171   mRasterStrokeColorButton->setAllowOpacity( true );
172   mRasterStrokeColorButton->setContext( QStringLiteral( "composer " ) );
173 
174   mMapComboBox->setCurrentLayout( legend->layout() );
175   mMapComboBox->setItemType( QgsLayoutItemRegistry::LayoutMap );
176   connect( mMapComboBox, &QgsLayoutItemComboBox::itemChanged, this, &QgsLayoutLegendWidget::composerMapChanged );
177 
178   //add widget for general composer item properties
179   mItemPropertiesWidget = new QgsLayoutItemPropertiesWidget( this, legend );
180   mainLayout->addWidget( mItemPropertiesWidget );
181 
182   mItemTreeView->setHeaderHidden( true );
183 
184   mItemTreeView->setModel( legend->model() );
185   mItemTreeView->setMenuProvider( new QgsLayoutLegendMenuProvider( mItemTreeView, this ) );
186   setLegendMapViewData();
187   connect( legend, &QgsLayoutObject::changed, this, &QgsLayoutLegendWidget::setGuiElements );
188 
189   // connect atlas state to the filter legend by atlas checkbox
190   if ( layoutAtlas() )
191   {
192     connect( layoutAtlas(), &QgsLayoutAtlas::toggled, this, &QgsLayoutLegendWidget::updateFilterLegendByAtlasButton );
193   }
194   connect( &legend->layout()->reportContext(), &QgsLayoutReportContext::layerChanged, this, &QgsLayoutLegendWidget::updateFilterLegendByAtlasButton );
195 
196   registerDataDefinedButton( mLegendTitleDDBtn, QgsLayoutObject::LegendTitle );
197   registerDataDefinedButton( mColumnsDDBtn, QgsLayoutObject::LegendColumnCount );
198 
199   setGuiElements();
200 
201   connect( mItemTreeView->selectionModel(), &QItemSelectionModel::currentChanged,
202            this, &QgsLayoutLegendWidget::selectedChanged );
203   connect( mTitleFontButton, &QgsFontButton::changed, this, &QgsLayoutLegendWidget::titleFontChanged );
204   connect( mGroupFontButton, &QgsFontButton::changed, this, &QgsLayoutLegendWidget::groupFontChanged );
205   connect( mLayerFontButton, &QgsFontButton::changed, this, &QgsLayoutLegendWidget::layerFontChanged );
206   connect( mItemFontButton, &QgsFontButton::changed, this, &QgsLayoutLegendWidget::itemFontChanged );
207 }
208 
setMasterLayout(QgsMasterLayoutInterface * masterLayout)209 void QgsLayoutLegendWidget::setMasterLayout( QgsMasterLayoutInterface *masterLayout )
210 {
211   if ( mItemPropertiesWidget )
212     mItemPropertiesWidget->setMasterLayout( masterLayout );
213 }
214 
setGuiElements()215 void QgsLayoutLegendWidget::setGuiElements()
216 {
217   if ( !mLegend )
218   {
219     return;
220   }
221 
222   blockAllSignals( true );
223   mTitleLineEdit->setText( mLegend->title() );
224   whileBlocking( mTitleAlignCombo )->setCurrentAlignment( mLegend->titleAlignment() );
225   whileBlocking( mGroupAlignCombo )->setCurrentAlignment( mLegend->style( QgsLegendStyle::Group ).alignment() );
226   whileBlocking( mSubgroupAlignCombo )->setCurrentAlignment( mLegend->style( QgsLegendStyle::Subgroup ).alignment() );
227   whileBlocking( mItemAlignCombo )->setCurrentAlignment( mLegend->style( QgsLegendStyle::SymbolLabel ).alignment() );
228   whileBlocking( mArrangementCombo )->setCurrentAlignment( mLegend->symbolAlignment() );
229   mFilterByMapCheckBox->setChecked( mLegend->legendFilterByMapEnabled() );
230   mColumnCountSpinBox->setValue( mLegend->columnCount() );
231   mSplitLayerCheckBox->setChecked( mLegend->splitLayer() );
232   mEqualColumnWidthCheckBox->setChecked( mLegend->equalColumnWidth() );
233   mSymbolWidthSpinBox->setValue( mLegend->symbolWidth() );
234   mSymbolHeightSpinBox->setValue( mLegend->symbolHeight() );
235   mMaxSymbolSizeSpinBox->setValue( mLegend->maximumSymbolSize() );
236   mMinSymbolSizeSpinBox->setValue( mLegend->minimumSymbolSize() );
237   mWmsLegendWidthSpinBox->setValue( mLegend->wmsLegendWidth() );
238   mWmsLegendHeightSpinBox->setValue( mLegend->wmsLegendHeight() );
239   mTitleSpaceBottomSpinBox->setValue( mLegend->style( QgsLegendStyle::Title ).margin( QgsLegendStyle::Bottom ) );
240   mGroupSpaceSpinBox->setValue( mLegend->style( QgsLegendStyle::Group ).margin( QgsLegendStyle::Top ) );
241   mGroupSideSpinBox->setValue( mLegend->style( QgsLegendStyle::Group ).margin( QgsLegendStyle::Left ) );
242   mSpaceBelowGroupHeadingSpinBox->setValue( mLegend->style( QgsLegendStyle::Group ).margin( QgsLegendStyle::Bottom ) );
243   mLayerSpaceSpinBox->setValue( mLegend->style( QgsLegendStyle::Subgroup ).margin( QgsLegendStyle::Top ) );
244   mSpaceBelowSubgroupHeadingSpinBox->setValue( mLegend->style( QgsLegendStyle::Subgroup ).margin( QgsLegendStyle::Bottom ) );
245   mSubgroupSideSpinBox->setValue( mLegend->style( QgsLegendStyle::Subgroup ).margin( QgsLegendStyle::Left ) );
246   // We keep Symbol and SymbolLabel Top in sync for now
247   mSymbolSpaceSpinBox->setValue( mLegend->style( QgsLegendStyle::Symbol ).margin( QgsLegendStyle::Top ) );
248   mIconLabelSpaceSpinBox->setValue( mLegend->style( QgsLegendStyle::SymbolLabel ).margin( QgsLegendStyle::Left ) );
249   mSymbolSideSpaceSpinBox->setValue( mLegend->style( QgsLegendStyle::Symbol ).margin( QgsLegendStyle::Left ) );
250   mBoxSpaceSpinBox->setValue( mLegend->boxSpace() );
251   mColumnSpaceSpinBox->setValue( mLegend->columnSpace() );
252   mLineSpacingSpinBox->setValue( mLegend->lineSpacing() );
253 
254   mRasterStrokeGroupBox->setChecked( mLegend->drawRasterStroke() );
255   mRasterStrokeWidthSpinBox->setValue( mLegend->rasterStrokeWidth() );
256   mRasterStrokeColorButton->setColor( mLegend->rasterStrokeColor() );
257 
258   mCheckBoxAutoUpdate->setChecked( mLegend->autoUpdateModel() );
259 
260   mCheckboxResizeContents->setChecked( mLegend->resizeToContents() );
261   mFilterLegendByAtlasCheckBox->setChecked( mLegend->legendFilterOutAtlas() );
262   mWrapCharLineEdit->setText( mLegend->wrapString() );
263 
264   QgsLayoutItemMap *map = mLegend->linkedMap();
265   mMapComboBox->setItem( map );
266   mFontColorButton->setColor( mLegend->fontColor() );
267   mTitleFontButton->setCurrentFont( mLegend->style( QgsLegendStyle::Title ).font() );
268   mGroupFontButton->setCurrentFont( mLegend->style( QgsLegendStyle::Group ).font() );
269   mLayerFontButton->setCurrentFont( mLegend->style( QgsLegendStyle::Subgroup ).font() );
270   mItemFontButton->setCurrentFont( mLegend->style( QgsLegendStyle::SymbolLabel ).font() );
271 
272   blockAllSignals( false );
273 
274   mCheckBoxAutoUpdate_stateChanged( mLegend->autoUpdateModel() ? Qt::Checked : Qt::Unchecked, false );
275   updateDataDefinedButton( mLegendTitleDDBtn );
276   updateDataDefinedButton( mColumnsDDBtn );
277 }
278 
mWrapCharLineEdit_textChanged(const QString & text)279 void QgsLayoutLegendWidget::mWrapCharLineEdit_textChanged( const QString &text )
280 {
281   if ( mLegend )
282   {
283     mLegend->beginCommand( tr( "Change Legend Wrap" ) );
284     mLegend->setWrapString( text );
285     mLegend->adjustBoxSize();
286     mLegend->update();
287     mLegend->endCommand();
288   }
289 }
290 
mTitleLineEdit_textChanged(const QString & text)291 void QgsLayoutLegendWidget::mTitleLineEdit_textChanged( const QString &text )
292 {
293   if ( mLegend )
294   {
295     mLegend->beginCommand( tr( "Change Legend Title" ), QgsLayoutItem::UndoLegendText );
296     mLegend->setTitle( text );
297     mLegend->adjustBoxSize();
298     mLegend->update();
299     mLegend->endCommand();
300   }
301 }
302 
titleAlignmentChanged()303 void QgsLayoutLegendWidget::titleAlignmentChanged()
304 {
305   if ( mLegend )
306   {
307     Qt::AlignmentFlag alignment = static_cast< Qt::AlignmentFlag >( static_cast< int  >( mTitleAlignCombo->currentAlignment() & Qt::AlignHorizontal_Mask ) );
308     mLegend->beginCommand( tr( "Change Title Alignment" ) );
309     mLegend->setTitleAlignment( alignment );
310     mLegend->update();
311     mLegend->endCommand();
312   }
313 }
314 
groupAlignmentChanged()315 void QgsLayoutLegendWidget::groupAlignmentChanged()
316 {
317   if ( mLegend )
318   {
319     mLegend->beginCommand( tr( "Change Group Alignment" ) );
320     mLegend->rstyle( QgsLegendStyle::Group ).setAlignment( mGroupAlignCombo->currentAlignment() );
321     mLegend->update();
322     mLegend->endCommand();
323   }
324 }
325 
subgroupAlignmentChanged()326 void QgsLayoutLegendWidget::subgroupAlignmentChanged()
327 {
328   if ( mLegend )
329   {
330     mLegend->beginCommand( tr( "Change Subgroup Alignment" ) );
331     mLegend->rstyle( QgsLegendStyle::Subgroup ).setAlignment( mSubgroupAlignCombo->currentAlignment() );
332     mLegend->update();
333     mLegend->endCommand();
334   }
335 }
336 
itemAlignmentChanged()337 void QgsLayoutLegendWidget::itemAlignmentChanged()
338 {
339   if ( mLegend )
340   {
341     mLegend->beginCommand( tr( "Change Item Alignment" ) );
342     mLegend->rstyle( QgsLegendStyle::SymbolLabel ).setAlignment( mItemAlignCombo->currentAlignment() );
343     mLegend->update();
344     mLegend->endCommand();
345   }
346 }
347 
arrangementChanged()348 void QgsLayoutLegendWidget::arrangementChanged()
349 {
350   if ( mLegend )
351   {
352     Qt::AlignmentFlag alignment = static_cast< Qt::AlignmentFlag >( static_cast< int  >( mArrangementCombo->currentAlignment() & Qt::AlignHorizontal_Mask ) );
353     mLegend->beginCommand( tr( "Change Legend Arrangement" ) );
354     mLegend->setSymbolAlignment( alignment );
355     mLegend->update();
356     mLegend->endCommand();
357   }
358 }
359 
mColumnCountSpinBox_valueChanged(int c)360 void QgsLayoutLegendWidget::mColumnCountSpinBox_valueChanged( int c )
361 {
362   if ( mLegend )
363   {
364     mLegend->beginCommand( tr( "Change Column Count" ), QgsLayoutItem::UndoLegendColumnCount );
365     mLegend->setColumnCount( c );
366     mLegend->adjustBoxSize();
367     mLegend->update();
368     mLegend->endCommand();
369   }
370   mSplitLayerCheckBox->setEnabled( c > 1 );
371   mEqualColumnWidthCheckBox->setEnabled( c > 1 );
372 }
373 
mSplitLayerCheckBox_toggled(bool checked)374 void QgsLayoutLegendWidget::mSplitLayerCheckBox_toggled( bool checked )
375 {
376   if ( mLegend )
377   {
378     mLegend->beginCommand( tr( "Split Legend Layers" ) );
379     mLegend->setSplitLayer( checked );
380     mLegend->adjustBoxSize();
381     mLegend->update();
382     mLegend->endCommand();
383   }
384 }
385 
mEqualColumnWidthCheckBox_toggled(bool checked)386 void QgsLayoutLegendWidget::mEqualColumnWidthCheckBox_toggled( bool checked )
387 {
388   if ( mLegend )
389   {
390     mLegend->beginCommand( tr( "Legend Column Width" ) );
391     mLegend->setEqualColumnWidth( checked );
392     mLegend->adjustBoxSize();
393     mLegend->update();
394     mLegend->endCommand();
395   }
396 }
397 
mSymbolWidthSpinBox_valueChanged(double d)398 void QgsLayoutLegendWidget::mSymbolWidthSpinBox_valueChanged( double d )
399 {
400   if ( mLegend )
401   {
402     mLegend->beginCommand( tr( "Resize Symbol Width" ), QgsLayoutItem::UndoLegendSymbolWidth );
403     mLegend->setSymbolWidth( d );
404     mLegend->adjustBoxSize();
405     mLegend->update();
406     mLegend->endCommand();
407   }
408 }
409 
mMaxSymbolSizeSpinBox_valueChanged(double d)410 void QgsLayoutLegendWidget::mMaxSymbolSizeSpinBox_valueChanged( double d )
411 {
412   if ( mLegend )
413   {
414     mLegend->beginCommand( tr( "Change Legend Maximum Symbol Size" ), QgsLayoutItem::UndoLegendMaxSymbolSize );
415     mLegend->setMaximumSymbolSize( d );
416     mLegend->adjustBoxSize();
417     mLegend->update();
418     mLegend->endCommand();
419   }
420 }
421 
mMinSymbolSizeSpinBox_valueChanged(double d)422 void QgsLayoutLegendWidget::mMinSymbolSizeSpinBox_valueChanged( double d )
423 {
424   if ( mLegend )
425   {
426     mLegend->beginCommand( tr( "Change Legend Minimum Symbol Size" ), QgsLayoutItem::UndoLegendMinSymbolSize );
427     mLegend->setMinimumSymbolSize( d );
428     mLegend->adjustBoxSize();
429     mLegend->update();
430     mLegend->endCommand();
431   }
432 }
433 
mSymbolHeightSpinBox_valueChanged(double d)434 void QgsLayoutLegendWidget::mSymbolHeightSpinBox_valueChanged( double d )
435 {
436   if ( mLegend )
437   {
438     mLegend->beginCommand( tr( "Resize Symbol Height" ), QgsLayoutItem::UndoLegendSymbolHeight );
439     mLegend->setSymbolHeight( d );
440     mLegend->adjustBoxSize();
441     mLegend->update();
442     mLegend->endCommand();
443   }
444 }
445 
mWmsLegendWidthSpinBox_valueChanged(double d)446 void QgsLayoutLegendWidget::mWmsLegendWidthSpinBox_valueChanged( double d )
447 {
448   if ( mLegend )
449   {
450     mLegend->beginCommand( tr( "Resize WMS Width" ), QgsLayoutItem::UndoLegendWmsLegendWidth );
451     mLegend->setWmsLegendWidth( d );
452     mLegend->adjustBoxSize();
453     mLegend->update();
454     mLegend->endCommand();
455   }
456 }
457 
mWmsLegendHeightSpinBox_valueChanged(double d)458 void QgsLayoutLegendWidget::mWmsLegendHeightSpinBox_valueChanged( double d )
459 {
460   if ( mLegend )
461   {
462     mLegend->beginCommand( tr( "Resize WMS Height" ), QgsLayoutItem::UndoLegendWmsLegendHeight );
463     mLegend->setWmsLegendHeight( d );
464     mLegend->adjustBoxSize();
465     mLegend->update();
466     mLegend->endCommand();
467   }
468 }
469 
mTitleSpaceBottomSpinBox_valueChanged(double d)470 void QgsLayoutLegendWidget::mTitleSpaceBottomSpinBox_valueChanged( double d )
471 {
472   if ( mLegend )
473   {
474     mLegend->beginCommand( tr( "Change Title Space" ), QgsLayoutItem::UndoLegendTitleSpaceBottom );
475     mLegend->rstyle( QgsLegendStyle::Title ).setMargin( QgsLegendStyle::Bottom, d );
476     mLegend->adjustBoxSize();
477     mLegend->update();
478     mLegend->endCommand();
479   }
480 }
481 
mGroupSpaceSpinBox_valueChanged(double d)482 void QgsLayoutLegendWidget::mGroupSpaceSpinBox_valueChanged( double d )
483 {
484   if ( mLegend )
485   {
486     mLegend->beginCommand( tr( "Change Group Space" ), QgsLayoutItem::UndoLegendGroupSpace );
487     mLegend->rstyle( QgsLegendStyle::Group ).setMargin( QgsLegendStyle::Top, d );
488     mLegend->adjustBoxSize();
489     mLegend->update();
490     mLegend->endCommand();
491   }
492 }
493 
spaceBelowGroupHeadingChanged(double space)494 void QgsLayoutLegendWidget::spaceBelowGroupHeadingChanged( double space )
495 {
496   if ( mLegend )
497   {
498     mLegend->beginCommand( tr( "Change Group Space" ), QgsLayoutItem::UndoLegendGroupSpace );
499     mLegend->rstyle( QgsLegendStyle::Group ).setMargin( QgsLegendStyle::Bottom, space );
500     mLegend->adjustBoxSize();
501     mLegend->update();
502     mLegend->endCommand();
503   }
504 }
505 
spaceGroupSideChanged(double space)506 void QgsLayoutLegendWidget::spaceGroupSideChanged( double space )
507 {
508   if ( mLegend )
509   {
510     mLegend->beginCommand( tr( "Change Side of Group Space" ), QgsLayoutItem::UndoLegendGroupSpace );
511     mLegend->rstyle( QgsLegendStyle::Group ).setMargin( QgsLegendStyle::Left, space );
512     mLegend->adjustBoxSize();
513     mLegend->update();
514     mLegend->endCommand();
515   }
516 }
517 
spaceSubGroupSideChanged(double space)518 void QgsLayoutLegendWidget::spaceSubGroupSideChanged( double space )
519 {
520   if ( mLegend )
521   {
522     mLegend->beginCommand( tr( "Change Side of Subgroup Space" ), QgsLayoutItem::UndoLegendLayerSpace );
523     mLegend->rstyle( QgsLegendStyle::Subgroup ).setMargin( QgsLegendStyle::Left, space );
524     mLegend->adjustBoxSize();
525     mLegend->update();
526     mLegend->endCommand();
527   }
528 }
529 
spaceSymbolSideChanged(double space)530 void QgsLayoutLegendWidget::spaceSymbolSideChanged( double space )
531 {
532   if ( mLegend )
533   {
534     mLegend->beginCommand( tr( "Change Side of Symbol Space" ), QgsLayoutItem::UndoLegendSymbolSpace );
535     mLegend->rstyle( QgsLegendStyle::Symbol ).setMargin( QgsLegendStyle::Left, space );
536     mLegend->adjustBoxSize();
537     mLegend->update();
538     mLegend->endCommand();
539   }
540 }
541 
mLayerSpaceSpinBox_valueChanged(double d)542 void QgsLayoutLegendWidget::mLayerSpaceSpinBox_valueChanged( double d )
543 {
544   if ( mLegend )
545   {
546     mLegend->beginCommand( tr( "Change Subgroup Space" ), QgsLayoutItem::UndoLegendLayerSpace );
547     mLegend->rstyle( QgsLegendStyle::Subgroup ).setMargin( QgsLegendStyle::Top, d );
548     mLegend->adjustBoxSize();
549     mLegend->update();
550     mLegend->endCommand();
551   }
552 }
553 
mSymbolSpaceSpinBox_valueChanged(double d)554 void QgsLayoutLegendWidget::mSymbolSpaceSpinBox_valueChanged( double d )
555 {
556   if ( mLegend )
557   {
558     mLegend->beginCommand( tr( "Change Symbol Space" ), QgsLayoutItem::UndoLegendSymbolSpace );
559     // We keep Symbol and SymbolLabel Top in sync for now
560     mLegend->rstyle( QgsLegendStyle::Symbol ).setMargin( QgsLegendStyle::Top, d );
561     mLegend->rstyle( QgsLegendStyle::SymbolLabel ).setMargin( QgsLegendStyle::Top, d );
562     mLegend->adjustBoxSize();
563     mLegend->update();
564     mLegend->endCommand();
565   }
566 }
567 
mIconLabelSpaceSpinBox_valueChanged(double d)568 void QgsLayoutLegendWidget::mIconLabelSpaceSpinBox_valueChanged( double d )
569 {
570   if ( mLegend )
571   {
572     mLegend->beginCommand( tr( "Change Label Space" ), QgsLayoutItem::UndoLegendIconSymbolSpace );
573     mLegend->rstyle( QgsLegendStyle::SymbolLabel ).setMargin( QgsLegendStyle::Left, d );
574     mLegend->adjustBoxSize();
575     mLegend->update();
576     mLegend->endCommand();
577   }
578 }
579 
titleFontChanged()580 void QgsLayoutLegendWidget::titleFontChanged()
581 {
582   if ( mLegend )
583   {
584     mLegend->beginCommand( tr( "Change Title Font" ), QgsLayoutItem::UndoLegendTitleFont );
585     mLegend->setStyleFont( QgsLegendStyle::Title, mTitleFontButton->currentFont() );
586     mLegend->adjustBoxSize();
587     mLegend->update();
588     mLegend->endCommand();
589   }
590 }
591 
groupFontChanged()592 void QgsLayoutLegendWidget::groupFontChanged()
593 {
594   if ( mLegend )
595   {
596     mLegend->beginCommand( tr( "Change Group Font" ), QgsLayoutItem::UndoLegendGroupFont );
597     mLegend->setStyleFont( QgsLegendStyle::Group, mGroupFontButton->currentFont() );
598     mLegend->adjustBoxSize();
599     mLegend->update();
600     mLegend->endCommand();
601   }
602 }
603 
layerFontChanged()604 void QgsLayoutLegendWidget::layerFontChanged()
605 {
606   if ( mLegend )
607   {
608     mLegend->beginCommand( tr( "Change Layer Font" ), QgsLayoutItem::UndoLegendLayerFont );
609     mLegend->setStyleFont( QgsLegendStyle::Subgroup, mLayerFontButton->currentFont() );
610     mLegend->adjustBoxSize();
611     mLegend->update();
612     mLegend->endCommand();
613   }
614 }
615 
itemFontChanged()616 void QgsLayoutLegendWidget::itemFontChanged()
617 {
618   if ( mLegend )
619   {
620     mLegend->beginCommand( tr( "Change Item Font" ), QgsLayoutItem::UndoLegendItemFont );
621     mLegend->setStyleFont( QgsLegendStyle::SymbolLabel, mItemFontButton->currentFont() );
622     mLegend->adjustBoxSize();
623     mLegend->update();
624     mLegend->endCommand();
625   }
626 }
627 
spaceBelowSubGroupHeadingChanged(double space)628 void QgsLayoutLegendWidget::spaceBelowSubGroupHeadingChanged( double space )
629 {
630   if ( mLegend )
631   {
632     mLegend->beginCommand( tr( "Change Subgroup Space" ), QgsLayoutItem::UndoLegendLayerSpace );
633     mLegend->rstyle( QgsLegendStyle::Subgroup ).setMargin( QgsLegendStyle::Bottom, space );
634     mLegend->adjustBoxSize();
635     mLegend->update();
636     mLegend->endCommand();
637   }
638 }
639 
mFontColorButton_colorChanged(const QColor & newFontColor)640 void QgsLayoutLegendWidget::mFontColorButton_colorChanged( const QColor &newFontColor )
641 {
642   if ( !mLegend )
643   {
644     return;
645   }
646 
647   mLegend->beginCommand( tr( "Change Font Color" ), QgsLayoutItem::UndoLegendFontColor );
648   mLegend->setFontColor( newFontColor );
649   mLegend->update();
650   mLegend->endCommand();
651 }
652 
mBoxSpaceSpinBox_valueChanged(double d)653 void QgsLayoutLegendWidget::mBoxSpaceSpinBox_valueChanged( double d )
654 {
655   if ( mLegend )
656   {
657     mLegend->beginCommand( tr( "Change Box Space" ), QgsLayoutItem::UndoLegendBoxSpace );
658     mLegend->setBoxSpace( d );
659     mLegend->adjustBoxSize();
660     mLegend->update();
661     mLegend->endCommand();
662   }
663 }
664 
mColumnSpaceSpinBox_valueChanged(double d)665 void QgsLayoutLegendWidget::mColumnSpaceSpinBox_valueChanged( double d )
666 {
667   if ( mLegend )
668   {
669     mLegend->beginCommand( tr( "Change Column Space" ), QgsLayoutItem::UndoLegendColumnSpace );
670     mLegend->setColumnSpace( d );
671     mLegend->adjustBoxSize();
672     mLegend->update();
673     mLegend->endCommand();
674   }
675 }
676 
mLineSpacingSpinBox_valueChanged(double d)677 void QgsLayoutLegendWidget::mLineSpacingSpinBox_valueChanged( double d )
678 {
679   if ( mLegend )
680   {
681     mLegend->beginCommand( tr( "Change Line Space" ), QgsLayoutItem::UndoLegendLineSpacing );
682     mLegend->setLineSpacing( d );
683     mLegend->adjustBoxSize();
684     mLegend->update();
685     mLegend->endCommand();
686   }
687 }
688 
_moveLegendNode(QgsLayerTreeLayer * nodeLayer,int legendNodeIndex,int offset)689 static void _moveLegendNode( QgsLayerTreeLayer *nodeLayer, int legendNodeIndex, int offset )
690 {
691   QList<int> order = QgsMapLayerLegendUtils::legendNodeOrder( nodeLayer );
692 
693   if ( legendNodeIndex < 0 || legendNodeIndex >= order.count() )
694     return;
695   if ( legendNodeIndex + offset < 0 || legendNodeIndex + offset >= order.count() )
696     return;
697 
698   int id = order.takeAt( legendNodeIndex );
699   order.insert( legendNodeIndex + offset, id );
700 
701   QgsMapLayerLegendUtils::setLegendNodeOrder( nodeLayer, order );
702 }
703 
704 
mMoveDownToolButton_clicked()705 void QgsLayoutLegendWidget::mMoveDownToolButton_clicked()
706 {
707   if ( !mLegend )
708   {
709     return;
710   }
711 
712   QModelIndex index = mItemTreeView->selectionModel()->currentIndex();
713   QModelIndex parentIndex = index.parent();
714   if ( !index.isValid() || index.row() == mItemTreeView->model()->rowCount( parentIndex ) - 1 )
715     return;
716 
717   QgsLayerTreeNode *node = mItemTreeView->layerTreeModel()->index2node( index );
718   QgsLayerTreeModelLegendNode *legendNode = mItemTreeView->layerTreeModel()->index2legendNode( index );
719   if ( !node && !legendNode )
720     return;
721 
722   mLegend->beginCommand( tr( "Moved Legend Item Down" ) );
723 
724   if ( node )
725   {
726     QgsLayerTreeGroup *parentGroup = QgsLayerTree::toGroup( node->parent() );
727     parentGroup->insertChildNode( index.row() + 2, node->clone() );
728     parentGroup->removeChildNode( node );
729   }
730   else // legend node
731   {
732     _moveLegendNode( legendNode->layerNode(), _unfilteredLegendNodeIndex( legendNode ), 1 );
733     mItemTreeView->layerTreeModel()->refreshLayerLegend( legendNode->layerNode() );
734   }
735 
736   mItemTreeView->setCurrentIndex( mItemTreeView->layerTreeModel()->index( index.row() + 1, 0, parentIndex ) );
737 
738   mLegend->update();
739   mLegend->endCommand();
740 }
741 
mMoveUpToolButton_clicked()742 void QgsLayoutLegendWidget::mMoveUpToolButton_clicked()
743 {
744   if ( !mLegend )
745   {
746     return;
747   }
748 
749   QModelIndex index = mItemTreeView->selectionModel()->currentIndex();
750   QModelIndex parentIndex = index.parent();
751   if ( !index.isValid() || index.row() == 0 )
752     return;
753 
754   QgsLayerTreeNode *node = mItemTreeView->layerTreeModel()->index2node( index );
755   QgsLayerTreeModelLegendNode *legendNode = mItemTreeView->layerTreeModel()->index2legendNode( index );
756   if ( !node && !legendNode )
757     return;
758 
759   mLegend->beginCommand( tr( "Move Legend Item Up" ) );
760 
761   if ( node )
762   {
763     QgsLayerTreeGroup *parentGroup = QgsLayerTree::toGroup( node->parent() );
764     parentGroup->insertChildNode( index.row() - 1, node->clone() );
765     parentGroup->removeChildNode( node );
766   }
767   else // legend node
768   {
769     _moveLegendNode( legendNode->layerNode(), _unfilteredLegendNodeIndex( legendNode ), -1 );
770     mItemTreeView->layerTreeModel()->refreshLayerLegend( legendNode->layerNode() );
771   }
772 
773   mItemTreeView->setCurrentIndex( mItemTreeView->layerTreeModel()->index( index.row() - 1, 0, parentIndex ) );
774 
775   mLegend->update();
776   mLegend->endCommand();
777 }
778 
mCheckBoxAutoUpdate_stateChanged(int state,bool userTriggered)779 void QgsLayoutLegendWidget::mCheckBoxAutoUpdate_stateChanged( int state, bool userTriggered )
780 {
781   if ( userTriggered )
782   {
783     mLegend->beginCommand( tr( "Change Auto Update" ) );
784 
785     mLegend->setAutoUpdateModel( state == Qt::Checked );
786 
787     mLegend->updateFilterByMap();
788     mLegend->endCommand();
789   }
790 
791   // do not allow editing of model if auto update is on - we would modify project's layer tree
792   QList<QWidget *> widgets;
793   widgets << mMoveDownToolButton << mMoveUpToolButton << mRemoveToolButton << mAddToolButton
794           << mEditPushButton << mCountToolButton << mUpdateAllPushButton << mAddGroupToolButton
795           << mExpressionFilterButton;
796   for ( QWidget *w : qgis::as_const( widgets ) )
797     w->setEnabled( state != Qt::Checked );
798 
799   if ( state == Qt::Unchecked )
800   {
801     // update widgets state based on current selection
802     selectedChanged( QModelIndex(), QModelIndex() );
803   }
804 }
805 
composerMapChanged(QgsLayoutItem * item)806 void QgsLayoutLegendWidget::composerMapChanged( QgsLayoutItem *item )
807 {
808   if ( !mLegend )
809   {
810     return;
811   }
812 
813   QgsLayout *layout = mLegend->layout();
814   if ( !layout )
815   {
816     return;
817   }
818 
819   QgsLayoutItemMap *map = qobject_cast< QgsLayoutItemMap * >( item );
820   if ( map )
821   {
822     mLegend->beginCommand( tr( "Change Legend Map" ) );
823     mLegend->setLinkedMap( map );
824     mLegend->updateFilterByMap();
825     mLegend->endCommand();
826 
827     setLegendMapViewData();
828   }
829 }
830 
mCheckboxResizeContents_toggled(bool checked)831 void QgsLayoutLegendWidget::mCheckboxResizeContents_toggled( bool checked )
832 {
833   if ( !mLegend )
834   {
835     return;
836   }
837 
838   mLegend->beginCommand( tr( "Resize Legend to Contents" ) );
839   mLegend->setResizeToContents( checked );
840   if ( checked )
841     mLegend->adjustBoxSize();
842   mLegend->updateFilterByMap();
843   mLegend->endCommand();
844 }
845 
mRasterStrokeGroupBox_toggled(bool state)846 void QgsLayoutLegendWidget::mRasterStrokeGroupBox_toggled( bool state )
847 {
848   if ( !mLegend )
849   {
850     return;
851   }
852 
853   mLegend->beginCommand( tr( "Change Legend Borders" ) );
854   mLegend->setDrawRasterStroke( state );
855   mLegend->adjustBoxSize();
856   mLegend->update();
857   mLegend->endCommand();
858 }
859 
mRasterStrokeWidthSpinBox_valueChanged(double d)860 void QgsLayoutLegendWidget::mRasterStrokeWidthSpinBox_valueChanged( double d )
861 {
862   if ( !mLegend )
863   {
864     return;
865   }
866 
867   mLegend->beginCommand( tr( "Resize Legend Borders" ), QgsLayoutItem::UndoLegendRasterStrokeWidth );
868   mLegend->setRasterStrokeWidth( d );
869   mLegend->adjustBoxSize();
870   mLegend->update();
871   mLegend->endCommand();
872 }
873 
mRasterStrokeColorButton_colorChanged(const QColor & newColor)874 void QgsLayoutLegendWidget::mRasterStrokeColorButton_colorChanged( const QColor &newColor )
875 {
876   if ( !mLegend )
877   {
878     return;
879   }
880 
881   mLegend->beginCommand( tr( "Change Legend Border Color" ), QgsLayoutItem::UndoLegendRasterStrokeColor );
882   mLegend->setRasterStrokeColor( newColor );
883   mLegend->update();
884   mLegend->endCommand();
885 }
886 
mAddToolButton_clicked()887 void QgsLayoutLegendWidget::mAddToolButton_clicked()
888 {
889   if ( !mLegend )
890   {
891     return;
892   }
893 
894   QList< QgsMapLayer * > visibleLayers;
895   if ( mLegend->linkedMap() )
896   {
897     visibleLayers = mLegend->linkedMap()->layersToRender();
898   }
899   if ( visibleLayers.isEmpty() )
900   {
901     // just use current canvas layers as visible layers
902     visibleLayers = mMapCanvas->layers();
903   }
904 
905   QgsLayoutLegendLayersDialog addDialog( this );
906   addDialog.setVisibleLayers( visibleLayers );
907   if ( addDialog.exec() == QDialog::Accepted )
908   {
909     const QList<QgsMapLayer *> layers = addDialog.selectedLayers();
910     if ( !layers.empty() )
911     {
912       mLegend->beginCommand( tr( "Add Legend Item(s)" ) );
913       for ( QgsMapLayer *layer : layers )
914       {
915         mLegend->model()->rootGroup()->addLayer( layer );
916       }
917       mLegend->updateLegend();
918       mLegend->update();
919       mLegend->endCommand();
920     }
921   }
922 }
923 
mRemoveToolButton_clicked()924 void QgsLayoutLegendWidget::mRemoveToolButton_clicked()
925 {
926   if ( !mLegend )
927   {
928     return;
929   }
930 
931   QItemSelectionModel *selectionModel = mItemTreeView->selectionModel();
932   if ( !selectionModel )
933   {
934     return;
935   }
936 
937   mLegend->beginCommand( tr( "Remove Legend Item" ) );
938 
939   QList<QPersistentModelIndex> indexes;
940   const auto constSelectedIndexes = selectionModel->selectedIndexes();
941   for ( const QModelIndex &index : constSelectedIndexes )
942     indexes << index;
943 
944   // first try to remove legend nodes
945   QHash<QgsLayerTreeLayer *, QList<int> > nodesWithRemoval;
946   for ( const QPersistentModelIndex &index : qgis::as_const( indexes ) )
947   {
948     if ( QgsLayerTreeModelLegendNode *legendNode = mItemTreeView->layerTreeModel()->index2legendNode( index ) )
949     {
950       QgsLayerTreeLayer *nodeLayer = legendNode->layerNode();
951       nodesWithRemoval[nodeLayer].append( _unfilteredLegendNodeIndex( legendNode ) );
952     }
953   }
954   for ( auto it = nodesWithRemoval.constBegin(); it != nodesWithRemoval.constEnd(); ++it )
955   {
956     QList<int> toDelete = it.value();
957     std::sort( toDelete.begin(), toDelete.end(), std::greater<int>() );
958     QList<int> order = QgsMapLayerLegendUtils::legendNodeOrder( it.key() );
959 
960     const auto constToDelete = toDelete;
961     for ( int i : constToDelete )
962     {
963       if ( i >= 0 && i < order.count() )
964         order.removeAt( i );
965     }
966 
967     QgsMapLayerLegendUtils::setLegendNodeOrder( it.key(), order );
968     mItemTreeView->layerTreeModel()->refreshLayerLegend( it.key() );
969   }
970 
971   // then remove layer tree nodes
972   for ( const QPersistentModelIndex &index : qgis::as_const( indexes ) )
973   {
974     if ( index.isValid() && mItemTreeView->layerTreeModel()->index2node( index ) )
975       mLegend->model()->removeRow( index.row(), index.parent() );
976   }
977 
978   mLegend->updateLegend();
979   mLegend->update();
980   mLegend->endCommand();
981 }
982 
mEditPushButton_clicked()983 void QgsLayoutLegendWidget::mEditPushButton_clicked()
984 {
985   if ( !mLegend )
986   {
987     return;
988   }
989 
990   QModelIndex idx = mItemTreeView->selectionModel()->currentIndex();
991   mItemTreeView_doubleClicked( idx );
992 }
993 
resetLayerNodeToDefaults()994 void QgsLayoutLegendWidget::resetLayerNodeToDefaults()
995 {
996   if ( !mLegend )
997   {
998     return;
999   }
1000 
1001   //get current item
1002   QModelIndex currentIndex = mItemTreeView->currentIndex();
1003   if ( !currentIndex.isValid() )
1004   {
1005     return;
1006   }
1007 
1008   QgsLayerTreeLayer *nodeLayer = nullptr;
1009   if ( QgsLayerTreeNode *node = mItemTreeView->layerTreeModel()->index2node( currentIndex ) )
1010   {
1011     if ( QgsLayerTree::isLayer( node ) )
1012       nodeLayer = QgsLayerTree::toLayer( node );
1013   }
1014   if ( QgsLayerTreeModelLegendNode *legendNode = mItemTreeView->layerTreeModel()->index2legendNode( currentIndex ) )
1015   {
1016     nodeLayer = legendNode->layerNode();
1017   }
1018 
1019   if ( !nodeLayer )
1020     return;
1021 
1022   mLegend->beginCommand( tr( "Update Legend" ) );
1023 
1024   const auto constCustomProperties = nodeLayer->customProperties();
1025   for ( const QString &key : constCustomProperties )
1026   {
1027     if ( key.startsWith( QLatin1String( "legend/" ) ) )
1028       nodeLayer->removeCustomProperty( key );
1029   }
1030 
1031   nodeLayer->setPatchShape( QgsLegendPatchShape() );
1032   nodeLayer->setPatchSize( QSizeF() );
1033 
1034   mItemTreeView->layerTreeModel()->refreshLayerLegend( nodeLayer );
1035 
1036   mLegend->updateLegend();
1037   mLegend->update();
1038   mLegend->endCommand();
1039 }
1040 
mCountToolButton_clicked(bool checked)1041 void QgsLayoutLegendWidget::mCountToolButton_clicked( bool checked )
1042 {
1043   if ( !mLegend )
1044   {
1045     return;
1046   }
1047 
1048   const QList< QModelIndex > selectedIndexes = mItemTreeView->selectionModel()->selectedIndexes();
1049   if ( selectedIndexes.empty() )
1050     return;
1051 
1052   mLegend->beginCommand( tr( "Update Legend" ) );
1053   for ( const QModelIndex &index : selectedIndexes )
1054   {
1055     QgsLayerTreeNode *currentNode = mItemTreeView->layerTreeModel()->index2node( index );
1056     if ( !QgsLayerTree::isLayer( currentNode ) )
1057       continue;
1058 
1059     currentNode->setCustomProperty( QStringLiteral( "showFeatureCount" ), checked ? 1 : 0 );
1060   }
1061   mLegend->updateFilterByMap();
1062   mLegend->adjustBoxSize();
1063   mLegend->endCommand();
1064 }
1065 
mFilterByMapCheckBox_toggled(bool checked)1066 void QgsLayoutLegendWidget::mFilterByMapCheckBox_toggled( bool checked )
1067 {
1068   mLegend->beginCommand( tr( "Update Legend" ) );
1069   mLegend->setLegendFilterByMapEnabled( checked );
1070   mLegend->adjustBoxSize();
1071   mLegend->update();
1072   mLegend->endCommand();
1073 }
1074 
mExpressionFilterButton_toggled(bool checked)1075 void QgsLayoutLegendWidget::mExpressionFilterButton_toggled( bool checked )
1076 {
1077   if ( !mLegend )
1078   {
1079     return;
1080   }
1081 
1082   //get current item
1083   QModelIndex currentIndex = mItemTreeView->currentIndex();
1084   if ( !currentIndex.isValid() )
1085   {
1086     return;
1087   }
1088 
1089   QgsLayerTreeNode *currentNode = mItemTreeView->currentNode();
1090   if ( !QgsLayerTree::isLayer( currentNode ) )
1091     return;
1092 
1093   QgsLayerTreeUtils::setLegendFilterByExpression( *qobject_cast<QgsLayerTreeLayer *>( currentNode ),
1094       mExpressionFilterButton->expressionText(),
1095       checked );
1096 
1097   mLegend->beginCommand( tr( "Update Legend" ) );
1098   mLegend->updateFilterByMap();
1099   mLegend->adjustBoxSize();
1100   mLegend->endCommand();
1101 }
1102 
mLayerExpressionButton_clicked()1103 void QgsLayoutLegendWidget::mLayerExpressionButton_clicked()
1104 {
1105   if ( !mLegend )
1106   {
1107     return;
1108   }
1109 
1110   QModelIndex currentIndex = mItemTreeView->currentIndex();
1111   if ( !currentIndex.isValid() )
1112     return;
1113 
1114   QgsLayerTreeNode *currentNode = mItemTreeView->currentNode();
1115   if ( !QgsLayerTree::isLayer( currentNode ) )
1116     return;
1117 
1118   QgsLayerTreeLayer *layerNode = qobject_cast<QgsLayerTreeLayer *>( currentNode );
1119   QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layerNode->layer() );
1120 
1121   if ( !vl )
1122     return;
1123 
1124   QString currentExpression;
1125   if ( layerNode->labelExpression().isEmpty() )
1126     currentExpression = QStringLiteral( "@symbol_label" );
1127   else
1128     currentExpression = layerNode->labelExpression();
1129   QgsExpressionContext legendContext = mLegend->createExpressionContext();
1130   legendContext.appendScope( vl->createExpressionContextScope() );
1131 
1132   QgsExpressionContextScope *symbolLegendScope = new QgsExpressionContextScope( tr( "Symbol scope" ) );
1133 
1134   QgsFeatureRenderer *r = vl->renderer();
1135 
1136   QStringList highlighted;
1137   if ( r )
1138   {
1139     const QgsLegendSymbolList legendSymbols = r->legendSymbolItems();
1140 
1141     if ( !legendSymbols.empty() )
1142     {
1143       QgsSymbolLegendNode legendNode( layerNode, legendSymbols.first() );
1144 
1145       symbolLegendScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_label" ), legendNode.symbolLabel().remove( QStringLiteral( "[%" ) ).remove( QStringLiteral( "%]" ) ), true ) );
1146       symbolLegendScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_id" ), legendSymbols.first().ruleKey(), true ) );
1147       highlighted << QStringLiteral( "symbol_label" ) << QStringLiteral( "symbol_id" );
1148       symbolLegendScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_count" ), QVariant::fromValue( vl->featureCount( legendSymbols.first().ruleKey() ) ), true ) );
1149       highlighted << QStringLiteral( "symbol_count" );
1150     }
1151   }
1152 
1153   legendContext.appendScope( symbolLegendScope );
1154 
1155   legendContext.setHighlightedVariables( highlighted );
1156 
1157   QgsExpressionBuilderDialog expressiondialog( vl, currentExpression, nullptr, QStringLiteral( "generic" ), legendContext );
1158   if ( expressiondialog.exec() )
1159     layerNode->setLabelExpression( expressiondialog.expressionText() );
1160 
1161   mLegend->beginCommand( tr( "Update Legend" ) );
1162   mLegend->refresh();
1163   mLegend->adjustBoxSize();
1164   mLegend->endCommand();
1165 }
1166 
mUpdateAllPushButton_clicked()1167 void QgsLayoutLegendWidget::mUpdateAllPushButton_clicked()
1168 {
1169   updateLegend();
1170 }
1171 
mAddGroupToolButton_clicked()1172 void QgsLayoutLegendWidget::mAddGroupToolButton_clicked()
1173 {
1174   if ( mLegend )
1175   {
1176     mLegend->beginCommand( tr( "Add Legend Group" ) );
1177     mLegend->model()->rootGroup()->addGroup( tr( "Group" ) );
1178     mLegend->updateLegend();
1179     mLegend->update();
1180     mLegend->endCommand();
1181   }
1182 }
1183 
mFilterLegendByAtlasCheckBox_toggled(bool toggled)1184 void QgsLayoutLegendWidget::mFilterLegendByAtlasCheckBox_toggled( bool toggled )
1185 {
1186   Q_UNUSED( toggled )
1187   if ( mLegend )
1188   {
1189     mLegend->setLegendFilterOutAtlas( toggled );
1190     // force update of legend when in preview mode
1191     mLegend->refresh();
1192   }
1193 }
1194 
updateLegend()1195 void QgsLayoutLegendWidget::updateLegend()
1196 {
1197   if ( mLegend )
1198   {
1199     mLegend->beginCommand( tr( "Update Legend" ) );
1200 
1201     // this will reset the model completely, losing any changes
1202     mLegend->setAutoUpdateModel( true );
1203     mLegend->setAutoUpdateModel( false );
1204     mLegend->updateFilterByMap();
1205     mLegend->endCommand();
1206   }
1207 }
1208 
setReportTypeString(const QString & string)1209 void QgsLayoutLegendWidget::setReportTypeString( const QString &string )
1210 {
1211   mFilterLegendByAtlasCheckBox->setText( tr( "Only show items inside current %1 feature" ).arg( string ) );
1212   mFilterLegendByAtlasCheckBox->setToolTip( tr( "Filter out legend elements that lie outside the current %1 feature." ).arg( string ) );
1213 }
1214 
setNewItem(QgsLayoutItem * item)1215 bool QgsLayoutLegendWidget::setNewItem( QgsLayoutItem *item )
1216 {
1217   if ( item->type() != QgsLayoutItemRegistry::LayoutLegend )
1218     return false;
1219 
1220   if ( mLegend )
1221   {
1222     disconnect( mLegend, &QgsLayoutObject::changed, this, &QgsLayoutLegendWidget::setGuiElements );
1223   }
1224 
1225   mLegend = qobject_cast< QgsLayoutItemLegend * >( item );
1226   mItemPropertiesWidget->setItem( mLegend );
1227 
1228   if ( mLegend )
1229   {
1230     mItemTreeView->setModel( mLegend->model() );
1231     connect( mLegend, &QgsLayoutObject::changed, this, &QgsLayoutLegendWidget::setGuiElements );
1232   }
1233 
1234   setGuiElements();
1235 
1236   return true;
1237 }
1238 
blockAllSignals(bool b)1239 void QgsLayoutLegendWidget::blockAllSignals( bool b )
1240 {
1241   mTitleLineEdit->blockSignals( b );
1242   mTitleAlignCombo->blockSignals( b );
1243   mItemTreeView->blockSignals( b );
1244   mCheckBoxAutoUpdate->blockSignals( b );
1245   mMapComboBox->blockSignals( b );
1246   mFilterByMapCheckBox->blockSignals( b );
1247   mColumnCountSpinBox->blockSignals( b );
1248   mSplitLayerCheckBox->blockSignals( b );
1249   mEqualColumnWidthCheckBox->blockSignals( b );
1250   mSymbolWidthSpinBox->blockSignals( b );
1251   mSymbolHeightSpinBox->blockSignals( b );
1252   mMaxSymbolSizeSpinBox->blockSignals( b );
1253   mMinSymbolSizeSpinBox->blockSignals( b );
1254   mGroupSpaceSpinBox->blockSignals( b );
1255   mSpaceBelowGroupHeadingSpinBox->blockSignals( b );
1256   mGroupSideSpinBox->blockSignals( b );
1257   mSpaceBelowSubgroupHeadingSpinBox->blockSignals( b );
1258   mSubgroupSideSpinBox->blockSignals( b );
1259   mLayerSpaceSpinBox->blockSignals( b );
1260   mSymbolSpaceSpinBox->blockSignals( b );
1261   mSymbolSideSpaceSpinBox->blockSignals( b );
1262   mIconLabelSpaceSpinBox->blockSignals( b );
1263   mBoxSpaceSpinBox->blockSignals( b );
1264   mColumnSpaceSpinBox->blockSignals( b );
1265   mFontColorButton->blockSignals( b );
1266   mRasterStrokeGroupBox->blockSignals( b );
1267   mRasterStrokeColorButton->blockSignals( b );
1268   mRasterStrokeWidthSpinBox->blockSignals( b );
1269   mWmsLegendWidthSpinBox->blockSignals( b );
1270   mWmsLegendHeightSpinBox->blockSignals( b );
1271   mCheckboxResizeContents->blockSignals( b );
1272   mTitleSpaceBottomSpinBox->blockSignals( b );
1273   mFilterLegendByAtlasCheckBox->blockSignals( b );
1274   mTitleFontButton->blockSignals( b );
1275   mGroupFontButton->blockSignals( b );
1276   mLayerFontButton->blockSignals( b );
1277   mItemFontButton->blockSignals( b );
1278   mWrapCharLineEdit->blockSignals( b );
1279   mLineSpacingSpinBox->blockSignals( b );
1280 }
1281 
selectedChanged(const QModelIndex & current,const QModelIndex & previous)1282 void QgsLayoutLegendWidget::selectedChanged( const QModelIndex &current, const QModelIndex &previous )
1283 {
1284   Q_UNUSED( current )
1285   Q_UNUSED( previous )
1286 
1287   mLayerExpressionButton->setEnabled( false );
1288 
1289   if ( mLegend && mLegend->autoUpdateModel() )
1290   {
1291     QgsLayerTreeNode *currentNode = mItemTreeView->currentNode();
1292     if ( !QgsLayerTree::isLayer( currentNode ) )
1293       return;
1294 
1295     QgsLayerTreeLayer *currentLayerNode = QgsLayerTree::toLayer( currentNode );
1296     QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( currentLayerNode->layer() );
1297     if ( !vl )
1298       return;
1299 
1300     mLayerExpressionButton->setEnabled( true );
1301     return;
1302   }
1303 
1304   mCountToolButton->setChecked( false );
1305   mCountToolButton->setEnabled( false );
1306 
1307 
1308   mExpressionFilterButton->blockSignals( true );
1309   mExpressionFilterButton->setChecked( false );
1310   mExpressionFilterButton->setEnabled( false );
1311   mExpressionFilterButton->blockSignals( false );
1312 
1313   QgsLayerTreeNode *currentNode = mItemTreeView->currentNode();
1314   if ( !QgsLayerTree::isLayer( currentNode ) )
1315     return;
1316 
1317   QgsLayerTreeLayer *currentLayerNode = QgsLayerTree::toLayer( currentNode );
1318   QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( currentLayerNode->layer() );
1319   if ( !vl )
1320     return;
1321 
1322   mCountToolButton->setChecked( currentNode->customProperty( QStringLiteral( "showFeatureCount" ), 0 ).toInt() );
1323   mCountToolButton->setEnabled( true );
1324   mLayerExpressionButton->setEnabled( true );
1325 
1326   bool exprEnabled;
1327   QString expr = QgsLayerTreeUtils::legendFilterByExpression( *qobject_cast<QgsLayerTreeLayer *>( currentNode ), &exprEnabled );
1328   mExpressionFilterButton->blockSignals( true );
1329   mExpressionFilterButton->setExpressionText( expr );
1330   mExpressionFilterButton->setVectorLayer( vl );
1331   mExpressionFilterButton->setEnabled( true );
1332   mExpressionFilterButton->setChecked( exprEnabled );
1333   mExpressionFilterButton->blockSignals( false );
1334 }
1335 
setCurrentNodeStyleFromAction()1336 void QgsLayoutLegendWidget::setCurrentNodeStyleFromAction()
1337 {
1338   QAction *a = qobject_cast<QAction *>( sender() );
1339   if ( !a || !mItemTreeView->currentNode() )
1340     return;
1341 
1342   QgsLegendRenderer::setNodeLegendStyle( mItemTreeView->currentNode(), static_cast< QgsLegendStyle::Style >( a->data().toInt() ) );
1343   mLegend->updateFilterByMap();
1344 }
1345 
setLegendMapViewData()1346 void QgsLayoutLegendWidget::setLegendMapViewData()
1347 {
1348   if ( mLegend->linkedMap() )
1349   {
1350     int dpi = qt_defaultDpiX();
1351     QgsLayoutMeasurementConverter measurementConverter = QgsLayoutMeasurementConverter();
1352     measurementConverter.setDpi( dpi );
1353     double mapWidth = measurementConverter.convert( mLegend->linkedMap()->sizeWithUnits(), QgsUnitTypes::LayoutPixels ).width();
1354     double mapHeight = measurementConverter.convert( mLegend->linkedMap()->sizeWithUnits(), QgsUnitTypes::LayoutPixels ).height();
1355     double mapUnitsPerPixelX = mLegend->linkedMap()->extent().width() / mapWidth;
1356     double mapUnitsPerPixelY = mLegend->linkedMap()->extent().height() / mapHeight;
1357     mLegend->model()->setLegendMapViewData( ( mapUnitsPerPixelX > mapUnitsPerPixelY ? mapUnitsPerPixelX : mapUnitsPerPixelY ), dpi, mLegend->linkedMap()->scale() );
1358   }
1359 }
1360 
updateFilterLegendByAtlasButton()1361 void QgsLayoutLegendWidget::updateFilterLegendByAtlasButton()
1362 {
1363   if ( QgsLayoutAtlas *atlas = layoutAtlas() )
1364   {
1365     mFilterLegendByAtlasCheckBox->setEnabled( atlas->enabled() && mLegend->layout()->reportContext().layer() && mLegend->layout()->reportContext().layer()->geometryType() == QgsWkbTypes::PolygonGeometry );
1366   }
1367 }
1368 
mItemTreeView_doubleClicked(const QModelIndex & idx)1369 void QgsLayoutLegendWidget::mItemTreeView_doubleClicked( const QModelIndex &idx )
1370 {
1371   if ( !mLegend || !idx.isValid() )
1372   {
1373     return;
1374   }
1375 
1376   if ( mLegend->autoUpdateModel() )
1377     return;
1378 
1379   QgsLayerTreeModel *model = mItemTreeView->layerTreeModel();
1380   QgsLayerTreeNode *currentNode = model->index2node( idx );
1381   QgsLayerTreeModelLegendNode *legendNode = model->index2legendNode( idx );
1382 
1383   int originalIndex = -1;
1384   if ( legendNode )
1385   {
1386     originalIndex = _originalLegendNodeIndex( legendNode );
1387     currentNode = legendNode->layerNode();
1388   }
1389 
1390   QgsLayoutLegendNodeWidget *widget = new QgsLayoutLegendNodeWidget( mLegend, currentNode, legendNode, originalIndex );
1391   openPanel( widget );
1392 }
1393 
1394 
1395 //
1396 // QgsComposerLegendMenuProvider
1397 //
1398 
QgsLayoutLegendMenuProvider(QgsLayerTreeView * view,QgsLayoutLegendWidget * w)1399 QgsLayoutLegendMenuProvider::QgsLayoutLegendMenuProvider( QgsLayerTreeView *view, QgsLayoutLegendWidget *w )
1400   : mView( view )
1401   , mWidget( w )
1402 {}
1403 
createContextMenu()1404 QMenu *QgsLayoutLegendMenuProvider::createContextMenu()
1405 {
1406   if ( !mView->currentNode() )
1407     return nullptr;
1408 
1409   if ( mWidget->legend()->autoUpdateModel() )
1410     return nullptr; // no editing allowed
1411 
1412   QMenu *menu = new QMenu();
1413 
1414   if ( QgsLayerTree::isLayer( mView->currentNode() ) )
1415   {
1416     menu->addAction( QObject::tr( "Reset to Defaults" ), mWidget, &QgsLayoutLegendWidget::resetLayerNodeToDefaults );
1417     menu->addSeparator();
1418   }
1419 
1420   QgsLegendStyle::Style currentStyle = QgsLegendRenderer::nodeLegendStyle( mView->currentNode(), mView->layerTreeModel() );
1421 
1422   QList<QgsLegendStyle::Style> lst;
1423   lst << QgsLegendStyle::Hidden << QgsLegendStyle::Group << QgsLegendStyle::Subgroup;
1424   for ( QgsLegendStyle::Style style : qgis::as_const( lst ) )
1425   {
1426     QAction *action = menu->addAction( QgsLegendStyle::styleLabel( style ), mWidget, &QgsLayoutLegendWidget::setCurrentNodeStyleFromAction );
1427     action->setCheckable( true );
1428     action->setChecked( currentStyle == style );
1429     action->setData( static_cast< int >( style ) );
1430   }
1431 
1432   return menu;
1433 }
1434 
1435 //
1436 // QgsLayoutLegendNodeWidget
1437 //
QgsLayoutLegendNodeWidget(QgsLayoutItemLegend * legend,QgsLayerTreeNode * node,QgsLayerTreeModelLegendNode * legendNode,int originalLegendNodeIndex,QWidget * parent)1438 QgsLayoutLegendNodeWidget::QgsLayoutLegendNodeWidget( QgsLayoutItemLegend *legend, QgsLayerTreeNode *node, QgsLayerTreeModelLegendNode *legendNode, int originalLegendNodeIndex, QWidget *parent )
1439   : QgsPanelWidget( parent )
1440   , mLegend( legend )
1441   , mNode( node )
1442   , mLayer( qobject_cast< QgsLayerTreeLayer* >( node ) )
1443   , mLegendNode( legendNode )
1444   , mOriginalLegendNodeIndex( originalLegendNodeIndex )
1445 {
1446   setupUi( this );
1447   setPanelTitle( tr( "Legend Item Properties" ) );
1448 
1449   // auto close panel if layer removed
1450   connect( node, &QObject::destroyed, this, &QgsPanelWidget::acceptPanel );
1451 
1452   mColumnSplitBehaviorComboBox->addItem( tr( "Follow Legend Default" ), QgsLayerTreeLayer::UseDefaultLegendSetting );
1453   mColumnSplitBehaviorComboBox->addItem( tr( "Allow Splitting Over Columns" ), QgsLayerTreeLayer::AllowSplittingLegendNodesOverMultipleColumns );
1454   mColumnSplitBehaviorComboBox->addItem( tr( "Prevent Splitting Over Columns" ), QgsLayerTreeLayer::PreventSplittingLegendNodesOverMultipleColumns );
1455 
1456   QString currentLabel;
1457   if ( mLegendNode )
1458   {
1459     currentLabel = mLegendNode->data( Qt::EditRole ).toString();
1460     mColumnBreakBeforeCheckBox->setChecked( mLegendNode->columnBreak() );
1461   }
1462   else if ( mLayer )
1463   {
1464     currentLabel = mLayer->name();
1465     QVariant v = mLayer->customProperty( QStringLiteral( "legend/title-label" ) );
1466     if ( !v.isNull() )
1467       currentLabel = v.toString();
1468     mColumnBreakBeforeCheckBox->setChecked( mLayer->customProperty( QStringLiteral( "legend/column-break" ) ).toInt() );
1469 
1470     mColumnSplitBehaviorComboBox->setCurrentIndex( mColumnSplitBehaviorComboBox->findData( mLayer->legendSplitBehavior() ) );
1471   }
1472   else
1473   {
1474     currentLabel = QgsLayerTree::toGroup( mNode )->name();
1475     mColumnBreakBeforeCheckBox->setChecked( mNode->customProperty( QStringLiteral( "legend/column-break" ) ).toInt() );
1476   }
1477 
1478   mWidthSpinBox->setClearValue( 0, tr( "Default" ) );
1479   mHeightSpinBox->setClearValue( 0, tr( "Default" ) );
1480   mWidthSpinBox->setVisible( mLegendNode || mLayer );
1481   mHeightSpinBox->setVisible( mLegendNode || mLayer );
1482   mPatchGroup->setVisible( mLegendNode || mLayer );
1483   mPatchWidthLabel->setVisible( mLegendNode || mLayer );
1484   mPatchHeightLabel->setVisible( mLegendNode || mLayer );
1485   mCustomSymbolCheckBox->setVisible( mLegendNode || mLegend->model()->legendNodeEmbeddedInParent( mLayer ) );
1486   mColumnSplitLabel->setVisible( mLayer && !mLegendNode );
1487   mColumnSplitBehaviorComboBox->setVisible( mLayer && !mLegendNode );
1488   if ( mLegendNode )
1489   {
1490     mWidthSpinBox->setValue( mLegendNode->userPatchSize().width() );
1491     mHeightSpinBox->setValue( mLegendNode->userPatchSize().height() );
1492   }
1493   else if ( mLayer )
1494   {
1495     mWidthSpinBox->setValue( mLayer->patchSize().width() );
1496     mHeightSpinBox->setValue( mLayer->patchSize().height() );
1497   }
1498 
1499   mCustomSymbolCheckBox->setChecked( false );
1500 
1501   QgsLegendPatchShape patchShape;
1502   if ( QgsSymbolLegendNode *symbolLegendNode = dynamic_cast< QgsSymbolLegendNode * >( mLegendNode ) )
1503   {
1504     patchShape = symbolLegendNode->patchShape();
1505 
1506     std::unique_ptr< QgsSymbol > customSymbol( symbolLegendNode->customSymbol() ? symbolLegendNode->customSymbol()->clone() : nullptr );
1507     mCustomSymbolCheckBox->setChecked( customSymbol.get() );
1508     if ( customSymbol )
1509     {
1510       mPatchShapeButton->setPreviewSymbol( customSymbol->clone() );
1511       mCustomSymbolButton->setSymbolType( customSymbol->type() );
1512       mCustomSymbolButton->setSymbol( customSymbol.release() );
1513     }
1514     else if ( symbolLegendNode->symbol() )
1515     {
1516       mPatchShapeButton->setPreviewSymbol( symbolLegendNode->symbol()->clone() );
1517       mCustomSymbolButton->setSymbolType( symbolLegendNode->symbol()->type() );
1518       mCustomSymbolButton->setSymbol( symbolLegendNode->symbol()->clone() );
1519     }
1520   }
1521   else if ( !mLegendNode && mLayer )
1522   {
1523     patchShape = mLayer->patchShape();
1524     if ( QgsSymbolLegendNode *symbolLegendNode = dynamic_cast< QgsSymbolLegendNode * >( mLegend->model()->legendNodeEmbeddedInParent( mLayer ) ) )
1525     {
1526       if ( QgsSymbol *customSymbol = symbolLegendNode->customSymbol() )
1527       {
1528         mCustomSymbolCheckBox->setChecked( true );
1529         mPatchShapeButton->setPreviewSymbol( customSymbol->clone() );
1530         mCustomSymbolButton->setSymbolType( customSymbol->type() );
1531         mCustomSymbolButton->setSymbol( customSymbol->clone() );
1532       }
1533       else
1534       {
1535         mPatchShapeButton->setPreviewSymbol( symbolLegendNode->symbol()->clone() );
1536         mCustomSymbolButton->setSymbolType( symbolLegendNode->symbol()->type() );
1537         mCustomSymbolButton->setSymbol( symbolLegendNode->symbol()->clone() );
1538       }
1539     }
1540   }
1541 
1542   if ( mLayer && mLayer->layer()  && mLayer->layer()->type() == QgsMapLayerType::VectorLayer )
1543   {
1544     switch ( qobject_cast< QgsVectorLayer * >( mLayer->layer() )->geometryType() )
1545     {
1546       case QgsWkbTypes::PolygonGeometry:
1547         mPatchShapeButton->setSymbolType( QgsSymbol::Fill );
1548         break;
1549 
1550       case QgsWkbTypes::LineGeometry:
1551         mPatchShapeButton->setSymbolType( QgsSymbol::Line );
1552         break;
1553 
1554       case QgsWkbTypes::PointGeometry:
1555         mPatchShapeButton->setSymbolType( QgsSymbol::Marker );
1556         break;
1557 
1558       default:
1559         mPatchShapeLabel->hide();
1560         mPatchShapeButton->hide();
1561         break;
1562     }
1563     if ( !patchShape.isNull() )
1564       mPatchShapeButton->setShape( patchShape );
1565 
1566   }
1567   else
1568   {
1569     mPatchShapeLabel->hide();
1570     mPatchShapeButton->hide();
1571   }
1572 
1573   if ( mLegendNode )
1574   {
1575     switch ( static_cast< QgsLayerTreeModelLegendNode::NodeTypes >( mLegendNode->data( QgsLayerTreeModelLegendNode::NodeTypeRole ).toInt() ) )
1576     {
1577       case QgsLayerTreeModelLegendNode::EmbeddedWidget:
1578       case QgsLayerTreeModelLegendNode::RasterSymbolLegend:
1579       case QgsLayerTreeModelLegendNode::ImageLegend:
1580       case QgsLayerTreeModelLegendNode::WmsLegend:
1581       case QgsLayerTreeModelLegendNode::DataDefinedSizeLegend:
1582         mCustomSymbolCheckBox->hide();
1583         break;
1584 
1585       case QgsLayerTreeModelLegendNode::SimpleLegend:
1586       case QgsLayerTreeModelLegendNode::SymbolLegend:
1587         break;
1588     }
1589   }
1590 
1591   mLabelEdit->setPlainText( currentLabel );
1592   connect( mLabelEdit, &QPlainTextEdit::textChanged, this, &QgsLayoutLegendNodeWidget::labelChanged );
1593   connect( mPatchShapeButton, &QgsLegendPatchShapeButton::changed, this, &QgsLayoutLegendNodeWidget::patchChanged );
1594   connect( mInsertExpressionButton, &QPushButton::clicked, this, &QgsLayoutLegendNodeWidget::insertExpression );
1595 
1596   connect( mWidthSpinBox, qgis::overload<double>::of( &QgsDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendNodeWidget::sizeChanged );
1597   connect( mHeightSpinBox, qgis::overload<double>::of( &QgsDoubleSpinBox::valueChanged ), this, &QgsLayoutLegendNodeWidget::sizeChanged );
1598 
1599   connect( mCustomSymbolCheckBox, &QGroupBox::toggled, this, &QgsLayoutLegendNodeWidget::customSymbolChanged );
1600   connect( mCustomSymbolButton, &QgsSymbolButton::changed, this, &QgsLayoutLegendNodeWidget::customSymbolChanged );
1601 
1602   connect( mColumnBreakBeforeCheckBox, &QCheckBox::toggled, this, &QgsLayoutLegendNodeWidget::columnBreakToggled );
1603 
1604   connect( mColumnSplitBehaviorComboBox, qgis::overload<int>::of( &QComboBox::currentIndexChanged ), this, &QgsLayoutLegendNodeWidget::columnSplitChanged );
1605 }
1606 
labelChanged()1607 void QgsLayoutLegendNodeWidget::labelChanged()
1608 {
1609   mLegend->beginCommand( tr( "Edit Legend Item" ), QgsLayoutItem::UndoLegendText );
1610 
1611   const QString label = mLabelEdit->toPlainText();
1612   if ( QgsLayerTree::isGroup( mNode ) )
1613   {
1614     QgsLayerTree::toGroup( mNode )->setName( label );
1615   }
1616   else if ( mLegendNode )
1617   {
1618     QgsMapLayerLegendUtils::setLegendNodeUserLabel( mLayer, mOriginalLegendNodeIndex, label );
1619     mLegend->model()->refreshLayerLegend( mLayer );
1620   }
1621   else if ( mLayer )
1622   {
1623     mLayer->setCustomProperty( QStringLiteral( "legend/title-label" ), label );
1624 
1625     // force update of label of the legend node with embedded icon (a bit clumsy i know)
1626     if ( QgsLayerTreeModelLegendNode *embeddedNode = mLegend->model()->legendNodeEmbeddedInParent( mLayer ) )
1627       embeddedNode->setUserLabel( QString() );
1628   }
1629 
1630   mLegend->adjustBoxSize();
1631   mLegend->updateFilterByMap();
1632   mLegend->endCommand();
1633 }
1634 
patchChanged()1635 void QgsLayoutLegendNodeWidget::patchChanged()
1636 {
1637   mLegend->beginCommand( tr( "Edit Legend Item" ) );
1638 
1639   QgsLegendPatchShape shape = mPatchShapeButton->shape();
1640   if ( mLegendNode )
1641   {
1642     QgsMapLayerLegendUtils::setLegendNodePatchShape( mLayer, mOriginalLegendNodeIndex, shape );
1643     mLegend->model()->refreshLayerLegend( mLayer );
1644   }
1645   else if ( mLayer )
1646   {
1647     mLayer->setPatchShape( shape );
1648     const QList<QgsLayerTreeModelLegendNode *> layerLegendNodes = mLegend->model()->layerLegendNodes( mLayer, false );
1649     for ( QgsLayerTreeModelLegendNode *node : layerLegendNodes )
1650     {
1651       QgsMapLayerLegendUtils::setLegendNodePatchShape( mLayer, _originalLegendNodeIndex( node ), shape );
1652     }
1653     mLegend->model()->refreshLayerLegend( mLayer );
1654   }
1655 
1656   mLegend->adjustBoxSize();
1657   mLegend->updateFilterByMap();
1658   mLegend->endCommand();
1659 }
1660 
insertExpression()1661 void QgsLayoutLegendNodeWidget::insertExpression()
1662 {
1663   if ( !mLegend )
1664     return;
1665 
1666   QString selText = mLabelEdit->textCursor().selectedText();
1667 
1668   // html editor replaces newlines with Paragraph Separator characters - see https://github.com/qgis/QGIS/issues/27568
1669   selText = selText.replace( QChar( 0x2029 ), QChar( '\n' ) );
1670 
1671   // edit the selected expression if there's one
1672   if ( selText.startsWith( QLatin1String( "[%" ) ) && selText.endsWith( QLatin1String( "%]" ) ) )
1673     selText = selText.mid( 2, selText.size() - 4 );
1674 
1675   // use the atlas coverage layer, if any
1676   QgsVectorLayer *layer = mLegend->layout() ? mLegend->layout()->reportContext().layer() : nullptr;
1677 
1678   QgsExpressionContext context = mLegend->createExpressionContext();
1679 
1680   if ( mLayer && mLayer->layer() )
1681   {
1682     context.appendScope( QgsExpressionContextUtils::layerScope( mLayer->layer() ) );
1683   }
1684 
1685   context.setHighlightedVariables( QStringList() << QStringLiteral( "legend_title" )
1686                                    << QStringLiteral( "legend_column_count" )
1687                                    << QStringLiteral( "legend_split_layers" )
1688                                    << QStringLiteral( "legend_wrap_string" )
1689                                    << QStringLiteral( "legend_filter_by_map" )
1690                                    << QStringLiteral( "legend_filter_out_atlas" ) );
1691 
1692   QgsExpressionBuilderDialog exprDlg( layer, selText, this, QStringLiteral( "generic" ), context );
1693 
1694   exprDlg.setWindowTitle( tr( "Insert Expression" ) );
1695   if ( exprDlg.exec() == QDialog::Accepted )
1696   {
1697     QString expression = exprDlg.expressionText();
1698     if ( !expression.isEmpty() )
1699     {
1700       mLegend->beginCommand( tr( "Insert expression" ) );
1701       mLabelEdit->insertPlainText( "[%" + expression + "%]" );
1702       mLegend->endCommand();
1703     }
1704   }
1705 }
1706 
sizeChanged(double)1707 void QgsLayoutLegendNodeWidget::sizeChanged( double )
1708 {
1709   mLegend->beginCommand( tr( "Edit Legend Item" ) );
1710   const QSizeF size = QSizeF( mWidthSpinBox->value(), mHeightSpinBox->value() );
1711 
1712   if ( mLegendNode )
1713   {
1714     QgsMapLayerLegendUtils::setLegendNodeSymbolSize( mLayer, mOriginalLegendNodeIndex, size );
1715     mLegend->model()->refreshLayerLegend( mLayer );
1716   }
1717   else if ( mLayer )
1718   {
1719     mLayer->setPatchSize( size );
1720     const QList<QgsLayerTreeModelLegendNode *> layerLegendNodes = mLegend->model()->layerLegendNodes( mLayer, false );
1721     for ( QgsLayerTreeModelLegendNode *node : layerLegendNodes )
1722     {
1723       QgsMapLayerLegendUtils::setLegendNodeSymbolSize( mLayer, _originalLegendNodeIndex( node ), size );
1724     }
1725     mLegend->model()->refreshLayerLegend( mLayer );
1726   }
1727 
1728   mLegend->adjustBoxSize();
1729   mLegend->updateFilterByMap();
1730   mLegend->endCommand();
1731 }
1732 
customSymbolChanged()1733 void QgsLayoutLegendNodeWidget::customSymbolChanged()
1734 {
1735   mLegend->beginCommand( tr( "Edit Legend Item" ) );
1736 
1737   if ( mCustomSymbolCheckBox->isChecked() )
1738   {
1739     if ( mLegendNode )
1740     {
1741       QgsMapLayerLegendUtils::setLegendNodeCustomSymbol( mLayer, mOriginalLegendNodeIndex, mCustomSymbolButton->symbol() );
1742       mLegend->model()->refreshLayerLegend( mLayer );
1743     }
1744     else if ( mLayer )
1745     {
1746       const QList<QgsLayerTreeModelLegendNode *> layerLegendNodes = mLegend->model()->layerLegendNodes( mLayer, false );
1747       for ( QgsLayerTreeModelLegendNode *node : layerLegendNodes )
1748       {
1749         QgsMapLayerLegendUtils::setLegendNodeCustomSymbol( mLayer, _originalLegendNodeIndex( node ), mCustomSymbolButton->symbol() );
1750       }
1751       mLegend->model()->refreshLayerLegend( mLayer );
1752     }
1753   }
1754   else
1755   {
1756     if ( mLegendNode )
1757     {
1758       QgsMapLayerLegendUtils::setLegendNodeCustomSymbol( mLayer, mOriginalLegendNodeIndex, nullptr );
1759       mLegend->model()->refreshLayerLegend( mLayer );
1760     }
1761     else if ( mLayer )
1762     {
1763       const QList<QgsLayerTreeModelLegendNode *> layerLegendNodes = mLegend->model()->layerLegendNodes( mLayer, false );
1764       for ( QgsLayerTreeModelLegendNode *node : layerLegendNodes )
1765       {
1766         QgsMapLayerLegendUtils::setLegendNodeCustomSymbol( mLayer, _originalLegendNodeIndex( node ), nullptr );
1767       }
1768       mLegend->model()->refreshLayerLegend( mLayer );
1769     }
1770   }
1771 
1772   mLegend->adjustBoxSize();
1773   mLegend->updateFilterByMap();
1774   mLegend->endCommand();
1775 }
1776 
columnBreakToggled(bool checked)1777 void QgsLayoutLegendNodeWidget::columnBreakToggled( bool checked )
1778 {
1779   mLegend->beginCommand( tr( "Edit Legend Columns" ) );
1780 
1781   if ( mLegendNode )
1782   {
1783     QgsMapLayerLegendUtils::setLegendNodeColumnBreak( mLayer, mOriginalLegendNodeIndex, checked );
1784     mLegend->model()->refreshLayerLegend( mLayer );
1785   }
1786   else if ( mLayer )
1787   {
1788     mLayer->setCustomProperty( QStringLiteral( "legend/column-break" ), QString( checked ? '1' : '0' ) );
1789   }
1790   else if ( mNode )
1791   {
1792     mNode->setCustomProperty( QStringLiteral( "legend/column-break" ), QString( checked ? '1' : '0' ) );
1793   }
1794 
1795   mLegend->adjustBoxSize();
1796   mLegend->updateFilterByMap();
1797   mLegend->endCommand();
1798 }
1799 
columnSplitChanged()1800 void QgsLayoutLegendNodeWidget::columnSplitChanged()
1801 {
1802   mLegend->beginCommand( tr( "Edit Legend Columns" ) );
1803 
1804   if ( mLayer && !mLegendNode )
1805   {
1806     mLayer->setLegendSplitBehavior( static_cast< QgsLayerTreeLayer::LegendNodesSplitBehavior >( mColumnSplitBehaviorComboBox->currentData().toInt() ) );
1807   }
1808 
1809   mLegend->adjustBoxSize();
1810   mLegend->updateFilterByMap();
1811   mLegend->endCommand();
1812 }
1813 
1814 ///@endcond
1815