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