1 /***************************************************************************
2 qgseffectstack.cpp
3 -------------------
4 begin : December 2014
5 copyright : (C) 2014 Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************/
8
9 /***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18 #include "qgseffectstack.h"
19 #include "qgspainteffectregistry.h"
20 #include "qgsrendercontext.h"
21 #include "qgsapplication.h"
22 #include <QPicture>
23
QgsEffectStack(const QgsEffectStack & other)24 QgsEffectStack::QgsEffectStack( const QgsEffectStack &other )
25 : QgsPaintEffect( other )
26 {
27 //deep copy
28 for ( int i = 0; i < other.count(); ++i )
29 {
30 appendEffect( other.effect( i )->clone() );
31 }
32 }
33
QgsEffectStack(QgsEffectStack && other)34 QgsEffectStack::QgsEffectStack( QgsEffectStack &&other )
35 : QgsPaintEffect( other )
36 {
37 std::swap( mEffectList, other.mEffectList );
38 }
39
QgsEffectStack(const QgsPaintEffect & effect)40 QgsEffectStack::QgsEffectStack( const QgsPaintEffect &effect )
41 {
42 appendEffect( effect.clone() );
43 }
44
~QgsEffectStack()45 QgsEffectStack::~QgsEffectStack()
46 {
47 clearStack();
48 }
49
operator =(const QgsEffectStack & rhs)50 QgsEffectStack &QgsEffectStack::operator=( const QgsEffectStack &rhs )
51 {
52 if ( &rhs == this )
53 return *this;
54
55 //deep copy
56 clearStack();
57 for ( int i = 0; i < rhs.count(); ++i )
58 {
59 appendEffect( rhs.effect( i )->clone() );
60 }
61 mEnabled = rhs.enabled();
62 return *this;
63 }
64
operator =(QgsEffectStack && other)65 QgsEffectStack &QgsEffectStack::operator=( QgsEffectStack &&other )
66 {
67 std::swap( mEffectList, other.mEffectList );
68 mEnabled = other.enabled();
69 return *this;
70 }
71
create(const QVariantMap & map)72 QgsPaintEffect *QgsEffectStack::create( const QVariantMap &map )
73 {
74 QgsEffectStack *effect = new QgsEffectStack();
75 effect->readProperties( map );
76 return effect;
77 }
78
draw(QgsRenderContext & context)79 void QgsEffectStack::draw( QgsRenderContext &context )
80 {
81 QPainter *destPainter = context.painter();
82
83 //first, we build up a list of rendered effects
84 //we do this moving backwards through the stack, so that each effect's results
85 //becomes the source of the previous effect
86 QPicture *sourcePic = new QPicture( *source() );
87 QPicture *currentPic = sourcePic;
88 QList< QPicture * > results;
89 for ( int i = mEffectList.count() - 1; i >= 0; --i )
90 {
91 QgsPaintEffect *effect = mEffectList.at( i );
92 if ( !effect->enabled() )
93 {
94 continue;
95 }
96
97 QPicture *pic = nullptr;
98 if ( effect->type() == QLatin1String( "drawSource" ) )
99 {
100 //draw source is always the original source, regardless of previous effect results
101 pic = sourcePic;
102 }
103 else
104 {
105 pic = currentPic;
106 }
107
108 QPicture *resultPic = new QPicture();
109 QPainter p( resultPic );
110 context.setPainter( &p );
111 //effect stack has it's own handling of the QPicture DPI issue, so
112 //we disable QgsPaintEffect's internal workaround
113 effect->requiresQPainterDpiFix = false;
114 effect->render( *pic, context );
115 effect->requiresQPainterDpiFix = true;
116 p.end();
117
118 results << resultPic;
119 if ( mEffectList.at( i )->drawMode() != QgsPaintEffect::Render )
120 {
121 currentPic = resultPic;
122 }
123 }
124 delete sourcePic;
125 sourcePic = nullptr;
126
127 context.setPainter( destPainter );
128 //then, we render all the results in the opposite order
129 for ( int i = 0; i < mEffectList.count(); ++i )
130 {
131 if ( !mEffectList[i]->enabled() )
132 {
133 continue;
134 }
135
136 QPicture *pic = results.takeLast();
137 if ( mEffectList.at( i )->drawMode() != QgsPaintEffect::Modifier )
138 {
139 const QgsScopedQPainterState painterState( context.painter() );
140 fixQPictureDpi( context.painter() );
141 context.painter()->drawPicture( 0, 0, *pic );
142 }
143 delete pic;
144 }
145 }
146
clone() const147 QgsEffectStack *QgsEffectStack::clone() const
148 {
149 return new QgsEffectStack( *this );
150 }
151
saveProperties(QDomDocument & doc,QDomElement & element) const152 bool QgsEffectStack::saveProperties( QDomDocument &doc, QDomElement &element ) const
153 {
154 //effect stack needs to save all child effects
155 if ( element.isNull() )
156 {
157 return false;
158 }
159
160 QDomElement effectElement = doc.createElement( QStringLiteral( "effect" ) );
161 effectElement.setAttribute( QStringLiteral( "type" ), type() );
162 effectElement.setAttribute( QStringLiteral( "enabled" ), mEnabled );
163
164 bool ok = true;
165 for ( QgsPaintEffect *effect : mEffectList )
166 {
167 if ( effect )
168 ok = ok && effect->saveProperties( doc, effectElement );
169 }
170
171 element.appendChild( effectElement );
172 return ok;
173 }
174
readProperties(const QDomElement & element)175 bool QgsEffectStack::readProperties( const QDomElement &element )
176 {
177 if ( element.isNull() )
178 {
179 return false;
180 }
181
182 mEnabled = ( element.attribute( QStringLiteral( "enabled" ), QStringLiteral( "0" ) ) != QLatin1String( "0" ) );
183
184 clearStack();
185
186 //restore all child effects
187 const QDomNodeList childNodes = element.childNodes();
188 for ( int i = 0; i < childNodes.size(); ++i )
189 {
190 const QDomElement childElement = childNodes.at( i ).toElement();
191 QgsPaintEffect *effect = QgsApplication::paintEffectRegistry()->createEffect( childElement );
192 if ( effect )
193 mEffectList << effect;
194 }
195 return true;
196 }
197
properties() const198 QVariantMap QgsEffectStack::properties() const
199 {
200 QVariantMap props;
201 return props;
202 }
203
readProperties(const QVariantMap & props)204 void QgsEffectStack::readProperties( const QVariantMap &props )
205 {
206 Q_UNUSED( props )
207 }
208
clearStack()209 void QgsEffectStack::clearStack()
210 {
211 qDeleteAll( mEffectList );
212 mEffectList.clear();
213 }
214
appendEffect(QgsPaintEffect * effect)215 void QgsEffectStack::appendEffect( QgsPaintEffect *effect )
216 {
217 mEffectList.append( effect );
218 }
219
insertEffect(const int index,QgsPaintEffect * effect)220 bool QgsEffectStack::insertEffect( const int index, QgsPaintEffect *effect )
221 {
222 if ( index < 0 || index > mEffectList.count() )
223 return false;
224 if ( !effect )
225 return false;
226
227 mEffectList.insert( index, effect );
228 return true;
229 }
230
changeEffect(const int index,QgsPaintEffect * effect)231 bool QgsEffectStack::changeEffect( const int index, QgsPaintEffect *effect )
232 {
233 if ( index < 0 || index >= mEffectList.count() )
234 return false;
235 if ( !effect )
236 return false;
237
238 delete mEffectList.at( index );
239 mEffectList[index] = effect;
240 return true;
241 }
242
takeEffect(const int index)243 QgsPaintEffect *QgsEffectStack::takeEffect( const int index )
244 {
245 if ( index < 0 || index >= mEffectList.count() )
246 return nullptr;
247
248 return mEffectList.takeAt( index );
249 }
250
effectList()251 QList<QgsPaintEffect *> *QgsEffectStack::effectList()
252 {
253 return &mEffectList;
254 }
255
effect(int index) const256 QgsPaintEffect *QgsEffectStack::effect( int index ) const
257 {
258 if ( index >= 0 && index < mEffectList.count() )
259 {
260 return mEffectList.at( index );
261 }
262 else
263 {
264 return nullptr;
265 }
266 }
267