1 /***************************************************************************
2 qgslegendpatchshapebutton.cpp
3 -----------------
4 Date : April 2020
5 Copyright : (C) 2020 by Nyall Dawson
6 Email : nyall dot dawson at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16 #include "qgslegendpatchshapebutton.h"
17 #include "qgslegendpatchshapewidget.h"
18 #include "qgis.h"
19 #include "qgsguiutils.h"
20 #include "qgsfillsymbol.h"
21 #include "qgsmarkersymbol.h"
22 #include "qgslinesymbol.h"
23
24 #include <QMenu>
25 #include <QBuffer>
26
QgsLegendPatchShapeButton(QWidget * parent,const QString & dialogTitle)27 QgsLegendPatchShapeButton::QgsLegendPatchShapeButton( QWidget *parent, const QString &dialogTitle )
28 : QToolButton( parent )
29 , mShape( QgsStyle::defaultStyle()->defaultPatch( Qgis::SymbolType::Fill, QSizeF( 10, 5 ) ) )
30 , mDialogTitle( dialogTitle.isEmpty() ? tr( "Legend Patch Shape" ) : dialogTitle )
31 {
32 mPreviewSymbol.reset( QgsFillSymbol::createSimple( QVariantMap() ) );
33
34 connect( this, &QAbstractButton::clicked, this, &QgsLegendPatchShapeButton::showSettingsDialog );
35
36 //setup dropdown menu
37 mMenu = new QMenu( this );
38 connect( mMenu, &QMenu::aboutToShow, this, &QgsLegendPatchShapeButton::prepareMenu );
39 setMenu( mMenu );
40 setPopupMode( QToolButton::MenuButtonPopup );
41
42 //make sure height of button looks good under different platforms
43 QSize size = QToolButton::minimumSizeHint();
44 int fontHeight = static_cast< int >( Qgis::UI_SCALE_FACTOR * fontMetrics().height() * 2.0 );
45 mSizeHint = QSize( size.width(), std::max( size.height(), fontHeight ) );
46 }
47
48 QgsLegendPatchShapeButton::~QgsLegendPatchShapeButton() = default;
49
minimumSizeHint() const50 QSize QgsLegendPatchShapeButton::minimumSizeHint() const
51 {
52 return mSizeHint;
53 }
54
sizeHint() const55 QSize QgsLegendPatchShapeButton::sizeHint() const
56 {
57 return mSizeHint;
58 }
59
setSymbolType(Qgis::SymbolType type)60 void QgsLegendPatchShapeButton::setSymbolType( Qgis::SymbolType type )
61 {
62 if ( mPreviewSymbol->type() != type )
63 {
64 switch ( type )
65 {
66 case Qgis::SymbolType::Marker:
67 mPreviewSymbol.reset( QgsMarkerSymbol::createSimple( QVariantMap() ) );
68 break;
69
70 case Qgis::SymbolType::Line:
71 mPreviewSymbol.reset( QgsLineSymbol::createSimple( QVariantMap() ) );
72 break;
73
74 case Qgis::SymbolType::Fill:
75 mPreviewSymbol.reset( QgsFillSymbol::createSimple( QVariantMap() ) );
76 break;
77
78 case Qgis::SymbolType::Hybrid:
79 break;
80 }
81 }
82
83 if ( type != mType )
84 {
85 mType = type;
86 setToDefault();
87 }
88
89 updatePreview();
90 }
91
setPreviewSymbol(QgsSymbol * symbol)92 void QgsLegendPatchShapeButton::setPreviewSymbol( QgsSymbol *symbol )
93 {
94 mPreviewSymbol.reset( symbol );
95 updatePreview();
96 }
97
showSettingsDialog()98 void QgsLegendPatchShapeButton::showSettingsDialog()
99 {
100 QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this );
101 if ( panel && panel->dockMode() )
102 {
103 QgsLegendPatchShapeWidget *widget = new QgsLegendPatchShapeWidget( this, mShape );
104 connect( widget, &QgsLegendPatchShapeWidget::changed, this, [ = ]
105 {
106 setShape( widget->shape() );
107 } );
108 widget->setPanelTitle( mDialogTitle );
109 panel->openPanel( widget );
110 }
111 }
112
setToDefault()113 void QgsLegendPatchShapeButton::setToDefault()
114 {
115 switch ( mType )
116 {
117 case Qgis::SymbolType::Marker:
118 mShape = QgsStyle::defaultStyle()->defaultPatch( Qgis::SymbolType::Marker, QSizeF( 10, 5 ) );
119 break;
120
121 case Qgis::SymbolType::Line:
122 mShape = QgsStyle::defaultStyle()->defaultPatch( Qgis::SymbolType::Line, QSizeF( 10, 5 ) );
123 break;
124
125 case Qgis::SymbolType::Fill:
126 mShape = QgsStyle::defaultStyle()->defaultPatch( Qgis::SymbolType::Fill, QSizeF( 10, 5 ) );
127 break;
128
129 case Qgis::SymbolType::Hybrid:
130 break;
131 }
132 mIsDefault = true;
133 updatePreview();
134 emit changed();
135 }
136
setMessageBar(QgsMessageBar * bar)137 void QgsLegendPatchShapeButton::setMessageBar( QgsMessageBar *bar )
138 {
139 mMessageBar = bar;
140 }
141
messageBar() const142 QgsMessageBar *QgsLegendPatchShapeButton::messageBar() const
143 {
144 return mMessageBar;
145 }
146
setShape(const QgsLegendPatchShape & shape)147 void QgsLegendPatchShapeButton::setShape( const QgsLegendPatchShape &shape )
148 {
149 mShape = shape.symbolType() == mType ? shape : QgsLegendPatchShape();
150 mIsDefault = mShape.isNull();
151 if ( mIsDefault )
152 {
153 switch ( mType )
154 {
155 case Qgis::SymbolType::Marker:
156 mShape = QgsStyle::defaultStyle()->defaultPatch( Qgis::SymbolType::Marker, QSizeF( 10, 5 ) );
157 break;
158
159 case Qgis::SymbolType::Line:
160 mShape = QgsStyle::defaultStyle()->defaultPatch( Qgis::SymbolType::Line, QSizeF( 10, 5 ) );
161 break;
162
163 case Qgis::SymbolType::Fill:
164 mShape = QgsStyle::defaultStyle()->defaultPatch( Qgis::SymbolType::Fill, QSizeF( 10, 5 ) );
165 break;
166
167 case Qgis::SymbolType::Hybrid:
168 break;
169 }
170 }
171
172 updatePreview();
173 emit changed();
174 }
175
mousePressEvent(QMouseEvent * e)176 void QgsLegendPatchShapeButton::mousePressEvent( QMouseEvent *e )
177 {
178 if ( e->button() == Qt::RightButton )
179 {
180 QToolButton::showMenu();
181 return;
182 }
183 QToolButton::mousePressEvent( e );
184 }
185
prepareMenu()186 void QgsLegendPatchShapeButton::prepareMenu()
187 {
188 mMenu->clear();
189
190 QAction *configureAction = new QAction( tr( "Configure Patch…" ), this );
191 mMenu->addAction( configureAction );
192 connect( configureAction, &QAction::triggered, this, &QgsLegendPatchShapeButton::showSettingsDialog );
193
194 QAction *defaultAction = new QAction( tr( "Reset to Default" ), this );
195 mMenu->addAction( defaultAction );
196 connect( defaultAction, &QAction::triggered, this, [ = ] { setToDefault(); emit changed(); } );
197
198 mMenu->addSeparator();
199
200 QStringList patchNames = QgsStyle::defaultStyle()->symbolsOfFavorite( QgsStyle::LegendPatchShapeEntity );
201 patchNames.sort();
202 const int iconSize = QgsGuiUtils::scaleIconSize( 16 );
203 for ( const QString &name : std::as_const( patchNames ) )
204 {
205 const QgsLegendPatchShape shape = QgsStyle::defaultStyle()->legendPatchShape( name );
206 if ( shape.symbolType() == mType )
207 {
208 if ( const QgsSymbol *symbol = QgsStyle::defaultStyle()->previewSymbolForPatchShape( shape ) )
209 {
210 QIcon icon = QgsSymbolLayerUtils::symbolPreviewPixmap( symbol, QSize( iconSize, iconSize ), 1, nullptr, false, nullptr, &shape );
211 QAction *action = new QAction( name, this );
212 action->setIcon( icon );
213 connect( action, &QAction::triggered, this, [ = ] { loadPatchFromStyle( name ); } );
214 mMenu->addAction( action );
215 }
216 }
217 }
218 }
219
loadPatchFromStyle(const QString & name)220 void QgsLegendPatchShapeButton::loadPatchFromStyle( const QString &name )
221 {
222 if ( !QgsStyle::defaultStyle()->legendPatchShapeNames().contains( name ) )
223 return;
224
225 const QgsLegendPatchShape newShape = QgsStyle::defaultStyle()->legendPatchShape( name );
226 setShape( newShape );
227 }
228
changeEvent(QEvent * e)229 void QgsLegendPatchShapeButton::changeEvent( QEvent *e )
230 {
231 if ( e->type() == QEvent::EnabledChange )
232 {
233 updatePreview();
234 }
235 QToolButton::changeEvent( e );
236 }
237
showEvent(QShowEvent * e)238 void QgsLegendPatchShapeButton::showEvent( QShowEvent *e )
239 {
240 updatePreview();
241 QToolButton::showEvent( e );
242 }
243
resizeEvent(QResizeEvent * event)244 void QgsLegendPatchShapeButton::resizeEvent( QResizeEvent *event )
245 {
246 QToolButton::resizeEvent( event );
247 //recalculate icon size and redraw icon
248 mIconSize = QSize();
249 updatePreview();
250 }
251
updatePreview()252 void QgsLegendPatchShapeButton::updatePreview()
253 {
254 QSize currentIconSize;
255 //icon size is button size with a small margin
256 if ( menu() )
257 {
258 if ( !mIconSize.isValid() )
259 {
260 //calculate size of push button part of widget (ie, without the menu dropdown button part)
261 QStyleOptionToolButton opt;
262 initStyleOption( &opt );
263 QRect buttonSize = QApplication::style()->subControlRect( QStyle::CC_ToolButton, &opt, QStyle::SC_ToolButton,
264 this );
265 //make sure height of icon looks good under different platforms
266 #ifdef Q_OS_WIN
267 mIconSize = QSize( buttonSize.width() - 10, height() - 6 );
268 #else
269 mIconSize = QSize( buttonSize.width() - 10, height() - 12 );
270 #endif
271 }
272 currentIconSize = mIconSize;
273 }
274 else
275 {
276 //no menu
277 #ifdef Q_OS_WIN
278 currentIconSize = QSize( width() - 10, height() - 6 );
279 #else
280 currentIconSize = QSize( width() - 10, height() - 12 );
281 #endif
282 }
283
284 if ( !currentIconSize.isValid() || currentIconSize.width() <= 0 || currentIconSize.height() <= 0 )
285 {
286 return;
287 }
288
289 //create an icon pixmap
290 QIcon icon = QgsSymbolLayerUtils::symbolPreviewIcon( mPreviewSymbol.get(), currentIconSize, currentIconSize.height() / 10, &mShape );
291 setIconSize( currentIconSize );
292 setIcon( icon );
293
294 // set tooltip
295 // create very large preview image
296
297 int width = static_cast< int >( Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'X' ) * 23 );
298 int height = static_cast< int >( width / 1.61803398875 ); // golden ratio
299
300 QPixmap pm = QgsSymbolLayerUtils::symbolPreviewPixmap( mPreviewSymbol.get(), QSize( width, height ), height / 20, nullptr, false, nullptr, &mShape );
301 QByteArray data;
302 QBuffer buffer( &data );
303 pm.save( &buffer, "PNG", 100 );
304 setToolTip( QStringLiteral( "<img src='data:image/png;base64, %3'>" ).arg( QString( data.toBase64() ) ) );
305 }
306
setDialogTitle(const QString & title)307 void QgsLegendPatchShapeButton::setDialogTitle( const QString &title )
308 {
309 mDialogTitle = title;
310 }
311
dialogTitle() const312 QString QgsLegendPatchShapeButton::dialogTitle() const
313 {
314 return mDialogTitle;
315 }
316
shape()317 QgsLegendPatchShape QgsLegendPatchShapeButton::shape()
318 {
319 return mIsDefault ? QgsLegendPatchShape() : mShape;
320 }
321