QGIS API Documentation  3.20.0-Odense (decaadbb31)
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 }
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:416
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.
static QgsRenderContext fromQPainter(QPainter *painter)
Creates a default render context given a pixel based QPainter destination.