diff --git a/krita/krita4.xmlgui b/krita/krita4.xmlgui
index 11cc07a646..b79131225a 100644
--- a/krita/krita4.xmlgui
+++ b/krita/krita4.xmlgui
@@ -1,407 +1,408 @@
&View
&Canvas
&Snap To
&Image
&Rotate
&Layer
New
+
&Import/Export
Import
&Convert
&Select
&Group
&Transform
&Rotate
Transform &All Layers
&Rotate
S&plit
S&plit Alpha
&Select
Select &Opaque
Filte&r
&Tools
Scripts
Setti&ngs
&Help
File
Brushes and Stuff
diff --git a/libs/image/kis_convolution_painter.cc b/libs/image/kis_convolution_painter.cc
index 2d74dcefbe..e4290b6542 100644
--- a/libs/image/kis_convolution_painter.cc
+++ b/libs/image/kis_convolution_painter.cc
@@ -1,185 +1,185 @@
/*
* Copyright (c) 2005 Cyrille Berger
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_convolution_painter.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kis_convolution_kernel.h"
#include "kis_global.h"
#include "kis_image.h"
#include "kis_layer.h"
#include "kis_paint_device.h"
#include "kis_painter.h"
#include "KoColorSpace.h"
#include
#include "kis_types.h"
#include "kis_selection.h"
#include "kis_convolution_worker.h"
#include "kis_convolution_worker_spatial.h"
#include "config_convolution.h"
#ifdef HAVE_FFTW3
#include "kis_convolution_worker_fft.h"
#endif
bool KisConvolutionPainter::useFFTImplemenation(const KisConvolutionKernelSP kernel) const
{
bool result = false;
#ifdef HAVE_FFTW3
#define THRESHOLD_SIZE 5
result =
m_enginePreference == FFTW ||
(m_enginePreference == NONE &&
(kernel->width() > THRESHOLD_SIZE ||
kernel->height() > THRESHOLD_SIZE));
#else
Q_UNUSED(kernel);
#endif
return result;
}
template
KisConvolutionWorker* KisConvolutionPainter::createWorker(const KisConvolutionKernelSP kernel,
KisPainter *painter,
KoUpdater *progress)
{
KisConvolutionWorker *worker;
#ifdef HAVE_FFTW3
if (useFFTImplemenation(kernel)) {
worker = new KisConvolutionWorkerFFT(painter, progress);
} else {
worker = new KisConvolutionWorkerSpatial(painter, progress);
}
#else
Q_UNUSED(kernel);
worker = new KisConvolutionWorkerSpatial(painter, progress);
#endif
return worker;
}
bool KisConvolutionPainter::supportsFFTW()
{
#ifdef HAVE_FFTW3
return true;
#else
return false;
#endif
}
KisConvolutionPainter::KisConvolutionPainter()
: KisPainter(),
m_enginePreference(NONE)
{
}
KisConvolutionPainter::KisConvolutionPainter(KisPaintDeviceSP device)
: KisPainter(device),
m_enginePreference(NONE)
{
}
KisConvolutionPainter::KisConvolutionPainter(KisPaintDeviceSP device, KisSelectionSP selection)
: KisPainter(device, selection),
m_enginePreference(NONE)
{
}
KisConvolutionPainter::KisConvolutionPainter(KisPaintDeviceSP device, TestingEnginePreference enginePreference)
: KisPainter(device),
m_enginePreference(enginePreference)
{
}
void KisConvolutionPainter::applyMatrix(const KisConvolutionKernelSP kernel, const KisPaintDeviceSP src, QPoint srcPos, QPoint dstPos, QSize areaSize, KisConvolutionBorderOp borderOp)
{
/**
* Force BORDER_IGNORE op for the wraparound mode,
* because the paint device has its own special
* iterators, which do everything for us.
*/
if (src->defaultBounds()->wrapAroundMode()) {
borderOp = BORDER_IGNORE;
}
// Determine whether we convolve border pixels, or not.
switch (borderOp) {
case BORDER_REPEAT: {
- const QRect boundsRect = src->exactBounds();
+ const QRect boundsRect = src->defaultBounds()->bounds();
const QRect requestedRect = QRect(srcPos, areaSize);
QRect dataRect = requestedRect | boundsRect;
/**
* FIXME: Implementation can return empty destination device
* on faults and has no way to report this. This will cause a crash
* on sequential convolutions inside iteratiors.
*
* o implementation should do it's work or assert otherwise
* (or report the issue somehow)
* o check other cases of the switch for the vulnerability
*/
if(dataRect.isValid()) {
KisConvolutionWorker *worker;
worker = createWorker(kernel, this, progressUpdater());
worker->execute(kernel, src, srcPos, dstPos, areaSize, dataRect);
delete worker;
}
break;
}
case BORDER_IGNORE:
default: {
KisConvolutionWorker *worker;
worker = createWorker(kernel, this, progressUpdater());
worker->execute(kernel, src, srcPos, dstPos, areaSize, QRect());
delete worker;
}
}
}
bool KisConvolutionPainter::needsTransaction(const KisConvolutionKernelSP kernel) const
{
return !useFFTImplemenation(kernel);
}
diff --git a/libs/image/kis_layer_utils.cpp b/libs/image/kis_layer_utils.cpp
index ec1d49d91c..8fda4e81c5 100644
--- a/libs/image/kis_layer_utils.cpp
+++ b/libs/image/kis_layer_utils.cpp
@@ -1,1571 +1,1595 @@
/*
* Copyright (c) 2015 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_layer_utils.h"
#include
#include
#include
#include
#include "kis_painter.h"
#include "kis_image.h"
#include "kis_node.h"
#include "kis_layer.h"
#include "kis_paint_layer.h"
#include "kis_clone_layer.h"
#include "kis_group_layer.h"
#include "kis_selection.h"
#include "kis_selection_mask.h"
#include "kis_meta_data_merge_strategy.h"
#include
#include "commands/kis_image_layer_add_command.h"
#include "commands/kis_image_layer_remove_command.h"
#include "commands/kis_image_layer_move_command.h"
#include "commands/kis_image_change_layers_command.h"
#include "commands_new/kis_activate_selection_mask_command.h"
#include "commands/kis_image_change_visibility_command.h"
#include "kis_abstract_projection_plane.h"
#include "kis_processing_applicator.h"
#include "kis_image_animation_interface.h"
#include "kis_keyframe_channel.h"
#include "kis_command_utils.h"
#include "commands_new/kis_change_projection_color_command.h"
#include "kis_layer_properties_icons.h"
#include "lazybrush/kis_colorize_mask.h"
#include "commands/kis_node_property_list_command.h"
#include "commands/kis_node_compositeop_command.h"
#include
#include "krita_utils.h"
#include "kis_image_signal_router.h"
namespace KisLayerUtils {
void fetchSelectionMasks(KisNodeList mergedNodes, QVector &selectionMasks)
{
foreach (KisNodeSP node, mergedNodes) {
Q_FOREACH(KisNodeSP child, node->childNodes(QStringList("KisSelectionMask"), KoProperties())) {
KisSelectionMaskSP mask = qobject_cast(child.data());
if (mask) {
selectionMasks.append(mask);
}
}
}
}
struct MergeDownInfoBase {
MergeDownInfoBase(KisImageSP _image)
: image(_image),
storage(new SwitchFrameCommand::SharedStorage())
{
}
virtual ~MergeDownInfoBase() {}
KisImageWSP image;
QVector selectionMasks;
KisNodeSP dstNode;
SwitchFrameCommand::SharedStorageSP storage;
QSet frames;
bool useInTimeline = false;
bool enableOnionSkins = false;
virtual KisNodeList allSrcNodes() = 0;
KisLayerSP dstLayer() {
return qobject_cast(dstNode.data());
}
};
struct MergeDownInfo : public MergeDownInfoBase {
MergeDownInfo(KisImageSP _image,
KisLayerSP _prevLayer,
KisLayerSP _currLayer)
: MergeDownInfoBase(_image),
prevLayer(_prevLayer),
currLayer(_currLayer)
{
frames =
fetchLayerFramesRecursive(prevLayer) |
fetchLayerFramesRecursive(currLayer);
useInTimeline = prevLayer->useInTimeline() || currLayer->useInTimeline();
const KisPaintLayer *paintLayer = qobject_cast(currLayer.data());
if (paintLayer) enableOnionSkins |= paintLayer->onionSkinEnabled();
paintLayer = qobject_cast(prevLayer.data());
if (paintLayer) enableOnionSkins |= paintLayer->onionSkinEnabled();
}
KisLayerSP prevLayer;
KisLayerSP currLayer;
KisNodeList allSrcNodes() override {
KisNodeList mergedNodes;
mergedNodes << currLayer;
mergedNodes << prevLayer;
return mergedNodes;
}
};
struct MergeMultipleInfo : public MergeDownInfoBase {
MergeMultipleInfo(KisImageSP _image,
KisNodeList _mergedNodes)
: MergeDownInfoBase(_image),
mergedNodes(_mergedNodes)
{
foreach (KisNodeSP node, mergedNodes) {
frames |= fetchLayerFramesRecursive(node);
useInTimeline |= node->useInTimeline();
const KisPaintLayer *paintLayer = qobject_cast(node.data());
if (paintLayer) {
enableOnionSkins |= paintLayer->onionSkinEnabled();
}
}
}
KisNodeList mergedNodes;
bool nodesCompositingVaries = false;
KisNodeList allSrcNodes() override {
return mergedNodes;
}
};
typedef QSharedPointer MergeDownInfoBaseSP;
typedef QSharedPointer MergeDownInfoSP;
typedef QSharedPointer MergeMultipleInfoSP;
struct FillSelectionMasks : public KUndo2Command {
FillSelectionMasks(MergeDownInfoBaseSP info) : m_info(info) {}
void redo() override {
fetchSelectionMasks(m_info->allSrcNodes(), m_info->selectionMasks);
}
private:
MergeDownInfoBaseSP m_info;
};
struct DisableColorizeKeyStrokes : public KisCommandUtils::AggregateCommand {
DisableColorizeKeyStrokes(MergeDownInfoBaseSP info) : m_info(info) {}
void populateChildCommands() override {
Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) {
recursiveApplyNodes(node,
[this] (KisNodeSP node) {
if (dynamic_cast(node.data()) &&
KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true).toBool()) {
KisBaseNode::PropertyList props = node->sectionModelProperties();
KisLayerPropertiesIcons::setNodeProperty(&props,
KisLayerPropertiesIcons::colorizeEditKeyStrokes,
false);
addCommand(new KisNodePropertyListCommand(node, props));
}
});
}
}
private:
MergeDownInfoBaseSP m_info;
};
struct DisableOnionSkins : public KisCommandUtils::AggregateCommand {
DisableOnionSkins(MergeDownInfoBaseSP info) : m_info(info) {}
void populateChildCommands() override {
Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) {
recursiveApplyNodes(node,
[this] (KisNodeSP node) {
if (KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::onionSkins, false).toBool()) {
KisBaseNode::PropertyList props = node->sectionModelProperties();
KisLayerPropertiesIcons::setNodeProperty(&props,
KisLayerPropertiesIcons::onionSkins,
false);
addCommand(new KisNodePropertyListCommand(node, props));
}
});
}
}
private:
MergeDownInfoBaseSP m_info;
};
struct DisableExtraCompositing : public KisCommandUtils::AggregateCommand {
DisableExtraCompositing(MergeMultipleInfoSP info) : m_info(info) {}
void populateChildCommands() override {
/**
* We disable extra compositing only in case all the layers have
* the same compositing properties, therefore, we can just sum them using
* Normal blend mode
*/
if (m_info->nodesCompositingVaries) return;
// we should disable dirty requests on **redo only**, otherwise
// the state of the layers will not be recovered on undo
m_info->image->disableDirtyRequests();
Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) {
if (node->compositeOpId() != COMPOSITE_OVER) {
addCommand(new KisNodeCompositeOpCommand(node, node->compositeOpId(), COMPOSITE_OVER));
}
if (KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::inheritAlpha, false).toBool()) {
KisBaseNode::PropertyList props = node->sectionModelProperties();
KisLayerPropertiesIcons::setNodeProperty(&props,
KisLayerPropertiesIcons::inheritAlpha,
false);
addCommand(new KisNodePropertyListCommand(node, props));
}
}
m_info->image->enableDirtyRequests();
}
private:
MergeMultipleInfoSP m_info;
};
struct DisablePassThroughForHeadsOnly : public KisCommandUtils::AggregateCommand {
DisablePassThroughForHeadsOnly(MergeDownInfoBaseSP info, bool skipIfDstIsGroup = false)
: m_info(info),
m_skipIfDstIsGroup(skipIfDstIsGroup)
{
}
void populateChildCommands() override {
if (m_skipIfDstIsGroup &&
m_info->dstLayer() &&
m_info->dstLayer()->inherits("KisGroupLayer")) {
return;
}
Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) {
if (KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::passThrough, false).toBool()) {
KisBaseNode::PropertyList props = node->sectionModelProperties();
KisLayerPropertiesIcons::setNodeProperty(&props,
KisLayerPropertiesIcons::passThrough,
false);
addCommand(new KisNodePropertyListCommand(node, props));
}
}
}
private:
MergeDownInfoBaseSP m_info;
bool m_skipIfDstIsGroup;
};
struct RefreshHiddenAreas : public KUndo2Command {
RefreshHiddenAreas(MergeDownInfoBaseSP info) : m_info(info) {}
void redo() override {
KisImageAnimationInterface *interface = m_info->image->animationInterface();
const QRect preparedRect = !interface->externalFrameActive() ?
m_info->image->bounds() : QRect();
foreach (KisNodeSP node, m_info->allSrcNodes()) {
refreshHiddenAreaAsync(node, preparedRect);
}
}
private:
QRect realNodeExactBounds(KisNodeSP rootNode, QRect currentRect = QRect()) {
KisNodeSP node = rootNode->firstChild();
while(node) {
currentRect |= realNodeExactBounds(node, currentRect);
node = node->nextSibling();
}
if (!rootNode->isFakeNode()) {
// TODO: it would be better to count up changeRect inside
// node's extent() method
currentRect |= rootNode->projectionPlane()->changeRect(rootNode->exactBounds());
}
return currentRect;
}
void refreshHiddenAreaAsync(KisNodeSP rootNode, const QRect &preparedArea) {
QRect realNodeRect = realNodeExactBounds(rootNode);
if (!preparedArea.contains(realNodeRect)) {
QRegion dirtyRegion = realNodeRect;
dirtyRegion -= preparedArea;
foreach(const QRect &rc, dirtyRegion.rects()) {
m_info->image->refreshGraphAsync(rootNode, rc, realNodeRect);
}
}
}
private:
MergeDownInfoBaseSP m_info;
};
struct RefreshDelayedUpdateLayers : public KUndo2Command {
RefreshDelayedUpdateLayers(MergeDownInfoBaseSP info) : m_info(info) {}
void redo() override {
foreach (KisNodeSP node, m_info->allSrcNodes()) {
forceAllDelayedNodesUpdate(node);
}
}
private:
MergeDownInfoBaseSP m_info;
};
struct KeepMergedNodesSelected : public KisCommandUtils::AggregateCommand {
KeepMergedNodesSelected(MergeDownInfoSP info, bool finalizing)
: m_singleInfo(info),
m_finalizing(finalizing) {}
KeepMergedNodesSelected(MergeMultipleInfoSP info, KisNodeSP putAfter, bool finalizing)
: m_multipleInfo(info),
m_finalizing(finalizing),
m_putAfter(putAfter) {}
void populateChildCommands() override {
KisNodeSP prevNode;
KisNodeSP nextNode;
KisNodeList prevSelection;
KisNodeList nextSelection;
KisImageSP image;
if (m_singleInfo) {
prevNode = m_singleInfo->currLayer;
nextNode = m_singleInfo->dstNode;
image = m_singleInfo->image;
} else if (m_multipleInfo) {
prevNode = m_putAfter;
nextNode = m_multipleInfo->dstNode;
prevSelection = m_multipleInfo->allSrcNodes();
image = m_multipleInfo->image;
}
if (!m_finalizing) {
addCommand(new KeepNodesSelectedCommand(prevSelection, KisNodeList(),
prevNode, KisNodeSP(),
image, false));
} else {
addCommand(new KeepNodesSelectedCommand(KisNodeList(), nextSelection,
KisNodeSP(), nextNode,
image, true));
}
}
private:
MergeDownInfoSP m_singleInfo;
MergeMultipleInfoSP m_multipleInfo;
bool m_finalizing;
KisNodeSP m_putAfter;
};
struct CreateMergedLayer : public KisCommandUtils::AggregateCommand {
CreateMergedLayer(MergeDownInfoSP info) : m_info(info) {}
void populateChildCommands() override {
// actual merging done by KisLayer::createMergedLayer (or specialized descendant)
m_info->dstNode = m_info->currLayer->createMergedLayerTemplate(m_info->prevLayer);
if (m_info->frames.size() > 0) {
m_info->dstNode->enableAnimation();
m_info->dstNode->getKeyframeChannel(KisKeyframeChannel::Content.id(), true);
}
m_info->dstNode->setUseInTimeline(m_info->useInTimeline);
KisPaintLayer *dstPaintLayer = qobject_cast(m_info->dstNode.data());
if (dstPaintLayer) {
dstPaintLayer->setOnionSkinEnabled(m_info->enableOnionSkins);
}
}
private:
MergeDownInfoSP m_info;
};
struct CreateMergedLayerMultiple : public KisCommandUtils::AggregateCommand {
CreateMergedLayerMultiple(MergeMultipleInfoSP info, const QString name = QString() )
: m_info(info),
m_name(name) {}
void populateChildCommands() override {
QString mergedLayerName;
if (m_name.isEmpty()){
const QString mergedLayerSuffix = i18n("Merged");
mergedLayerName = m_info->mergedNodes.first()->name();
if (!mergedLayerName.endsWith(mergedLayerSuffix)) {
mergedLayerName = QString("%1 %2")
.arg(mergedLayerName).arg(mergedLayerSuffix);
}
} else {
mergedLayerName = m_name;
}
KisPaintLayer *dstPaintLayer = new KisPaintLayer(m_info->image, mergedLayerName, OPACITY_OPAQUE_U8);
m_info->dstNode = dstPaintLayer;
if (m_info->frames.size() > 0) {
m_info->dstNode->enableAnimation();
m_info->dstNode->getKeyframeChannel(KisKeyframeChannel::Content.id(), true);
}
auto channelFlagsLazy = [](KisNodeSP node) {
KisLayer *layer = dynamic_cast(node.data());
return layer ? layer->channelFlags() : QBitArray();
};
QString compositeOpId;
QBitArray channelFlags;
bool compositionVaries = false;
bool isFirstCycle = true;
foreach (KisNodeSP node, m_info->allSrcNodes()) {
if (isFirstCycle) {
compositeOpId = node->compositeOpId();
channelFlags = channelFlagsLazy(node);
isFirstCycle = false;
} else if (compositeOpId != node->compositeOpId() ||
channelFlags != channelFlagsLazy(node)) {
compositionVaries = true;
break;
}
KisLayerSP layer = qobject_cast(node.data());
if (layer && layer->layerStyle()) {
compositionVaries = true;
break;
}
}
if (!compositionVaries) {
if (!compositeOpId.isEmpty()) {
m_info->dstNode->setCompositeOpId(compositeOpId);
}
if (m_info->dstLayer() && !channelFlags.isEmpty()) {
m_info->dstLayer()->setChannelFlags(channelFlags);
}
}
m_info->nodesCompositingVaries = compositionVaries;
m_info->dstNode->setUseInTimeline(m_info->useInTimeline);
dstPaintLayer->setOnionSkinEnabled(m_info->enableOnionSkins);
}
private:
MergeMultipleInfoSP m_info;
QString m_name;
};
struct MergeLayers : public KisCommandUtils::AggregateCommand {
MergeLayers(MergeDownInfoSP info) : m_info(info) {}
void populateChildCommands() override {
// actual merging done by KisLayer::createMergedLayer (or specialized descendant)
m_info->currLayer->fillMergedLayerTemplate(m_info->dstLayer(), m_info->prevLayer);
}
private:
MergeDownInfoSP m_info;
};
struct MergeLayersMultiple : public KisCommandUtils::AggregateCommand {
MergeLayersMultiple(MergeMultipleInfoSP info) : m_info(info) {}
void populateChildCommands() override {
KisPainter gc(m_info->dstNode->paintDevice());
foreach (KisNodeSP node, m_info->allSrcNodes()) {
QRect rc = node->exactBounds() | m_info->image->bounds();
node->projectionPlane()->apply(&gc, rc);
}
}
private:
MergeMultipleInfoSP m_info;
};
struct MergeMetaData : public KUndo2Command {
MergeMetaData(MergeDownInfoSP info, const KisMetaData::MergeStrategy* strategy)
: m_info(info),
m_strategy(strategy) {}
void redo() override {
QRect layerProjectionExtent = m_info->currLayer->projection()->extent();
QRect prevLayerProjectionExtent = m_info->prevLayer->projection()->extent();
int prevLayerArea = prevLayerProjectionExtent.width() * prevLayerProjectionExtent.height();
int layerArea = layerProjectionExtent.width() * layerProjectionExtent.height();
QList scores;
double norm = qMax(prevLayerArea, layerArea);
scores.append(prevLayerArea / norm);
scores.append(layerArea / norm);
QList srcs;
srcs.append(m_info->prevLayer->metaData());
srcs.append(m_info->currLayer->metaData());
m_strategy->merge(m_info->dstLayer()->metaData(), srcs, scores);
}
private:
MergeDownInfoSP m_info;
const KisMetaData::MergeStrategy *m_strategy;
};
KeepNodesSelectedCommand::KeepNodesSelectedCommand(const KisNodeList &selectedBefore,
const KisNodeList &selectedAfter,
KisNodeSP activeBefore,
KisNodeSP activeAfter,
KisImageSP image,
bool finalize, KUndo2Command *parent)
: FlipFlopCommand(finalize, parent),
m_selectedBefore(selectedBefore),
m_selectedAfter(selectedAfter),
m_activeBefore(activeBefore),
m_activeAfter(activeAfter),
m_image(image)
{
}
void KeepNodesSelectedCommand::partB() {
KisImageSignalType type;
if (getState() == State::FINALIZING) {
type = ComplexNodeReselectionSignal(m_activeAfter, m_selectedAfter);
} else {
type = ComplexNodeReselectionSignal(m_activeBefore, m_selectedBefore);
}
m_image->signalRouter()->emitNotification(type);
}
SelectGlobalSelectionMask::SelectGlobalSelectionMask(KisImageSP image)
: m_image(image)
{
}
void SelectGlobalSelectionMask::redo() {
KisImageSignalType type =
ComplexNodeReselectionSignal(m_image->rootLayer()->selectionMask(), KisNodeList());
m_image->signalRouter()->emitNotification(type);
}
RemoveNodeHelper::~RemoveNodeHelper()
{
}
/**
* The removal of two nodes in one go may be a bit tricky, because one
* of them may be the clone of another. If we remove the source of a
* clone layer, it will reincarnate into a paint layer. In this case
* the pointer to the second layer will be lost.
*
* That's why we need to care about the order of the nodes removal:
* the clone --- first, the source --- last.
*/
void RemoveNodeHelper::safeRemoveMultipleNodes(KisNodeList nodes, KisImageSP image) {
const bool lastLayer = scanForLastLayer(image, nodes);
auto isNodeWeird = [] (KisNodeSP node) {
const bool normalCompositeMode = node->compositeOpId() == COMPOSITE_OVER;
KisLayer *layer = dynamic_cast(node.data());
const bool hasInheritAlpha = layer && layer->alphaChannelDisabled();
return !normalCompositeMode && !hasInheritAlpha;
};
while (!nodes.isEmpty()) {
KisNodeList::iterator it = nodes.begin();
while (it != nodes.end()) {
if (!checkIsSourceForClone(*it, nodes)) {
KisNodeSP node = *it;
addCommandImpl(new KisImageLayerRemoveCommand(image, node, !isNodeWeird(node), true));
it = nodes.erase(it);
} else {
++it;
}
}
}
if (lastLayer) {
KisLayerSP newLayer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, image->colorSpace());
addCommandImpl(new KisImageLayerAddCommand(image, newLayer,
image->root(),
KisNodeSP(),
false, false));
}
}
bool RemoveNodeHelper::checkIsSourceForClone(KisNodeSP src, const KisNodeList &nodes) {
foreach (KisNodeSP node, nodes) {
if (node == src) continue;
KisCloneLayer *clone = dynamic_cast(node.data());
if (clone && KisNodeSP(clone->copyFrom()) == src) {
return true;
}
}
return false;
}
bool RemoveNodeHelper::scanForLastLayer(KisImageWSP image, KisNodeList nodesToRemove) {
bool removeLayers = false;
Q_FOREACH(KisNodeSP nodeToRemove, nodesToRemove) {
if (qobject_cast(nodeToRemove.data())) {
removeLayers = true;
break;
}
}
if (!removeLayers) return false;
bool lastLayer = true;
KisNodeSP node = image->root()->firstChild();
while (node) {
if (!nodesToRemove.contains(node) &&
qobject_cast(node.data()) &&
!node->isFakeNode()) {
lastLayer = false;
break;
}
node = node->nextSibling();
}
return lastLayer;
}
SimpleRemoveLayers::SimpleRemoveLayers(const KisNodeList &nodes,
KisImageSP image)
: m_nodes(nodes),
m_image(image)
{
}
void SimpleRemoveLayers::populateChildCommands() {
if (m_nodes.isEmpty()) return;
safeRemoveMultipleNodes(m_nodes, m_image);
}
void SimpleRemoveLayers::addCommandImpl(KUndo2Command *cmd) {
addCommand(cmd);
}
struct InsertNode : public KisCommandUtils::AggregateCommand {
InsertNode(MergeDownInfoBaseSP info, KisNodeSP putAfter)
: m_info(info), m_putAfter(putAfter) {}
void populateChildCommands() override {
addCommand(new KisImageLayerAddCommand(m_info->image,
m_info->dstNode,
m_putAfter->parent(),
m_putAfter,
true, false));
}
private:
virtual void addCommandImpl(KUndo2Command *cmd) {
addCommand(cmd);
}
private:
MergeDownInfoBaseSP m_info;
KisNodeSP m_putAfter;
};
struct CleanUpNodes : private RemoveNodeHelper, public KisCommandUtils::AggregateCommand {
CleanUpNodes(MergeDownInfoBaseSP info, KisNodeSP putAfter)
: m_info(info), m_putAfter(putAfter) {}
static void findPerfectParent(KisNodeList nodesToDelete, KisNodeSP &putAfter, KisNodeSP &parent) {
if (!putAfter) {
putAfter = nodesToDelete.last();
}
// Add the new merged node on top of the active node
// -- checking all parents if they are included in nodesToDelete
// Not every descendant is included in nodesToDelete even if in fact
// they are going to be deleted, so we need to check it.
// If we consider the path from root to the putAfter node,
// if there are any nodes marked for deletion, any node afterwards
// is going to be deleted, too.
// example: root . . . . . ! ! . . ! ! ! ! . . . . putAfter
// it should be: root . . . . . ! ! ! ! ! ! ! ! ! ! ! ! !putAfter
// and here: root . . . . X ! ! . . ! ! ! ! . . . . putAfter
// you can see which node is "the perfect ancestor"
// (marked X; called "parent" in the function arguments).
// and here: root . . . . . O ! . . ! ! ! ! . . . . putAfter
// you can see which node is "the topmost deleted ancestor" (marked 'O')
KisNodeSP node = putAfter->parent();
bool foundDeletedAncestor = false;
KisNodeSP topmostAncestorToDelete = nullptr;
while (node) {
if (nodesToDelete.contains(node)
&& !nodesToDelete.contains(node->parent())) {
foundDeletedAncestor = true;
topmostAncestorToDelete = node;
// Here node is to be deleted and its parent is not,
// so its parent is the one of the first not deleted (="perfect") ancestors.
// We need the one that is closest to the top (root)
}
node = node->parent();
}
if (foundDeletedAncestor) {
parent = topmostAncestorToDelete->parent();
putAfter = topmostAncestorToDelete;
}
else {
parent = putAfter->parent(); // putAfter (and none of its ancestors) is to be deleted, so its parent is the first not deleted ancestor
}
}
void populateChildCommands() override {
KisNodeList nodesToDelete = m_info->allSrcNodes();
KisNodeSP parent;
findPerfectParent(nodesToDelete, m_putAfter, parent);
if (!parent) {
KisNodeSP oldRoot = m_info->image->root();
KisNodeSP newRoot(new KisGroupLayer(m_info->image, "root", OPACITY_OPAQUE_U8));
// copy all fake nodes into the new image
KisLayerUtils::recursiveApplyNodes(oldRoot, [this, oldRoot, newRoot] (KisNodeSP node) {
if (node->isFakeNode() && node->parent() == oldRoot) {
addCommand(new KisImageLayerAddCommand(m_info->image,
node->clone(),
newRoot,
KisNodeSP(),
false, false));
}
});
addCommand(new KisImageLayerAddCommand(m_info->image,
m_info->dstNode,
newRoot,
KisNodeSP(),
true, false));
addCommand(new KisImageChangeLayersCommand(m_info->image, oldRoot, newRoot));
}
else {
addCommand(new KisImageLayerAddCommand(m_info->image,
m_info->dstNode,
parent,
m_putAfter,
true, false));
/**
* We can merge selection masks, in this case dstLayer is not defined!
*/
if (m_info->dstLayer()) {
reparentSelectionMasks(m_info->image,
m_info->dstLayer(),
m_info->selectionMasks);
}
KisNodeList safeNodesToDelete = m_info->allSrcNodes();
for (KisNodeList::iterator it = safeNodesToDelete.begin(); it != safeNodesToDelete.end(); ++it) {
KisNodeSP node = *it;
if (node->userLocked() && node->visible()) {
addCommand(new KisImageChangeVisibilityCommand(false, node));
}
}
KritaUtils::filterContainer(safeNodesToDelete, [](KisNodeSP node) {
return !node->userLocked();
});
safeRemoveMultipleNodes(safeNodesToDelete, m_info->image);
}
}
private:
void addCommandImpl(KUndo2Command *cmd) override {
addCommand(cmd);
}
void reparentSelectionMasks(KisImageSP image,
KisLayerSP newLayer,
const QVector &selectionMasks) {
KIS_SAFE_ASSERT_RECOVER_RETURN(newLayer);
foreach (KisSelectionMaskSP mask, selectionMasks) {
addCommand(new KisImageLayerMoveCommand(image, mask, newLayer, newLayer->lastChild()));
addCommand(new KisActivateSelectionMaskCommand(mask, false));
}
}
private:
MergeDownInfoBaseSP m_info;
KisNodeSP m_putAfter;
};
SwitchFrameCommand::SharedStorage::~SharedStorage() {
}
SwitchFrameCommand::SwitchFrameCommand(KisImageSP image, int time, bool finalize, SharedStorageSP storage)
: FlipFlopCommand(finalize),
m_image(image),
m_newTime(time),
m_storage(storage) {}
SwitchFrameCommand::~SwitchFrameCommand() {}
void SwitchFrameCommand::partA() {
KisImageAnimationInterface *interface = m_image->animationInterface();
const int currentTime = interface->currentTime();
if (currentTime == m_newTime) {
m_storage->value = m_newTime;
return;
}
interface->image()->disableUIUpdates();
interface->saveAndResetCurrentTime(m_newTime, &m_storage->value);
}
void SwitchFrameCommand::partB() {
KisImageAnimationInterface *interface = m_image->animationInterface();
const int currentTime = interface->currentTime();
if (currentTime == m_storage->value) {
return;
}
interface->restoreCurrentTime(&m_storage->value);
interface->image()->enableUIUpdates();
}
struct AddNewFrame : public KisCommandUtils::AggregateCommand {
AddNewFrame(MergeDownInfoBaseSP info, int frame) : m_info(info), m_frame(frame) {}
void populateChildCommands() override {
KUndo2Command *cmd = new KisCommandUtils::SkipFirstRedoWrapper();
KisKeyframeChannel *channel = m_info->dstNode->getKeyframeChannel(KisKeyframeChannel::Content.id());
KisKeyframeSP keyframe = channel->addKeyframe(m_frame, cmd);
applyKeyframeColorLabel(keyframe);
addCommand(cmd);
}
void applyKeyframeColorLabel(KisKeyframeSP dstKeyframe) {
Q_FOREACH(KisNodeSP srcNode, m_info->allSrcNodes()) {
Q_FOREACH(KisKeyframeChannel *channel, srcNode->keyframeChannels().values()) {
KisKeyframeSP keyframe = channel->keyframeAt(m_frame);
if (!keyframe.isNull() && keyframe->colorLabel() != 0) {
dstKeyframe->setColorLabel(keyframe->colorLabel());
return;
}
}
}
dstKeyframe->setColorLabel(0);
}
private:
MergeDownInfoBaseSP m_info;
int m_frame;
};
QSet fetchLayerFrames(KisNodeSP node) {
KisKeyframeChannel *channel = node->getKeyframeChannel(KisKeyframeChannel::Content.id());
if (!channel) return QSet();
return channel->allKeyframeIds();
}
QSet fetchLayerFramesRecursive(KisNodeSP rootNode) {
QSet frames = fetchLayerFrames(rootNode);
KisNodeSP node = rootNode->firstChild();
while(node) {
frames |= fetchLayerFramesRecursive(node);
node = node->nextSibling();
}
return frames;
}
void updateFrameJobs(FrameJobs *jobs, KisNodeSP node) {
QSet frames = fetchLayerFrames(node);
if (frames.isEmpty()) {
(*jobs)[0].insert(node);
} else {
foreach (int frame, frames) {
(*jobs)[frame].insert(node);
}
}
}
void updateFrameJobsRecursive(FrameJobs *jobs, KisNodeSP rootNode) {
updateFrameJobs(jobs, rootNode);
KisNodeSP node = rootNode->firstChild();
while(node) {
updateFrameJobsRecursive(jobs, node);
node = node->nextSibling();
}
}
void mergeDown(KisImageSP image, KisLayerSP layer, const KisMetaData::MergeStrategy* strategy)
{
if (!layer->prevSibling()) return;
// XXX: this breaks if we allow free mixing of masks and layers
KisLayerSP prevLayer = qobject_cast(layer->prevSibling().data());
if (!prevLayer) return;
if (!layer->visible() && !prevLayer->visible()) {
return;
}
KisImageSignalVector emitSignals;
emitSignals << ModifiedSignal;
KisProcessingApplicator applicator(image, 0,
KisProcessingApplicator::NONE,
emitSignals,
kundo2_i18n("Merge Down"));
if (layer->visible() && prevLayer->visible()) {
MergeDownInfoSP info(new MergeDownInfo(image, prevLayer, layer));
// disable key strokes on all colorize masks, all onion skins on
// paint layers and wait until update is finished with a barrier
applicator.applyCommand(new DisableColorizeKeyStrokes(info));
applicator.applyCommand(new DisableOnionSkins(info));
applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER);
applicator.applyCommand(new KeepMergedNodesSelected(info, false));
applicator.applyCommand(new FillSelectionMasks(info));
applicator.applyCommand(new CreateMergedLayer(info), KisStrokeJobData::BARRIER);
// NOTE: shape layer may have emitted spontaneous jobs during layer creation,
// wait for them to complete!
applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER);
applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER);
// in two-layer mode we disable pass through only when the destination layer
// is not a group layer
applicator.applyCommand(new DisablePassThroughForHeadsOnly(info, true));
applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER);
if (info->frames.size() > 0) {
foreach (int frame, info->frames) {
applicator.applyCommand(new SwitchFrameCommand(info->image, frame, false, info->storage));
applicator.applyCommand(new AddNewFrame(info, frame));
applicator.applyCommand(new RefreshHiddenAreas(info));
applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER);
applicator.applyCommand(new MergeLayers(info), KisStrokeJobData::BARRIER);
applicator.applyCommand(new SwitchFrameCommand(info->image, frame, true, info->storage));
}
} else {
applicator.applyCommand(new RefreshHiddenAreas(info));
applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER);
applicator.applyCommand(new MergeLayers(info), KisStrokeJobData::BARRIER);
}
applicator.applyCommand(new MergeMetaData(info, strategy), KisStrokeJobData::BARRIER);
applicator.applyCommand(new CleanUpNodes(info, layer),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
applicator.applyCommand(new KeepMergedNodesSelected(info, true));
} else if (layer->visible()) {
applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(),
layer, KisNodeSP(),
image, false));
applicator.applyCommand(
new SimpleRemoveLayers(KisNodeList() << prevLayer,
image),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(),
KisNodeSP(), layer,
image, true));
} else if (prevLayer->visible()) {
applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(),
layer, KisNodeSP(),
image, false));
applicator.applyCommand(
new SimpleRemoveLayers(KisNodeList() << layer,
image),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(),
KisNodeSP(), prevLayer,
image, true));
}
applicator.end();
}
bool checkIsChildOf(KisNodeSP node, const KisNodeList &parents)
{
KisNodeList nodeParents;
KisNodeSP parent = node->parent();
while (parent) {
nodeParents << parent;
parent = parent->parent();
}
foreach(KisNodeSP perspectiveParent, parents) {
if (nodeParents.contains(perspectiveParent)) {
return true;
}
}
return false;
}
bool checkIsCloneOf(KisNodeSP node, const KisNodeList &nodes)
{
bool result = false;
KisCloneLayer *clone = dynamic_cast(node.data());
if (clone) {
KisNodeSP cloneSource = KisNodeSP(clone->copyFrom());
Q_FOREACH(KisNodeSP subtree, nodes) {
result =
recursiveFindNode(subtree,
[cloneSource](KisNodeSP node) -> bool
{
return node == cloneSource;
});
if (!result) {
result = checkIsCloneOf(cloneSource, nodes);
}
if (result) {
break;
}
}
}
return result;
}
void filterMergableNodes(KisNodeList &nodes, bool allowMasks)
{
KisNodeList::iterator it = nodes.begin();
while (it != nodes.end()) {
if ((!allowMasks && !qobject_cast(it->data())) ||
checkIsChildOf(*it, nodes)) {
//qDebug() << "Skipping node" << ppVar((*it)->name());
it = nodes.erase(it);
} else {
++it;
}
}
}
void sortMergableNodes(KisNodeSP root, KisNodeList &inputNodes, KisNodeList &outputNodes)
{
KisNodeList::iterator it = std::find(inputNodes.begin(), inputNodes.end(), root);
if (it != inputNodes.end()) {
outputNodes << *it;
inputNodes.erase(it);
}
if (inputNodes.isEmpty()) {
return;
}
KisNodeSP child = root->firstChild();
while (child) {
sortMergableNodes(child, inputNodes, outputNodes);
child = child->nextSibling();
}
/**
* By the end of recursion \p inputNodes must be empty
*/
KIS_ASSERT_RECOVER_NOOP(root->parent() || inputNodes.isEmpty());
}
KisNodeList sortMergableNodes(KisNodeSP root, KisNodeList nodes)
{
KisNodeList result;
sortMergableNodes(root, nodes, result);
return result;
}
KisNodeList sortAndFilterMergableInternalNodes(KisNodeList nodes, bool allowMasks)
{
KIS_ASSERT_RECOVER(!nodes.isEmpty()) { return nodes; }
KisNodeSP root;
Q_FOREACH(KisNodeSP node, nodes) {
KisNodeSP localRoot = node;
while (localRoot->parent()) {
localRoot = localRoot->parent();
}
if (!root) {
root = localRoot;
}
KIS_ASSERT_RECOVER(root == localRoot) { return nodes; }
}
KisNodeList result;
sortMergableNodes(root, nodes, result);
filterMergableNodes(result, allowMasks);
return result;
}
+ KisNodeList sortAndFilterAnyMergableNodesSafe(const KisNodeList &nodes, KisImageSP image) {
+ KisNodeList filteredNodes = nodes;
+ KisNodeList sortedNodes;
+
+ KisLayerUtils::filterMergableNodes(filteredNodes, true);
+
+ bool haveExternalNodes = false;
+ Q_FOREACH (KisNodeSP node, nodes) {
+ if (node->graphListener() != image->root()->graphListener()) {
+ haveExternalNodes = true;
+ break;
+ }
+ }
+
+ if (!haveExternalNodes) {
+ KisLayerUtils::sortMergableNodes(image->root(), filteredNodes, sortedNodes);
+ } else {
+ sortedNodes = filteredNodes;
+ }
+
+ return sortedNodes;
+ }
+
+
void addCopyOfNameTag(KisNodeSP node)
{
const QString prefix = i18n("Copy of");
QString newName = node->name();
if (!newName.startsWith(prefix)) {
newName = QString("%1 %2").arg(prefix).arg(newName);
node->setName(newName);
}
}
KisNodeList findNodesWithProps(KisNodeSP root, const KoProperties &props, bool excludeRoot)
{
KisNodeList nodes;
if ((!excludeRoot || root->parent()) && root->check(props)) {
nodes << root;
}
KisNodeSP node = root->firstChild();
while (node) {
nodes += findNodesWithProps(node, props, excludeRoot);
node = node->nextSibling();
}
return nodes;
}
KisNodeList filterInvisibleNodes(const KisNodeList &nodes, KisNodeList *invisibleNodes, KisNodeSP *putAfter)
{
KIS_ASSERT_RECOVER(invisibleNodes) { return nodes; }
KIS_ASSERT_RECOVER(putAfter) { return nodes; }
KisNodeList visibleNodes;
int putAfterIndex = -1;
Q_FOREACH(KisNodeSP node, nodes) {
if (node->visible() || node->userLocked()) {
visibleNodes << node;
} else {
*invisibleNodes << node;
if (node == *putAfter) {
putAfterIndex = visibleNodes.size() - 1;
}
}
}
if (!visibleNodes.isEmpty() && putAfterIndex >= 0) {
putAfterIndex = qBound(0, putAfterIndex, visibleNodes.size() - 1);
*putAfter = visibleNodes[putAfterIndex];
}
return visibleNodes;
}
void filterUnlockedNodes(KisNodeList &nodes)
{
KisNodeList::iterator it = nodes.begin();
while (it != nodes.end()) {
if ((*it)->userLocked()) {
it = nodes.erase(it);
} else {
++it;
}
}
}
void changeImageDefaultProjectionColor(KisImageSP image, const KoColor &color)
{
KisImageSignalVector emitSignals;
emitSignals << ModifiedSignal;
KisProcessingApplicator applicator(image,
image->root(),
KisProcessingApplicator::RECURSIVE,
emitSignals,
kundo2_i18n("Change projection color"),
0,
142857 + 1);
applicator.applyCommand(new KisChangeProjectionColorCommand(image, color), KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE);
applicator.end();
}
void mergeMultipleLayersImpl(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter,
bool flattenSingleLayer, const KUndo2MagicString &actionName,
bool cleanupNodes = true, const QString layerName = QString())
{
if (!putAfter) {
putAfter = mergedNodes.first();
}
filterMergableNodes(mergedNodes);
{
KisNodeList tempNodes;
std::swap(mergedNodes, tempNodes);
sortMergableNodes(image->root(), tempNodes, mergedNodes);
}
if (mergedNodes.size() <= 1 &&
(!flattenSingleLayer && mergedNodes.size() == 1)) return;
KisImageSignalVector emitSignals;
emitSignals << ModifiedSignal;
emitSignals << ComplexNodeReselectionSignal(KisNodeSP(), KisNodeList(), KisNodeSP(), mergedNodes);
KisProcessingApplicator applicator(image, 0,
KisProcessingApplicator::NONE,
emitSignals,
actionName);
KisNodeList originalNodes = mergedNodes;
KisNodeList invisibleNodes;
mergedNodes = filterInvisibleNodes(originalNodes, &invisibleNodes, &putAfter);
if (!invisibleNodes.isEmpty() && !mergedNodes.isEmpty()) {
/* If the putAfter node is invisible,
* we should instead pick one of the nodes
* to be merged to avoid a null putAfter.
*/
if (!putAfter->visible()){
putAfter = mergedNodes.first();
}
applicator.applyCommand(
new SimpleRemoveLayers(invisibleNodes,
image),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
}
if (mergedNodes.size() > 1 || invisibleNodes.isEmpty()) {
MergeMultipleInfoSP info(new MergeMultipleInfo(image, mergedNodes));
// disable key strokes on all colorize masks, all onion skins on
// paint layers and wait until update is finished with a barrier
applicator.applyCommand(new DisableColorizeKeyStrokes(info));
applicator.applyCommand(new DisableOnionSkins(info));
applicator.applyCommand(new DisablePassThroughForHeadsOnly(info));
applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER);
applicator.applyCommand(new KeepMergedNodesSelected(info, putAfter, false));
applicator.applyCommand(new FillSelectionMasks(info));
applicator.applyCommand(new CreateMergedLayerMultiple(info, layerName), KisStrokeJobData::BARRIER);
applicator.applyCommand(new DisableExtraCompositing(info));
applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER);
if (!info->frames.isEmpty()) {
foreach (int frame, info->frames) {
applicator.applyCommand(new SwitchFrameCommand(info->image, frame, false, info->storage));
applicator.applyCommand(new AddNewFrame(info, frame));
applicator.applyCommand(new RefreshHiddenAreas(info));
applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER);
applicator.applyCommand(new MergeLayersMultiple(info), KisStrokeJobData::BARRIER);
applicator.applyCommand(new SwitchFrameCommand(info->image, frame, true, info->storage));
}
} else {
applicator.applyCommand(new RefreshHiddenAreas(info));
applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER);
applicator.applyCommand(new MergeLayersMultiple(info), KisStrokeJobData::BARRIER);
}
//applicator.applyCommand(new MergeMetaData(info, strategy), KisStrokeJobData::BARRIER);
if (cleanupNodes){
applicator.applyCommand(new CleanUpNodes(info, putAfter),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
} else {
applicator.applyCommand(new InsertNode(info, putAfter),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
}
applicator.applyCommand(new KeepMergedNodesSelected(info, putAfter, true));
}
applicator.end();
}
void mergeMultipleLayers(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter)
{
mergeMultipleLayersImpl(image, mergedNodes, putAfter, false, kundo2_i18n("Merge Selected Nodes"));
}
void newLayerFromVisible(KisImageSP image, KisNodeSP putAfter)
{
KisNodeList mergedNodes;
mergedNodes << image->root();
mergeMultipleLayersImpl(image, mergedNodes, putAfter, true, kundo2_i18n("New From Visible"), false, i18nc("New layer created from all the visible layers", "Visible"));
}
struct MergeSelectionMasks : public KisCommandUtils::AggregateCommand {
MergeSelectionMasks(MergeDownInfoBaseSP info, KisNodeSP putAfter)
: m_info(info),
m_putAfter(putAfter){}
void populateChildCommands() override {
KisNodeSP parent;
CleanUpNodes::findPerfectParent(m_info->allSrcNodes(), m_putAfter, parent);
KisLayerSP parentLayer;
do {
parentLayer = qobject_cast(parent.data());
parent = parent->parent();
} while(!parentLayer && parent);
KisSelectionSP selection = new KisSelection();
foreach (KisNodeSP node, m_info->allSrcNodes()) {
KisMaskSP mask = dynamic_cast(node.data());
if (!mask) continue;
selection->pixelSelection()->applySelection(
mask->selection()->pixelSelection(), SELECTION_ADD);
}
KisSelectionMaskSP mergedMask = new KisSelectionMask(m_info->image);
mergedMask->initSelection(parentLayer);
mergedMask->setSelection(selection);
m_info->dstNode = mergedMask;
}
private:
MergeDownInfoBaseSP m_info;
KisNodeSP m_putAfter;
};
struct ActivateSelectionMask : public KisCommandUtils::AggregateCommand {
ActivateSelectionMask(MergeDownInfoBaseSP info)
: m_info(info) {}
void populateChildCommands() override {
KisSelectionMaskSP mergedMask = dynamic_cast(m_info->dstNode.data());
addCommand(new KisActivateSelectionMaskCommand(mergedMask, true));
}
private:
MergeDownInfoBaseSP m_info;
};
bool tryMergeSelectionMasks(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter)
{
QList selectionMasks;
for (auto it = mergedNodes.begin(); it != mergedNodes.end(); /*noop*/) {
KisSelectionMaskSP mask = dynamic_cast(it->data());
if (!mask) {
it = mergedNodes.erase(it);
} else {
selectionMasks.append(mask);
++it;
}
}
if (mergedNodes.isEmpty()) return false;
KisLayerSP parentLayer = qobject_cast(selectionMasks.first()->parent().data());
KIS_ASSERT_RECOVER(parentLayer) { return 0; }
KisImageSignalVector emitSignals;
emitSignals << ModifiedSignal;
KisProcessingApplicator applicator(image, 0,
KisProcessingApplicator::NONE,
emitSignals,
kundo2_i18n("Merge Selection Masks"));
MergeMultipleInfoSP info(new MergeMultipleInfo(image, mergedNodes));
applicator.applyCommand(new MergeSelectionMasks(info, putAfter));
applicator.applyCommand(new CleanUpNodes(info, putAfter),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
applicator.applyCommand(new ActivateSelectionMask(info));
applicator.end();
return true;
}
void flattenLayer(KisImageSP image, KisLayerSP layer)
{
if (!layer->childCount() && !layer->layerStyle())
return;
KisNodeList mergedNodes;
mergedNodes << layer;
mergeMultipleLayersImpl(image, mergedNodes, layer, true, kundo2_i18n("Flatten Layer"));
}
void flattenImage(KisImageSP image, KisNodeSP activeNode)
{
if (!activeNode) {
activeNode = image->root()->lastChild();
}
KisNodeList mergedNodes;
mergedNodes << image->root();
mergeMultipleLayersImpl(image, mergedNodes, activeNode, true, kundo2_i18n("Flatten Image"));
}
KisSimpleUpdateCommand::KisSimpleUpdateCommand(KisNodeList nodes, bool finalize, KUndo2Command *parent)
: FlipFlopCommand(finalize, parent),
m_nodes(nodes)
{
}
void KisSimpleUpdateCommand::partB()
{
updateNodes(m_nodes);
}
void KisSimpleUpdateCommand::updateNodes(const KisNodeList &nodes)
{
Q_FOREACH(KisNodeSP node, nodes) {
node->setDirty(node->extent());
}
}
KisNodeSP recursiveFindNode(KisNodeSP node, std::function func)
{
if (func(node)) {
return node;
}
node = node->firstChild();
while (node) {
KisNodeSP resultNode = recursiveFindNode(node, func);
if (resultNode) {
return resultNode;
}
node = node->nextSibling();
}
return 0;
}
KisNodeSP findNodeByUuid(KisNodeSP root, const QUuid &uuid)
{
return recursiveFindNode(root,
[uuid] (KisNodeSP node) {
return node->uuid() == uuid;
});
}
void forceAllDelayedNodesUpdate(KisNodeSP root)
{
KisLayerUtils::recursiveApplyNodes(root,
[] (KisNodeSP node) {
KisDelayedUpdateNodeInterface *delayedUpdate =
dynamic_cast(node.data());
if (delayedUpdate) {
delayedUpdate->forceUpdateTimedNode();
}
});
}
bool hasDelayedNodeWithUpdates(KisNodeSP root)
{
return recursiveFindNode(root,
[] (KisNodeSP node) {
KisDelayedUpdateNodeInterface *delayedUpdate =
dynamic_cast(node.data());
return delayedUpdate ? delayedUpdate->hasPendingTimedUpdates() : false;
});
}
KisImageSP findImageByHierarchy(KisNodeSP node)
{
while (node) {
const KisLayer *layer = dynamic_cast(node.data());
if (layer) {
return layer->image();
}
node = node->parent();
}
return 0;
}
}
diff --git a/libs/image/kis_layer_utils.h b/libs/image/kis_layer_utils.h
index b81e2cc65c..339f6e664e 100644
--- a/libs/image/kis_layer_utils.h
+++ b/libs/image/kis_layer_utils.h
@@ -1,234 +1,235 @@
/*
* Copyright (c) 2015 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_LAYER_UTILS_H
#define __KIS_LAYER_UTILS_H
#include
#include "kundo2command.h"
#include "kis_types.h"
#include "kritaimage_export.h"
#include "kis_command_utils.h"
class KoProperties;
class KoColor;
class QUuid;
namespace KisMetaData
{
class MergeStrategy;
}
namespace KisLayerUtils
{
KRITAIMAGE_EXPORT void sortMergableNodes(KisNodeSP root, QList &inputNodes, QList &outputNodes);
KRITAIMAGE_EXPORT KisNodeList sortMergableNodes(KisNodeSP root, KisNodeList nodes);
KRITAIMAGE_EXPORT void filterMergableNodes(KisNodeList &nodes, bool allowMasks = false);
+ KRITAIMAGE_EXPORT KisNodeList sortAndFilterAnyMergableNodesSafe(const KisNodeList &nodes, KisImageSP image);
KRITAIMAGE_EXPORT bool checkIsChildOf(KisNodeSP node, const KisNodeList &parents);
KRITAIMAGE_EXPORT void filterUnlockedNodes(KisNodeList &nodes);
/**
* Returns true if:
* o \p node is a clone of some layer in \p nodes
* o \p node is a clone any child layer of any layer in \p nodes
* o \p node is a clone of a clone of a ..., that in the end points
* to any layer in \p nodes of their children.
*/
KRITAIMAGE_EXPORT bool checkIsCloneOf(KisNodeSP node, const KisNodeList &nodes);
KRITAIMAGE_EXPORT void forceAllDelayedNodesUpdate(KisNodeSP root);
KRITAIMAGE_EXPORT bool hasDelayedNodeWithUpdates(KisNodeSP root);
KRITAIMAGE_EXPORT KisNodeList sortAndFilterMergableInternalNodes(KisNodeList nodes, bool allowMasks = false);
KRITAIMAGE_EXPORT void mergeDown(KisImageSP image, KisLayerSP layer, const KisMetaData::MergeStrategy* strategy);
KRITAIMAGE_EXPORT QSet fetchLayerFrames(KisNodeSP node);
KRITAIMAGE_EXPORT QSet fetchLayerFramesRecursive(KisNodeSP rootNode);
KRITAIMAGE_EXPORT void mergeMultipleLayers(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter);
KRITAIMAGE_EXPORT void newLayerFromVisible(KisImageSP image, KisNodeSP putAfter);
KRITAIMAGE_EXPORT bool tryMergeSelectionMasks(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter);
KRITAIMAGE_EXPORT void flattenLayer(KisImageSP image, KisLayerSP layer);
KRITAIMAGE_EXPORT void flattenImage(KisImageSP image, KisNodeSP activeNode);
KRITAIMAGE_EXPORT void addCopyOfNameTag(KisNodeSP node);
KRITAIMAGE_EXPORT KisNodeList findNodesWithProps(KisNodeSP root, const KoProperties &props, bool excludeRoot);
KRITAIMAGE_EXPORT void changeImageDefaultProjectionColor(KisImageSP image, const KoColor &color);
typedef QMap > FrameJobs;
void updateFrameJobs(FrameJobs *jobs, KisNodeSP node);
void updateFrameJobsRecursive(FrameJobs *jobs, KisNodeSP rootNode);
struct SwitchFrameCommand : public KisCommandUtils::FlipFlopCommand {
struct SharedStorage {
/**
* For some reason the absence of a destructor in the SharedStorage
* makes Krita crash on exit. Seems like some compiler weirdness... (DK)
*/
~SharedStorage();
int value;
};
typedef QSharedPointer SharedStorageSP;
public:
SwitchFrameCommand(KisImageSP image, int time, bool finalize, SharedStorageSP storage);
~SwitchFrameCommand() override;
private:
void partA() override;
void partB() override;
private:
KisImageWSP m_image;
int m_newTime;
SharedStorageSP m_storage;
};
/**
* A command to keep correct set of selected/active nodes thoroughout
* the action.
*/
class KRITAIMAGE_EXPORT KeepNodesSelectedCommand : public KisCommandUtils::FlipFlopCommand
{
public:
KeepNodesSelectedCommand(const KisNodeList &selectedBefore,
const KisNodeList &selectedAfter,
KisNodeSP activeBefore,
KisNodeSP activeAfter,
KisImageSP image,
bool finalize, KUndo2Command *parent = 0);
void partB() override;
private:
KisNodeList m_selectedBefore;
KisNodeList m_selectedAfter;
KisNodeSP m_activeBefore;
KisNodeSP m_activeAfter;
KisImageWSP m_image;
};
struct KRITAIMAGE_EXPORT SelectGlobalSelectionMask : public KUndo2Command
{
SelectGlobalSelectionMask(KisImageSP image);
void redo() override;
KisImageSP m_image;
};
class KRITAIMAGE_EXPORT RemoveNodeHelper {
public:
virtual ~RemoveNodeHelper();
protected:
virtual void addCommandImpl(KUndo2Command *cmd) = 0;
void safeRemoveMultipleNodes(KisNodeList nodes, KisImageSP image);
private:
bool checkIsSourceForClone(KisNodeSP src, const KisNodeList &nodes);
static bool scanForLastLayer(KisImageWSP image, KisNodeList nodesToRemove);
};
struct SimpleRemoveLayers : private KisLayerUtils::RemoveNodeHelper, public KisCommandUtils::AggregateCommand {
SimpleRemoveLayers(const KisNodeList &nodes,
KisImageSP image);
void populateChildCommands() override;
protected:
void addCommandImpl(KUndo2Command *cmd) override;
private:
KisNodeList m_nodes;
KisImageSP m_image;
KisNodeList m_selectedNodes;
KisNodeSP m_activeNode;
};
class KRITAIMAGE_EXPORT KisSimpleUpdateCommand : public KisCommandUtils::FlipFlopCommand
{
public:
KisSimpleUpdateCommand(KisNodeList nodes, bool finalize, KUndo2Command *parent = 0);
void partB() override;
static void updateNodes(const KisNodeList &nodes);
private:
KisNodeList m_nodes;
};
template
bool checkNodesDiffer(KisNodeList nodes, std::function checkerFunc)
{
bool valueDiffers = false;
bool initialized = false;
T currentValue = T();
Q_FOREACH (KisNodeSP node, nodes) {
if (!initialized) {
currentValue = checkerFunc(node);
initialized = true;
} else if (currentValue != checkerFunc(node)) {
valueDiffers = true;
break;
}
}
return valueDiffers;
}
/**
* Applies \p func to \p node and all its children recursively
*/
template
void recursiveApplyNodes(NodePointer node, Functor func)
{
func(node);
node = node->firstChild();
while (node) {
recursiveApplyNodes(node, func);
node = node->nextSibling();
}
}
/**
* Walks through \p node and all its children recursively until
* \p func returns true. When \p func returns true, the node is
* considered to be found, the search is stopped and the found
* node is returned to the caller.
*/
KisNodeSP KRITAIMAGE_EXPORT recursiveFindNode(KisNodeSP node, std::function func);
/**
* Recursively searches for a node with specified Uuid
*/
KisNodeSP KRITAIMAGE_EXPORT findNodeByUuid(KisNodeSP root, const QUuid &uuid);
KisImageSP KRITAIMAGE_EXPORT findImageByHierarchy(KisNodeSP node);
template
T* findNodeByType(KisNodeSP root) {
return dynamic_cast(recursiveFindNode(root, [] (KisNodeSP node) {
return bool(dynamic_cast(node.data()));
}).data());
}
}
#endif /* __KIS_LAYER_UTILS_H */
diff --git a/libs/ui/kis_layer_manager.cc b/libs/ui/kis_layer_manager.cc
index d49fe4b799..b2cadff1a5 100644
--- a/libs/ui/kis_layer_manager.cc
+++ b/libs/ui/kis_layer_manager.cc
@@ -1,1007 +1,1011 @@
/*
* Copyright (C) 2006 Boudewijn Rempt
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_layer_manager.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kis_config.h"
#include "kis_cursor.h"
#include "dialogs/kis_dlg_adj_layer_props.h"
#include "dialogs/kis_dlg_adjustment_layer.h"
#include "dialogs/kis_dlg_layer_properties.h"
#include "dialogs/kis_dlg_generator_layer.h"
#include "dialogs/kis_dlg_file_layer.h"
#include "dialogs/kis_dlg_layer_style.h"
#include "dialogs/KisDlgChangeCloneSource.h"
#include "kis_filter_manager.h"
#include "kis_node_visitor.h"
#include "kis_paint_layer.h"
#include "commands/kis_image_commands.h"
#include "commands/kis_node_commands.h"
#include "kis_change_file_layer_command.h"
#include "kis_canvas_resource_provider.h"
#include "kis_selection_manager.h"
#include "kis_statusbar.h"
#include "KisViewManager.h"
#include "kis_zoom_manager.h"
#include "canvas/kis_canvas2.h"
#include "widgets/kis_meta_data_merge_strategy_chooser_widget.h"
#include "widgets/kis_wdg_generator.h"
#include "kis_progress_widget.h"
#include "kis_node_commands_adapter.h"
#include "kis_node_manager.h"
#include "kis_action.h"
#include "kis_action_manager.h"
#include "kis_raster_keyframe_channel.h"
#include "kis_signal_compressor_with_param.h"
#include "kis_abstract_projection_plane.h"
#include "commands_new/kis_set_layer_style_command.h"
#include "kis_post_execution_undo_adapter.h"
#include "kis_selection_mask.h"
#include "kis_layer_utils.h"
#include "lazybrush/kis_colorize_mask.h"
#include "kis_processing_applicator.h"
#include "KisSaveGroupVisitor.h"
KisLayerManager::KisLayerManager(KisViewManager * view)
: m_view(view)
, m_imageView(0)
, m_imageFlatten(0)
, m_imageMergeLayer(0)
, m_groupLayersSave(0)
, m_imageResizeToLayer(0)
, m_flattenLayer(0)
, m_rasterizeLayer(0)
, m_commandsAdapter(new KisNodeCommandsAdapter(m_view))
, m_layerStyle(0)
{
}
KisLayerManager::~KisLayerManager()
{
delete m_commandsAdapter;
}
void KisLayerManager::setView(QPointerview)
{
m_imageView = view;
}
KisLayerSP KisLayerManager::activeLayer()
{
if (m_imageView) {
return m_imageView->currentLayer();
}
return 0;
}
KisPaintDeviceSP KisLayerManager::activeDevice()
{
if (activeLayer()) {
return activeLayer()->paintDevice();
}
return 0;
}
void KisLayerManager::activateLayer(KisLayerSP layer)
{
if (m_imageView) {
emit sigLayerActivated(layer);
layersUpdated();
if (layer) {
m_view->canvasResourceProvider()->slotNodeActivated(layer.data());
}
}
}
void KisLayerManager::setup(KisActionManager* actionManager)
{
m_imageFlatten = actionManager->createAction("flatten_image");
connect(m_imageFlatten, SIGNAL(triggered()), this, SLOT(flattenImage()));
m_imageMergeLayer = actionManager->createAction("merge_layer");
connect(m_imageMergeLayer, SIGNAL(triggered()), this, SLOT(mergeLayer()));
m_flattenLayer = actionManager->createAction("flatten_layer");
connect(m_flattenLayer, SIGNAL(triggered()), this, SLOT(flattenLayer()));
m_rasterizeLayer = actionManager->createAction("rasterize_layer");
connect(m_rasterizeLayer, SIGNAL(triggered()), this, SLOT(rasterizeLayer()));
m_groupLayersSave = actionManager->createAction("save_groups_as_images");
connect(m_groupLayersSave, SIGNAL(triggered()), this, SLOT(saveGroupLayers()));
m_convertGroupAnimated = actionManager->createAction("convert_group_to_animated");
connect(m_convertGroupAnimated, SIGNAL(triggered()), this, SLOT(convertGroupToAnimated()));
m_imageResizeToLayer = actionManager->createAction("resizeimagetolayer");
connect(m_imageResizeToLayer, SIGNAL(triggered()), this, SLOT(imageResizeToActiveLayer()));
KisAction *action = actionManager->createAction("trim_to_image");
connect(action, SIGNAL(triggered()), this, SLOT(trimToImage()));
m_layerStyle = actionManager->createAction("layer_style");
connect(m_layerStyle, SIGNAL(triggered()), this, SLOT(layerStyle()));
}
void KisLayerManager::updateGUI()
{
KisImageSP image = m_view->image();
KisLayerSP layer = activeLayer();
const bool isGroupLayer = layer && layer->inherits("KisGroupLayer");
m_imageMergeLayer->setText(
isGroupLayer ?
i18nc("@action:inmenu", "Merge Group") :
i18nc("@action:inmenu", "Merge with Layer Below"));
m_flattenLayer->setVisible(!isGroupLayer);
if (m_view->statusBar())
m_view->statusBar()->setProfile(image);
}
void KisLayerManager::imageResizeToActiveLayer()
{
KisLayerSP layer;
KisImageWSP image = m_view->image();
if (image && (layer = activeLayer())) {
QRect cropRect = layer->projection()->nonDefaultPixelArea();
if (!cropRect.isEmpty()) {
image->cropImage(cropRect);
} else {
m_view->showFloatingMessage(
i18nc("floating message in layer manager",
"Layer is empty "),
QIcon(), 2000, KisFloatingMessage::Low);
}
}
}
void KisLayerManager::trimToImage()
{
KisImageWSP image = m_view->image();
if (image) {
image->cropImage(image->bounds());
}
}
void KisLayerManager::layerProperties()
{
if (!m_view) return;
if (!m_view->document()) return;
KisLayerSP layer = activeLayer();
if (!layer) return;
QList selectedNodes = m_view->nodeManager()->selectedNodes();
const bool multipleLayersSelected = selectedNodes.size() > 1;
KisAdjustmentLayerSP adjustmentLayer = KisAdjustmentLayerSP(dynamic_cast(layer.data()));
KisGeneratorLayerSP generatorLayer = KisGeneratorLayerSP(dynamic_cast(layer.data()));
KisFileLayerSP fileLayer = KisFileLayerSP(dynamic_cast(layer.data()));
if (adjustmentLayer && !multipleLayersSelected) {
KisPaintDeviceSP dev = adjustmentLayer->projection();
KisDlgAdjLayerProps dlg(adjustmentLayer, adjustmentLayer.data(), dev, m_view, adjustmentLayer->filter().data(), adjustmentLayer->name(), i18n("Filter Layer Properties"), m_view->mainWindow(), "dlgadjlayerprops");
dlg.resize(dlg.minimumSizeHint());
KisFilterConfigurationSP configBefore(adjustmentLayer->filter());
KIS_ASSERT_RECOVER_RETURN(configBefore);
QString xmlBefore = configBefore->toXML();
if (dlg.exec() == QDialog::Accepted) {
adjustmentLayer->setName(dlg.layerName());
KisFilterConfigurationSP configAfter(dlg.filterConfiguration());
Q_ASSERT(configAfter);
QString xmlAfter = configAfter->toXML();
if(xmlBefore != xmlAfter) {
KisChangeFilterCmd *cmd
= new KisChangeFilterCmd(adjustmentLayer,
configBefore->name(),
xmlBefore,
configAfter->name(),
xmlAfter,
false);
// FIXME: check whether is needed
cmd->redo();
m_view->undoAdapter()->addCommand(cmd);
m_view->document()->setModified(true);
}
}
else {
KisFilterConfigurationSP configAfter(dlg.filterConfiguration());
Q_ASSERT(configAfter);
QString xmlAfter = configAfter->toXML();
if(xmlBefore != xmlAfter) {
adjustmentLayer->setFilter(KisFilterRegistry::instance()->cloneConfiguration(configBefore.data()));
adjustmentLayer->setDirty();
}
}
}
else if (generatorLayer && !multipleLayersSelected) {
KisFilterConfigurationSP configBefore(generatorLayer->filter());
Q_ASSERT(configBefore);
QString xmlBefore = configBefore->toXML();
KisDlgGeneratorLayer *dlg = new KisDlgGeneratorLayer(generatorLayer->name(), m_view, m_view->mainWindow(), generatorLayer, configBefore);
dlg->setCaption(i18n("Fill Layer Properties"));
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->setConfiguration(configBefore.data());
dlg->resize(dlg->minimumSizeHint());
Qt::WindowFlags flags = dlg->windowFlags();
dlg->setWindowFlags(flags | Qt::Tool | Qt::Dialog);
dlg->show();
}
else if (fileLayer && !multipleLayersSelected){
QString basePath = QFileInfo(m_view->document()->url().toLocalFile()).absolutePath();
QString fileNameOld = fileLayer->fileName();
KisFileLayer::ScalingMethod scalingMethodOld = fileLayer->scalingMethod();
KisDlgFileLayer dlg(basePath, fileLayer->name(), m_view->mainWindow());
dlg.setCaption(i18n("File Layer Properties"));
dlg.setFileName(fileNameOld);
dlg.setScalingMethod(scalingMethodOld);
if (dlg.exec() == QDialog::Accepted) {
const QString fileNameNew = dlg.fileName();
KisFileLayer::ScalingMethod scalingMethodNew = dlg.scaleToImageResolution();
if(fileNameNew.isEmpty()){
QMessageBox::critical(m_view->mainWindow(), i18nc("@title:window", "Krita"), i18n("No file name specified"));
return;
}
fileLayer->setName(dlg.layerName());
if (fileNameOld!= fileNameNew || scalingMethodOld != scalingMethodNew) {
KisChangeFileLayerCmd *cmd
= new KisChangeFileLayerCmd(fileLayer,
basePath,
fileNameOld,
scalingMethodOld,
basePath,
fileNameNew,
scalingMethodNew);
m_view->undoAdapter()->addCommand(cmd);
}
}
} else { // If layer == normal painting layer, vector layer, or group layer
QList selectedNodes = m_view->nodeManager()->selectedNodes();
KisDlgLayerProperties *dialog = new KisDlgLayerProperties(selectedNodes, m_view);
dialog->resize(dialog->minimumSizeHint());
dialog->setAttribute(Qt::WA_DeleteOnClose);
Qt::WindowFlags flags = dialog->windowFlags();
dialog->setWindowFlags(flags | Qt::Tool | Qt::Dialog);
dialog->show();
}
}
void KisLayerManager::changeCloneSource()
{
QList selectedNodes = m_view->nodeManager()->selectedNodes();
if (selectedNodes.isEmpty()) {
return;
}
QList cloneLayers;
KisNodeSP node;
Q_FOREACH (node, selectedNodes) {
KisCloneLayerSP cloneLayer(qobject_cast(node.data()));
if (cloneLayer) {
cloneLayers << cloneLayer;
}
}
if (cloneLayers.isEmpty()) {
return;
}
KisDlgChangeCloneSource *dialog = new KisDlgChangeCloneSource(cloneLayers, m_view);
dialog->setCaption(i18n("Change Clone Layer"));
dialog->resize(dialog->minimumSizeHint());
dialog->setAttribute(Qt::WA_DeleteOnClose);
Qt::WindowFlags flags = dialog->windowFlags();
dialog->setWindowFlags(flags | Qt::Tool | Qt::Dialog);
dialog->show();
}
void KisLayerManager::convertNodeToPaintLayer(KisNodeSP source)
{
KisImageWSP image = m_view->image();
if (!image) return;
KisLayer *srcLayer = qobject_cast(source.data());
if (srcLayer && (srcLayer->inherits("KisGroupLayer") || srcLayer->layerStyle() || srcLayer->childCount() > 0)) {
image->flattenLayer(srcLayer);
return;
}
KisPaintDeviceSP srcDevice =
source->paintDevice() ? source->projection() : source->original();
bool putBehind = false;
QString newCompositeOp = source->compositeOpId();
KisColorizeMask *colorizeMask = dynamic_cast(source.data());
if (colorizeMask) {
srcDevice = colorizeMask->coloringProjection();
putBehind = colorizeMask->compositeOpId() == COMPOSITE_BEHIND;
if (putBehind) {
newCompositeOp = COMPOSITE_OVER;
}
}
if (!srcDevice) return;
KisPaintDeviceSP clone;
if (*srcDevice->colorSpace() !=
*srcDevice->compositionSourceColorSpace()) {
clone = new KisPaintDevice(srcDevice->compositionSourceColorSpace());
QRect rc(srcDevice->extent());
KisPainter::copyAreaOptimized(rc.topLeft(), srcDevice, clone, rc);
} else {
clone = new KisPaintDevice(*srcDevice);
}
KisLayerSP layer = new KisPaintLayer(image,
source->name(),
source->opacity(),
clone);
layer->setCompositeOpId(newCompositeOp);
KisNodeSP parent = source->parent();
KisNodeSP above = source->prevSibling();
while (parent && !parent->allowAsChild(layer)) {
above = above ? above->parent() : source->parent();
parent = above ? above->parent() : 0;
}
if (putBehind && above == source->parent()) {
above = above->prevSibling();
}
m_commandsAdapter->beginMacro(kundo2_i18n("Convert to a Paint Layer"));
m_commandsAdapter->removeNode(source);
m_commandsAdapter->addNode(layer, parent, above);
m_commandsAdapter->endMacro();
}
void KisLayerManager::convertGroupToAnimated()
{
KisGroupLayerSP group = dynamic_cast(activeLayer().data());
if (group.isNull()) return;
KisPaintLayerSP animatedLayer = new KisPaintLayer(m_view->image(), group->name(), OPACITY_OPAQUE_U8);
animatedLayer->enableAnimation();
KisRasterKeyframeChannel *contentChannel = dynamic_cast(
animatedLayer->getKeyframeChannel(KisKeyframeChannel::Content.id(), true));
KIS_ASSERT_RECOVER_RETURN(contentChannel);
KisNodeSP child = group->firstChild();
int time = 0;
while (child) {
contentChannel->importFrame(time, child->projection(), NULL);
time++;
child = child->nextSibling();
}
m_commandsAdapter->beginMacro(kundo2_i18n("Convert to an animated layer"));
m_commandsAdapter->addNode(animatedLayer, group->parent(), group);
m_commandsAdapter->removeNode(group);
m_commandsAdapter->endMacro();
}
void KisLayerManager::convertLayerToFileLayer(KisNodeSP source)
{
KisImageSP image = m_view->image();
if (!image) return;
QStringList listMimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export);
KoDialog dlg;
QWidget *page = new QWidget(&dlg);
dlg.setMainWidget(page);
QBoxLayout *layout = new QVBoxLayout(page);
dlg.setWindowTitle(i18n("Save layers to..."));
QLabel *lbl = new QLabel(i18n("Choose the location where the layer will be saved to. The new file layer will then reference this location."));
lbl->setWordWrap(true);
layout->addWidget(lbl);
KisFileNameRequester *urlRequester = new KisFileNameRequester(page);
urlRequester->setMode(KoFileDialog::SaveFile);
urlRequester->setMimeTypeFilters(listMimeFilter);
urlRequester->setFileName(m_view->document()->url().toLocalFile());
if (m_view->document()->url().isLocalFile()) {
QFileInfo location = QFileInfo(m_view->document()->url().toLocalFile()).completeBaseName();
location.setFile(location.dir(), location.completeBaseName() + "_" + source->name() + ".png");
urlRequester->setFileName(location.absoluteFilePath());
}
else {
const QFileInfo location = QFileInfo(QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
const QString proposedFileName = QDir(location.absoluteFilePath()).absoluteFilePath(source->name() + ".png");
urlRequester->setFileName(proposedFileName);
}
// We don't want .kra files as file layers, Krita cannot handle the load.
QStringList mimes = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export);
int i = mimes.indexOf(KIS_MIME_TYPE);
if (i >= 0 && i < mimes.size()) {
mimes.removeAt(i);
}
urlRequester->setMimeTypeFilters(mimes);
layout->addWidget(urlRequester);
if (!dlg.exec()) return;
QString path = urlRequester->fileName();
if (path.isEmpty()) return;
QFileInfo f(path);
QString mimeType= KisMimeDatabase::mimeTypeForFile(f.fileName());
if (mimeType.isEmpty()) {
mimeType = "image/png";
}
QScopedPointer doc(KisPart::instance()->createDocument());
QRect bounds = source->exactBounds();
if (bounds.isEmpty()) {
bounds = image->bounds();
}
KisImageSP dst = new KisImage(doc->createUndoStore(),
image->width(),
image->height(),
image->projection()->compositionSourceColorSpace(),
source->name());
dst->setResolution(image->xRes(), image->yRes());
doc->setFileBatchMode(false);
doc->setCurrentImage(dst);
KisNodeSP node = source->clone();
dst->addNode(node);
dst->initialRefreshGraph();
dst->cropImage(bounds);
dst->waitForDone();
bool r = doc->exportDocumentSync(QUrl::fromLocalFile(path), mimeType.toLatin1());
if (!r) {
qWarning() << "Converting layer to file layer. path:"<< path << "gave errors" << doc->errorMessage();
} else {
QString basePath = QFileInfo(m_view->document()->url().toLocalFile()).absolutePath();
QString relativePath = QDir(basePath).relativeFilePath(path);
KisFileLayer *fileLayer = new KisFileLayer(image, basePath, relativePath, KisFileLayer::None, source->name(), OPACITY_OPAQUE_U8);
fileLayer->setX(bounds.x());
fileLayer->setY(bounds.y());
KisNodeSP dstParent = source->parent();
KisNodeSP dstAboveThis = source->prevSibling();
m_commandsAdapter->beginMacro(kundo2_i18n("Convert to a file layer"));
m_commandsAdapter->removeNode(source);
m_commandsAdapter->addNode(fileLayer, dstParent, dstAboveThis);
m_commandsAdapter->endMacro();
}
doc->closeUrl(false);
}
void KisLayerManager::adjustLayerPosition(KisNodeSP node, KisNodeSP activeNode, KisNodeSP &parent, KisNodeSP &above)
{
Q_ASSERT(activeNode);
parent = activeNode;
above = parent->lastChild();
if (parent->inherits("KisGroupLayer") && parent->collapsed()) {
above = parent;
parent = parent->parent();
return;
}
while (parent &&
(!parent->allowAsChild(node) || parent->userLocked())) {
above = parent;
parent = parent->parent();
}
if (!parent) {
warnKrita << "KisLayerManager::adjustLayerPosition:"
<< "No node accepted newly created node";
parent = m_view->image()->root();
above = parent->lastChild();
}
}
void KisLayerManager::addLayerCommon(KisNodeSP activeNode, KisNodeSP layer, bool updateImage, KisProcessingApplicator *applicator)
{
KisNodeSP parent;
KisNodeSP above;
adjustLayerPosition(layer, activeNode, parent, above);
KisGroupLayer *group = dynamic_cast(parent.data());
const bool parentForceUpdate = group && !group->projectionIsValid();
updateImage |= parentForceUpdate;
m_commandsAdapter->addNodeAsync(layer, parent, above, updateImage, updateImage, applicator);
}
KisLayerSP KisLayerManager::addPaintLayer(KisNodeSP activeNode)
{
KisImageWSP image = m_view->image();
KisLayerSP layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, image->colorSpace());
addLayerCommon(activeNode, layer, false, 0);
return layer;
}
KisNodeSP KisLayerManager::addGroupLayer(KisNodeSP activeNode)
{
KisImageWSP image = m_view->image();
KisGroupLayerSP group = new KisGroupLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8);
addLayerCommon(activeNode, group, false, 0);
return group;
}
-KisNodeSP KisLayerManager::addCloneLayer(KisNodeSP activeNode)
+KisNodeSP KisLayerManager::addCloneLayer(KisNodeList nodes)
{
KisImageWSP image = m_view->image();
- KisNodeList selection = m_view->nodeManager()->selectedNodes();
- KisNodeSP node, clonedNode;
- Q_FOREACH (node, selection) {
- KisNodeSP clonedNode = new KisCloneLayer(qobject_cast(node.data()), image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8);
- addLayerCommon(activeNode, clonedNode, true, 0 );
+ KisNodeList filteredNodes = KisLayerUtils::sortAndFilterMergableInternalNodes(nodes, false);
+ if (filteredNodes.isEmpty()) return KisNodeSP();
+
+ KisNodeSP newAbove = filteredNodes.last();
+
+ KisNodeSP node, lastClonedNode;
+ Q_FOREACH (node, filteredNodes) {
+ lastClonedNode = new KisCloneLayer(qobject_cast(node.data()), image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8);
+ addLayerCommon(newAbove, lastClonedNode, true, 0 );
}
- return clonedNode;
+ return lastClonedNode;
}
KisNodeSP KisLayerManager::addShapeLayer(KisNodeSP activeNode)
{
if (!m_view) return 0;
if (!m_view->document()) return 0;
KisImageWSP image = m_view->image();
KisShapeLayerSP layer = new KisShapeLayer(m_view->document()->shapeController(), image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8);
addLayerCommon(activeNode, layer, false, 0);
return layer;
}
KisNodeSP KisLayerManager::addAdjustmentLayer(KisNodeSP activeNode)
{
KisImageWSP image = m_view->image();
KisSelectionSP selection = m_view->selection();
KisProcessingApplicator applicator(image, 0, KisProcessingApplicator::NONE,
KisImageSignalVector() << ModifiedSignal,
kundo2_i18n("Add Layer"));
KisAdjustmentLayerSP adjl = addAdjustmentLayer(activeNode, QString(), 0, selection, &applicator);
KisPaintDeviceSP previewDevice = new KisPaintDevice(*adjl->original());
KisDlgAdjustmentLayer dlg(adjl, adjl.data(), previewDevice, image->nextLayerName(), i18n("New Filter Layer"), m_view, qApp->activeWindow());
dlg.resize(dlg.minimumSizeHint());
// ensure that the device may be free'd by the dialog
// when it is not needed anymore
previewDevice = 0;
if (dlg.exec() != QDialog::Accepted || adjl->filter().isNull()) {
// XXX: add messagebox warning if there's no filter set!
applicator.cancel();
} else {
adjl->setName(dlg.layerName());
applicator.end();
}
return adjl;
}
KisAdjustmentLayerSP KisLayerManager::addAdjustmentLayer(KisNodeSP activeNode, const QString & name,
KisFilterConfigurationSP filter,
KisSelectionSP selection,
KisProcessingApplicator *applicator)
{
KisImageWSP image = m_view->image();
KisAdjustmentLayerSP layer = new KisAdjustmentLayer(image, name, filter, selection);
addLayerCommon(activeNode, layer, true, applicator);
return layer;
}
KisNodeSP KisLayerManager::addGeneratorLayer(KisNodeSP activeNode)
{
KisImageWSP image = m_view->image();
QColor currentForeground = m_view->canvasResourceProvider()->fgColor().toQColor();
KisDlgGeneratorLayer dlg(image->nextLayerName(), m_view, m_view->mainWindow(), 0, 0);
KisFilterConfigurationSP defaultConfig = dlg.configuration();
defaultConfig->setProperty("color", currentForeground);
dlg.setConfiguration(defaultConfig);
dlg.resize(dlg.minimumSizeHint());
if (dlg.exec() == QDialog::Accepted) {
KisSelectionSP selection = m_view->selection();
KisFilterConfigurationSP generator = dlg.configuration();
QString name = dlg.layerName();
KisNodeSP node = new KisGeneratorLayer(image, name, generator, selection);
addLayerCommon(activeNode, node, true, 0);
return node;
}
return 0;
}
void KisLayerManager::flattenImage()
{
KisImageSP image = m_view->image();
if (!m_view->blockUntilOperationsFinished(image)) return;
if (image) {
bool doIt = true;
if (image->nHiddenLayers() > 0) {
int answer = QMessageBox::warning(m_view->mainWindow(),
i18nc("@title:window", "Flatten Image"),
i18n("The image contains hidden layers that will be lost. Do you want to flatten the image?"),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No);
if (answer != QMessageBox::Yes) {
doIt = false;
}
}
if (doIt) {
image->flatten(m_view->activeNode());
}
}
}
inline bool isSelectionMask(KisNodeSP node) {
return dynamic_cast(node.data());
}
bool tryMergeSelectionMasks(KisNodeSP currentNode, KisImageSP image)
{
bool result = false;
KisNodeSP prevNode = currentNode->prevSibling();
if (isSelectionMask(currentNode) &&
prevNode && isSelectionMask(prevNode)) {
QList mergedNodes;
mergedNodes.append(currentNode);
mergedNodes.append(prevNode);
image->mergeMultipleLayers(mergedNodes, currentNode);
result = true;
}
return result;
}
bool tryFlattenGroupLayer(KisNodeSP currentNode, KisImageSP image)
{
bool result = false;
if (currentNode->inherits("KisGroupLayer")) {
KisGroupLayer *layer = qobject_cast(currentNode.data());
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(layer, false);
image->flattenLayer(layer);
result = true;
}
return result;
}
void KisLayerManager::mergeLayer()
{
KisImageSP image = m_view->image();
if (!image) return;
KisLayerSP layer = activeLayer();
if (!layer) return;
if (!m_view->blockUntilOperationsFinished(image)) return;
QList selectedNodes = m_view->nodeManager()->selectedNodes();
if (selectedNodes.size() > 1) {
image->mergeMultipleLayers(selectedNodes, m_view->activeNode());
}
else if (tryMergeSelectionMasks(m_view->activeNode(), image)) {
// already done!
} else if (tryFlattenGroupLayer(m_view->activeNode(), image)) {
// already done!
} else {
if (!layer->prevSibling()) return;
KisLayer *prevLayer = qobject_cast(layer->prevSibling().data());
if (!prevLayer) return;
if (prevLayer->userLocked()) {
m_view->showFloatingMessage(
i18nc("floating message in layer manager",
"Layer is locked "),
QIcon(), 2000, KisFloatingMessage::Low);
}
else if (layer->metaData()->isEmpty() && prevLayer->metaData()->isEmpty()) {
image->mergeDown(layer, KisMetaData::MergeStrategyRegistry::instance()->get("Drop"));
}
else {
const KisMetaData::MergeStrategy* strategy = KisMetaDataMergeStrategyChooserWidget::showDialog(m_view->mainWindow());
if (!strategy) return;
image->mergeDown(layer, strategy);
}
}
m_view->updateGUI();
}
void KisLayerManager::flattenLayer()
{
KisImageSP image = m_view->image();
if (!image) return;
KisLayerSP layer = activeLayer();
if (!layer) return;
if (!m_view->blockUntilOperationsFinished(image)) return;
convertNodeToPaintLayer(layer);
m_view->updateGUI();
}
void KisLayerManager::rasterizeLayer()
{
KisImageSP image = m_view->image();
if (!image) return;
KisLayerSP layer = activeLayer();
if (!layer) return;
if (!m_view->blockUntilOperationsFinished(image)) return;
KisPaintLayerSP paintLayer = new KisPaintLayer(image, layer->name(), layer->opacity());
KisPainter gc(paintLayer->paintDevice());
QRect rc = layer->projection()->exactBounds();
gc.bitBlt(rc.topLeft(), layer->projection(), rc);
m_commandsAdapter->beginMacro(kundo2_i18n("Rasterize Layer"));
m_commandsAdapter->addNode(paintLayer.data(), layer->parent().data(), layer.data());
int childCount = layer->childCount();
for (int i = 0; i < childCount; i++) {
m_commandsAdapter->moveNode(layer->firstChild(), paintLayer, paintLayer->lastChild());
}
m_commandsAdapter->removeNode(layer);
m_commandsAdapter->endMacro();
updateGUI();
}
void KisLayerManager::layersUpdated()
{
KisLayerSP layer = activeLayer();
if (!layer) return;
m_view->updateGUI();
}
void KisLayerManager::saveGroupLayers()
{
QStringList listMimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export);
KoDialog dlg;
QWidget *page = new QWidget(&dlg);
dlg.setMainWidget(page);
QBoxLayout *layout = new QVBoxLayout(page);
KisFileNameRequester *urlRequester = new KisFileNameRequester(page);
urlRequester->setMode(KoFileDialog::SaveFile);
if (m_view->document()->url().isLocalFile()) {
urlRequester->setStartDir(QFileInfo(m_view->document()->url().toLocalFile()).absolutePath());
}
urlRequester->setMimeTypeFilters(listMimeFilter);
urlRequester->setFileName(m_view->document()->url().toLocalFile());
layout->addWidget(urlRequester);
QCheckBox *chkInvisible = new QCheckBox(i18n("Convert Invisible Groups"), page);
chkInvisible->setChecked(false);
layout->addWidget(chkInvisible);
QCheckBox *chkDepth = new QCheckBox(i18n("Export Only Toplevel Groups"), page);
chkDepth->setChecked(true);
layout->addWidget(chkDepth);
if (!dlg.exec()) return;
QString path = urlRequester->fileName();
if (path.isEmpty()) return;
QFileInfo f(path);
QString mimeType= KisMimeDatabase::mimeTypeForFile(f.fileName(), false);
if (mimeType.isEmpty()) {
mimeType = "image/png";
}
QString extension = KisMimeDatabase::suffixesForMimeType(mimeType).first();
QString basename = f.completeBaseName();
KisImageSP image = m_view->image();
if (!image) return;
KisSaveGroupVisitor v(image, chkInvisible->isChecked(), chkDepth->isChecked(), f.absolutePath(), basename, extension, mimeType);
image->rootLayer()->accept(v);
}
bool KisLayerManager::activeLayerHasSelection()
{
return (activeLayer()->selection() != 0);
}
KisNodeSP KisLayerManager::addFileLayer(KisNodeSP activeNode)
{
QString basePath;
QUrl url = m_view->document()->url();
if (url.isLocalFile()) {
basePath = QFileInfo(url.toLocalFile()).absolutePath();
}
KisImageWSP image = m_view->image();
KisDlgFileLayer dlg(basePath, image->nextLayerName(), m_view->mainWindow());
dlg.resize(dlg.minimumSizeHint());
if (dlg.exec() == QDialog::Accepted) {
QString name = dlg.layerName();
QString fileName = dlg.fileName();
if(fileName.isEmpty()){
QMessageBox::critical(m_view->mainWindow(), i18nc("@title:window", "Krita"), i18n("No file name specified"));
return 0;
}
KisFileLayer::ScalingMethod scalingMethod = dlg.scaleToImageResolution();
KisNodeSP node = new KisFileLayer(image, basePath, fileName, scalingMethod, name, OPACITY_OPAQUE_U8);
addLayerCommon(activeNode, node, true, 0);
return node;
}
return 0;
}
void updateLayerStyles(KisLayerSP layer, KisDlgLayerStyle *dlg)
{
KisSetLayerStyleCommand::updateLayerStyle(layer, dlg->style()->clone());
}
void KisLayerManager::layerStyle()
{
KisImageWSP image = m_view->image();
if (!image) return;
KisLayerSP layer = activeLayer();
if (!layer) return;
if (!m_view->blockUntilOperationsFinished(image)) return;
KisPSDLayerStyleSP oldStyle;
if (layer->layerStyle()) {
oldStyle = layer->layerStyle()->clone();
}
else {
oldStyle = toQShared(new KisPSDLayerStyle());
}
KisDlgLayerStyle dlg(oldStyle->clone(), m_view->canvasResourceProvider());
std::function updateCall(std::bind(updateLayerStyles, layer, &dlg));
SignalToFunctionProxy proxy(updateCall);
connect(&dlg, SIGNAL(configChanged()), &proxy, SLOT(start()));
if (dlg.exec() == QDialog::Accepted) {
KisPSDLayerStyleSP newStyle = dlg.style();
KUndo2CommandSP command = toQShared(
new KisSetLayerStyleCommand(layer, oldStyle, newStyle));
image->postExecutionUndoAdapter()->addCommand(command);
}
}
diff --git a/libs/ui/kis_layer_manager.h b/libs/ui/kis_layer_manager.h
index 6bc545405a..d938ef0cf4 100644
--- a/libs/ui/kis_layer_manager.h
+++ b/libs/ui/kis_layer_manager.h
@@ -1,136 +1,136 @@
/*
* Copyright (C) 2006 Boudewijn Rempt
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_LAYER_MANAGER
#define KIS_LAYER_MANAGER
#include
#include
#include
#include "kis_adjustment_layer.h"
#include "kis_types.h"
#include "KisView.h"
#include
class KisViewManager;
class KisNodeCommandsAdapter;
class KisAction;
class KisActionManager;
class KisProcessingApplicator;
/**
* KisLayerManager takes care of the gui around working with layers:
* adding, removing, editing. It also keeps track of the active layer
* for this view.
*/
class KisLayerManager : public QObject
{
Q_OBJECT
public:
KisLayerManager(KisViewManager * view);
~KisLayerManager() override;
void setView(QPointerview);
Q_SIGNALS:
void sigLayerActivated(KisLayerSP layer);
private:
friend class KisNodeManager;
/**
* Activate the specified layer. The layer may be 0.
*/
void activateLayer(KisLayerSP layer);
KisLayerSP activeLayer();
KisPaintDeviceSP activeDevice();
void setup(KisActionManager *actionManager);
void updateGUI();
private Q_SLOTS:
void mergeLayer();
void imageResizeToActiveLayer();
void trimToImage();
void layerProperties();
void flattenImage();
void flattenLayer();
void rasterizeLayer();
void layersUpdated();
void saveGroupLayers();
bool activeLayerHasSelection();
void convertNodeToPaintLayer(KisNodeSP source);
void convertGroupToAnimated();
void convertLayerToFileLayer(KisNodeSP source);
KisLayerSP addPaintLayer(KisNodeSP activeNode);
KisNodeSP addGroupLayer(KisNodeSP activeNode);
- KisNodeSP addCloneLayer(KisNodeSP activeNode);
+ KisNodeSP addCloneLayer(KisNodeList nodes);
KisNodeSP addShapeLayer(KisNodeSP activeNode);
KisNodeSP addAdjustmentLayer(KisNodeSP activeNode);
KisAdjustmentLayerSP addAdjustmentLayer(KisNodeSP activeNode, const QString & name, KisFilterConfigurationSP filter, KisSelectionSP selection, KisProcessingApplicator *applicator);
KisNodeSP addGeneratorLayer(KisNodeSP activeNode);
KisNodeSP addFileLayer(KisNodeSP activeNode);
void layerStyle();
void changeCloneSource();
private:
void adjustLayerPosition(KisNodeSP node, KisNodeSP activeNode, KisNodeSP &parent, KisNodeSP &above);
void addLayerCommon(KisNodeSP activeNode, KisNodeSP layer, bool updateImage = true, KisProcessingApplicator *applicator = 0);
private:
KisViewManager * m_view;
QPointerm_imageView;
KisAction *m_imageFlatten;
KisAction *m_imageMergeLayer;
KisAction *m_groupLayersSave;
KisAction *m_convertGroupAnimated;
KisAction *m_imageResizeToLayer;
KisAction *m_flattenLayer;
KisAction *m_rasterizeLayer;
KisNodeCommandsAdapter* m_commandsAdapter;
KisAction *m_layerStyle;
};
#endif
diff --git a/libs/ui/kis_node_juggler_compressed.cpp b/libs/ui/kis_node_juggler_compressed.cpp
index 8809d2fa09..e1b830b57a 100644
--- a/libs/ui/kis_node_juggler_compressed.cpp
+++ b/libs/ui/kis_node_juggler_compressed.cpp
@@ -1,902 +1,880 @@
/*
* Copyright (c) 2015 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_node_juggler_compressed.h"
#include
#include
#include
#include "kis_global.h"
#include "kis_image.h"
#include "kis_processing_applicator.h"
#include "commands/kis_image_layer_move_command.h"
#include "commands/kis_image_layer_add_command.h"
#include "kis_signal_compressor.h"
#include "kis_command_utils.h"
#include "kis_layer_utils.h"
#include "kis_node_manager.h"
#include "kis_layer.h"
#include "kis_selection_mask.h"
/**
* A special structure that stores information about a node that was
* moved. The purpose of the object is twofold:
*
* 1) When the reordering stroke is already started than the
* parent and sibling nodes may be not consistent anymore. So
* we store it separately.
*
* 2) This objects allows merging (compressing) multiple moves of
* a layer into a single action. This behavior is implemented
* in tryMerge() method.
*/
struct MoveNodeStruct {
MoveNodeStruct(KisImageSP _image, KisNodeSP _node, KisNodeSP _parent, KisNodeSP _above)
: image(_image),
node(_node),
newParent(_parent),
newAbove(_above),
oldParent(_node->parent()),
oldAbove(_node->prevSibling()),
suppressNewParentRefresh(false),
suppressOldParentRefresh(false)
{
}
bool tryMerge(const MoveNodeStruct &rhs) {
if (rhs.node != node) return false;
bool result = true;
// qDebug() << "Merging";
// qDebug() << ppVar(node);
// qDebug() << ppVar(oldParent) << ppVar(newParent);
// qDebug() << ppVar(oldAbove) << ppVar(newAbove);
// qDebug() << ppVar(rhs.oldParent) << ppVar(rhs.newParent);
// qDebug() << ppVar(rhs.oldAbove) << ppVar(rhs.newAbove);
if (newParent == rhs.oldParent) {
// 'rhs' is newer
newParent = rhs.newParent;
newAbove = rhs.newAbove;
} else if (oldParent == rhs.newParent) {
// 'this' is newer
oldParent = rhs.oldParent;
oldAbove = rhs.oldAbove;
} else {
warnKrita << "MoveNodeStruct: Trying to merge unsequential moves!";
result = false;
}
return result;
}
void doRedoUpdates() {
if (oldParent && !suppressOldParentRefresh) {
image->refreshGraphAsync(oldParent);
}
if (newParent && oldParent != newParent) {
node->setDirty(image->bounds());
}
}
void doUndoUpdates() {
if (newParent && !suppressNewParentRefresh) {
image->refreshGraphAsync(newParent);
}
if (oldParent && oldParent != newParent) {
node->setDirty(image->bounds());
}
}
void resolveParentCollisions(MoveNodeStruct *rhs) const {
if (rhs->newParent == newParent) {
rhs->suppressNewParentRefresh = true;
}
if (rhs->oldParent == oldParent) {
rhs->suppressOldParentRefresh = true;
}
}
KisImageSP image;
KisNodeSP node;
KisNodeSP newParent;
KisNodeSP newAbove;
KisNodeSP oldParent;
KisNodeSP oldAbove;
bool suppressNewParentRefresh;
bool suppressOldParentRefresh;
};
typedef QSharedPointer MoveNodeStructSP;
typedef QHash MovedNodesHash;
/**
* All the commands executed bythe stroke system are running in the
* background asynchronously. But, at the same time, they emit updates
* in parallel to the ones emitted by the juggler. Therefore, the
* juggler and all its commands should share some data: which updates
* have been requested, but not yet dispatched (m_movedNodesInitial),
* and what updates have already been processed and executed
* (m_movedNodesUpdated). This object is shared via a shared pointer
* and guarantees safe (including thread-safe) access to the shared
* data.
*/
class BatchMoveUpdateData {
MovedNodesHash m_movedNodesInitial;
MovedNodesHash m_movedNodesUpdated;
QMutex m_mutex;
QPointer m_parentJuggler;
public:
BatchMoveUpdateData(KisNodeJugglerCompressed *parentJuggler)
: m_parentJuggler(parentJuggler) {}
private:
static void addToHashLazy(MovedNodesHash *hash, MoveNodeStructSP moveStruct) {
if (hash->contains(moveStruct->node)) {
bool result = hash->value(moveStruct->node)->tryMerge(*moveStruct);
KIS_ASSERT_RECOVER_NOOP(result);
} else {
MovedNodesHash::const_iterator it = hash->constBegin();
MovedNodesHash::const_iterator end = hash->constEnd();
for (; it != end; ++it) {
it.value()->resolveParentCollisions(moveStruct.data());
}
hash->insert(moveStruct->node, moveStruct);
}
}
public:
void processUnhandledUpdates() {
QMutexLocker l(&m_mutex);
if (m_movedNodesInitial.isEmpty()) return;
MovedNodesHash::const_iterator it = m_movedNodesInitial.constBegin();
MovedNodesHash::const_iterator end = m_movedNodesInitial.constEnd();
for (; it != end; ++it) {
it.value()->doRedoUpdates();
addToHashLazy(&m_movedNodesUpdated, it.value());
}
m_movedNodesInitial.clear();
}
void addInitialUpdate(MoveNodeStructSP moveStruct) {
{
QMutexLocker l(&m_mutex);
addToHashLazy(&m_movedNodesInitial, moveStruct);
// the juggler might directly forward the signal to processUnhandledUpdates,
// which would also like to get a lock, so we should release it beforehand
}
if (m_parentJuggler) {
emit m_parentJuggler->requestUpdateAsyncFromCommand();
}
}
void emitFinalUpdates(KisCommandUtils::FlipFlopCommand::State state) {
QMutexLocker l(&m_mutex);
if (m_movedNodesUpdated.isEmpty()) return;
MovedNodesHash::const_iterator it = m_movedNodesUpdated.constBegin();
MovedNodesHash::const_iterator end = m_movedNodesUpdated.constEnd();
for (; it != end; ++it) {
if (state == KisCommandUtils::FlipFlopCommand::State::FINALIZING) {
it.value()->doRedoUpdates();
} else {
it.value()->doUndoUpdates();
}
}
}
};
typedef QSharedPointer BatchMoveUpdateDataSP;
/**
* A command that emits a update signals on the compressed move undo
* or redo.
*/
class UpdateMovedNodesCommand : public KisCommandUtils::FlipFlopCommand
{
public:
UpdateMovedNodesCommand(BatchMoveUpdateDataSP updateData, bool finalize, KUndo2Command *parent = 0)
: FlipFlopCommand(finalize, parent),
m_updateData(updateData)
{
}
void partB() override {
State currentState = getState();
if (currentState == FINALIZING && isFirstRedo()) {
/**
* When doing the first redo() some of the updates might
* have already been executed by the juggler itself, so we
* should process'unhandled' updates only
*/
m_updateData->processUnhandledUpdates();
} else {
/**
* When being executed by real undo/redo operations, we
* should emit all the update signals. No one else will do
* that for us (juggler, which did it in the previous
* case, might have already died).
*/
m_updateData->emitFinalUpdates(currentState);
}
}
private:
BatchMoveUpdateDataSP m_updateData;
};
/**
* A command to activate newly created selection masks after any action
*/
class ActivateSelectionMasksCommand : public KisCommandUtils::FlipFlopCommand
{
public:
ActivateSelectionMasksCommand(const QList &activeBefore,
const QList &activeAfter,
bool finalize, KUndo2Command *parent = 0)
: FlipFlopCommand(finalize, parent),
m_activeBefore(activeBefore),
m_activeAfter(activeAfter)
{
}
void partA() override {
QList *newActiveMasks;
if (getState() == FINALIZING) {
newActiveMasks = &m_activeAfter;
} else {
newActiveMasks = &m_activeBefore;
}
Q_FOREACH (KisSelectionMaskSP mask, *newActiveMasks) {
mask->setActive(false);
}
}
void partB() override {
QList *newActiveMasks;
if (getState() == FINALIZING) {
newActiveMasks = &m_activeAfter;
} else {
newActiveMasks = &m_activeBefore;
}
Q_FOREACH (KisSelectionMaskSP mask, *newActiveMasks) {
mask->setActive(true);
}
}
private:
QList m_activeBefore;
QList m_activeAfter;
};
-KisNodeList sortAndFilterNodes(const KisNodeList &nodes, KisImageSP image) {
- KisNodeList filteredNodes = nodes;
- KisNodeList sortedNodes;
-
- KisLayerUtils::filterMergableNodes(filteredNodes, true);
-
- bool haveExternalNodes = false;
- Q_FOREACH (KisNodeSP node, nodes) {
- if (node->graphListener() != image->root()->graphListener()) {
- haveExternalNodes = true;
- break;
- }
- }
-
- if (!haveExternalNodes) {
- KisLayerUtils::sortMergableNodes(image->root(), filteredNodes, sortedNodes);
- } else {
- sortedNodes = filteredNodes;
- }
-
- return sortedNodes;
-}
/**
* A generalized command to muve up/down a set of layer
*/
struct LowerRaiseLayer : public KisCommandUtils::AggregateCommand {
LowerRaiseLayer(BatchMoveUpdateDataSP updateData,
KisImageSP image,
const KisNodeList &nodes,
KisNodeSP activeNode,
bool lower)
: m_updateData(updateData),
m_image(image),
m_nodes(nodes),
m_activeNode(activeNode),
m_lower (lower) {}
enum NodesType {
AllLayers,
Mixed,
AllMasks
};
NodesType getNodesType(KisNodeList nodes) {
bool hasLayers = false;
bool hasMasks = false;
Q_FOREACH (KisNodeSP node, nodes) {
hasLayers |= bool(qobject_cast(node.data()));
hasMasks |= bool(qobject_cast(node.data()));
}
return hasLayers && hasMasks ? Mixed :
hasLayers ? AllLayers :
AllMasks;
}
void populateChildCommands() override {
- KisNodeList sortedNodes = sortAndFilterNodes(m_nodes, m_image);
+ KisNodeList sortedNodes = KisLayerUtils::sortAndFilterAnyMergableNodesSafe(m_nodes, m_image);
KisNodeSP headNode = m_lower ? sortedNodes.first() : sortedNodes.last();
const NodesType nodesType = getNodesType(sortedNodes);
KisNodeSP parent = headNode->parent();
KisNodeSP grandParent = parent ? parent->parent() : 0;
KisNodeSP newAbove;
KisNodeSP newParent;
if (m_lower) {
KisNodeSP prevNode = headNode->prevSibling();
if (prevNode) {
if ((prevNode->inherits("KisGroupLayer") &&
!prevNode->collapsed())
||
(nodesType == AllMasks &&
prevNode->inherits("KisLayer"))) {
newAbove = prevNode->lastChild();
newParent = prevNode;
} else {
newAbove = prevNode->prevSibling();
newParent = parent;
}
} else if ((nodesType == AllLayers && grandParent) ||
(nodesType == AllMasks && grandParent && grandParent->parent())) {
newAbove = parent->prevSibling();
newParent = grandParent;
} else if (nodesType == AllMasks &&
grandParent && !grandParent->parent() &&
(prevNode = parent->prevSibling()) &&
prevNode->inherits("KisLayer")) {
newAbove = prevNode->lastChild();
newParent = prevNode; // NOTE: this is an updated 'prevNode'!
}
} else {
KisNodeSP nextNode = headNode->nextSibling();
if (nextNode) {
if ((nextNode->inherits("KisGroupLayer") &&
!nextNode->collapsed())
||
(nodesType == AllMasks &&
nextNode->inherits("KisLayer"))) {
newAbove = 0;
newParent = nextNode;
} else {
newAbove = nextNode;
newParent = parent;
}
} else if ((nodesType == AllLayers && grandParent) ||
(nodesType == AllMasks && grandParent && grandParent->parent())) {
newAbove = parent;
newParent = grandParent;
} else if (nodesType == AllMasks &&
grandParent && !grandParent->parent() &&
(nextNode = parent->nextSibling()) &&
nextNode->inherits("KisLayer")) {
newAbove = 0;
newParent = nextNode; // NOTE: this is an updated 'nextNode'!
}
}
if (!newParent) return;
addCommand(new KisLayerUtils::KeepNodesSelectedCommand(sortedNodes, sortedNodes,
m_activeNode, m_activeNode,
m_image, false));
KisNodeSP currentAbove = newAbove;
Q_FOREACH (KisNodeSP node, sortedNodes) {
if (node->parent() != newParent && !newParent->allowAsChild(node)) {
continue;
}
MoveNodeStructSP moveStruct = toQShared(new MoveNodeStruct(m_image, node, newParent, currentAbove));
addCommand(new KisImageLayerMoveCommand(m_image, node, newParent, currentAbove, false));
m_updateData->addInitialUpdate(moveStruct);
currentAbove = node;
}
addCommand(new KisLayerUtils::KeepNodesSelectedCommand(sortedNodes, sortedNodes,
m_activeNode, m_activeNode,
m_image, true));
}
private:
BatchMoveUpdateDataSP m_updateData;
KisImageSP m_image;
KisNodeList m_nodes;
KisNodeSP m_activeNode;
bool m_lower;
};
struct DuplicateLayers : public KisCommandUtils::AggregateCommand {
enum Mode {
MOVE,
COPY,
ADD
};
DuplicateLayers(BatchMoveUpdateDataSP updateData,
KisImageSP image,
const KisNodeList &nodes,
KisNodeSP dstParent,
KisNodeSP dstAbove,
KisNodeSP activeNode,
Mode mode)
: m_updateData(updateData),
m_image(image),
m_nodes(nodes),
m_dstParent(dstParent),
m_dstAbove(dstAbove),
m_activeNode(activeNode),
m_mode(mode) {}
void populateChildCommands() override {
- KisNodeList filteredNodes = sortAndFilterNodes(m_nodes, m_image);
+ KisNodeList filteredNodes = KisLayerUtils::sortAndFilterAnyMergableNodesSafe(m_nodes, m_image);
if (filteredNodes.isEmpty()) return;
KisNodeSP newAbove = filteredNodes.last();
KisNodeSP newParent = newAbove->parent();
// override parent if provided externally
if (m_dstParent) {
newAbove = m_dstAbove;
newParent = m_dstParent;
}
const int indexOfActiveNode = filteredNodes.indexOf(m_activeNode);
QList activeMasks = findActiveSelectionMasks(filteredNodes);
// we will deactivate the masks before processing, so we should
// save their list in a convenient form
QSet activeMaskNodes;
Q_FOREACH (KisSelectionMaskSP mask, activeMasks) {
activeMaskNodes.insert(mask);
}
const bool haveActiveMasks = !activeMasks.isEmpty();
if (!newParent) return;
addCommand(new KisLayerUtils::KeepNodesSelectedCommand(filteredNodes, KisNodeList(),
m_activeNode, KisNodeSP(),
m_image, false));
if (haveActiveMasks) {
/**
* We should first disable the currently active masks, after the operation
* completed their cloned counterparts will be activated instead.
*
* HINT: we should deactivate the masks before cloning, because otherwise
* KisGroupLayer::allowAsChild() will not let the second mask to be
* added to the list of child nodes. See bug 382315.
*/
addCommand(new ActivateSelectionMasksCommand(activeMasks,
QList(),
false));
}
KisNodeList newNodes;
QList newActiveMasks;
KisNodeSP currentAbove = newAbove;
Q_FOREACH (KisNodeSP node, filteredNodes) {
if (m_mode == COPY || m_mode == ADD) {
KisNodeSP newNode;
if (m_mode == COPY) {
newNode = node->clone();
KisLayerUtils::addCopyOfNameTag(newNode);
} else {
newNode = node;
}
newNodes << newNode;
if (haveActiveMasks && activeMaskNodes.contains(node)) {
KisSelectionMaskSP mask = dynamic_cast(newNode.data());
newActiveMasks << mask;
}
MoveNodeStructSP moveStruct = toQShared(new MoveNodeStruct(m_image, newNode, newParent, currentAbove));
m_updateData->addInitialUpdate(moveStruct);
addCommand(new KisImageLayerAddCommand(m_image, newNode,
newParent,
currentAbove,
false, false));
currentAbove = newNode;
} else if (m_mode == MOVE) {
KisNodeSP newNode = node;
newNodes << newNode;
if (haveActiveMasks && activeMaskNodes.contains(node)) {
KisSelectionMaskSP mask = dynamic_cast(newNode.data());
newActiveMasks << mask;
}
MoveNodeStructSP moveStruct = toQShared(new MoveNodeStruct(m_image, newNode, newParent, currentAbove));
m_updateData->addInitialUpdate(moveStruct);
addCommand(new KisImageLayerMoveCommand(m_image, newNode,
newParent,
currentAbove,
false));
currentAbove = newNode;
}
}
if (haveActiveMasks) {
/**
* Activate the cloned counterparts of the masks after the operation
* is complete.
*/
addCommand(new ActivateSelectionMasksCommand(QList(),
newActiveMasks,
true));
}
KisNodeSP newActiveNode = newNodes[qBound(0, indexOfActiveNode, newNodes.size() - 1)];
addCommand(new KisLayerUtils::KeepNodesSelectedCommand(KisNodeList(), newNodes,
KisNodeSP(), newActiveNode,
m_image, true));
}
private:
KisSelectionMaskSP toActiveSelectionMask(KisNodeSP node) {
KisSelectionMask *mask = dynamic_cast(node.data());
return mask && mask->active() ? mask : 0;
}
QList findActiveSelectionMasks(KisNodeList nodes) {
QList masks;
foreach (KisNodeSP node, nodes) {
KisSelectionMaskSP mask = toActiveSelectionMask(node);
if (mask) {
masks << mask;
}
}
return masks;
}
private:
BatchMoveUpdateDataSP m_updateData;
KisImageSP m_image;
KisNodeList m_nodes;
KisNodeSP m_dstParent;
KisNodeSP m_dstAbove;
KisNodeSP m_activeNode;
Mode m_mode;
};
struct RemoveLayers : private KisLayerUtils::RemoveNodeHelper, public KisCommandUtils::AggregateCommand {
RemoveLayers(BatchMoveUpdateDataSP updateData,
KisImageSP image,
const KisNodeList &nodes,
KisNodeSP activeNode)
: m_updateData(updateData),
m_image(image),
m_nodes(nodes),
m_activeNode(activeNode){}
void populateChildCommands() override {
KisNodeList filteredNodes = m_nodes;
KisLayerUtils::filterMergableNodes(filteredNodes, true);
KisLayerUtils::filterUnlockedNodes(filteredNodes);
if (filteredNodes.isEmpty()) return;
Q_FOREACH (KisNodeSP node, filteredNodes) {
MoveNodeStructSP moveStruct = toQShared(new MoveNodeStruct(m_image, node, KisNodeSP(), KisNodeSP()));
m_updateData->addInitialUpdate(moveStruct);
}
addCommand(new KisLayerUtils::KeepNodesSelectedCommand(filteredNodes, KisNodeList(),
m_activeNode, KisNodeSP(),
m_image, false));
safeRemoveMultipleNodes(filteredNodes, m_image);
addCommand(new KisLayerUtils::KeepNodesSelectedCommand(filteredNodes, KisNodeList(),
m_activeNode, KisNodeSP(),
m_image, true));
}
protected:
void addCommandImpl(KUndo2Command *cmd) override {
addCommand(cmd);
}
private:
BatchMoveUpdateDataSP m_updateData;
KisImageSP m_image;
KisNodeList m_nodes;
KisNodeSP m_activeNode;
};
struct KisNodeJugglerCompressed::Private
{
Private(KisNodeJugglerCompressed *juggler, const KUndo2MagicString &_actionName, KisImageSP _image, KisNodeManager *_nodeManager, int _timeout)
: actionName(_actionName),
image(_image),
nodeManager(_nodeManager),
compressor(_timeout, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT),
selfDestructionCompressor(3 * _timeout, KisSignalCompressor::POSTPONE),
updateData(new BatchMoveUpdateData(juggler)),
autoDelete(false),
isStarted(false)
{}
KUndo2MagicString actionName;
KisImageSP image;
KisNodeManager *nodeManager;
QScopedPointer applicator;
KisSignalCompressor compressor;
KisSignalCompressor selfDestructionCompressor;
BatchMoveUpdateDataSP updateData;
bool autoDelete;
bool isStarted;
};
KisNodeJugglerCompressed::KisNodeJugglerCompressed(const KUndo2MagicString &actionName, KisImageSP image, KisNodeManager *nodeManager, int timeout)
: m_d(new Private(this, actionName, image, nodeManager, timeout))
{
connect(m_d->image, SIGNAL(sigStrokeCancellationRequested()), SLOT(slotEndStrokeRequested()));
connect(m_d->image, SIGNAL(sigUndoDuringStrokeRequested()), SLOT(slotCancelStrokeRequested()));
connect(m_d->image, SIGNAL(sigStrokeEndRequestedActiveNodeFiltered()), SLOT(slotEndStrokeRequested()));
connect(m_d->image, SIGNAL(sigAboutToBeDeleted()), SLOT(slotImageAboutToBeDeleted()));
KisImageSignalVector emitSignals;
emitSignals << ModifiedSignal;
m_d->applicator.reset(
new KisProcessingApplicator(m_d->image, 0,
KisProcessingApplicator::NONE,
emitSignals,
actionName));
connect(this, SIGNAL(requestUpdateAsyncFromCommand()), SLOT(startTimers()));
connect(&m_d->compressor, SIGNAL(timeout()), SLOT(slotUpdateTimeout()));
m_d->applicator->applyCommand(
new UpdateMovedNodesCommand(m_d->updateData, false));
m_d->isStarted = true;
}
KisNodeJugglerCompressed::~KisNodeJugglerCompressed()
{
KIS_ASSERT_RECOVER(!m_d->applicator) {
m_d->applicator->end();
m_d->applicator.reset();
}
}
bool KisNodeJugglerCompressed::canMergeAction(const KUndo2MagicString &actionName)
{
return actionName == m_d->actionName;
}
void KisNodeJugglerCompressed::lowerNode(const KisNodeList &nodes)
{
KisNodeSP activeNode = m_d->nodeManager ? m_d->nodeManager->activeNode() : 0;
m_d->applicator->applyCommand(
new LowerRaiseLayer(m_d->updateData,
m_d->image,
nodes, activeNode, true));
}
void KisNodeJugglerCompressed::raiseNode(const KisNodeList &nodes)
{
KisNodeSP activeNode = m_d->nodeManager ? m_d->nodeManager->activeNode() : 0;
m_d->applicator->applyCommand(
new LowerRaiseLayer(m_d->updateData,
m_d->image,
nodes, activeNode, false));
}
void KisNodeJugglerCompressed::removeNode(const KisNodeList &nodes)
{
KisNodeSP activeNode = m_d->nodeManager ? m_d->nodeManager->activeNode() : 0;
m_d->applicator->applyCommand(
new RemoveLayers(m_d->updateData,
m_d->image,
nodes, activeNode));
}
void KisNodeJugglerCompressed::duplicateNode(const KisNodeList &nodes)
{
KisNodeSP activeNode = m_d->nodeManager ? m_d->nodeManager->activeNode() : 0;
m_d->applicator->applyCommand(
new DuplicateLayers(m_d->updateData,
m_d->image,
nodes,
KisNodeSP(), KisNodeSP(),
activeNode,
DuplicateLayers::COPY));
}
void KisNodeJugglerCompressed::copyNode(const KisNodeList &nodes, KisNodeSP dstParent, KisNodeSP dstAbove)
{
KisNodeSP activeNode = m_d->nodeManager ? m_d->nodeManager->activeNode() : 0;
m_d->applicator->applyCommand(
new DuplicateLayers(m_d->updateData,
m_d->image,
nodes,
dstParent, dstAbove,
activeNode,
DuplicateLayers::COPY));
}
void KisNodeJugglerCompressed::moveNode(const KisNodeList &nodes, KisNodeSP dstParent, KisNodeSP dstAbove)
{
KisNodeSP activeNode = m_d->nodeManager ? m_d->nodeManager->activeNode() : 0;
m_d->applicator->applyCommand(
new DuplicateLayers(m_d->updateData,
m_d->image,
nodes,
dstParent, dstAbove,
activeNode,
DuplicateLayers::MOVE));
}
void KisNodeJugglerCompressed::addNode(const KisNodeList &nodes, KisNodeSP dstParent, KisNodeSP dstAbove)
{
KisNodeSP activeNode = m_d->nodeManager ? m_d->nodeManager->activeNode() : 0;
m_d->applicator->applyCommand(
new DuplicateLayers(m_d->updateData,
m_d->image,
nodes,
dstParent, dstAbove,
activeNode,
DuplicateLayers::ADD));
}
void KisNodeJugglerCompressed::moveNode(KisNodeSP node, KisNodeSP parent, KisNodeSP above)
{
m_d->applicator->applyCommand(new KisImageLayerMoveCommand(m_d->image, node, parent, above, false));
MoveNodeStructSP moveStruct = toQShared(new MoveNodeStruct(m_d->image, node, parent, above));
m_d->updateData->addInitialUpdate(moveStruct);
}
void KisNodeJugglerCompressed::startTimers()
{
m_d->compressor.start();
if (m_d->autoDelete) {
m_d->selfDestructionCompressor.start();
}
}
void KisNodeJugglerCompressed::slotUpdateTimeout()
{
m_d->updateData->processUnhandledUpdates();
}
void KisNodeJugglerCompressed::end()
{
if (!m_d->isStarted) return;
m_d->applicator->applyCommand(
new UpdateMovedNodesCommand(m_d->updateData, true));
m_d->applicator->end();
cleanup();
}
void KisNodeJugglerCompressed::cleanup()
{
m_d->applicator.reset();
m_d->compressor.stop();
m_d->isStarted = false;
if (m_d->autoDelete) {
m_d->selfDestructionCompressor.stop();
this->deleteLater();
}
}
void KisNodeJugglerCompressed::setAutoDelete(bool value)
{
m_d->autoDelete = value;
connect(&m_d->selfDestructionCompressor, SIGNAL(timeout()), SLOT(end()));
}
void KisNodeJugglerCompressed::slotEndStrokeRequested()
{
if (!m_d->isStarted) return;
end();
}
void KisNodeJugglerCompressed::slotCancelStrokeRequested()
{
if (!m_d->isStarted) return;
m_d->applicator->cancel();
cleanup();
}
void KisNodeJugglerCompressed::slotImageAboutToBeDeleted()
{
if (!m_d->isStarted) return;
m_d->applicator->end();
cleanup();
}
bool KisNodeJugglerCompressed::isEnded() const
{
return !m_d->isStarted;
}
diff --git a/libs/ui/kis_node_manager.cpp b/libs/ui/kis_node_manager.cpp
index 69d2db0f35..e0e418c779 100644
--- a/libs/ui/kis_node_manager.cpp
+++ b/libs/ui/kis_node_manager.cpp
@@ -1,1578 +1,1583 @@
/*
* Copyright (C) 2007 Boudewijn Rempt
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_node_manager.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "KisPart.h"
#include "canvas/kis_canvas2.h"
#include "kis_shape_controller.h"
#include "kis_canvas_resource_provider.h"
#include "KisViewManager.h"
#include "KisDocument.h"
#include "kis_mask_manager.h"
#include "kis_group_layer.h"
#include "kis_layer_manager.h"
#include "kis_selection_manager.h"
#include "kis_node_commands_adapter.h"
#include "kis_action.h"
#include "kis_action_manager.h"
#include "kis_processing_applicator.h"
#include "kis_sequential_iterator.h"
#include "kis_transaction.h"
#include "kis_node_selection_adapter.h"
#include "kis_node_insertion_adapter.h"
#include "kis_node_juggler_compressed.h"
#include "KisNodeDisplayModeAdapter.h"
#include "kis_clipboard.h"
#include "kis_node_dummies_graph.h"
#include "kis_mimedata.h"
#include "kis_layer_utils.h"
#include "krita_utils.h"
#include "kis_shape_layer.h"
#include "processing/kis_mirror_processing_visitor.h"
#include "KisView.h"
#include
#include
#include
struct KisNodeManager::Private {
Private(KisNodeManager *_q, KisViewManager *v)
: q(_q)
, view(v)
, imageView(0)
, layerManager(v)
, maskManager(v)
, commandsAdapter(v)
, nodeSelectionAdapter(new KisNodeSelectionAdapter(q))
, nodeInsertionAdapter(new KisNodeInsertionAdapter(q))
, nodeDisplayModeAdapter(new KisNodeDisplayModeAdapter())
, lastRequestedIsolatedModeStatus(false)
{
}
KisNodeManager * q;
KisViewManager * view;
QPointerimageView;
KisLayerManager layerManager;
KisMaskManager maskManager;
KisNodeCommandsAdapter commandsAdapter;
QScopedPointer nodeSelectionAdapter;
QScopedPointer nodeInsertionAdapter;
QScopedPointer nodeDisplayModeAdapter;
KisAction *showInTimeline;
KisNodeList selectedNodes;
QPointer nodeJuggler;
KisNodeWSP previouslyActiveNode;
bool activateNodeImpl(KisNodeSP node);
QSignalMapper nodeCreationSignalMapper;
QSignalMapper nodeConversionSignalMapper;
bool lastRequestedIsolatedModeStatus;
void saveDeviceAsImage(KisPaintDeviceSP device,
const QString &defaultName,
const QRect &bounds,
qreal xRes,
qreal yRes,
quint8 opacity);
void mergeTransparencyMaskAsAlpha(bool writeToLayers);
KisNodeJugglerCompressed* lazyGetJuggler(const KUndo2MagicString &actionName);
};
bool KisNodeManager::Private::activateNodeImpl(KisNodeSP node)
{
Q_ASSERT(view);
Q_ASSERT(view->canvasBase());
Q_ASSERT(view->canvasBase()->globalShapeManager());
Q_ASSERT(imageView);
if (node && node == q->activeNode()) {
return false;
}
// Set the selection on the shape manager to the active layer
// and set call KoSelection::setActiveLayer( KoShapeLayer* layer )
// with the parent of the active layer.
KoSelection *selection = view->canvasBase()->globalShapeManager()->selection();
Q_ASSERT(selection);
selection->deselectAll();
if (!node) {
selection->setActiveLayer(0);
imageView->setCurrentNode(0);
maskManager.activateMask(0);
layerManager.activateLayer(0);
previouslyActiveNode = q->activeNode();
} else {
previouslyActiveNode = q->activeNode();
KoShape * shape = view->document()->shapeForNode(node);
//if (!shape) return false;
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape, false);
selection->select(shape);
KoShapeLayer * shapeLayer = dynamic_cast(shape);
//if (!shapeLayer) return false;
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shapeLayer, false);
// shapeLayer->setGeometryProtected(node->userLocked());
// shapeLayer->setVisible(node->visible());
selection->setActiveLayer(shapeLayer);
imageView->setCurrentNode(node);
if (KisLayerSP layer = qobject_cast(node.data())) {
maskManager.activateMask(0);
layerManager.activateLayer(layer);
} else if (KisMaskSP mask = dynamic_cast(node.data())) {
maskManager.activateMask(mask);
// XXX_NODE: for now, masks cannot be nested.
layerManager.activateLayer(static_cast(node->parent().data()));
}
}
return true;
}
KisNodeManager::KisNodeManager(KisViewManager *view)
: m_d(new Private(this, view))
{
connect(&m_d->layerManager, SIGNAL(sigLayerActivated(KisLayerSP)), SIGNAL(sigLayerActivated(KisLayerSP)));
}
KisNodeManager::~KisNodeManager()
{
delete m_d;
}
void KisNodeManager::setView(QPointerimageView)
{
m_d->maskManager.setView(imageView);
m_d->layerManager.setView(imageView);
if (m_d->imageView) {
KisShapeController *shapeController = dynamic_cast(m_d->imageView->document()->shapeController());
Q_ASSERT(shapeController);
shapeController->disconnect(SIGNAL(sigActivateNode(KisNodeSP)), this);
m_d->imageView->image()->disconnect(this);
}
m_d->imageView = imageView;
if (m_d->imageView) {
KisShapeController *shapeController = dynamic_cast(m_d->imageView->document()->shapeController());
Q_ASSERT(shapeController);
connect(shapeController, SIGNAL(sigActivateNode(KisNodeSP)), SLOT(slotNonUiActivatedNode(KisNodeSP)));
connect(m_d->imageView->image(), SIGNAL(sigIsolatedModeChanged()),this, SLOT(slotUpdateIsolateModeActionImageStatusChange()));
connect(m_d->imageView->image(), SIGNAL(sigRequestNodeReselection(KisNodeSP,KisNodeList)),this, SLOT(slotImageRequestNodeReselection(KisNodeSP,KisNodeList)));
m_d->imageView->resourceProvider()->slotNodeActivated(m_d->imageView->currentNode());
}
}
#define NEW_LAYER_ACTION(id, layerType) \
{ \
action = actionManager->createAction(id); \
m_d->nodeCreationSignalMapper.setMapping(action, layerType); \
connect(action, SIGNAL(triggered()), \
&m_d->nodeCreationSignalMapper, SLOT(map())); \
}
#define CONVERT_NODE_ACTION_2(id, layerType, exclude) \
{ \
action = actionManager->createAction(id); \
action->setExcludedNodeTypes(QStringList(exclude)); \
actionManager->addAction(id, action); \
m_d->nodeConversionSignalMapper.setMapping(action, layerType); \
connect(action, SIGNAL(triggered()), \
&m_d->nodeConversionSignalMapper, SLOT(map())); \
}
#define CONVERT_NODE_ACTION(id, layerType) \
CONVERT_NODE_ACTION_2(id, layerType, layerType)
void KisNodeManager::setup(KActionCollection * actionCollection, KisActionManager* actionManager)
{
m_d->layerManager.setup(actionManager);
m_d->maskManager.setup(actionCollection, actionManager);
KisAction * action = 0;
action = actionManager->createAction("mirrorNodeX");
connect(action, SIGNAL(triggered()), this, SLOT(mirrorNodeX()));
action = actionManager->createAction("mirrorNodeY");
connect(action, SIGNAL(triggered()), this, SLOT(mirrorNodeY()));
action = actionManager->createAction("mirrorAllNodesX");
connect(action, SIGNAL(triggered()), this, SLOT(mirrorAllNodesX()));
action = actionManager->createAction("mirrorAllNodesY");
connect(action, SIGNAL(triggered()), this, SLOT(mirrorAllNodesY()));
action = actionManager->createAction("activateNextLayer");
connect(action, SIGNAL(triggered()), this, SLOT(activateNextNode()));
action = actionManager->createAction("activatePreviousLayer");
connect(action, SIGNAL(triggered()), this, SLOT(activatePreviousNode()));
action = actionManager->createAction("switchToPreviouslyActiveNode");
connect(action, SIGNAL(triggered()), this, SLOT(switchToPreviouslyActiveNode()));
action = actionManager->createAction("save_node_as_image");
connect(action, SIGNAL(triggered()), this, SLOT(saveNodeAsImage()));
action = actionManager->createAction("save_vector_node_to_svg");
connect(action, SIGNAL(triggered()), this, SLOT(saveVectorLayerAsImage()));
action->setActivationFlags(KisAction::ACTIVE_SHAPE_LAYER);
action = actionManager->createAction("duplicatelayer");
connect(action, SIGNAL(triggered()), this, SLOT(duplicateActiveNode()));
action = actionManager->createAction("copy_layer_clipboard");
connect(action, SIGNAL(triggered()), this, SLOT(copyLayersToClipboard()));
action = actionManager->createAction("cut_layer_clipboard");
connect(action, SIGNAL(triggered()), this, SLOT(cutLayersToClipboard()));
action = actionManager->createAction("paste_layer_from_clipboard");
connect(action, SIGNAL(triggered()), this, SLOT(pasteLayersFromClipboard()));
action = actionManager->createAction("create_quick_group");
connect(action, SIGNAL(triggered()), this, SLOT(createQuickGroup()));
action = actionManager->createAction("create_quick_clipping_group");
connect(action, SIGNAL(triggered()), this, SLOT(createQuickClippingGroup()));
action = actionManager->createAction("quick_ungroup");
connect(action, SIGNAL(triggered()), this, SLOT(quickUngroup()));
action = actionManager->createAction("select_all_layers");
connect(action, SIGNAL(triggered()), this, SLOT(selectAllNodes()));
action = actionManager->createAction("select_visible_layers");
connect(action, SIGNAL(triggered()), this, SLOT(selectVisibleNodes()));
action = actionManager->createAction("select_locked_layers");
connect(action, SIGNAL(triggered()), this, SLOT(selectLockedNodes()));
action = actionManager->createAction("select_invisible_layers");
connect(action, SIGNAL(triggered()), this, SLOT(selectInvisibleNodes()));
action = actionManager->createAction("select_unlocked_layers");
connect(action, SIGNAL(triggered()), this, SLOT(selectUnlockedNodes()));
action = actionManager->createAction("new_from_visible");
connect(action, SIGNAL(triggered()), this, SLOT(createFromVisible()));
action = actionManager->createAction("show_in_timeline");
action->setCheckable(true);
connect(action, SIGNAL(toggled(bool)), this, SLOT(slotShowHideTimeline(bool)));
m_d->showInTimeline = action;
NEW_LAYER_ACTION("add_new_paint_layer", "KisPaintLayer");
NEW_LAYER_ACTION("add_new_group_layer", "KisGroupLayer");
NEW_LAYER_ACTION("add_new_clone_layer", "KisCloneLayer");
NEW_LAYER_ACTION("add_new_shape_layer", "KisShapeLayer");
NEW_LAYER_ACTION("add_new_adjustment_layer", "KisAdjustmentLayer");
NEW_LAYER_ACTION("add_new_fill_layer", "KisGeneratorLayer");
NEW_LAYER_ACTION("add_new_file_layer", "KisFileLayer");
NEW_LAYER_ACTION("add_new_transparency_mask", "KisTransparencyMask");
NEW_LAYER_ACTION("add_new_filter_mask", "KisFilterMask");
NEW_LAYER_ACTION("add_new_colorize_mask", "KisColorizeMask");
NEW_LAYER_ACTION("add_new_transform_mask", "KisTransformMask");
NEW_LAYER_ACTION("add_new_selection_mask", "KisSelectionMask");
connect(&m_d->nodeCreationSignalMapper, SIGNAL(mapped(QString)),
this, SLOT(createNode(QString)));
CONVERT_NODE_ACTION("convert_to_paint_layer", "KisPaintLayer");
CONVERT_NODE_ACTION_2("convert_to_selection_mask", "KisSelectionMask", QStringList() << "KisSelectionMask" << "KisColorizeMask");
CONVERT_NODE_ACTION_2("convert_to_filter_mask", "KisFilterMask", QStringList() << "KisFilterMask" << "KisColorizeMask");
CONVERT_NODE_ACTION_2("convert_to_transparency_mask", "KisTransparencyMask", QStringList() << "KisTransparencyMask" << "KisColorizeMask");
CONVERT_NODE_ACTION("convert_to_animated", "animated");
CONVERT_NODE_ACTION_2("convert_to_file_layer", "KisFileLayer", QStringList() << "KisFileLayer" << "KisCloneLayer");
connect(&m_d->nodeConversionSignalMapper, SIGNAL(mapped(QString)),
this, SLOT(convertNode(QString)));
action = actionManager->createAction("isolate_layer");
connect(action, SIGNAL(triggered(bool)), this, SLOT(toggleIsolateMode(bool)));
action = actionManager->createAction("toggle_layer_visibility");
connect(action, SIGNAL(triggered()), this, SLOT(toggleVisibility()));
action = actionManager->createAction("toggle_layer_lock");
connect(action, SIGNAL(triggered()), this, SLOT(toggleLock()));
action = actionManager->createAction("toggle_layer_inherit_alpha");
connect(action, SIGNAL(triggered()), this, SLOT(toggleInheritAlpha()));
action = actionManager->createAction("toggle_layer_alpha_lock");
connect(action, SIGNAL(triggered()), this, SLOT(toggleAlphaLock()));
action = actionManager->createAction("split_alpha_into_mask");
connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaIntoMask()));
action = actionManager->createAction("split_alpha_write");
connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaWrite()));
// HINT: we can save even when the nodes are not editable
action = actionManager->createAction("split_alpha_save_merged");
connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaSaveMerged()));
connect(this, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotUpdateIsolateModeAction()));
connect(this, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotTryRestartIsolatedMode()));
}
void KisNodeManager::updateGUI()
{
// enable/disable all relevant actions
m_d->layerManager.updateGUI();
m_d->maskManager.updateGUI();
}
KisNodeSP KisNodeManager::activeNode()
{
if (m_d->imageView) {
return m_d->imageView->currentNode();
}
return 0;
}
KisLayerSP KisNodeManager::activeLayer()
{
return m_d->layerManager.activeLayer();
}
const KoColorSpace* KisNodeManager::activeColorSpace()
{
if (m_d->maskManager.activeDevice()) {
return m_d->maskManager.activeDevice()->colorSpace();
} else {
Q_ASSERT(m_d->layerManager.activeLayer());
if (m_d->layerManager.activeLayer()->parentLayer())
return m_d->layerManager.activeLayer()->parentLayer()->colorSpace();
else
return m_d->view->image()->colorSpace();
}
}
void KisNodeManager::moveNodeAt(KisNodeSP node, KisNodeSP parent, int index)
{
if (parent->allowAsChild(node)) {
if (node->inherits("KisSelectionMask") && parent->inherits("KisLayer")) {
KisSelectionMask *m = dynamic_cast(node.data());
KisLayer *l = qobject_cast(parent.data());
if (m && m->active() && l && l->selectionMask()) {
l->selectionMask()->setActive(false);
}
}
m_d->commandsAdapter.moveNode(node, parent, index);
}
}
void KisNodeManager::moveNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis)
{
KUndo2MagicString actionName = kundo2_i18n("Move Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->moveNode(nodes, parent, aboveThis);
}
void KisNodeManager::copyNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis)
{
KUndo2MagicString actionName = kundo2_i18n("Copy Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->copyNode(nodes, parent, aboveThis);
}
void KisNodeManager::addNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis)
{
KUndo2MagicString actionName = kundo2_i18n("Add Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->addNode(nodes, parent, aboveThis);
}
void KisNodeManager::toggleIsolateActiveNode()
{
KisImageWSP image = m_d->view->image();
KisNodeSP activeNode = this->activeNode();
KIS_ASSERT_RECOVER_RETURN(activeNode);
if (activeNode == image->isolatedModeRoot()) {
toggleIsolateMode(false);
} else {
toggleIsolateMode(true);
}
}
void KisNodeManager::toggleIsolateMode(bool checked)
{
KisImageWSP image = m_d->view->image();
KisNodeSP activeNode = this->activeNode();
if (checked && activeNode) {
// Transform and colorize masks don't have pixel data...
if (activeNode->inherits("KisTransformMask") ||
activeNode->inherits("KisColorizeMask")) return;
if (!image->startIsolatedMode(activeNode)) {
KisAction *action = m_d->view->actionManager()->actionByName("isolate_layer");
action->setChecked(false);
}
} else {
image->stopIsolatedMode();
}
m_d->lastRequestedIsolatedModeStatus = checked;
}
void KisNodeManager::slotUpdateIsolateModeActionImageStatusChange()
{
slotUpdateIsolateModeAction();
KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot();
if (this->activeNode() &&
bool(isolatedRootNode) != m_d->lastRequestedIsolatedModeStatus) {
slotTryRestartIsolatedMode();
}
}
void KisNodeManager::slotUpdateIsolateModeAction()
{
KisAction *action = m_d->view->actionManager()->actionByName("isolate_layer");
Q_ASSERT(action);
KisNodeSP activeNode = this->activeNode();
KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot();
action->setChecked(isolatedRootNode && isolatedRootNode == activeNode);
}
void KisNodeManager::slotTryRestartIsolatedMode()
{
/**
* It might be that we have multiple Krita windows open. In such a case
* only the currently active one should restart isolated mode
*/
if (!m_d->view->mainWindow()->isActiveWindow()) return;
KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot();
if (!isolatedRootNode && !m_d->lastRequestedIsolatedModeStatus) return;
this->toggleIsolateMode(true);
}
KisNodeSP KisNodeManager::createNode(const QString & nodeType, bool quiet, KisPaintDeviceSP copyFrom)
{
if (!m_d->view->blockUntilOperationsFinished(m_d->view->image())) {
return 0;
}
KisNodeSP activeNode = this->activeNode();
if (!activeNode) {
activeNode = m_d->view->image()->root();
}
KIS_ASSERT_RECOVER_RETURN_VALUE(activeNode, 0);
// XXX: make factories for this kind of stuff,
// with a registry
if (nodeType == "KisPaintLayer") {
return m_d->layerManager.addPaintLayer(activeNode);
} else if (nodeType == "KisGroupLayer") {
return m_d->layerManager.addGroupLayer(activeNode);
} else if (nodeType == "KisAdjustmentLayer") {
return m_d->layerManager.addAdjustmentLayer(activeNode);
} else if (nodeType == "KisGeneratorLayer") {
return m_d->layerManager.addGeneratorLayer(activeNode);
} else if (nodeType == "KisShapeLayer") {
return m_d->layerManager.addShapeLayer(activeNode);
} else if (nodeType == "KisCloneLayer") {
- return m_d->layerManager.addCloneLayer(activeNode);
+ KisNodeList nodes = selectedNodes();
+ if (nodes.isEmpty()) {
+ nodes.append(activeNode);
+ }
+
+ return m_d->layerManager.addCloneLayer(nodes);
} else if (nodeType == "KisTransparencyMask") {
return m_d->maskManager.createTransparencyMask(activeNode, copyFrom, false);
} else if (nodeType == "KisFilterMask") {
return m_d->maskManager.createFilterMask(activeNode, copyFrom, quiet, false);
} else if (nodeType == "KisColorizeMask") {
return m_d->maskManager.createColorizeMask(activeNode);
} else if (nodeType == "KisTransformMask") {
return m_d->maskManager.createTransformMask(activeNode);
} else if (nodeType == "KisSelectionMask") {
return m_d->maskManager.createSelectionMask(activeNode, copyFrom, false);
} else if (nodeType == "KisFileLayer") {
return m_d->layerManager.addFileLayer(activeNode);
}
return 0;
}
void KisNodeManager::createFromVisible()
{
KisLayerUtils::newLayerFromVisible(m_d->view->image(), m_d->view->image()->root()->lastChild());
}
void KisNodeManager::slotShowHideTimeline(bool value)
{
Q_FOREACH (KisNodeSP node, selectedNodes()) {
node->setUseInTimeline(value);
}
}
KisLayerSP KisNodeManager::createPaintLayer()
{
KisNodeSP node = createNode("KisPaintLayer");
return dynamic_cast(node.data());
}
void KisNodeManager::convertNode(const QString &nodeType)
{
if (!m_d->view->blockUntilOperationsFinished(m_d->view->image())) {
return;
}
KisNodeSP activeNode = this->activeNode();
if (!activeNode) return;
if (nodeType == "KisPaintLayer") {
m_d->layerManager.convertNodeToPaintLayer(activeNode);
} else if (nodeType == "KisSelectionMask" ||
nodeType == "KisFilterMask" ||
nodeType == "KisTransparencyMask") {
KisPaintDeviceSP copyFrom = activeNode->paintDevice() ?
activeNode->paintDevice() : activeNode->projection();
m_d->commandsAdapter.beginMacro(kundo2_i18n("Convert to a Selection Mask"));
bool result = false;
if (nodeType == "KisSelectionMask") {
result = !m_d->maskManager.createSelectionMask(activeNode, copyFrom, true).isNull();
} else if (nodeType == "KisFilterMask") {
result = !m_d->maskManager.createFilterMask(activeNode, copyFrom, false, true).isNull();
} else if (nodeType == "KisTransparencyMask") {
result = !m_d->maskManager.createTransparencyMask(activeNode, copyFrom, true).isNull();
}
m_d->commandsAdapter.endMacro();
if (!result) {
m_d->view->blockUntilOperationsFinishedForced(m_d->imageView->image());
m_d->commandsAdapter.undoLastCommand();
}
} else if (nodeType == "KisFileLayer") {
m_d->layerManager.convertLayerToFileLayer(activeNode);
} else {
warnKrita << "Unsupported node conversion type:" << nodeType;
}
}
void KisNodeManager::slotSomethingActivatedNodeImpl(KisNodeSP node)
{
KisDummiesFacadeBase *dummiesFacade = dynamic_cast(m_d->imageView->document()->shapeController());
KIS_SAFE_ASSERT_RECOVER_RETURN(dummiesFacade);
const bool nodeVisible = !isNodeHidden(node, !m_d->nodeDisplayModeAdapter->showGlobalSelectionMask());
if (!nodeVisible) {
return;
}
KIS_ASSERT_RECOVER_RETURN(node != activeNode());
if (m_d->activateNodeImpl(node)) {
emit sigUiNeedChangeActiveNode(node);
emit sigNodeActivated(node);
nodesUpdated();
if (node) {
bool toggled = m_d->view->actionCollection()->action("view_show_canvas_only")->isChecked();
if (toggled) {
m_d->view->showFloatingMessage( activeLayer()->name(), QIcon(), 1600, KisFloatingMessage::Medium, Qt::TextSingleLine);
}
}
}
}
void KisNodeManager::slotNonUiActivatedNode(KisNodeSP node)
{
// the node must still be in the graph, some asynchronous
// signals may easily break this requirement
if (node && !node->graphListener()) {
node = 0;
}
if (node == activeNode()) return;
slotSomethingActivatedNodeImpl(node);
if (node) {
bool toggled = m_d->view->actionCollection()->action("view_show_canvas_only")->isChecked();
if (toggled) {
m_d->view->showFloatingMessage( activeLayer()->name(), QIcon(), 1600, KisFloatingMessage::Medium, Qt::TextSingleLine);
}
}
}
void KisNodeManager::slotUiActivatedNode(KisNodeSP node)
{
// the node must still be in the graph, some asynchronous
// signals may easily break this requirement
if (node && !node->graphListener()) {
node = 0;
}
if (node) {
QStringList vectorTools = QStringList()
<< "InteractionTool"
<< "KarbonPatternTool"
<< "KarbonGradientTool"
<< "KarbonCalligraphyTool"
<< "CreateShapesTool"
<< "PathTool";
QStringList pixelTools = QStringList()
<< "KritaShape/KisToolBrush"
<< "KritaShape/KisToolDyna"
<< "KritaShape/KisToolMultiBrush"
<< "KritaFill/KisToolFill"
<< "KritaFill/KisToolGradient";
KisSelectionMask *selectionMask = dynamic_cast(node.data());
const bool nodeHasVectorAbilities = node->inherits("KisShapeLayer") ||
(selectionMask && selectionMask->selection()->hasShapeSelection());
if (nodeHasVectorAbilities) {
if (pixelTools.contains(KoToolManager::instance()->activeToolId())) {
KoToolManager::instance()->switchToolRequested("InteractionTool");
}
}
else {
if (vectorTools.contains(KoToolManager::instance()->activeToolId())) {
KoToolManager::instance()->switchToolRequested("KritaShape/KisToolBrush");
}
}
}
if (node == activeNode()) return;
slotSomethingActivatedNodeImpl(node);
}
void KisNodeManager::nodesUpdated()
{
KisNodeSP node = activeNode();
if (!node) return;
m_d->layerManager.layersUpdated();
m_d->maskManager.masksUpdated();
m_d->view->updateGUI();
m_d->view->selectionManager()->selectionChanged();
{
KisSignalsBlocker b(m_d->showInTimeline);
m_d->showInTimeline->setChecked(node->useInTimeline());
}
}
KisPaintDeviceSP KisNodeManager::activePaintDevice()
{
return m_d->maskManager.activeMask() ?
m_d->maskManager.activeDevice() :
m_d->layerManager.activeDevice();
}
void KisNodeManager::nodeProperties(KisNodeSP node)
{
if ((selectedNodes().size() > 1 && node->inherits("KisLayer")) || node->inherits("KisLayer")) {
m_d->layerManager.layerProperties();
}
else if (node->inherits("KisMask")) {
m_d->maskManager.maskProperties();
}
}
void KisNodeManager::changeCloneSource()
{
m_d->layerManager.changeCloneSource();
}
qint32 KisNodeManager::convertOpacityToInt(qreal opacity)
{
/**
* Scales opacity from the range 0...100
* to the integer range 0...255
*/
return qMin(255, int(opacity * 2.55 + 0.5));
}
void KisNodeManager::setNodeName(KisNodeSP node, const QString &name)
{
if (!node) return;
if (node->name() == name) return;
m_d->commandsAdapter.setNodeName(node, name);
}
void KisNodeManager::setNodeOpacity(KisNodeSP node, qint32 opacity)
{
if (!node) return;
if (node->opacity() == opacity) return;
m_d->commandsAdapter.setOpacity(node, opacity);
}
void KisNodeManager::setNodeCompositeOp(KisNodeSP node,
const KoCompositeOp* compositeOp)
{
if (!node) return;
if (node->compositeOp() == compositeOp) return;
m_d->commandsAdapter.setCompositeOp(node, compositeOp);
}
void KisNodeManager::slotImageRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes)
{
if (activeNode) {
slotNonUiActivatedNode(activeNode);
}
if (!selectedNodes.isEmpty()) {
slotSetSelectedNodes(selectedNodes);
}
}
void KisNodeManager::slotSetSelectedNodes(const KisNodeList &nodes)
{
m_d->selectedNodes = nodes;
emit sigUiNeedChangeSelectedNodes(nodes);
}
KisNodeList KisNodeManager::selectedNodes()
{
return m_d->selectedNodes;
}
KisNodeSelectionAdapter* KisNodeManager::nodeSelectionAdapter() const
{
return m_d->nodeSelectionAdapter.data();
}
KisNodeInsertionAdapter* KisNodeManager::nodeInsertionAdapter() const
{
return m_d->nodeInsertionAdapter.data();
}
KisNodeDisplayModeAdapter *KisNodeManager::nodeDisplayModeAdapter() const
{
return m_d->nodeDisplayModeAdapter.data();
}
bool KisNodeManager::isNodeHidden(KisNodeSP node, bool isGlobalSelectionHidden)
{
if (node && node->isFakeNode()) {
return true;
}
if (isGlobalSelectionHidden && dynamic_cast(node.data()) &&
(!node->parent() || !node->parent()->parent())) {
return true;
}
return false;
}
bool KisNodeManager::trySetNodeProperties(KisNodeSP node, KisImageSP image, KisBaseNode::PropertyList properties) const
{
const KisPaintLayer *paintLayer = dynamic_cast(node.data());
if (paintLayer) {
const auto onionSkinOn = KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::onionSkins, true);
if (properties.contains(onionSkinOn)) {
const KisPaintDeviceSP &paintDevice = paintLayer->paintDevice();
if (paintDevice && paintDevice->defaultPixel().opacityU8() == 255) {
m_d->view->showFloatingMessage(i18n("Onion skins require a layer with transparent background."), QIcon());
return false;
}
}
}
KisNodePropertyListCommand::setNodePropertiesNoUndo(node, image, properties);
return true;
}
void KisNodeManager::nodeOpacityChanged(qreal opacity)
{
KisNodeSP node = activeNode();
setNodeOpacity(node, convertOpacityToInt(opacity));
}
void KisNodeManager::nodeCompositeOpChanged(const KoCompositeOp* op)
{
KisNodeSP node = activeNode();
setNodeCompositeOp(node, op);
}
void KisNodeManager::duplicateActiveNode()
{
KUndo2MagicString actionName = kundo2_i18n("Duplicate Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->duplicateNode(selectedNodes());
}
KisNodeJugglerCompressed* KisNodeManager::Private::lazyGetJuggler(const KUndo2MagicString &actionName)
{
KisImageWSP image = view->image();
if (!nodeJuggler ||
(nodeJuggler &&
(nodeJuggler->isEnded() ||
!nodeJuggler->canMergeAction(actionName)))) {
nodeJuggler = new KisNodeJugglerCompressed(actionName, image, q, 750);
nodeJuggler->setAutoDelete(true);
}
return nodeJuggler;
}
void KisNodeManager::raiseNode()
{
KUndo2MagicString actionName = kundo2_i18n("Raise Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->raiseNode(selectedNodes());
}
void KisNodeManager::lowerNode()
{
KUndo2MagicString actionName = kundo2_i18n("Lower Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->lowerNode(selectedNodes());
}
void KisNodeManager::removeSingleNode(KisNodeSP node)
{
if (!node || !node->parent()) {
return;
}
KisNodeList nodes;
nodes << node;
removeSelectedNodes(nodes);
}
void KisNodeManager::removeSelectedNodes(KisNodeList nodes)
{
KUndo2MagicString actionName = kundo2_i18n("Remove Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->removeNode(nodes);
}
void KisNodeManager::removeNode()
{
removeSelectedNodes(selectedNodes());
}
void KisNodeManager::mirrorNodeX()
{
KisNodeSP node = activeNode();
KUndo2MagicString commandName;
if (node->inherits("KisLayer")) {
commandName = kundo2_i18n("Mirror Layer X");
} else if (node->inherits("KisMask")) {
commandName = kundo2_i18n("Mirror Mask X");
}
mirrorNode(node, commandName, Qt::Horizontal, m_d->view->selection());
}
void KisNodeManager::mirrorNodeY()
{
KisNodeSP node = activeNode();
KUndo2MagicString commandName;
if (node->inherits("KisLayer")) {
commandName = kundo2_i18n("Mirror Layer Y");
} else if (node->inherits("KisMask")) {
commandName = kundo2_i18n("Mirror Mask Y");
}
mirrorNode(node, commandName, Qt::Vertical, m_d->view->selection());
}
void KisNodeManager::mirrorAllNodesX()
{
KisNodeSP node = m_d->view->image()->root();
mirrorNode(node, kundo2_i18n("Mirror All Layers X"),
Qt::Horizontal, m_d->view->selection());
}
void KisNodeManager::mirrorAllNodesY()
{
KisNodeSP node = m_d->view->image()->root();
mirrorNode(node, kundo2_i18n("Mirror All Layers Y"),
Qt::Vertical, m_d->view->selection());
}
void KisNodeManager::activateNextNode()
{
KisNodeSP activeNode = this->activeNode();
if (!activeNode) return;
KisNodeSP node = activeNode->nextSibling();
while (node && node->childCount() > 0) {
node = node->firstChild();
}
if (!node && activeNode->parent() && activeNode->parent()->parent()) {
node = activeNode->parent();
}
while(node && isNodeHidden(node, true)) {
node = node->nextSibling();
}
if (node) {
slotNonUiActivatedNode(node);
}
}
void KisNodeManager::activatePreviousNode()
{
KisNodeSP activeNode = this->activeNode();
if (!activeNode) return;
KisNodeSP node;
if (activeNode->childCount() > 0) {
node = activeNode->lastChild();
}
else {
node = activeNode->prevSibling();
}
while (!node && activeNode->parent()) {
node = activeNode->parent()->prevSibling();
activeNode = activeNode->parent();
}
while(node && isNodeHidden(node, true)) {
node = node->prevSibling();
}
if (node) {
slotNonUiActivatedNode(node);
}
}
void KisNodeManager::switchToPreviouslyActiveNode()
{
if (m_d->previouslyActiveNode && m_d->previouslyActiveNode->parent()) {
slotNonUiActivatedNode(m_d->previouslyActiveNode);
}
}
void KisNodeManager::mirrorNode(KisNodeSP node,
const KUndo2MagicString& actionName,
Qt::Orientation orientation,
KisSelectionSP selection)
{
KisImageSignalVector emitSignals;
emitSignals << ModifiedSignal;
KisProcessingApplicator applicator(m_d->view->image(), node,
KisProcessingApplicator::RECURSIVE,
emitSignals, actionName);
KisProcessingVisitorSP visitor;
if (selection) {
visitor = new KisMirrorProcessingVisitor(selection, orientation);
} else {
visitor = new KisMirrorProcessingVisitor(m_d->view->image()->bounds(), orientation);
}
if (!selection) {
applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
} else {
applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
}
applicator.end();
nodesUpdated();
}
void KisNodeManager::Private::saveDeviceAsImage(KisPaintDeviceSP device,
const QString &defaultName,
const QRect &bounds,
qreal xRes,
qreal yRes,
quint8 opacity)
{
KoFileDialog dialog(view->mainWindow(), KoFileDialog::SaveFile, "savenodeasimage");
dialog.setCaption(i18n("Export \"%1\"", defaultName));
dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export));
QString filename = dialog.filename();
if (filename.isEmpty()) return;
QUrl url = QUrl::fromLocalFile(filename);
if (url.isEmpty()) return;
QString mimefilter = KisMimeDatabase::mimeTypeForFile(filename, false);
QScopedPointer doc(KisPart::instance()->createDocument());
KisImageSP dst = new KisImage(doc->createUndoStore(),
bounds.width(),
bounds.height(),
device->compositionSourceColorSpace(),
defaultName);
dst->setResolution(xRes, yRes);
doc->setCurrentImage(dst);
KisPaintLayer* paintLayer = new KisPaintLayer(dst, "paint device", opacity);
paintLayer->paintDevice()->makeCloneFrom(device, bounds);
dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0));
dst->initialRefreshGraph();
if (!doc->exportDocumentSync(url, mimefilter.toLatin1())) {
QMessageBox::warning(0,
i18nc("@title:window", "Krita"),
i18n("Could not save the layer. %1", doc->errorMessage().toUtf8().data()),
QMessageBox::Ok);
}
}
void KisNodeManager::saveNodeAsImage()
{
KisNodeSP node = activeNode();
if (!node) {
warnKrita << "BUG: Save Node As Image was called without any node selected";
return;
}
KisImageWSP image = m_d->view->image();
QRect saveRect = image->bounds() | node->exactBounds();
m_d->saveDeviceAsImage(node->projection(),
node->name(),
saveRect,
image->xRes(), image->yRes(),
node->opacity());
}
#include "SvgWriter.h"
void KisNodeManager::saveVectorLayerAsImage()
{
KisShapeLayerSP shapeLayer = qobject_cast(activeNode().data());
if (!shapeLayer) {
return;
}
KoFileDialog dialog(m_d->view->mainWindow(), KoFileDialog::SaveFile, "savenodeasimage");
dialog.setCaption(i18nc("@title:window", "Export to SVG"));
dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
dialog.setMimeTypeFilters(QStringList() << "image/svg+xml", "image/svg+xml");
QString filename = dialog.filename();
if (filename.isEmpty()) return;
QUrl url = QUrl::fromLocalFile(filename);
if (url.isEmpty()) return;
const QSizeF sizeInPx = m_d->view->image()->bounds().size();
const QSizeF sizeInPt(sizeInPx.width() / m_d->view->image()->xRes(),
sizeInPx.height() / m_d->view->image()->yRes());
QList shapes = shapeLayer->shapes();
std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
SvgWriter writer(shapes);
if (!writer.save(filename, sizeInPt, true)) {
QMessageBox::warning(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("Could not save to svg: %1", filename));
}
}
void KisNodeManager::slotSplitAlphaIntoMask()
{
KisNodeSP node = activeNode();
// guaranteed by KisActionManager
KIS_ASSERT_RECOVER_RETURN(node->hasEditablePaintDevice());
KisPaintDeviceSP srcDevice = node->paintDevice();
const KoColorSpace *srcCS = srcDevice->colorSpace();
const QRect processRect =
srcDevice->exactBounds() |
srcDevice->defaultBounds()->bounds();
KisPaintDeviceSP selectionDevice =
new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8());
m_d->commandsAdapter.beginMacro(kundo2_i18n("Split Alpha into a Mask"));
KisTransaction transaction(kundo2_noi18n("__split_alpha_channel__"), srcDevice);
KisSequentialIterator srcIt(srcDevice, processRect);
KisSequentialIterator dstIt(selectionDevice, processRect);
while (srcIt.nextPixel() && dstIt.nextPixel()) {
quint8 *srcPtr = srcIt.rawData();
quint8 *alpha8Ptr = dstIt.rawData();
*alpha8Ptr = srcCS->opacityU8(srcPtr);
srcCS->setOpacity(srcPtr, OPACITY_OPAQUE_U8, 1);
}
m_d->commandsAdapter.addExtraCommand(transaction.endAndTake());
createNode("KisTransparencyMask", false, selectionDevice);
m_d->commandsAdapter.endMacro();
}
void KisNodeManager::Private::mergeTransparencyMaskAsAlpha(bool writeToLayers)
{
KisNodeSP node = q->activeNode();
KisNodeSP parentNode = node->parent();
// guaranteed by KisActionManager
KIS_ASSERT_RECOVER_RETURN(node->inherits("KisTransparencyMask"));
if (writeToLayers && !parentNode->hasEditablePaintDevice()) {
QMessageBox::information(view->mainWindow(),
i18nc("@title:window", "Layer %1 is not editable", parentNode->name()),
i18n("Cannot write alpha channel of "
"the parent layer \"%1\".\n"
"The operation will be cancelled.", parentNode->name()));
return;
}
KisPaintDeviceSP dstDevice;
if (writeToLayers) {
KIS_ASSERT_RECOVER_RETURN(parentNode->paintDevice());
dstDevice = parentNode->paintDevice();
} else {
KisPaintDeviceSP copyDevice = parentNode->paintDevice();
if (!copyDevice) {
copyDevice = parentNode->original();
}
dstDevice = new KisPaintDevice(*copyDevice);
}
const KoColorSpace *dstCS = dstDevice->colorSpace();
KisPaintDeviceSP selectionDevice = node->paintDevice();
KIS_ASSERT_RECOVER_RETURN(selectionDevice->colorSpace()->pixelSize() == 1);
const QRect processRect =
selectionDevice->exactBounds() |
dstDevice->exactBounds() |
selectionDevice->defaultBounds()->bounds();
QScopedPointer transaction;
if (writeToLayers) {
commandsAdapter.beginMacro(kundo2_i18n("Write Alpha into a Layer"));
transaction.reset(new KisTransaction(kundo2_noi18n("__write_alpha_channel__"), dstDevice));
}
KisSequentialIterator srcIt(selectionDevice, processRect);
KisSequentialIterator dstIt(dstDevice, processRect);
while (srcIt.nextPixel() && dstIt.nextPixel()) {
quint8 *alpha8Ptr = srcIt.rawData();
quint8 *dstPtr = dstIt.rawData();
dstCS->setOpacity(dstPtr, *alpha8Ptr, 1);
}
if (writeToLayers) {
commandsAdapter.addExtraCommand(transaction->endAndTake());
commandsAdapter.removeNode(node);
commandsAdapter.endMacro();
} else {
KisImageWSP image = view->image();
QRect saveRect = image->bounds();
saveDeviceAsImage(dstDevice, parentNode->name(),
saveRect,
image->xRes(), image->yRes(),
OPACITY_OPAQUE_U8);
}
}
void KisNodeManager::slotSplitAlphaWrite()
{
m_d->mergeTransparencyMaskAsAlpha(true);
}
void KisNodeManager::slotSplitAlphaSaveMerged()
{
m_d->mergeTransparencyMaskAsAlpha(false);
}
void KisNodeManager::toggleLock()
{
KisNodeList nodes = this->selectedNodes();
KisNodeSP active = activeNode();
if (nodes.isEmpty() || !active) return;
bool isLocked = active->userLocked();
for (auto &node : nodes) {
node->setUserLocked(!isLocked);
}
}
void KisNodeManager::toggleVisibility()
{
KisNodeList nodes = this->selectedNodes();
KisNodeSP active = activeNode();
if (nodes.isEmpty() || !active) return;
bool isVisible = active->visible();
for (auto &node : nodes) {
node->setVisible(!isVisible);
node->setDirty();
}
}
void KisNodeManager::toggleAlphaLock()
{
KisNodeList nodes = this->selectedNodes();
KisNodeSP active = activeNode();
if (nodes.isEmpty() || !active) return;
auto layer = qobject_cast(active.data());
if (!layer) {
return;
}
bool isAlphaLocked = layer->alphaLocked();
for (auto &node : nodes) {
auto layer = qobject_cast(node.data());
if (layer) {
layer->setAlphaLocked(!isAlphaLocked);
}
}
}
void KisNodeManager::toggleInheritAlpha()
{
KisNodeList nodes = this->selectedNodes();
KisNodeSP active = activeNode();
if (nodes.isEmpty() || !active) return;
auto layer = qobject_cast(active.data());
if (!layer) {
return;
}
bool isAlphaDisabled = layer->alphaChannelDisabled();
for (auto &node : nodes) {
auto layer = qobject_cast(node.data());
if (layer) {
layer->disableAlphaChannel(!isAlphaDisabled);
node->setDirty();
}
}
}
void KisNodeManager::cutLayersToClipboard()
{
KisNodeList nodes = this->selectedNodes();
if (nodes.isEmpty()) return;
KisClipboard::instance()->setLayers(nodes, m_d->view->image(), false);
KUndo2MagicString actionName = kundo2_i18n("Cut Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->removeNode(nodes);
}
void KisNodeManager::copyLayersToClipboard()
{
KisNodeList nodes = this->selectedNodes();
KisClipboard::instance()->setLayers(nodes, m_d->view->image(), true);
}
void KisNodeManager::pasteLayersFromClipboard()
{
const QMimeData *data = KisClipboard::instance()->layersMimeData();
if (!data) return;
KisNodeSP activeNode = this->activeNode();
KisShapeController *shapeController = dynamic_cast(m_d->imageView->document()->shapeController());
Q_ASSERT(shapeController);
KisDummiesFacadeBase *dummiesFacade = dynamic_cast(m_d->imageView->document()->shapeController());
Q_ASSERT(dummiesFacade);
const bool copyNode = false;
KisImageSP image = m_d->view->image();
KisNodeDummy *parentDummy = dummiesFacade->dummyForNode(activeNode);
KisNodeDummy *aboveThisDummy = parentDummy ? parentDummy->lastChild() : 0;
KisMimeData::insertMimeLayers(data,
image,
shapeController,
parentDummy,
aboveThisDummy,
copyNode,
nodeInsertionAdapter());
}
void KisNodeManager::createQuickGroupImpl(KisNodeJugglerCompressed *juggler,
const QString &overrideGroupName,
KisNodeSP *newGroup,
KisNodeSP *newLastChild)
{
KisNodeSP active = activeNode();
if (!active) return;
KisImageSP image = m_d->view->image();
QString groupName = !overrideGroupName.isEmpty() ? overrideGroupName : image->nextLayerName();
KisGroupLayerSP group = new KisGroupLayer(image.data(), groupName, OPACITY_OPAQUE_U8);
KisNodeList nodes1;
nodes1 << group;
KisNodeList nodes2;
nodes2 = KisLayerUtils::sortMergableNodes(image->root(), selectedNodes());
KisLayerUtils::filterMergableNodes(nodes2);
if (nodes2.size() == 0) return;
if (KisLayerUtils::checkIsChildOf(active, nodes2)) {
active = nodes2.first();
}
KisNodeSP parent = active->parent();
KisNodeSP aboveThis = active;
juggler->addNode(nodes1, parent, aboveThis);
juggler->moveNode(nodes2, group, 0);
*newGroup = group;
*newLastChild = nodes2.last();
}
void KisNodeManager::createQuickGroup()
{
KUndo2MagicString actionName = kundo2_i18n("Quick Group");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
KisNodeSP parent;
KisNodeSP above;
createQuickGroupImpl(juggler, "", &parent, &above);
}
void KisNodeManager::createQuickClippingGroup()
{
KUndo2MagicString actionName = kundo2_i18n("Quick Clipping Group");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
KisNodeSP parent;
KisNodeSP above;
KisImageSP image = m_d->view->image();
createQuickGroupImpl(juggler, image->nextLayerName(i18nc("default name for a clipping group layer", "Clipping Group")), &parent, &above);
KisPaintLayerSP maskLayer = new KisPaintLayer(image.data(), i18nc("default name for quick clip group mask layer", "Mask Layer"), OPACITY_OPAQUE_U8, image->colorSpace());
maskLayer->disableAlphaChannel(true);
juggler->addNode(KisNodeList() << maskLayer, parent, above);
}
void KisNodeManager::quickUngroup()
{
KisNodeSP active = activeNode();
if (!active) return;
KisNodeSP parent = active->parent();
KisNodeSP aboveThis = active;
KUndo2MagicString actionName = kundo2_i18n("Quick Ungroup");
if (parent && dynamic_cast