QGIS API Documentation  3.27.0-Master (e113457133)
qgseffectstackpropertieswidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgseffectstackpropertieswidget.h
3  --------------------------------
4  begin : January 2015
5  copyright : (C) 2015 by Nyall Dawson
6  email : nyall dot dawson at gmail.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 
17 #include "qgspainteffectregistry.h"
18 #include "qgspainteffect.h"
19 #include "qgseffectstack.h"
21 #include "qgspainteffectwidget.h"
22 #include "qgsapplication.h"
23 #include "qgssymbollayerutils.h"
24 #include "qgspanelwidget.h"
25 #include "qgshelp.h"
26 
27 #include <QPicture>
28 #include <QPainter>
29 #include <QStandardItemModel>
30 #include <QStandardItem>
31 #include <QCheckBox>
32 #include <QToolButton>
33 
35 
36 static const int EFFECT_ITEM_TYPE = QStandardItem::UserType + 1;
37 
38 class EffectItem : public QStandardItem
39 {
40  public:
41  EffectItem( QgsPaintEffect *effect, QgsEffectStackPropertiesWidget *propertiesWidget )
42  {
43  setEffect( effect );
44  setCheckable( true );
45  mWidget = propertiesWidget;
46  }
47 
48  void setEffect( QgsPaintEffect *effect )
49  {
50  mEffect = effect;
51  emitDataChanged();
52  }
53 
54  int type() const override { return EFFECT_ITEM_TYPE; }
55 
56  QgsPaintEffect *effect()
57  {
58  return mEffect;
59  }
60 
61  QVariant data( int role ) const override
62  {
63  if ( role == Qt::DisplayRole || role == Qt::EditRole )
64  {
65  return QgsApplication::paintEffectRegistry()->effectMetadata( mEffect->type() )->visibleName();
66  }
67  if ( role == Qt::CheckStateRole )
68  {
69  return mEffect->enabled() ? Qt::Checked : Qt::Unchecked;
70  }
71  return QStandardItem::data( role );
72  }
73 
74  void setData( const QVariant &value, int role ) override
75  {
76  if ( role == Qt::CheckStateRole )
77  {
78  mEffect->setEnabled( value.toBool() );
79  mWidget->updatePreview();
80  }
81  else
82  {
83  QStandardItem::setData( value, role );
84  }
85  }
86 
87  protected:
88  QgsPaintEffect *mEffect = nullptr;
89  QgsEffectStackPropertiesWidget *mWidget = nullptr;
90 };
92 
93 //
94 // QgsEffectStackPropertiesWidget
95 //
96 
98  : QgsPanelWidget( parent )
99  , mStack( stack )
100 
101 {
102 
103 // TODO
104 #ifdef Q_OS_MAC
105  //setWindowModality( Qt::WindowModal );
106 #endif
107 
108  mPresentWidget = nullptr;
109 
110  setupUi( this );
111  this->layout()->setContentsMargins( 0, 0, 0, 0 );
112 
113  mEffectsList->setMaximumHeight( static_cast< int >( Qgis::UI_SCALE_FACTOR * fontMetrics().height() * 7 ) );
114  mEffectsList->setMinimumHeight( mEffectsList->maximumHeight() );
115  lblPreview->setMaximumWidth( mEffectsList->maximumHeight() );
116 
117  mAddButton->setIcon( QIcon( QgsApplication::iconPath( "symbologyAdd.svg" ) ) );
118  mRemoveButton->setIcon( QIcon( QgsApplication::iconPath( "symbologyRemove.svg" ) ) );
119  mUpButton->setIcon( QIcon( QgsApplication::iconPath( "mActionArrowUp.svg" ) ) );
120  mDownButton->setIcon( QIcon( QgsApplication::iconPath( "mActionArrowDown.svg" ) ) );
121 
122  mModel = new QStandardItemModel();
123  // Set the effect
124  mEffectsList->setModel( mModel );
125 
126  QItemSelectionModel *selModel = mEffectsList->selectionModel();
127  connect( selModel, &QItemSelectionModel::currentChanged, this, &QgsEffectStackPropertiesWidget::effectChanged );
128 
129  loadStack( stack );
130  updatePreview();
131 
132  connect( mUpButton, &QAbstractButton::clicked, this, &QgsEffectStackPropertiesWidget::moveEffectUp );
133  connect( mDownButton, &QAbstractButton::clicked, this, &QgsEffectStackPropertiesWidget::moveEffectDown );
134  connect( mAddButton, &QAbstractButton::clicked, this, &QgsEffectStackPropertiesWidget::addEffect );
135  connect( mRemoveButton, &QAbstractButton::clicked, this, &QgsEffectStackPropertiesWidget::removeEffect );
136 
137  updateUi();
138 
139  // set first selected effect as active item in the tree
140  int initialRow = 0;
141  for ( int i = 0; i < stack->count(); ++i )
142  {
143  // list shows effects in opposite order to stack
144  if ( stack->effect( stack->count() - i - 1 )->enabled() )
145  {
146  initialRow = i;
147  break;
148  }
149  }
150  const QModelIndex newIndex = mEffectsList->model()->index( initialRow, 0 );
151  mEffectsList->setCurrentIndex( newIndex );
152 
153  setPanelTitle( tr( "Effects Properties" ) );
154 }
155 
157 
159 {
160  mPreviewPicture = picture;
161  updatePreview();
162 }
163 
165 {
166  if ( !stack )
167  {
168  return;
169  }
170 
171  EffectItem *parent = static_cast<EffectItem *>( mModel->invisibleRootItem() );
172 
173  const int count = stack->count();
174  for ( int i = count - 1; i >= 0; i-- )
175  {
176  EffectItem *effectItem = new EffectItem( stack->effect( i ), this );
177  effectItem->setEditable( false );
178  parent->appendRow( effectItem );
179  }
180 }
181 
182 
184 {
185  mModel->clear();
186  loadStack( mStack );
187 }
188 
190 {
191  const QModelIndex currentIdx = mEffectsList->currentIndex();
192  if ( !currentIdx.isValid() )
193  return;
194 
195  EffectItem *item = static_cast<EffectItem *>( mModel->itemFromIndex( currentIdx ) );
196 
197  QStandardItem *root = mModel->invisibleRootItem();
198  const int rowCount = root->rowCount();
199  const int currentRow = item ? item->row() : 0;
200 
201  mUpButton->setEnabled( currentRow > 0 );
202  mDownButton->setEnabled( currentRow < rowCount - 1 );
203  mRemoveButton->setEnabled( rowCount > 1 );
204 }
205 
207 {
208  QPainter painter;
209  QImage previewImage( 100, 100, QImage::Format_ARGB32 );
210  previewImage.fill( Qt::transparent );
211  painter.begin( &previewImage );
212  painter.setRenderHint( QPainter::Antialiasing );
213  QgsRenderContext context = QgsRenderContext::fromQPainter( &painter );
215  if ( mPreviewPicture.isNull() )
216  {
217  QPicture previewPic;
218  QPainter previewPicPainter;
219  previewPicPainter.begin( &previewPic );
220  previewPicPainter.setPen( Qt::red );
221  previewPicPainter.setBrush( QColor( 255, 100, 100, 255 ) );
222  previewPicPainter.drawEllipse( QPoint( 50, 50 ), 20, 20 );
223  previewPicPainter.end();
224  mStack->render( previewPic, context );
225  }
226  else
227  {
228  context.painter()->translate( 20, 20 );
229  mStack->render( mPreviewPicture, context );
230  }
231  painter.end();
232 
233  lblPreview->setPixmap( QPixmap::fromImage( previewImage ) );
234  emit widgetChanged();
235 }
236 
238 {
239  const QModelIndex idx = mEffectsList->currentIndex();
240  if ( !idx.isValid() )
241  return nullptr;
242 
243  EffectItem *item = static_cast<EffectItem *>( mModel->itemFromIndex( idx ) );
244  return item;
245 }
246 
248 {
249  updateUi();
250 
251  EffectItem *currentItem = currentEffectItem();
252  if ( !currentItem )
253  return;
254 
255  QgsPaintEffectPropertiesWidget *effectPropertiesWidget = new QgsPaintEffectPropertiesWidget( currentItem->effect() );
256  setWidget( effectPropertiesWidget );
257 
260 }
261 
263 {
264  const int index = stackedWidget->addWidget( widget );
265  stackedWidget->setCurrentIndex( index );
266  if ( mPresentWidget )
267  {
268  stackedWidget->removeWidget( mPresentWidget );
269  QWidget *dummy = mPresentWidget;
270  mPresentWidget = widget;
271  delete dummy; // auto disconnects all signals
272  }
273 }
274 
276 {
277  QgsPaintEffect *newEffect = new QgsDrawSourceEffect();
278  mStack->insertEffect( 0, newEffect );
279 
280  EffectItem *newEffectItem = new EffectItem( newEffect, this );
281  mModel->invisibleRootItem()->insertRow( mStack->count() - 1, newEffectItem );
282 
283  mEffectsList->setCurrentIndex( mModel->indexFromItem( newEffectItem ) );
284  updateUi();
285  updatePreview();
286 }
287 
289 {
290  EffectItem *item = currentEffectItem();
291  const int row = item->row();
292  QStandardItem *root = mModel->invisibleRootItem();
293 
294  const int layerIdx = root->rowCount() - row - 1;
295  QgsPaintEffect *tmpEffect = mStack->takeEffect( layerIdx );
296 
297  mModel->invisibleRootItem()->removeRow( row );
298 
299  const int newSelection = std::min( row, root->rowCount() - 1 );
300  const QModelIndex newIdx = root->child( newSelection )->index();
301  mEffectsList->setCurrentIndex( newIdx );
302 
303  updateUi();
304  updatePreview();
305 
306  delete tmpEffect;
307 }
308 
310 {
311  moveEffectByOffset( + 1 );
312 }
313 
315 {
316  moveEffectByOffset( -1 );
317 }
318 
320 {
321  EffectItem *item = currentEffectItem();
322  if ( !item )
323  return;
324 
325  const int row = item->row();
326 
327  QStandardItem *root = mModel->invisibleRootItem();
328 
329  const int layerIdx = root->rowCount() - row - 1;
330  // switch effects
331  QgsPaintEffect *tmpEffect = mStack->takeEffect( layerIdx );
332  mStack->insertEffect( layerIdx - offset, tmpEffect );
333 
334  QList<QStandardItem *> toMove = root->takeRow( row );
335  root->insertRows( row + offset, toMove );
336 
337  const QModelIndex newIdx = toMove[ 0 ]->index();
338  mEffectsList->setCurrentIndex( newIdx );
339 
340  updatePreview();
341  updateUi();
342 }
343 
345 {
346  EffectItem *item = currentEffectItem();
347  item->setEffect( newEffect );
348 
349  QStandardItem *root = mModel->invisibleRootItem();
350  const int effectIdx = root->rowCount() - item->row() - 1;
351  mStack->changeEffect( effectIdx, newEffect );
352 
353  updatePreview();
354  // Important: This lets the effect to have its own effect properties widget
355  effectChanged();
356 }
357 
358 
359 //
360 // QgsEffectStackPropertiesDialog
361 //
362 
364  : QgsDialog( parent, f, QDialogButtonBox::Cancel | QDialogButtonBox::Help | QDialogButtonBox::Ok )
365 
366 {
367  setWindowTitle( tr( "Effect Properties" ) );
369 
370  QDialogButtonBox *buttonBox = this->findChild<QDialogButtonBox *>( QString(), Qt::FindDirectChildrenOnly );
371  connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsEffectStackPropertiesDialog::showHelp );
372 
373  layout()->addWidget( mPropertiesWidget );
374 }
375 
377 {
378  return mPropertiesWidget->stack();
379 }
380 
382 {
384 }
385 
386 void QgsEffectStackPropertiesDialog::showHelp()
387 {
388  QgsHelp::openHelp( QStringLiteral( "working_with_vector/vector_properties.html#draw-effects" ) );
389 }
390 
391 
392 //
393 // QgsEffectStackCompactWidget
394 //
395 
397  : QgsPanelWidget( parent )
398 
399 {
400  QHBoxLayout *layout = new QHBoxLayout();
401  layout->setContentsMargins( 0, 0, 0, 0 );
402  layout->setSpacing( 6 );
403  setLayout( layout );
404 
405  mEnabledCheckBox = new QCheckBox( this );
406  mEnabledCheckBox->setText( tr( "Draw effects" ) );
407  layout->addWidget( mEnabledCheckBox );
408 
409  mButton = new QToolButton( this );
410  mButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mIconPaintEffects.svg" ) ) );
411  mButton->setToolTip( tr( "Customize effects" ) );
412  layout->addWidget( mButton );
413 
414  setFocusPolicy( Qt::StrongFocus );
415  setFocusProxy( mEnabledCheckBox );
416 
417  connect( mButton, &QAbstractButton::clicked, this, &QgsEffectStackCompactWidget::showDialog );
418  connect( mEnabledCheckBox, &QAbstractButton::toggled, this, &QgsEffectStackCompactWidget::enableToggled );
419 
420  setPaintEffect( effect );
421 }
422 
424 
426 {
427  if ( !effect )
428  {
429  mEnabledCheckBox->setChecked( false );
430  mEnabledCheckBox->setEnabled( false );
431  mButton->setEnabled( false );
432  mStack = nullptr;
433  return;
434  }
435 
436  //is effect a stack?
437  QgsEffectStack *stack = dynamic_cast<QgsEffectStack *>( effect );
438  if ( !stack )
439  {
440  //not already a stack, so promote to stack
441  stack = new QgsEffectStack( *effect );
442  }
443 
444  mStack = stack;
445  mEnabledCheckBox->setChecked( mStack->enabled() );
446  mEnabledCheckBox->setEnabled( true );
447  mButton->setEnabled( mStack->enabled() );
448 }
449 
451 {
452  return mStack;
453 }
454 
455 void QgsEffectStackCompactWidget::setPreviewPicture( const QPicture &picture )
456 {
457  mPreviewPicture = picture;
458 }
459 
460 void QgsEffectStackCompactWidget::showDialog()
461 {
462  if ( !mStack )
463  return;
464 
465  QgsEffectStack *clone = mStack->clone();
466  QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( qobject_cast< QWidget * >( parent() ) );
467  if ( panel && panel->dockMode() )
468  {
469  QgsEffectStackPropertiesWidget *widget = new QgsEffectStackPropertiesWidget( clone, nullptr );
470  widget->setPreviewPicture( mPreviewPicture );
471 
472  connect( widget, &QgsPanelWidget::widgetChanged, this, &QgsEffectStackCompactWidget::updateEffectLive );
473  connect( widget, &QgsPanelWidget::panelAccepted, this, &QgsEffectStackCompactWidget::updateAcceptWidget );
474  panel->openPanel( widget );
475  }
476  else
477  {
478  QgsEffectStackPropertiesDialog dlg( clone, this );
479  dlg.setPreviewPicture( mPreviewPicture );
480 
481  if ( dlg.exec() == QDialog::Accepted )
482  {
483  *mStack = *clone;
484  emit changed();
485  }
486  }
487 }
488 
489 void QgsEffectStackCompactWidget::enableToggled( bool checked )
490 {
491  if ( !mStack )
492  {
493  return;
494  }
495 
496  mStack->setEnabled( checked );
497  mButton->setEnabled( checked );
498  emit changed();
499 }
500 
501 void QgsEffectStackCompactWidget::updateAcceptWidget( QgsPanelWidget *panel )
502 {
503  QgsEffectStackPropertiesWidget *widget = qobject_cast<QgsEffectStackPropertiesWidget *>( panel );
504  *mStack = *widget->stack();
505  emit changed();
506 // delete widget->stack();
507 }
508 
509 void QgsEffectStackCompactWidget::updateEffectLive()
510 {
511  QgsEffectStackPropertiesWidget *widget = qobject_cast<QgsEffectStackPropertiesWidget *>( sender() );
512  *mStack = *widget->stack();
513  emit changed();
514 }
@ RenderSymbolPreview
The render is for a symbol preview only and map based properties may not be available,...
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:2039
static QgsPaintEffectRegistry * paintEffectRegistry()
Returns the application's paint effect registry, used for managing paint effects.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QString iconPath(const QString &iconFile)
Returns path to the desired icon file.
A generic dialog with layout and button box.
Definition: qgsdialog.h:34
QVBoxLayout * layout()
Returns the central layout. Widgets added to it must have this dialog as parent.
Definition: qgsdialog.h:46
QDialogButtonBox * buttonBox()
Returns the button box.
Definition: qgsdialog.h:48
A paint effect which draws the source picture with minor or no alterations.
~QgsEffectStackCompactWidget() override
QgsEffectStackCompactWidget(QWidget *parent=nullptr, QgsPaintEffect *effect=nullptr)
QgsEffectStackCompactWidget constructor.
void setPreviewPicture(const QPicture &picture)
Sets the picture to use for effect previews for the dialog.
QgsPaintEffect * paintEffect() const
Returns paint effect attached to the widget.
void setPaintEffect(QgsPaintEffect *effect)
Sets paint effect attached to the widget,.
void changed()
Emitted when the paint effect properties change.
A dialog for modifying the properties of a QgsEffectStack, including adding and reordering effects wi...
void setPreviewPicture(const QPicture &picture)
Sets the picture to use for effect previews for the dialog.
QgsEffectStackPropertiesWidget * mPropertiesWidget
QgsEffectStackPropertiesDialog(QgsEffectStack *stack, QWidget *parent=nullptr, Qt::WindowFlags f=Qt::WindowFlags())
QgsEffectStackPropertiesDialog constructor.
QgsEffectStack * stack()
Returns effect stack attached to the dialog.
A widget for modifying the properties of a QgsEffectStack, including adding and reordering effects wi...
void moveEffectByOffset(int offset)
Moves the currently selected effect within the stack by a specified offset.
void loadStack()
Refreshes the widget to reflect the current state of the stack.
void moveEffectUp()
Moves the currently selected effect up in the stack.
QgsEffectStackPropertiesWidget(QgsEffectStack *stack, QWidget *parent=nullptr)
QgsEffectStackPropertiesWidget constructor.
void updateUi()
Enables or disables widgets depending on the selected effect within the stack.
void updatePreview()
Updates the effect preview icon.
void changeEffect(QgsPaintEffect *newEffect)
Updates the effect stack when the currently selected effect changes properties.
~QgsEffectStackPropertiesWidget() override
void addEffect()
Adds a new effect to the stack.
void effectChanged()
Updates the widget when the selected effect changes type.
void setPreviewPicture(const QPicture &picture)
Sets the picture to use for effect previews for the dialog.
void setWidget(QWidget *widget)
Sets the effect properties widget.
EffectItem * currentEffectItem()
Returns the currently selected effect within the stack.
void removeEffect()
Removes the currently selected effect from the stack.
void moveEffectDown()
Moves the currently selected effect down in the stack.
QgsEffectStack * stack()
Returns effect stack attached to the widget.
A paint effect which consists of a stack of other chained paint effects.
int count() const
Returns count of effects contained by the stack.
QgsEffectStack * clone() const override
Duplicates an effect by creating a deep copy of the effect.
bool insertEffect(int index, QgsPaintEffect *effect)
Inserts an effect at a specified index within the stack.
QgsPaintEffect * takeEffect(int index)
Removes an effect from the stack and returns a pointer to it.
bool changeEffect(int index, QgsPaintEffect *effect)
Replaces the effect at a specified position within the stack.
QgsPaintEffect * effect(int index) const
Returns a pointer to the effect at a specified index within the stack.
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Definition: qgshelp.cpp:36
QString visibleName() const
Returns the user visible string representing the paint effect class.
A widget which modifies the properties of a QgsPaintEffect.
void changeEffect(QgsPaintEffect *effect)
Emitted when paint effect type changes.
void changed()
Emitted when paint effect properties changes.
QgsPaintEffectAbstractMetadata * effectMetadata(const QString &name) const
Returns the metadata for a specific effect.
Base class for visual effects which can be applied to QPicture drawings.
void setEnabled(bool enabled)
Sets whether the effect is enabled.
virtual void render(QPicture &picture, QgsRenderContext &context)
Renders a picture using the effect.
bool enabled() const
Returns whether the effect is enabled.
Base class for any widget that can be shown as a inline panel.
void openPanel(QgsPanelWidget *panel)
Open a panel or dialog depending on dock mode setting If dock mode is true this method will emit the ...
void panelAccepted(QgsPanelWidget *panel)
Emitted when the panel is accepted by the user.
void widgetChanged()
Emitted when the widget state changes.
static QgsPanelWidget * findParentPanel(QWidget *widget)
Traces through the parents of a widget to find if it is contained within a QgsPanelWidget widget.
void setPanelTitle(const QString &panelTitle)
Set the title of the panel when shown in the interface.
bool dockMode()
Returns the dock mode state.
Contains information about the context of a rendering operation.
QPainter * painter()
Returns the destination QPainter for the render operation.
void setFlag(Qgis::RenderContextFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
static QgsRenderContext fromQPainter(QPainter *painter)
Creates a default render context given a pixel based QPainter destination.