QGIS API Documentation 3.31.0-Master (9f23a2c1dc)
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
18#include "qgspainteffect.h"
19#include "qgseffectstack.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
36static const int EFFECT_ITEM_TYPE = QStandardItem::UserType + 1;
37
38class 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 {
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 );
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;
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 );
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();
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();
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
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
354 // Important: This lets the effect to have its own effect properties widget
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
386void 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
456{
457 mPreviewPicture = picture;
458}
459
460void 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 {
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
489void 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
501void 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
509void 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:3664
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
QDialogButtonBox * buttonBox()
Returns the button box.
Definition: qgsdialog.h:48
QVBoxLayout * layout()
Returns the central layout. Widgets added to it must have this dialog as parent.
Definition: qgsdialog.h:46
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.
QgsEffectStack * stack()
Returns effect stack attached to the widget.
~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.
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:38
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.