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