QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
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  QModelIndex newIndex = mEffectsList->model()->index( initialRow, 0 );
151  mEffectsList->setCurrentIndex( newIndex );
152 
153  setPanelTitle( tr( "Effects Properties" ) );
154 }
155 
157 {
158  delete mPreviewPicture;
159 }
160 
162 {
163  delete mPreviewPicture;
164  mPreviewPicture = new QPicture( picture );
165  updatePreview();
166 }
167 
169 {
170  if ( !stack )
171  {
172  return;
173  }
174 
175  EffectItem *parent = static_cast<EffectItem *>( mModel->invisibleRootItem() );
176 
177  int count = stack->count();
178  for ( int i = count - 1; i >= 0; i-- )
179  {
180  EffectItem *effectItem = new EffectItem( stack->effect( i ), this );
181  effectItem->setEditable( false );
182  parent->appendRow( effectItem );
183  }
184 }
185 
186 
188 {
189  mModel->clear();
190  loadStack( mStack );
191 }
192 
194 {
195  QModelIndex currentIdx = mEffectsList->currentIndex();
196  if ( !currentIdx.isValid() )
197  return;
198 
199  EffectItem *item = static_cast<EffectItem *>( mModel->itemFromIndex( currentIdx ) );
200 
201  QStandardItem *root = mModel->invisibleRootItem();
202  int rowCount = root->rowCount();
203  int currentRow = item ? item->row() : 0;
204 
205  mUpButton->setEnabled( currentRow > 0 );
206  mDownButton->setEnabled( currentRow < rowCount - 1 );
207  mRemoveButton->setEnabled( rowCount > 1 );
208 }
209 
211 {
212  QPainter painter;
213  QImage previewImage( 100, 100, QImage::Format_ARGB32 );
214  previewImage.fill( Qt::transparent );
215  painter.begin( &previewImage );
216  painter.setRenderHint( QPainter::Antialiasing );
217  QgsRenderContext context = QgsRenderContext::fromQPainter( &painter );
218  if ( !mPreviewPicture )
219  {
220  QPicture previewPic;
221  QPainter previewPicPainter;
222  previewPicPainter.begin( &previewPic );
223  previewPicPainter.setPen( Qt::red );
224  previewPicPainter.setBrush( QColor( 255, 100, 100, 255 ) );
225  previewPicPainter.drawEllipse( QPoint( 50, 50 ), 20, 20 );
226  previewPicPainter.end();
227  mStack->render( previewPic, context );
228  }
229  else
230  {
231  context.painter()->translate( 20, 20 );
232  mStack->render( *mPreviewPicture, context );
233  }
234  painter.end();
235 
236  lblPreview->setPixmap( QPixmap::fromImage( previewImage ) );
237  emit widgetChanged();
238 }
239 
241 {
242  QModelIndex idx = mEffectsList->currentIndex();
243  if ( !idx.isValid() )
244  return nullptr;
245 
246  EffectItem *item = static_cast<EffectItem *>( mModel->itemFromIndex( idx ) );
247  return item;
248 }
249 
251 {
252  updateUi();
253 
254  EffectItem *currentItem = currentEffectItem();
255  if ( !currentItem )
256  return;
257 
258  QgsPaintEffectPropertiesWidget *effectPropertiesWidget = new QgsPaintEffectPropertiesWidget( currentItem->effect() );
259  setWidget( effectPropertiesWidget );
260 
263 }
264 
266 {
267  int index = stackedWidget->addWidget( widget );
268  stackedWidget->setCurrentIndex( index );
269  if ( mPresentWidget )
270  {
271  stackedWidget->removeWidget( mPresentWidget );
272  QWidget *dummy = mPresentWidget;
273  mPresentWidget = widget;
274  delete dummy; // auto disconnects all signals
275  }
276 }
277 
279 {
280  QgsPaintEffect *newEffect = new QgsDrawSourceEffect();
281  mStack->insertEffect( 0, newEffect );
282 
283  EffectItem *newEffectItem = new EffectItem( newEffect, this );
284  mModel->invisibleRootItem()->insertRow( mStack->count() - 1, newEffectItem );
285 
286  mEffectsList->setCurrentIndex( mModel->indexFromItem( newEffectItem ) );
287  updateUi();
288  updatePreview();
289 }
290 
292 {
293  EffectItem *item = currentEffectItem();
294  int row = item->row();
295  QStandardItem *root = mModel->invisibleRootItem();
296 
297  int layerIdx = root->rowCount() - row - 1;
298  QgsPaintEffect *tmpEffect = mStack->takeEffect( layerIdx );
299 
300  mModel->invisibleRootItem()->removeRow( row );
301 
302  int newSelection = std::min( row, root->rowCount() - 1 );
303  QModelIndex newIdx = root->child( newSelection )->index();
304  mEffectsList->setCurrentIndex( newIdx );
305 
306  updateUi();
307  updatePreview();
308 
309  delete tmpEffect;
310 }
311 
313 {
314  moveEffectByOffset( + 1 );
315 }
316 
318 {
319  moveEffectByOffset( -1 );
320 }
321 
323 {
324  EffectItem *item = currentEffectItem();
325  if ( !item )
326  return;
327 
328  int row = item->row();
329 
330  QStandardItem *root = mModel->invisibleRootItem();
331 
332  int layerIdx = root->rowCount() - row - 1;
333  // switch effects
334  QgsPaintEffect *tmpEffect = mStack->takeEffect( layerIdx );
335  mStack->insertEffect( layerIdx - offset, tmpEffect );
336 
337  QList<QStandardItem *> toMove = root->takeRow( row );
338  root->insertRows( row + offset, toMove );
339 
340  QModelIndex newIdx = toMove[ 0 ]->index();
341  mEffectsList->setCurrentIndex( newIdx );
342 
343  updatePreview();
344  updateUi();
345 }
346 
348 {
349  EffectItem *item = currentEffectItem();
350  item->setEffect( newEffect );
351 
352  QStandardItem *root = mModel->invisibleRootItem();
353  int effectIdx = root->rowCount() - item->row() - 1;
354  mStack->changeEffect( effectIdx, newEffect );
355 
356  updatePreview();
357  // Important: This lets the effect to have its own effect properties widget
358  effectChanged();
359 }
360 
361 
362 //
363 // QgsEffectStackPropertiesDialog
364 //
365 
367  : QgsDialog( parent, f, QDialogButtonBox::Cancel | QDialogButtonBox::Help | QDialogButtonBox::Ok )
368 
369 {
370  setWindowTitle( tr( "Effect Properties" ) );
372 
373  QDialogButtonBox *buttonBox = this->findChild<QDialogButtonBox *>( QString(), Qt::FindDirectChildrenOnly );
374  connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsEffectStackPropertiesDialog::showHelp );
375 
376  layout()->addWidget( mPropertiesWidget );
377 }
378 
380 {
381  return mPropertiesWidget->stack();
382 }
383 
385 {
387 }
388 
389 void QgsEffectStackPropertiesDialog::showHelp()
390 {
391  QgsHelp::openHelp( QStringLiteral( "working_with_vector/vector_properties.html#draw-effects" ) );
392 }
393 
394 
395 //
396 // QgsEffectStackCompactWidget
397 //
398 
400  : QgsPanelWidget( parent )
401 
402 {
403  QHBoxLayout *layout = new QHBoxLayout();
404  layout->setContentsMargins( 0, 0, 0, 0 );
405  layout->setSpacing( 6 );
406  setLayout( layout );
407 
408  mEnabledCheckBox = new QCheckBox( this );
409  mEnabledCheckBox->setText( tr( "Draw effects" ) );
410  layout->addWidget( mEnabledCheckBox );
411 
412  mButton = new QToolButton( this );
413  mButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mIconPaintEffects.svg" ) ) );
414  mButton->setToolTip( tr( "Customize effects" ) );
415  layout->addWidget( mButton );
416 
417  setFocusPolicy( Qt::StrongFocus );
418  setFocusProxy( mEnabledCheckBox );
419 
420  connect( mButton, &QAbstractButton::clicked, this, &QgsEffectStackCompactWidget::showDialog );
421  connect( mEnabledCheckBox, &QAbstractButton::toggled, this, &QgsEffectStackCompactWidget::enableToggled );
422 
423  setPaintEffect( effect );
424 }
425 
427 {
428  delete mPreviewPicture;
429 }
430 
432 {
433  if ( !effect )
434  {
435  mEnabledCheckBox->setChecked( false );
436  mEnabledCheckBox->setEnabled( false );
437  mButton->setEnabled( false );
438  mStack = nullptr;
439  return;
440  }
441 
442  //is effect a stack?
443  QgsEffectStack *stack = dynamic_cast<QgsEffectStack *>( effect );
444  if ( !stack )
445  {
446  //not already a stack, so promote to stack
447  stack = new QgsEffectStack( *effect );
448  }
449 
450  mStack = stack;
451  mEnabledCheckBox->setChecked( mStack->enabled() );
452  mEnabledCheckBox->setEnabled( true );
453  mButton->setEnabled( mStack->enabled() );
454 }
455 
457 {
458  return mStack;
459 }
460 
461 void QgsEffectStackCompactWidget::setPreviewPicture( const QPicture &picture )
462 {
463  delete mPreviewPicture;
464  mPreviewPicture = new QPicture( picture );
465 }
466 
467 void QgsEffectStackCompactWidget::showDialog()
468 {
469  if ( !mStack )
470  return;
471 
472  QgsEffectStack *clone = mStack->clone();
473  QgsEffectStackPropertiesWidget *widget = new QgsEffectStackPropertiesWidget( clone, nullptr );
474  if ( mPreviewPicture )
475  {
476  widget->setPreviewPicture( *mPreviewPicture );
477  }
478 
479  QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( qobject_cast< QWidget * >( parent() ) );
480  if ( panel && panel->dockMode() )
481  {
482  connect( widget, &QgsPanelWidget::widgetChanged, this, &QgsEffectStackCompactWidget::updateEffectLive );
483  connect( widget, &QgsPanelWidget::panelAccepted, this, &QgsEffectStackCompactWidget::updateAcceptWidget );
484  panel->openPanel( widget );
485  }
486  else
487  {
488  QgsEffectStackPropertiesDialog dlg( clone, this );
489  if ( dlg.exec() == QDialog::Accepted )
490  {
491  *mStack = *clone;
492  emit changed();
493  }
494  }
495 }
496 
497 void QgsEffectStackCompactWidget::enableToggled( bool checked )
498 {
499  if ( !mStack )
500  {
501  return;
502  }
503 
504  mStack->setEnabled( checked );
505  mButton->setEnabled( checked );
506  emit changed();
507 }
508 
509 void QgsEffectStackCompactWidget::updateAcceptWidget( QgsPanelWidget *panel )
510 {
511  QgsEffectStackPropertiesWidget *widget = qobject_cast<QgsEffectStackPropertiesWidget *>( panel );
512  *mStack = *widget->stack();
513  emit changed();
514 // delete widget->stack();
515 }
516 
517 void QgsEffectStackCompactWidget::updateEffectLive()
518 {
519  QgsEffectStackPropertiesWidget *widget = qobject_cast<QgsEffectStackPropertiesWidget *>( sender() );
520  *mStack = *widget->stack();
521  emit changed();
522 }
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 addEffect()
Adds a new effect to the stack.
EffectItem * currentEffectItem()
Returns the currently selected effect within the stack.
bool dockMode()
Returns the dock mode state.
void setPaintEffect(QgsPaintEffect *effect)
Sets paint effect attached to the widget,.
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:154
bool insertEffect(int index, QgsPaintEffect *effect)
Inserts an effect at a specified index within the stack.
static QString iconPath(const QString &iconFile)
Returns path to the desired icon file.
A dialog for modifying the properties of a QgsEffectStack, including adding and reordering effects wi...
void changeEffect(QgsPaintEffect *effect)
Emitted when paint effect type changes.
Base class for visual effects which can be applied to QPicture drawings.
A generic dialog with layout and button box.
Definition: qgsdialog.h:33
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
Base class for any widget that can be shown as a inline panel.
void updatePreview()
Updates the effect preview icon.
void panelAccepted(QgsPanelWidget *panel)
Emitted when the panel is accepted by the user.
QgsEffectStackCompactWidget(QWidget *parent=nullptr, QgsPaintEffect *effect=nullptr)
QgsEffectStackCompactWidget constructor.
static QgsPaintEffectRegistry * paintEffectRegistry()
Returns the application&#39;s paint effect registry, used for managing paint effects. ...
A widget for modifying the properties of a QgsEffectStack, including adding and reordering effects wi...
void changeEffect(QgsPaintEffect *newEffect)
Updates the effect stack when the currently selected effect changes properties.
QDialogButtonBox * buttonBox()
Returns the button box.
Definition: qgsdialog.h:48
void setEnabled(bool enabled)
Sets whether the effect is enabled.
int count() const
Returns count of effects contained by the stack.
void setPreviewPicture(const QPicture &picture)
Sets the picture to use for effect previews for the dialog.
static QgsPanelWidget * findParentPanel(QWidget *widget)
Traces through the parents of a widget to find if it is contained within a QgsPanelWidget widget...
QgsPaintEffect * effect(int index) const
Returns a pointer to the effect at a specified index within the stack.
QgsEffectStack * stack()
Returns effect stack attached to the widget.
QgsPaintEffect * paintEffect() const
Returns paint effect attached to the widget.
static QgsRenderContext fromQPainter(QPainter *painter)
Creates a default render context given a pixel based QPainter destination.
A widget which modifies the properties of a QgsPaintEffect.
QgsEffectStackPropertiesWidget(QgsEffectStack *stack, QWidget *parent=nullptr)
QgsEffectStackPropertiesWidget constructor.
A paint effect which consists of a stack of other chained paint effects.
QgsEffectStack * clone() const override
Duplicates an effect by creating a deep copy of the effect.
bool enabled() const
Returns whether the effect is enabled.
void setPreviewPicture(const QPicture &picture)
Sets the picture to use for effect previews for the dialog.
void loadStack()
Refreshes the widget to reflect the current state of the stack.
void widgetChanged()
Emitted when the widget state changes.
QgsEffectStackPropertiesDialog(QgsEffectStack *stack, QWidget *parent=nullptr, Qt::WindowFlags f=nullptr)
QgsEffectStackPropertiesDialog constructor.
void setPreviewPicture(const QPicture &picture)
Sets the picture to use for effect previews for the dialog.
QgsEffectStack * stack()
Returns effect stack attached to the dialog.
QgsPaintEffectAbstractMetadata * effectMetadata(const QString &name) const
Returns the metadata for a specific effect.
void updateUi()
Enables or disables widgets depending on the selected effect within the stack.
Contains information about the context of a rendering operation.
QPainter * painter()
Returns the destination QPainter for the render operation.
void changed()
Emitted when the paint effect properties change.
void changed()
Emitted when paint effect properties changes.
void moveEffectUp()
Moves the currently selected effect up in the stack.
QVBoxLayout * layout()
Returns the central layout. Widgets added to it must have this dialog as parent.
Definition: qgsdialog.h:46
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Definition: qgshelp.cpp:36
void setWidget(QWidget *widget)
Sets the effect properties widget.
QgsEffectStackPropertiesWidget * mPropertiesWidget
virtual void render(QPicture &picture, QgsRenderContext &context)
Renders a picture using the effect.
QgsPaintEffect * takeEffect(int index)
Removes an effect from the stack and returns a pointer to it.
void effectChanged()
Updates the widget when the selected effect changes type.
void moveEffectDown()
Moves the currently selected effect down in the stack.
A paint effect which draws the source picture with minor or no alterations.
void setPanelTitle(const QString &panelTitle)
Set the title of the panel when shown in the interface.
bool changeEffect(int index, QgsPaintEffect *effect)
Replaces the effect at a specified position within the stack.
void removeEffect()
Removes the currently selected effect from the stack.
void moveEffectByOffset(int offset)
Moves the currently selected effect within the stack by a specified offset.