QGIS API Documentation  3.8.0-Zanzibar (11aff65)
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 
26 #include <QPicture>
27 #include <QPainter>
28 #include <QStandardItemModel>
29 #include <QStandardItem>
30 #include <QCheckBox>
31 #include <QToolButton>
32 
34 
35 static const int EFFECT_ITEM_TYPE = QStandardItem::UserType + 1;
36 
37 class EffectItem : public QStandardItem
38 {
39  public:
40  EffectItem( QgsPaintEffect *effect, QgsEffectStackPropertiesWidget *propertiesWidget )
41  {
42  setEffect( effect );
43  setCheckable( true );
44  mWidget = propertiesWidget;
45  }
46 
47  void setEffect( QgsPaintEffect *effect )
48  {
49  mEffect = effect;
50  emitDataChanged();
51  }
52 
53  int type() const override { return EFFECT_ITEM_TYPE; }
54 
55  QgsPaintEffect *effect()
56  {
57  return mEffect;
58  }
59 
60  QVariant data( int role ) const override
61  {
62  if ( role == Qt::DisplayRole || role == Qt::EditRole )
63  {
64  return QgsApplication::paintEffectRegistry()->effectMetadata( mEffect->type() )->visibleName();
65  }
66  if ( role == Qt::CheckStateRole )
67  {
68  return mEffect->enabled() ? Qt::Checked : Qt::Unchecked;
69  }
70  return QStandardItem::data( role );
71  }
72 
73  void setData( const QVariant &value, int role ) override
74  {
75  if ( role == Qt::CheckStateRole )
76  {
77  mEffect->setEnabled( value.toBool() );
78  mWidget->updatePreview();
79  }
80  else
81  {
82  QStandardItem::setData( value, role );
83  }
84  }
85 
86  protected:
87  QgsPaintEffect *mEffect = nullptr;
88  QgsEffectStackPropertiesWidget *mWidget = nullptr;
89 };
91 
92 //
93 // QgsEffectStackPropertiesWidget
94 //
95 
97  : QgsPanelWidget( parent )
98  , mStack( stack )
99 
100 {
101 
102 // TODO
103 #ifdef Q_OS_MAC
104  //setWindowModality( Qt::WindowModal );
105 #endif
106 
107  mPresentWidget = nullptr;
108 
109  setupUi( this );
110  this->layout()->setContentsMargins( 0, 0, 0, 0 );
111 
112  mEffectsList->setMaximumHeight( static_cast< int >( Qgis::UI_SCALE_FACTOR * fontMetrics().height() * 7 ) );
113  mEffectsList->setMinimumHeight( mEffectsList->maximumHeight() );
114  lblPreview->setMaximumWidth( mEffectsList->maximumHeight() );
115 
116  mAddButton->setIcon( QIcon( QgsApplication::iconPath( "symbologyAdd.svg" ) ) );
117  mRemoveButton->setIcon( QIcon( QgsApplication::iconPath( "symbologyRemove.svg" ) ) );
118  mUpButton->setIcon( QIcon( QgsApplication::iconPath( "mActionArrowUp.svg" ) ) );
119  mDownButton->setIcon( QIcon( QgsApplication::iconPath( "mActionArrowDown.svg" ) ) );
120 
121  mModel = new QStandardItemModel();
122  // Set the effect
123  mEffectsList->setModel( mModel );
124 
125  QItemSelectionModel *selModel = mEffectsList->selectionModel();
126  connect( selModel, &QItemSelectionModel::currentChanged, this, &QgsEffectStackPropertiesWidget::effectChanged );
127 
128  loadStack( stack );
129  updatePreview();
130 
131  connect( mUpButton, &QAbstractButton::clicked, this, &QgsEffectStackPropertiesWidget::moveEffectUp );
132  connect( mDownButton, &QAbstractButton::clicked, this, &QgsEffectStackPropertiesWidget::moveEffectDown );
133  connect( mAddButton, &QAbstractButton::clicked, this, &QgsEffectStackPropertiesWidget::addEffect );
134  connect( mRemoveButton, &QAbstractButton::clicked, this, &QgsEffectStackPropertiesWidget::removeEffect );
135 
136  updateUi();
137 
138  // set first selected effect as active item in the tree
139  int initialRow = 0;
140  for ( int i = 0; i < stack->count(); ++i )
141  {
142  // list shows effects in opposite order to stack
143  if ( stack->effect( stack->count() - i - 1 )->enabled() )
144  {
145  initialRow = i;
146  break;
147  }
148  }
149  QModelIndex newIndex = mEffectsList->model()->index( initialRow, 0 );
150  mEffectsList->setCurrentIndex( newIndex );
151 
152  setPanelTitle( tr( "Effects Properties" ) );
153 }
154 
156 {
157  delete mPreviewPicture;
158 }
159 
161 {
162  delete mPreviewPicture;
163  mPreviewPicture = new QPicture( picture );
164  updatePreview();
165 }
166 
168 {
169  if ( !stack )
170  {
171  return;
172  }
173 
174  EffectItem *parent = static_cast<EffectItem *>( mModel->invisibleRootItem() );
175 
176  int count = stack->count();
177  for ( int i = count - 1; i >= 0; i-- )
178  {
179  EffectItem *effectItem = new EffectItem( stack->effect( i ), this );
180  effectItem->setEditable( false );
181  parent->appendRow( effectItem );
182  }
183 }
184 
185 
187 {
188  mModel->clear();
189  loadStack( mStack );
190 }
191 
193 {
194  QModelIndex currentIdx = mEffectsList->currentIndex();
195  if ( !currentIdx.isValid() )
196  return;
197 
198  EffectItem *item = static_cast<EffectItem *>( mModel->itemFromIndex( currentIdx ) );
199 
200  QStandardItem *root = mModel->invisibleRootItem();
201  int rowCount = root->rowCount();
202  int currentRow = item ? item->row() : 0;
203 
204  mUpButton->setEnabled( currentRow > 0 );
205  mDownButton->setEnabled( currentRow < rowCount - 1 );
206  mRemoveButton->setEnabled( rowCount > 1 );
207 }
208 
210 {
211  QPainter painter;
212  QImage previewImage( 100, 100, QImage::Format_ARGB32 );
213  previewImage.fill( Qt::transparent );
214  painter.begin( &previewImage );
215  painter.setRenderHint( QPainter::Antialiasing );
216  QgsRenderContext context = QgsRenderContext::fromQPainter( &painter );
217  if ( !mPreviewPicture )
218  {
219  QPicture previewPic;
220  QPainter previewPicPainter;
221  previewPicPainter.begin( &previewPic );
222  previewPicPainter.setPen( Qt::red );
223  previewPicPainter.setBrush( QColor( 255, 100, 100, 255 ) );
224  previewPicPainter.drawEllipse( QPoint( 50, 50 ), 20, 20 );
225  previewPicPainter.end();
226  mStack->render( previewPic, context );
227  }
228  else
229  {
230  context.painter()->translate( 20, 20 );
231  mStack->render( *mPreviewPicture, context );
232  }
233  painter.end();
234 
235  lblPreview->setPixmap( QPixmap::fromImage( previewImage ) );
236  emit widgetChanged();
237 }
238 
240 {
241  QModelIndex idx = mEffectsList->currentIndex();
242  if ( !idx.isValid() )
243  return nullptr;
244 
245  EffectItem *item = static_cast<EffectItem *>( mModel->itemFromIndex( idx ) );
246  return item;
247 }
248 
250 {
251  updateUi();
252 
253  EffectItem *currentItem = currentEffectItem();
254  if ( !currentItem )
255  return;
256 
257  QgsPaintEffectPropertiesWidget *effectPropertiesWidget = new QgsPaintEffectPropertiesWidget( currentItem->effect() );
258  setWidget( effectPropertiesWidget );
259 
262 }
263 
265 {
266  int index = stackedWidget->addWidget( widget );
267  stackedWidget->setCurrentIndex( index );
268  if ( mPresentWidget )
269  {
270  stackedWidget->removeWidget( mPresentWidget );
271  QWidget *dummy = mPresentWidget;
272  mPresentWidget = widget;
273  delete dummy; // auto disconnects all signals
274  }
275 }
276 
278 {
279  QgsPaintEffect *newEffect = new QgsDrawSourceEffect();
280  mStack->insertEffect( 0, newEffect );
281 
282  EffectItem *newEffectItem = new EffectItem( newEffect, this );
283  mModel->invisibleRootItem()->insertRow( mStack->count() - 1, newEffectItem );
284 
285  mEffectsList->setCurrentIndex( mModel->indexFromItem( newEffectItem ) );
286  updateUi();
287  updatePreview();
288 }
289 
291 {
292  EffectItem *item = currentEffectItem();
293  int row = item->row();
294  QStandardItem *root = mModel->invisibleRootItem();
295 
296  int layerIdx = root->rowCount() - row - 1;
297  QgsPaintEffect *tmpEffect = mStack->takeEffect( layerIdx );
298 
299  mModel->invisibleRootItem()->removeRow( row );
300 
301  int newSelection = std::min( row, root->rowCount() - 1 );
302  QModelIndex newIdx = root->child( newSelection )->index();
303  mEffectsList->setCurrentIndex( newIdx );
304 
305  updateUi();
306  updatePreview();
307 
308  delete tmpEffect;
309 }
310 
312 {
313  moveEffectByOffset( + 1 );
314 }
315 
317 {
318  moveEffectByOffset( -1 );
319 }
320 
322 {
323  EffectItem *item = currentEffectItem();
324  if ( !item )
325  return;
326 
327  int row = item->row();
328 
329  QStandardItem *root = mModel->invisibleRootItem();
330 
331  int layerIdx = root->rowCount() - row - 1;
332  // switch effects
333  QgsPaintEffect *tmpEffect = mStack->takeEffect( layerIdx );
334  mStack->insertEffect( layerIdx - offset, tmpEffect );
335 
336  QList<QStandardItem *> toMove = root->takeRow( row );
337  root->insertRows( row + offset, toMove );
338 
339  QModelIndex newIdx = toMove[ 0 ]->index();
340  mEffectsList->setCurrentIndex( newIdx );
341 
342  updatePreview();
343  updateUi();
344 }
345 
347 {
348  EffectItem *item = currentEffectItem();
349  item->setEffect( newEffect );
350 
351  QStandardItem *root = mModel->invisibleRootItem();
352  int effectIdx = root->rowCount() - item->row() - 1;
353  mStack->changeEffect( effectIdx, newEffect );
354 
355  updatePreview();
356  // Important: This lets the effect to have its own effect properties widget
357  effectChanged();
358 }
359 
360 
361 //
362 // QgsEffectStackPropertiesDialog
363 //
364 
366  : QgsDialog( parent, f, QDialogButtonBox::Ok | QDialogButtonBox::Cancel )
367 
368 {
369  setWindowTitle( tr( "Effect Properties" ) );
371  layout()->addWidget( mPropertiesWidget );
372 }
373 
375 {
376  return mPropertiesWidget->stack();
377 }
378 
380 {
382 }
383 
384 //
385 // QgsEffectStackCompactWidget
386 //
387 
389  : QgsPanelWidget( parent )
390 
391 {
392  QHBoxLayout *layout = new QHBoxLayout();
393  layout->setContentsMargins( 0, 0, 0, 0 );
394  layout->setSpacing( 6 );
395  setLayout( layout );
396 
397  mEnabledCheckBox = new QCheckBox( this );
398  mEnabledCheckBox->setText( tr( "Draw effects" ) );
399  layout->addWidget( mEnabledCheckBox );
400 
401  mButton = new QToolButton( this );
402  mButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mIconPaintEffects.svg" ) ) );
403  mButton->setToolTip( tr( "Customize effects" ) );
404  layout->addWidget( mButton );
405 
406  setFocusPolicy( Qt::StrongFocus );
407  setFocusProxy( mEnabledCheckBox );
408 
409  connect( mButton, &QAbstractButton::clicked, this, &QgsEffectStackCompactWidget::showDialog );
410  connect( mEnabledCheckBox, &QAbstractButton::toggled, this, &QgsEffectStackCompactWidget::enableToggled );
411 
412  setPaintEffect( effect );
413 }
414 
416 {
417  delete mPreviewPicture;
418 }
419 
421 {
422  if ( !effect )
423  {
424  mEnabledCheckBox->setChecked( false );
425  mEnabledCheckBox->setEnabled( false );
426  mButton->setEnabled( false );
427  mStack = nullptr;
428  return;
429  }
430 
431  //is effect a stack?
432  QgsEffectStack *stack = dynamic_cast<QgsEffectStack *>( effect );
433  if ( !stack )
434  {
435  //not already a stack, so promote to stack
436  stack = new QgsEffectStack( *effect );
437  }
438 
439  mStack = stack;
440  mEnabledCheckBox->setChecked( mStack->enabled() );
441  mEnabledCheckBox->setEnabled( true );
442  mButton->setEnabled( mStack->enabled() );
443 }
444 
446 {
447  return mStack;
448 }
449 
450 void QgsEffectStackCompactWidget::setPreviewPicture( const QPicture &picture )
451 {
452  delete mPreviewPicture;
453  mPreviewPicture = new QPicture( picture );
454 }
455 
456 void QgsEffectStackCompactWidget::showDialog()
457 {
458  if ( !mStack )
459  return;
460 
461  QgsEffectStack *clone = static_cast<QgsEffectStack *>( mStack->clone() );
462  QgsEffectStackPropertiesWidget *widget = new QgsEffectStackPropertiesWidget( clone, nullptr );
463  if ( mPreviewPicture )
464  {
465  widget->setPreviewPicture( *mPreviewPicture );
466  }
467  connect( widget, &QgsPanelWidget::widgetChanged, this, &QgsEffectStackCompactWidget::updateEffectLive );
468  connect( widget, &QgsPanelWidget::panelAccepted, this, &QgsEffectStackCompactWidget::updateAcceptWidget );
469 
470  QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( qobject_cast< QWidget * >( parent() ) );
471  if ( panel && panel->dockMode() )
472  {
473  panel->openPanel( widget );
474  }
475  else
476  {
477  openPanel( widget );
478  }
479 }
480 
481 void QgsEffectStackCompactWidget::enableToggled( bool checked )
482 {
483  if ( !mStack )
484  {
485  return;
486  }
487 
488  mStack->setEnabled( checked );
489  mButton->setEnabled( checked );
490  emit changed();
491 }
492 
493 void QgsEffectStackCompactWidget::updateAcceptWidget( QgsPanelWidget *panel )
494 {
495  QgsEffectStackPropertiesWidget *widget = qobject_cast<QgsEffectStackPropertiesWidget *>( panel );
496  *mStack = *widget->stack();
497  emit changed();
498 // delete widget->stack();
499 }
500 
501 void QgsEffectStackCompactWidget::updateEffectLive()
502 {
503  QgsEffectStackPropertiesWidget *widget = qobject_cast<QgsEffectStackPropertiesWidget *>( sender() );
504  *mStack = *widget->stack();
505  emit changed();
506 }
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:139
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.
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.
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
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.