1 /***************************************************************************
2     qgsrendererpropertiesdialog.cpp
3     ---------------------
4     begin                : December 2009
5     copyright            : (C) 2009 by Martin Dobias
6     email                : wonder dot sk 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 #include "qgsrendererpropertiesdialog.h"
16 
17 #include "qgsrenderer.h"
18 #include "qgsrendererregistry.h"
19 
20 #include "qgsrendererwidget.h"
21 #include "qgssinglesymbolrendererwidget.h"
22 #include "qgscategorizedsymbolrendererwidget.h"
23 #include "qgsgraduatedsymbolrendererwidget.h"
24 #include "qgsrulebasedrendererwidget.h"
25 #include "qgspointdisplacementrendererwidget.h"
26 #include "qgspointclusterrendererwidget.h"
27 #include "qgsinvertedpolygonrendererwidget.h"
28 #include "qgsheatmaprendererwidget.h"
29 #include "qgs25drendererwidget.h"
30 #include "qgsnullsymbolrendererwidget.h"
31 #include "qgspanelwidget.h"
32 #include "qgspainteffect.h"
33 
34 #include "qgsorderbydialog.h"
35 #include "qgsapplication.h"
36 #include "qgslogger.h"
37 #include "qgsvectorlayer.h"
38 
39 #include <QKeyEvent>
40 #include <QMessageBox>
41 
_initRenderer(const QString & name,QgsRendererWidgetFunc f,const QString & iconName=QString ())42 static bool _initRenderer( const QString &name, QgsRendererWidgetFunc f, const QString &iconName = QString() )
43 {
44   QgsRendererRegistry *reg = QgsApplication::rendererRegistry();
45   QgsRendererAbstractMetadata *am = reg->rendererMetadata( name );
46   if ( !am )
47     return false;
48   QgsRendererMetadata *m = dynamic_cast<QgsRendererMetadata *>( am );
49   if ( !m )
50     return false;
51 
52   m->setWidgetFunction( f );
53 
54   if ( !iconName.isEmpty() )
55   {
56     m->setIcon( QgsApplication::getThemeIcon( iconName ) );
57   }
58 
59   QgsDebugMsgLevel( "Set for " + name, 2 );
60   return true;
61 }
62 
_initRendererWidgetFunctions()63 static void _initRendererWidgetFunctions()
64 {
65   static bool sInitialized = false;
66   if ( sInitialized )
67     return;
68 
69   _initRenderer( QStringLiteral( "singleSymbol" ), QgsSingleSymbolRendererWidget::create, QStringLiteral( "rendererSingleSymbol.svg" ) );
70   _initRenderer( QStringLiteral( "categorizedSymbol" ), QgsCategorizedSymbolRendererWidget::create, QStringLiteral( "rendererCategorizedSymbol.svg" ) );
71   _initRenderer( QStringLiteral( "graduatedSymbol" ), QgsGraduatedSymbolRendererWidget::create, QStringLiteral( "rendererGraduatedSymbol.svg" ) );
72   _initRenderer( QStringLiteral( "RuleRenderer" ), QgsRuleBasedRendererWidget::create, QStringLiteral( "rendererRuleBasedSymbol.svg" ) );
73   _initRenderer( QStringLiteral( "pointDisplacement" ), QgsPointDisplacementRendererWidget::create, QStringLiteral( "rendererPointDisplacementSymbol.svg" ) );
74   _initRenderer( QStringLiteral( "pointCluster" ), QgsPointClusterRendererWidget::create, QStringLiteral( "rendererPointClusterSymbol.svg" ) );
75   _initRenderer( QStringLiteral( "invertedPolygonRenderer" ), QgsInvertedPolygonRendererWidget::create, QStringLiteral( "rendererInvertedSymbol.svg" ) );
76   _initRenderer( QStringLiteral( "heatmapRenderer" ), QgsHeatmapRendererWidget::create, QStringLiteral( "rendererHeatmapSymbol.svg" ) );
77   _initRenderer( QStringLiteral( "25dRenderer" ), Qgs25DRendererWidget::create, QStringLiteral( "renderer25dSymbol.svg" ) );
78   _initRenderer( QStringLiteral( "nullSymbol" ), QgsNullSymbolRendererWidget::create, QStringLiteral( "rendererNullSymbol.svg" ) );
79   sInitialized = true;
80 }
81 
QgsRendererPropertiesDialog(QgsVectorLayer * layer,QgsStyle * style,bool embedded,QWidget * parent)82 QgsRendererPropertiesDialog::QgsRendererPropertiesDialog( QgsVectorLayer *layer, QgsStyle *style, bool embedded, QWidget *parent )
83   : QDialog( parent )
84   , mLayer( layer )
85   , mStyle( style )
86 
87 {
88   setupUi( this );
89   QgsGui::enableAutoGeometryRestore( this );
90   mLayerRenderingGroupBox->setSettingGroup( QStringLiteral( "layerRenderingGroupBox" ) );
91 
92   // can be embedded in vector layer properties
93   if ( embedded )
94   {
95     buttonBox->hide();
96     layout()->setContentsMargins( 0, 0, 0, 0 );
97   }
98 
99   // initialize registry's widget functions
100   _initRendererWidgetFunctions();
101 
102   QgsRendererRegistry *reg = QgsApplication::rendererRegistry();
103   QStringList renderers = reg->renderersList( mLayer );
104   const auto constRenderers = renderers;
105   for ( const QString &name : constRenderers )
106   {
107     QgsRendererAbstractMetadata *m = reg->rendererMetadata( name );
108     cboRenderers->addItem( m->icon(), m->visibleName(), name );
109   }
110 
111   cboRenderers->setCurrentIndex( -1 ); // set no current renderer
112 
113   connect( buttonBox, &QDialogButtonBox::accepted, this, &QgsRendererPropertiesDialog::onOK );
114 
115   // connect layer opacity slider and spin box
116   connect( cboRenderers, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsRendererPropertiesDialog::rendererChanged );
117   connect( checkboxEnableOrderBy, &QAbstractButton::toggled, btnOrderBy, &QWidget::setEnabled );
118   connect( btnOrderBy, &QAbstractButton::clicked, this, &QgsRendererPropertiesDialog::showOrderByDialog );
119 
120   syncToLayer();
121 
122   QList<QWidget *> widgets;
123   widgets << mOpacityWidget
124           << cboRenderers
125           << checkboxEnableOrderBy
126           << mBlendModeComboBox
127           << mFeatureBlendComboBox
128           << mEffectWidget;
129 
130   connectValueChanged( widgets, SIGNAL( widgetChanged() ) );
131   connect( mEffectWidget, &QgsPanelWidget::showPanel, this, &QgsRendererPropertiesDialog::openPanel );
132 }
133 
connectValueChanged(const QList<QWidget * > & widgets,const char * slot)134 void QgsRendererPropertiesDialog::connectValueChanged( const QList<QWidget *> &widgets, const char *slot )
135 {
136   for ( QWidget *widget : widgets )
137   {
138     if ( QgsPropertyOverrideButton *w = qobject_cast<QgsPropertyOverrideButton *>( widget ) )
139     {
140       connect( w, SIGNAL( changed ), this, slot );
141     }
142     else if ( QgsFieldExpressionWidget *w = qobject_cast<QgsFieldExpressionWidget *>( widget ) )
143     {
144       connect( w, SIGNAL( fieldChanged( QString ) ), this,  slot );
145     }
146     else if ( QgsOpacityWidget *w = qobject_cast<QgsOpacityWidget *>( widget ) )
147     {
148       connect( w, SIGNAL( opacityChanged( double ) ), this,  slot );
149     }
150     else if ( QComboBox *w = qobject_cast<QComboBox *>( widget ) )
151     {
152       connect( w, SIGNAL( currentIndexChanged( int ) ), this, slot );
153     }
154     else if ( QSpinBox *w = qobject_cast<QSpinBox *>( widget ) )
155     {
156       connect( w, SIGNAL( valueChanged( int ) ), this, slot );
157     }
158     else if ( QDoubleSpinBox *w = qobject_cast<QDoubleSpinBox *>( widget ) )
159     {
160       connect( w, SIGNAL( valueChanged( double ) ), this, slot );
161     }
162     else if ( QgsColorButton *w = qobject_cast<QgsColorButton *>( widget ) )
163     {
164       connect( w, SIGNAL( colorChanged( QColor ) ), this, slot );
165     }
166     else if ( QCheckBox *w = qobject_cast<QCheckBox *>( widget ) )
167     {
168       connect( w, SIGNAL( toggled( bool ) ), this, slot );
169     }
170     else if ( QLineEdit *w = qobject_cast<QLineEdit *>( widget ) )
171     {
172       connect( w, SIGNAL( textEdited( QString ) ), this, slot );
173       connect( w, SIGNAL( textChanged( QString ) ), this, slot );
174     }
175     else if ( QgsEffectStackCompactWidget *w = qobject_cast<QgsEffectStackCompactWidget *>( widget ) )
176     {
177       connect( w, SIGNAL( changed() ), this, slot );
178     }
179   }
180 }
181 
~QgsRendererPropertiesDialog()182 QgsRendererPropertiesDialog::~QgsRendererPropertiesDialog()
183 {
184   delete mPaintEffect;
185 }
186 
setMapCanvas(QgsMapCanvas * canvas)187 void QgsRendererPropertiesDialog::setMapCanvas( QgsMapCanvas *canvas )
188 {
189   mMapCanvas = canvas;
190   if ( mActiveWidget )
191   {
192     QgsSymbolWidgetContext context;
193     context.setMapCanvas( mMapCanvas );
194     mActiveWidget->setContext( context );
195   }
196 }
197 
setContext(const QgsSymbolWidgetContext & context)198 void QgsRendererPropertiesDialog::setContext( const QgsSymbolWidgetContext &context )
199 {
200   mMapCanvas = context.mapCanvas();
201   mMessageBar = context.messageBar();
202   if ( mActiveWidget )
203   {
204     mActiveWidget->setContext( context );
205   }
206 }
207 
setDockMode(bool dockMode)208 void QgsRendererPropertiesDialog::setDockMode( bool dockMode )
209 {
210   mDockMode = dockMode;
211   mEffectWidget->setDockMode( dockMode );
212   if ( mActiveWidget )
213     mActiveWidget->setDockMode( mDockMode );
214 }
215 
216 
rendererChanged()217 void QgsRendererPropertiesDialog::rendererChanged()
218 {
219   if ( cboRenderers->currentIndex() == -1 )
220   {
221     QgsDebugMsg( QStringLiteral( "No current item -- this should never happen!" ) );
222     return;
223   }
224 
225   QString rendererName = cboRenderers->currentData().toString();
226 
227   //Retrieve the previous renderer: from the old active widget if possible, otherwise from the layer
228   QgsFeatureRenderer *oldRenderer = nullptr;
229   if ( mActiveWidget && mActiveWidget->renderer() )
230   {
231     oldRenderer = mActiveWidget->renderer()->clone();
232   }
233   else
234   {
235     oldRenderer = mLayer->renderer()->clone();
236   }
237 
238   // get rid of old active widget (if any)
239   if ( mActiveWidget )
240   {
241     stackedWidget->removeWidget( mActiveWidget );
242 
243     delete mActiveWidget;
244     mActiveWidget = nullptr;
245   }
246 
247   QgsRendererWidget *w = nullptr;
248   QgsRendererAbstractMetadata *m = QgsApplication::rendererRegistry()->rendererMetadata( rendererName );
249   if ( m )
250     w = m->createRendererWidget( mLayer, mStyle, oldRenderer );
251   delete oldRenderer;
252 
253   if ( w )
254   {
255     // instantiate the widget and set as active
256     mActiveWidget = w;
257     stackedWidget->addWidget( mActiveWidget );
258     stackedWidget->setCurrentWidget( mActiveWidget );
259     if ( mActiveWidget->renderer() )
260     {
261       if ( mMapCanvas || mMessageBar )
262       {
263         QgsSymbolWidgetContext context;
264         context.setMapCanvas( mMapCanvas );
265         context.setMessageBar( mMessageBar );
266         mActiveWidget->setContext( context );
267       }
268       changeOrderBy( mActiveWidget->renderer()->orderBy(), mActiveWidget->renderer()->orderByEnabled() );
269       connect( mActiveWidget, &QgsRendererWidget::layerVariablesChanged, this, &QgsRendererPropertiesDialog::layerVariablesChanged );
270     }
271     connect( mActiveWidget, &QgsPanelWidget::widgetChanged, this, &QgsRendererPropertiesDialog::widgetChanged );
272     connect( mActiveWidget, &QgsPanelWidget::showPanel, this, &QgsRendererPropertiesDialog::openPanel );
273     w->setDockMode( mDockMode );
274   }
275   else
276   {
277     // set default "no edit widget available" page
278     stackedWidget->setCurrentWidget( pageNoWidget );
279   }
280 }
281 
apply()282 void QgsRendererPropertiesDialog::apply()
283 {
284   if ( !mActiveWidget || !mLayer )
285   {
286     return;
287   }
288 
289   mActiveWidget->applyChanges();
290 
291   QgsFeatureRenderer *renderer = mActiveWidget->renderer();
292   if ( renderer )
293   {
294     renderer->setPaintEffect( mPaintEffect->clone() );
295     // set the order by
296     renderer->setOrderBy( mOrderBy );
297     renderer->setOrderByEnabled( checkboxEnableOrderBy->isChecked() );
298 
299     mLayer->setRenderer( renderer->clone() );
300   }
301 
302   // set the blend modes for the layer
303   mLayer->setBlendMode( mBlendModeComboBox->blendMode() );
304   mLayer->setFeatureBlendMode( mFeatureBlendComboBox->blendMode() );
305 
306   // set opacity for the layer
307   mLayer->setOpacity( mOpacityWidget->opacity() );
308 }
309 
onOK()310 void QgsRendererPropertiesDialog::onOK()
311 {
312   apply();
313   accept();
314 }
315 
openPanel(QgsPanelWidget * panel)316 void QgsRendererPropertiesDialog::openPanel( QgsPanelWidget *panel )
317 {
318   if ( mDockMode )
319   {
320     emit showPanel( panel );
321   }
322   else
323   {
324     // Show the dialog version if no one is connected
325     QDialog *dlg = new QDialog();
326     QString key = QStringLiteral( "/UI/paneldialog/%1" ).arg( panel->panelTitle() );
327     QgsSettings settings;
328     dlg->restoreGeometry( settings.value( key ).toByteArray() );
329     dlg->setWindowTitle( panel->panelTitle() );
330     dlg->setLayout( new QVBoxLayout() );
331     dlg->layout()->addWidget( panel );
332     QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok );
333     connect( buttonBox, &QDialogButtonBox::accepted, dlg, &QDialog::accept );
334     dlg->layout()->addWidget( buttonBox );
335     dlg->exec();
336     settings.setValue( key, dlg->saveGeometry() );
337     panel->acceptPanel();
338   }
339 }
340 
syncToLayer()341 void QgsRendererPropertiesDialog::syncToLayer()
342 {
343   // Blend mode
344   mBlendModeComboBox->setBlendMode( mLayer->blendMode() );
345 
346   // Feature blend mode
347   mFeatureBlendComboBox->setBlendMode( mLayer->featureBlendMode() );
348 
349   // Layer opacity
350   mOpacityWidget->setOpacity( mLayer->opacity() );
351 
352   //paint effect widget
353   if ( mLayer->renderer() )
354   {
355     if ( mLayer->renderer()->paintEffect() )
356     {
357       mPaintEffect = mLayer->renderer()->paintEffect()->clone();
358       mEffectWidget->setPaintEffect( mPaintEffect );
359     }
360 
361     mOrderBy = mLayer->renderer()->orderBy();
362   }
363 
364   // setup slot rendererChanged()
365   //setup order by
366   if ( mLayer->renderer() &&
367        mLayer->renderer()->orderByEnabled() )
368   {
369     checkboxEnableOrderBy->setChecked( true );
370   }
371   else
372   {
373     btnOrderBy->setEnabled( false );
374     checkboxEnableOrderBy->setChecked( false );
375   }
376 
377   if ( mLayer->renderer() )
378   {
379     // set current renderer from layer
380     QString rendererName = mLayer->renderer()->type();
381 
382     int rendererIdx = cboRenderers->findData( rendererName );
383     cboRenderers->setCurrentIndex( rendererIdx );
384 
385     // no renderer found... this mustn't happen
386     Q_ASSERT( rendererIdx != -1 && "there must be a renderer!" );
387   }
388 
389 }
390 
showOrderByDialog()391 void QgsRendererPropertiesDialog::showOrderByDialog()
392 {
393   QgsOrderByDialog dlg( mLayer, this );
394 
395   dlg.setOrderBy( mOrderBy );
396   if ( dlg.exec() )
397   {
398     mOrderBy = dlg.orderBy();
399     emit widgetChanged();
400   }
401 }
402 
changeOrderBy(const QgsFeatureRequest::OrderBy & orderBy,bool orderByEnabled)403 void QgsRendererPropertiesDialog::changeOrderBy( const QgsFeatureRequest::OrderBy &orderBy, bool orderByEnabled )
404 {
405   mOrderBy = orderBy;
406   checkboxEnableOrderBy->setChecked( orderByEnabled );
407 }
408 
updateUIState(bool hidden)409 void QgsRendererPropertiesDialog::updateUIState( bool hidden )
410 {
411   mLayerRenderingGroupBox->setHidden( hidden );
412   cboRenderers->setHidden( hidden );
413 }
414 
415 
keyPressEvent(QKeyEvent * e)416 void QgsRendererPropertiesDialog::keyPressEvent( QKeyEvent *e )
417 {
418   // Ignore the ESC key to avoid close the dialog without the properties window
419   if ( !isWindow() && e->key() == Qt::Key_Escape )
420   {
421     e->ignore();
422   }
423   else
424   {
425     QDialog::keyPressEvent( e );
426   }
427 }
428