QGIS API Documentation  3.12.1-BucureČ™ti (121cc00ff0)
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 
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  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  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  int rowCount = root->rowCount();
199  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 );
214  if ( mPreviewPicture.isNull() )
215  {
216  QPicture previewPic;
217  QPainter previewPicPainter;
218  previewPicPainter.begin( &previewPic );
219  previewPicPainter.setPen( Qt::red );
220  previewPicPainter.setBrush( QColor( 255, 100, 100, 255 ) );
221  previewPicPainter.drawEllipse( QPoint( 50, 50 ), 20, 20 );
222  previewPicPainter.end();
223  mStack->render( previewPic, context );
224  }
225  else
226  {
227  context.painter()->translate( 20, 20 );
228  mStack->render( mPreviewPicture, context );
229  }
230  painter.end();
231 
232  lblPreview->setPixmap( QPixmap::fromImage( previewImage ) );
233  emit widgetChanged();
234 }
235 
237 {
238  QModelIndex idx = mEffectsList->currentIndex();
239  if ( !idx.isValid() )
240  return nullptr;
241 
242  EffectItem *item = static_cast<EffectItem *>( mModel->itemFromIndex( idx ) );
243  return item;
244 }
245 
247 {
248  updateUi();
249 
250  EffectItem *currentItem = currentEffectItem();
251  if ( !currentItem )
252  return;
253 
254  QgsPaintEffectPropertiesWidget *effectPropertiesWidget = new QgsPaintEffectPropertiesWidget( currentItem->effect() );
255  setWidget( effectPropertiesWidget );
256 
259 }
260 
262 {
263  int index = stackedWidget->addWidget( widget );
264  stackedWidget->setCurrentIndex( index );
265  if ( mPresentWidget )
266  {
267  stackedWidget->removeWidget( mPresentWidget );
268  QWidget *dummy = mPresentWidget;
269  mPresentWidget = widget;
270  delete dummy; // auto disconnects all signals
271  }
272 }
273 
275 {
276  QgsPaintEffect *newEffect = new QgsDrawSourceEffect();
277  mStack->insertEffect( 0, newEffect );
278 
279  EffectItem *newEffectItem = new EffectItem( newEffect, this );
280  mModel->invisibleRootItem()->insertRow( mStack->count() - 1, newEffectItem );
281 
282  mEffectsList->setCurrentIndex( mModel->indexFromItem( newEffectItem ) );
283  updateUi();
284  updatePreview();
285 }
286 
288 {
289  EffectItem *item = currentEffectItem();
290  int row = item->row();
291  QStandardItem *root = mModel->invisibleRootItem();
292 
293  int layerIdx = root->rowCount() - row - 1;
294  QgsPaintEffect *tmpEffect = mStack->takeEffect( layerIdx );
295 
296  mModel->invisibleRootItem()->removeRow( row );
297 
298  int newSelection = std::min( row, root->rowCount() - 1 );
299  QModelIndex newIdx = root->child( newSelection )->index();
300  mEffectsList->setCurrentIndex( newIdx );
301 
302  updateUi();
303  updatePreview();
304 
305  delete tmpEffect;
306 }
307 
309 {
310  moveEffectByOffset( + 1 );
311 }
312 
314 {
315  moveEffectByOffset( -1 );
316 }
317 
319 {
320  EffectItem *item = currentEffectItem();
321  if ( !item )
322  return;
323 
324  int row = item->row();
325 
326  QStandardItem *root = mModel->invisibleRootItem();
327 
328  int layerIdx = root->rowCount() - row - 1;
329  // switch effects
330  QgsPaintEffect *tmpEffect = mStack->takeEffect( layerIdx );
331  mStack->insertEffect( layerIdx - offset, tmpEffect );
332 
333  QList<QStandardItem *> toMove = root->takeRow( row );
334  root->insertRows( row + offset, toMove );
335 
336  QModelIndex newIdx = toMove[ 0 ]->index();
337  mEffectsList->setCurrentIndex( newIdx );
338 
339  updatePreview();
340  updateUi();
341 }
342 
344 {
345  EffectItem *item = currentEffectItem();
346  item->setEffect( newEffect );
347 
348  QStandardItem *root = mModel->invisibleRootItem();
349  int effectIdx = root->rowCount() - item->row() - 1;
350  mStack->changeEffect( effectIdx, newEffect );
351 
352  updatePreview();
353  // Important: This lets the effect to have its own effect properties widget
354  effectChanged();
355 }
356 
357 
358 //
359 // QgsEffectStackPropertiesDialog
360 //
361 
363  : QgsDialog( parent, f, QDialogButtonBox::Cancel | QDialogButtonBox::Help | QDialogButtonBox::Ok )
364 
365 {
366  setWindowTitle( tr( "Effect Properties" ) );
368 
369  QDialogButtonBox *buttonBox = this->findChild<QDialogButtonBox *>( QString(), Qt::FindDirectChildrenOnly );
370  connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsEffectStackPropertiesDialog::showHelp );
371 
372  layout()->addWidget( mPropertiesWidget );
373 }
374 
376 {
377  return mPropertiesWidget->stack();
378 }
379 
381 {
383 }
384 
385 void QgsEffectStackPropertiesDialog::showHelp()
386 {
387  QgsHelp::openHelp( QStringLiteral( "working_with_vector/vector_properties.html#draw-effects" ) );
388 }
389 
390 
391 //
392 // QgsEffectStackCompactWidget
393 //
394 
396  : QgsPanelWidget( parent )
397 
398 {
399  QHBoxLayout *layout = new QHBoxLayout();
400  layout->setContentsMargins( 0, 0, 0, 0 );
401  layout->setSpacing( 6 );
402  setLayout( layout );
403 
404  mEnabledCheckBox = new QCheckBox( this );
405  mEnabledCheckBox->setText( tr( "Draw effects" ) );
406  layout->addWidget( mEnabledCheckBox );
407 
408  mButton = new QToolButton( this );
409  mButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mIconPaintEffects.svg" ) ) );
410  mButton->setToolTip( tr( "Customize effects" ) );
411  layout->addWidget( mButton );
412 
413  setFocusPolicy( Qt::StrongFocus );
414  setFocusProxy( mEnabledCheckBox );
415 
416  connect( mButton, &QAbstractButton::clicked, this, &QgsEffectStackCompactWidget::showDialog );
417  connect( mEnabledCheckBox, &QAbstractButton::toggled, this, &QgsEffectStackCompactWidget::enableToggled );
418 
419  setPaintEffect( effect );
420 }
421 
423 
425 {
426  if ( !effect )
427  {
428  mEnabledCheckBox->setChecked( false );
429  mEnabledCheckBox->setEnabled( false );
430  mButton->setEnabled( false );
431  mStack = nullptr;
432  return;
433  }
434 
435  //is effect a stack?
436  QgsEffectStack *stack = dynamic_cast<QgsEffectStack *>( effect );
437  if ( !stack )
438  {
439  //not already a stack, so promote to stack
440  stack = new QgsEffectStack( *effect );
441  }
442 
443  mStack = stack;
444  mEnabledCheckBox->setChecked( mStack->enabled() );
445  mEnabledCheckBox->setEnabled( true );
446  mButton->setEnabled( mStack->enabled() );
447 }
448 
450 {
451  return mStack;
452 }
453 
454 void QgsEffectStackCompactWidget::setPreviewPicture( const QPicture &picture )
455 {
456  mPreviewPicture = picture;
457 }
458 
459 void QgsEffectStackCompactWidget::showDialog()
460 {
461  if ( !mStack )
462  return;
463 
464  QgsEffectStack *clone = mStack->clone();
465  QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( qobject_cast< QWidget * >( parent() ) );
466  if ( panel && panel->dockMode() )
467  {
468  QgsEffectStackPropertiesWidget *widget = new QgsEffectStackPropertiesWidget( clone, nullptr );
469  widget->setPreviewPicture( mPreviewPicture );
470 
471  connect( widget, &QgsPanelWidget::widgetChanged, this, &QgsEffectStackCompactWidget::updateEffectLive );
472  connect( widget, &QgsPanelWidget::panelAccepted, this, &QgsEffectStackCompactWidget::updateAcceptWidget );
473  panel->openPanel( widget );
474  }
475  else
476  {
477  QgsEffectStackPropertiesDialog dlg( clone, this );
478  dlg.setPreviewPicture( mPreviewPicture );
479 
480  if ( dlg.exec() == QDialog::Accepted )
481  {
482  *mStack = *clone;
483  emit changed();
484  }
485  }
486 }
487 
488 void QgsEffectStackCompactWidget::enableToggled( bool checked )
489 {
490  if ( !mStack )
491  {
492  return;
493  }
494 
495  mStack->setEnabled( checked );
496  mButton->setEnabled( checked );
497  emit changed();
498 }
499 
500 void QgsEffectStackCompactWidget::updateAcceptWidget( QgsPanelWidget *panel )
501 {
502  QgsEffectStackPropertiesWidget *widget = qobject_cast<QgsEffectStackPropertiesWidget *>( panel );
503  *mStack = *widget->stack();
504  emit changed();
505 // delete widget->stack();
506 }
507 
508 void QgsEffectStackCompactWidget::updateEffectLive()
509 {
510  QgsEffectStackPropertiesWidget *widget = qobject_cast<QgsEffectStackPropertiesWidget *>( sender() );
511  *mStack = *widget->stack();
512  emit changed();
513 }
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:182
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.
~QgsEffectStackCompactWidget() override
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.
~QgsEffectStackPropertiesWidget() override
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.