1 /*
2  *  Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18 
19 #include "kis_base_node.h"
20 #include <klocalizedstring.h>
21 
22 #include <kis_image.h>
23 #include <kis_icon.h>
24 #include <KoProperties.h>
25 #include <KoColorSpace.h>
26 #include <KoCompositeOpRegistry.h>
27 #include "kis_paint_device.h"
28 #include "kis_layer_properties_icons.h"
29 
30 #include "kis_scalar_keyframe_channel.h"
31 
32 struct Q_DECL_HIDDEN KisBaseNode::Private
33 {
34     QString compositeOp;
35     KoProperties properties;
36     KisBaseNode::Property hack_visible; //HACK
37     QUuid id;
38     QMap<QString, KisKeyframeChannel*> keyframeChannels;
39     QScopedPointer<KisScalarKeyframeChannel> opacityChannel;
40 
41     bool systemLocked {false};
42     bool collapsed {false};
43     bool supportsLodMoves {false};
44     bool animated {false};
45     bool useInTimeline {true};
46 
47     KisImageWSP image;
48 
PrivateKisBaseNode::Private49     Private(KisImageWSP image)
50         : id(QUuid::createUuid())
51         , image(image)
52     {
53     }
54 
PrivateKisBaseNode::Private55     Private(const Private &rhs)
56         : compositeOp(rhs.compositeOp),
57           id(QUuid::createUuid()),
58           collapsed(rhs.collapsed),
59           supportsLodMoves(rhs.supportsLodMoves),
60           animated(rhs.animated),
61           useInTimeline(rhs.useInTimeline),
62           image(rhs.image)
63     {
64         QMapIterator<QString, QVariant> iter = rhs.properties.propertyIterator();
65         while (iter.hasNext()) {
66             iter.next();
67             properties.setProperty(iter.key(), iter.value());
68         }
69     }
70 };
71 
KisBaseNode(KisImageWSP image)72 KisBaseNode::KisBaseNode(KisImageWSP image)
73     : m_d(new Private(image))
74 {
75     /**
76      * Be cautious! These two calls are vital to warm-up KoProperties.
77      * We use it and its QMap in a threaded environment. This is not
78      * officially supported by Qt, but our environment guarantees, that
79      * there will be the only writer and several readers. Whilst the
80      * value of the QMap is boolean and there are no implicit-sharing
81      * calls provocated, it is safe to work with it in such an
82      * environment.
83      */
84     setVisible(true, true);
85     setUserLocked(false);
86     setCollapsed(false);
87     setSupportsLodMoves(true);
88 
89     m_d->compositeOp = COMPOSITE_OVER;
90 }
91 
92 
KisBaseNode(const KisBaseNode & rhs)93 KisBaseNode::KisBaseNode(const KisBaseNode & rhs)
94     : QObject()
95     , KisShared()
96     , m_d(new Private(*rhs.m_d))
97 {
98     if (rhs.m_d->keyframeChannels.size() > 0) {
99         Q_FOREACH(QString key, rhs.m_d->keyframeChannels.keys()) {
100             KisKeyframeChannel* channel = rhs.m_d->keyframeChannels.value(key);
101             if (!channel) {
102                 continue;
103             }
104 
105             if (channel->inherits("KisScalarKeyframeChannel")) {
106                 KisScalarKeyframeChannel* pchannel = qobject_cast<KisScalarKeyframeChannel*>(channel);
107                 KIS_ASSERT_RECOVER(pchannel) { continue; }
108 
109                 KisScalarKeyframeChannel* channelNew = new KisScalarKeyframeChannel(*pchannel, nullptr);
110                 KIS_ASSERT(channelNew);
111                 m_d->keyframeChannels.insert(channelNew->id(), channelNew);
112 
113                 if (KoID(key) == KisKeyframeChannel::Opacity) {
114                     m_d->opacityChannel.reset(channelNew);
115                 }
116             }
117 
118         }
119     }
120 }
121 
~KisBaseNode()122 KisBaseNode::~KisBaseNode()
123 {
124     delete m_d;
125 }
126 
colorPickSourceDevice() const127 KisPaintDeviceSP KisBaseNode::colorPickSourceDevice() const
128 {
129     return projection();
130 }
131 
opacity() const132 quint8 KisBaseNode::opacity() const
133 {
134     if (m_d->opacityChannel) {
135         qreal value = m_d->opacityChannel->currentValue();
136 
137         if (!qIsNaN(value)) {
138             return value;
139         }
140     }
141 
142     return nodeProperties().intProperty("opacity", OPACITY_OPAQUE_U8);
143 }
144 
setOpacity(quint8 val)145 void KisBaseNode::setOpacity(quint8 val)
146 {
147     if (m_d->opacityChannel) {
148         KisKeyframeSP activeKeyframe = m_d->opacityChannel->currentlyActiveKeyframe();
149 
150         if (activeKeyframe) {
151             m_d->opacityChannel->setScalarValue(activeKeyframe, val);
152         }
153     }
154 
155     if (opacity() == val) return;
156 
157     setNodeProperty("opacity", val);
158 
159     baseNodeInvalidateAllFramesCallback();
160 }
161 
percentOpacity() const162 quint8 KisBaseNode::percentOpacity() const
163 {
164     return int(float(opacity() * 100) / 255 + 0.5);
165 }
166 
setPercentOpacity(quint8 val)167 void KisBaseNode::setPercentOpacity(quint8 val)
168 {
169     setOpacity(int(float(val * 255) / 100 + 0.5));
170 }
171 
compositeOpId() const172 const QString& KisBaseNode::compositeOpId() const
173 {
174     return m_d->compositeOp;
175 }
176 
setCompositeOpId(const QString & compositeOp)177 void KisBaseNode::setCompositeOpId(const QString& compositeOp)
178 {
179     if (m_d->compositeOp == compositeOp) return;
180 
181     m_d->compositeOp = compositeOp;
182     baseNodeChangedCallback();
183     baseNodeInvalidateAllFramesCallback();
184 }
185 
sectionModelProperties() const186 KisBaseNode::PropertyList KisBaseNode::sectionModelProperties() const
187 {
188     KisBaseNode::PropertyList l;
189     l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::visible, visible(), m_d->hack_visible.isInStasis, m_d->hack_visible.stateInStasis);
190     l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::locked, userLocked());
191     return l;
192 }
193 
setSectionModelProperties(const KisBaseNode::PropertyList & properties)194 void KisBaseNode::setSectionModelProperties(const KisBaseNode::PropertyList &properties)
195 {
196     setVisible(properties.at(0).state.toBool());
197     m_d->hack_visible = properties.at(0);
198     setUserLocked(properties.at(1).state.toBool());
199 }
200 
nodeProperties() const201 const KoProperties & KisBaseNode::nodeProperties() const
202 {
203     return m_d->properties;
204 }
205 
setNodeProperty(const QString & name,const QVariant & value)206 void KisBaseNode::setNodeProperty(const QString & name, const QVariant & value)
207 {
208     m_d->properties.setProperty(name, value);
209     baseNodeChangedCallback();
210 }
211 
mergeNodeProperties(const KoProperties & properties)212 void KisBaseNode::mergeNodeProperties(const KoProperties & properties)
213 {
214     QMapIterator<QString, QVariant> iter = properties.propertyIterator();
215     while (iter.hasNext()) {
216         iter.next();
217         m_d->properties.setProperty(iter.key(), iter.value());
218     }
219     baseNodeChangedCallback();
220     baseNodeInvalidateAllFramesCallback();
221 }
222 
check(const KoProperties & properties) const223 bool KisBaseNode::check(const KoProperties & properties) const
224 {
225     QMapIterator<QString, QVariant> iter = properties.propertyIterator();
226     while (iter.hasNext()) {
227         iter.next();
228         if (m_d->properties.contains(iter.key())) {
229             if (m_d->properties.value(iter.key()) != iter.value())
230                 return false;
231         }
232     }
233     return true;
234 }
235 
236 
createThumbnail(qint32 w,qint32 h,Qt::AspectRatioMode aspectRatioMode)237 QImage KisBaseNode::createThumbnail(qint32 w, qint32 h, Qt::AspectRatioMode aspectRatioMode)
238 {
239     Q_UNUSED(aspectRatioMode);
240 
241     try {
242         QImage image(w, h, QImage::Format_ARGB32);
243         image.fill(0);
244         return image;
245     } catch (const std::bad_alloc&) {
246         return QImage();
247     }
248 
249 }
250 
createThumbnailForFrame(qint32 w,qint32 h,int time,Qt::AspectRatioMode aspectRatioMode)251 QImage KisBaseNode::createThumbnailForFrame(qint32 w, qint32 h, int time, Qt::AspectRatioMode aspectRatioMode)
252 {
253     Q_UNUSED(time)
254     Q_UNUSED(aspectRatioMode);
255     return createThumbnail(w, h);
256 }
257 
visible(bool recursive) const258 bool KisBaseNode::visible(bool recursive) const
259 {
260     bool isVisible = m_d->properties.boolProperty(KisLayerPropertiesIcons::visible.id(), true);
261     KisBaseNodeSP parentNode = parentCallback();
262 
263     return recursive && isVisible && parentNode ?
264         parentNode->visible(recursive) : isVisible;
265 }
266 
setVisible(bool visible,bool loading)267 void KisBaseNode::setVisible(bool visible, bool loading)
268 {
269     const bool isVisible = m_d->properties.boolProperty(KisLayerPropertiesIcons::visible.id(), true);
270     if (!loading && isVisible == visible) return;
271 
272     m_d->properties.setProperty(KisLayerPropertiesIcons::visible.id(), visible);
273     notifyParentVisibilityChanged(visible);
274 
275     if (!loading) {
276         baseNodeChangedCallback();
277         baseNodeInvalidateAllFramesCallback();
278     }
279 }
280 
userLocked() const281 bool KisBaseNode::userLocked() const
282 {
283     return m_d->properties.boolProperty(KisLayerPropertiesIcons::locked.id(), false);
284 }
285 
belongsToIsolatedGroup() const286 bool KisBaseNode::belongsToIsolatedGroup() const
287 {
288     if (!m_d->image) {
289         return false;
290     }
291 
292     const KisBaseNode* element = this;
293 
294     while (element) {
295         if (element->isIsolatedRoot()) {
296             return true;
297         } else {
298             element = element->parentCallback().data();
299         }
300     }
301 
302     return false;
303 }
304 
isIsolatedRoot() const305 bool KisBaseNode::isIsolatedRoot() const
306 {
307     if (!m_d->image) {
308         return false;
309     }
310 
311     const KisBaseNode* isolatedRoot = m_d->image->isolatedModeRoot().data();
312 
313     return (this == isolatedRoot);
314 }
315 
setUserLocked(bool locked)316 void KisBaseNode::setUserLocked(bool locked)
317 {
318     const bool isLocked = m_d->properties.boolProperty(KisLayerPropertiesIcons::locked.id(), true);
319     if (isLocked == locked) return;
320 
321     m_d->properties.setProperty(KisLayerPropertiesIcons::locked.id(), locked);
322     baseNodeChangedCallback();
323 }
324 
isEditable(bool checkVisibility) const325 bool KisBaseNode::isEditable(bool checkVisibility) const
326 {
327     bool editable = true;
328     if (checkVisibility) {
329         editable = ((visible(false) || belongsToIsolatedGroup()) && !userLocked());
330     }
331     else {
332         editable = (!userLocked());
333     }
334 
335     if (editable) {
336         KisBaseNodeSP parentNode = parentCallback();
337         if (parentNode && parentNode != this) {
338             editable = parentNode->isEditable(checkVisibility);
339         }
340     }
341     return editable;
342 }
343 
hasEditablePaintDevice() const344 bool KisBaseNode::hasEditablePaintDevice() const
345 {
346     return paintDevice() && isEditable();
347 }
348 
setCollapsed(bool collapsed)349 void KisBaseNode::setCollapsed(bool collapsed)
350 {
351     const bool oldCollapsed = m_d->collapsed;
352 
353     m_d->collapsed = collapsed;
354 
355     if (oldCollapsed != collapsed) {
356         baseNodeCollapsedChangedCallback();
357     }
358 }
359 
collapsed() const360 bool KisBaseNode::collapsed() const
361 {
362     return m_d->collapsed;
363 }
364 
setColorLabelIndex(int index)365 void KisBaseNode::setColorLabelIndex(int index)
366 {
367     const int currentLabel = colorLabelIndex();
368 
369     if (currentLabel == index) return;
370 
371     m_d->properties.setProperty(KisLayerPropertiesIcons::colorLabelIndex.id(), index);
372     baseNodeChangedCallback();
373 }
374 
colorLabelIndex() const375 int KisBaseNode::colorLabelIndex() const
376 {
377     return m_d->properties.intProperty(KisLayerPropertiesIcons::colorLabelIndex.id(), 0);
378 }
379 
uuid() const380 QUuid KisBaseNode::uuid() const
381 {
382     return m_d->id;
383 }
384 
setUuid(const QUuid & id)385 void KisBaseNode::setUuid(const QUuid& id)
386 {
387     m_d->id = id;
388     baseNodeChangedCallback();
389 }
390 
supportsLodMoves() const391 bool KisBaseNode::supportsLodMoves() const
392 {
393     return m_d->supportsLodMoves;
394 }
395 
supportsLodPainting() const396 bool KisBaseNode::supportsLodPainting() const
397 {
398     return true;
399 }
400 
setImage(KisImageWSP image)401 void KisBaseNode::setImage(KisImageWSP image)
402 {
403     m_d->image = image;
404 }
405 
image() const406 KisImageWSP KisBaseNode::image() const
407 {
408     return m_d->image;
409 }
410 
isFakeNode() const411 bool KisBaseNode::isFakeNode() const
412 {
413     return false;
414 }
415 
setSupportsLodMoves(bool value)416 void KisBaseNode::setSupportsLodMoves(bool value)
417 {
418     m_d->supportsLodMoves = value;
419 }
420 
421 
keyframeChannels() const422 QMap<QString, KisKeyframeChannel*> KisBaseNode::keyframeChannels() const
423 {
424     return m_d->keyframeChannels;
425 }
426 
getKeyframeChannel(const QString & id) const427 KisKeyframeChannel * KisBaseNode::getKeyframeChannel(const QString &id) const
428 {
429     QMap<QString, KisKeyframeChannel*>::const_iterator i = m_d->keyframeChannels.constFind(id);
430     if (i == m_d->keyframeChannels.constEnd()) {
431         return 0;
432     }
433     return i.value();
434 }
435 
getKeyframeChannel(const QString & id,bool create)436 KisKeyframeChannel * KisBaseNode::getKeyframeChannel(const QString &id, bool create)
437 {
438     KisKeyframeChannel *channel = getKeyframeChannel(id);
439 
440     if (!channel && create) {
441         channel = requestKeyframeChannel(id);
442 
443         if (channel) {
444             addKeyframeChannel(channel);
445         }
446     }
447 
448     return channel;
449 }
450 
isAnimated() const451 bool KisBaseNode::isAnimated() const
452 {
453     return m_d->animated;
454 }
455 
enableAnimation()456 void KisBaseNode::enableAnimation()
457 {
458     m_d->animated = true;
459     baseNodeChangedCallback();
460 }
461 
useInTimeline() const462 bool KisBaseNode::useInTimeline() const
463 {
464     return m_d->useInTimeline;
465 }
466 
setUseInTimeline(bool value)467 void KisBaseNode::setUseInTimeline(bool value)
468 {
469     if (value == m_d->useInTimeline) return;
470 
471     m_d->useInTimeline = value;
472     baseNodeChangedCallback();
473 }
474 
addKeyframeChannel(KisKeyframeChannel * channel)475 void KisBaseNode::addKeyframeChannel(KisKeyframeChannel *channel)
476 {
477     m_d->keyframeChannels.insert(channel->id(), channel);
478     emit keyframeChannelAdded(channel);
479 }
480 
requestKeyframeChannel(const QString & id)481 KisKeyframeChannel *KisBaseNode::requestKeyframeChannel(const QString &id)
482 {
483     if (id == KisKeyframeChannel::Opacity.id()) {
484         Q_ASSERT(m_d->opacityChannel.isNull());
485 
486         KisPaintDeviceSP device = original();
487 
488         if (device) {
489             KisNode* node = dynamic_cast<KisNode*>(this);
490             KisScalarKeyframeChannel * channel = new KisScalarKeyframeChannel(
491                 KisKeyframeChannel::Opacity,
492                 0, 255,
493                 KisNodeWSP( node ),
494                 KisKeyframe::Linear
495             );
496 
497             m_d->opacityChannel.reset(channel);
498 
499             return channel;
500         }
501     }
502 
503     return 0;
504 }
505