Commit e0dec404 authored by Tusooa Zhu's avatar Tusooa Zhu

Add a dialog to change the source of clone layers

This commit adds an action named `Set Copy From' to the context menu
of the Layers Docker, if one or more clone layers are selected. The
action pops up a dialog that can change the `Copy From' property of
selected clone layers.

The dialog previews the changes to these layers.
If there is any change to the document when the dialog is open, the
changes in the dialog will be applied and the dialog closed.
parent 57686d84
......@@ -3588,5 +3588,17 @@
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="set-copy-from">
<icon></icon>
<text>Set Copy F&amp;rom...</text>
<whatsThis></whatsThis>
<toolTip>Set the source for the selected clone layer(s).</toolTip>
<iconText>Set Copy From</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
</ActionCollection>
......@@ -64,6 +64,7 @@ set(kritaui_LIB_SRCS
dialogs/kis_delayed_save_dialog.cpp
dialogs/KisSessionManagerDialog.cpp
dialogs/KisNewWindowLayoutDialog.cpp
dialogs/KisDlgChangeCloneSource.cpp
flake/kis_node_dummies_graph.cpp
flake/kis_dummies_facade_base.cpp
flake/kis_dummies_facade.cpp
......@@ -155,6 +156,7 @@ set(kritaui_LIB_SRCS
kis_multinode_property.cpp
kis_stopgradient_editor.cpp
KisWelcomePageWidget.cpp
KisChangeCloneLayersCommand.cpp
kisexiv2/kis_exif_io.cpp
kisexiv2/kis_exiv2.cpp
......@@ -508,6 +510,7 @@ ki18n_wrap_ui(kritaui_LIB_SRCS
forms/WdgDlgPaletteEditor.ui
forms/KisNewsPage.ui
forms/wdgGamutMaskToolbar.ui
forms/wdgchangeclonesource.ui
brushhud/kis_dlg_brush_hud_config.ui
dialogs/kis_delayed_save_dialog.ui
......
/*
* Copyright (c) 2019 Tusooa Zhu <tusooa@vista.aero>
*
* 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 "KisChangeCloneLayersCommand.h"
#include <kis_clone_layer.h>
struct KisChangeCloneLayersCommand::Private
{
QList<KisCloneLayerSP> cloneLayers;
QList<KisLayerSP> originalSource;
KisLayerSP newSource;
};
KisChangeCloneLayersCommand::KisChangeCloneLayersCommand(QList<KisCloneLayerSP> cloneLayers, KisLayerSP newSource, KUndo2Command *parent)
: KUndo2Command(kundo2_i18n("Change Clone Layers"), parent)
, d(new Private())
{
KIS_SAFE_ASSERT_RECOVER_RETURN(!cloneLayers.isEmpty());
d->cloneLayers = cloneLayers;
Q_FOREACH (KisCloneLayerSP layer, d->cloneLayers) {
d->originalSource << layer->copyFrom();
}
d->newSource = newSource;
}
void KisChangeCloneLayersCommand::redo()
{
Q_FOREACH (KisCloneLayerSP layer, d->cloneLayers) {
layer->setCopyFrom(d->newSource);
layer->setDirty();
}
}
void KisChangeCloneLayersCommand::undo()
{
KIS_SAFE_ASSERT_RECOVER_RETURN(d->cloneLayers.size() == d->originalSource.size());
for (int i = 0; i < d->cloneLayers.size(); ++i) {
KisCloneLayerSP layer = d->cloneLayers.at(i);
layer->setCopyFrom(d->originalSource.at(i));
layer->setDirty();
}
}
bool KisChangeCloneLayersCommand::mergeWith(const KUndo2Command *command)
{
const KisChangeCloneLayersCommand *other = dynamic_cast<const KisChangeCloneLayersCommand *>(command);
if (other && d->cloneLayers == other->d->cloneLayers) {
d->newSource = other->d->newSource;
return true;
}
return false;
}
/*
* Copyright (c) 2019 Tusooa Zhu <tusooa@vista.aero>
*
* 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_CHANGE_CLONE_LAYERS_COMMAND_H_
#define KIS_CHANGE_CLONE_LAYERS_COMMAND_H_
#include <kundo2command.h>
#include "kis_types.h"
class KisChangeCloneLayersCommand : public KUndo2Command
{
public:
KisChangeCloneLayersCommand(QList<KisCloneLayerSP> cloneLayers, KisLayerSP newSource, KUndo2Command *parent = 0);
void undo() override;
void redo() override;
bool mergeWith(const KUndo2Command *) override;
private:
struct Private;
QScopedPointer<Private> d;
};
#endif // KIS_CHANGE_CLONE_LAYERS_COMMAND_H_
/*
* Copyright (c) 2019 Tusooa Zhu <tusooa@vista.aero>
*
* 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 "KisDlgChangeCloneSource.h"
#include <kis_clone_layer.h>
#include <kis_image.h>
#include <kis_undo_adapter.h>
#include <kis_processing_applicator.h>
#include <KisImageSignals.h>
#include <kis_signals_blocker.h>
#include "KisViewManager.h"
#include "KisChangeCloneLayersCommand.h"
struct KisDlgChangeCloneSource::Private
{
Private(QList<KisCloneLayerSP> layers, KisViewManager *view)
: cloneLayers(layers)
, view(view)
, image(view->image())
, applicator(new KisProcessingApplicator(image, 0,
KisProcessingApplicator::NONE,
/* emitSignals = */ KisImageSignalVector() << ModifiedSignal,
kundo2_i18n("Change Clone Layers")))
, modified(false) {}
QList<KisCloneLayerSP> cloneLayers;
KisViewManager *view;
KisImageSP image;
QList<KisLayerSP> validTargets;
Ui::WdgChangeCloneSource ui;
QScopedPointer<KisProcessingApplicator> applicator;
bool modified;
void addToTargetListRecursively(KisNodeSP node, bool addSelf = true);
void filterOutAncestorsAndClonesRecursively(KisLayerSP layer);
KisLayerSP getSelectedTargetLayer();
KUndo2Command *createCommand(KisLayerSP targetLayer);
};
void KisDlgChangeCloneSource::Private::addToTargetListRecursively(KisNodeSP node, bool addSelf)
{
if (!node) {
return;
}
if (addSelf) {
KisLayerSP layer(qobject_cast<KisLayer *>(node.data()));
if (layer) {
validTargets << layer;
}
}
for (KisNodeSP childNode = node->lastChild(); childNode; childNode = childNode->prevSibling()) {
KisLayerSP childLayer(qobject_cast<KisLayer *>(childNode.data()));
if (childLayer) {
addToTargetListRecursively(childLayer);
}
}
}
void KisDlgChangeCloneSource::Private::filterOutAncestorsAndClonesRecursively(KisLayerSP layer)
{
validTargets.removeOne(layer);
// remove `layer` and its ancestors
KisLayerSP parent = qobject_cast<KisLayer *>(layer->parent().data());
if (parent) {
filterOutAncestorsAndClonesRecursively(parent);
}
// remove all clones of `layer`, and their ancestors
Q_FOREACH (KisCloneLayerSP clone, layer->registeredClones()) {
filterOutAncestorsAndClonesRecursively(clone);
}
}
KisLayerSP KisDlgChangeCloneSource::Private::getSelectedTargetLayer()
{
int index = ui.cmbSourceLayer->currentIndex();
if (index != -1) {
return validTargets.at(index);
} else {
return 0;
}
}
KUndo2Command *KisDlgChangeCloneSource::Private::createCommand(KisLayerSP targetLayer)
{
return new KisChangeCloneLayersCommand(cloneLayers, targetLayer);
}
KisDlgChangeCloneSource::KisDlgChangeCloneSource(QList<KisCloneLayerSP> layers, KisViewManager *view, QWidget *parent)
: KoDialog(parent)
, d(new Private(layers, view))
{
KIS_SAFE_ASSERT_RECOVER_RETURN(!layers.isEmpty());
connect(d->image.data(), &KisImage::sigStrokeCancellationRequested,
this, &KisDlgChangeCloneSource::reject);
connect(d->image.data(), &KisImage::sigUndoDuringStrokeRequested,
this, &KisDlgChangeCloneSource::reject);
connect(d->image.data(), &KisImage::sigStrokeEndRequested,
this, &KisDlgChangeCloneSource::accept);
setButtons(Ok | Cancel);
setDefaultButton(Ok);
QWidget *widget = new QWidget(this);
d->ui.setupUi(widget);
setMainWidget(widget);
connect(d->ui.cmbSourceLayer, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &KisDlgChangeCloneSource::slotCancelChangesAndSetNewTarget);
updateTargetLayerList();
}
KisDlgChangeCloneSource::~KisDlgChangeCloneSource()
{
dbgUI << "dialog destroyed";
if (d->applicator) {
if (result() == QDialog::Accepted && d->modified) {
dbgUI << "Accepted";
d->applicator->end();
} else {
dbgUI << "Rejected";
d->applicator->cancel();
}
}
}
void KisDlgChangeCloneSource::updateTargetLayerList()
{
KisSignalsBlocker b(d->ui.cmbSourceLayer);
if (!d->image) {
return;
}
KisNodeSP root = d->image->root();
d->validTargets.clear();
d->addToTargetListRecursively(root, /* addSelf = */ false);
KisLayerSP commonCopyFrom(d->cloneLayers.first()->copyFrom());
Q_FOREACH (KisCloneLayerSP clone, d->cloneLayers) {
// filter out invalid targets:
// selected clone layers, their ancestors;
// the clone layers' registered clone, the clones' ancestors.
d->filterOutAncestorsAndClonesRecursively(clone);
// assume that clone->copyFrom() != 0
if (clone->copyFrom() != commonCopyFrom) {
commonCopyFrom = 0;
}
}
d->ui.cmbSourceLayer->clear();
Q_FOREACH (KisNodeSP node, d->validTargets) {
d->ui.cmbSourceLayer->addItem(node->name());
}
if (commonCopyFrom) {
d->ui.cmbSourceLayer->setCurrentIndex(d->validTargets.indexOf(commonCopyFrom));
} else {
d->ui.cmbSourceLayer->setCurrentIndex(-1);
}
}
void KisDlgChangeCloneSource::slotCancelChangesAndSetNewTarget()
{
KisLayerSP targetLayer = d->getSelectedTargetLayer();
if (targetLayer) {
d->applicator->applyCommand(d->createCommand(targetLayer));
d->modified = true;
}
}
/*
* Copyright (c) 2019 Tusooa Zhu <tusooa@vista.aero>
*
* 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_DLG_CHANGE_CLONE_SOURCE_H_
#define KIS_DLG_CHANGE_CLONE_SOURCE_H_
#include "kis_types.h"
#include <KoDialog.h>
#include "ui_wdgchangeclonesource.h"
class QWidget;
class KisViewManager;
class KisDlgChangeCloneSource : public KoDialog
{
Q_OBJECT
public:
KisDlgChangeCloneSource(QList<KisCloneLayerSP> layers, KisViewManager *view, QWidget *parent = 0);
~KisDlgChangeCloneSource() override;
private:
void updateTargetLayerList();
public Q_SLOTS:
void slotCancelChangesAndSetNewTarget();
private:
struct Private;
const QScopedPointer<Private> d;
};
#endif // KIS_DLG_CHANGE_CLONE_SOURCE_H_
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WdgChangeCloneSource</class>
<widget class="QWidget" name="WdgChangeCloneSource">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>358</width>
<height>166</height>
</rect>
</property>
<property name="windowTitle">
<string/>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="lblSourceLayer">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Copy from:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="cmbSourceLayer">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
......@@ -73,6 +73,7 @@
#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"
......@@ -352,6 +353,35 @@ void KisLayerManager::layerProperties()
}
}
void KisLayerManager::changeCloneSource()
{
QList<KisNodeSP> selectedNodes = m_view->nodeManager()->selectedNodes();
if (selectedNodes.isEmpty()) {
return;
}
QList<KisCloneLayerSP> cloneLayers;
KisNodeSP node;
Q_FOREACH (node, selectedNodes) {
KisCloneLayerSP cloneLayer(qobject_cast<KisCloneLayer *>(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::WindowStaysOnTopHint | Qt::Dialog);
dialog->show();
}
void KisLayerManager::convertNodeToPaintLayer(KisNodeSP source)
{
KisImageWSP image = m_view->image();
......
......@@ -110,6 +110,8 @@ private Q_SLOTS:
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);
......
......@@ -766,6 +766,11 @@ void KisNodeManager::nodeProperties(KisNodeSP node)
}
}
void KisNodeManager::changeCloneSource()
{
m_d->layerManager.changeCloneSource();
}
qint32 KisNodeManager::convertOpacityToInt(qreal opacity)
{
/**
......
......@@ -183,6 +183,8 @@ public Q_SLOTS:
void convertNode(const QString &nodeType);
void nodesUpdated();
void nodeProperties(KisNodeSP node);
/// pop up a window for changing the source of the selected Clone Layers
void changeCloneSource();
void nodeOpacityChanged(qreal opacity);
void nodeCompositeOpChanged(const KoCompositeOp* op);
void duplicateActiveNode();
......
......@@ -284,6 +284,7 @@ LayerBox::LayerBox()
connect(thumbnailSizeSlider, SIGNAL(sliderMoved(int)), &m_thumbnailSizeCompressor, SLOT(start()));
connect(&m_thumbnailSizeCompressor, SIGNAL(timeout()), SLOT(slotUpdateThumbnailIconSize()));
}
LayerBox::~LayerBox()
......@@ -367,6 +368,11 @@ void LayerBox::setViewManager(KisViewManager* kisview)
Q_ASSERT(action);
new SyncButtonAndAction(action, m_wdgLayerBox->bnLower, this);
connect(action, SIGNAL(triggered()), this, SLOT(slotLowerClicked()));
m_changeCloneSourceAction = actionManager->createAction("set-copy-from");
Q_ASSERT(m_changeCloneSourceAction);
connect(m_changeCloneSourceAction, &KisAction::triggered,
this, &LayerBox::slotChangeCloneSourceClicked);
}
void LayerBox::setCanvas(KoCanvasBase *canvas)
......@@ -609,6 +615,13 @@ void LayerBox::slotContextMenuRequested(const QPoint &pos, const QModelIndex &in
addActionToMenu(&menu, "layer_style");
}
Q_FOREACH(KisNodeSP node, nodes) {
if (node && node->inherits("KisCloneLayer")) {
menu.addAction(m_changeCloneSourceAction);
break;
}
}
{
KisSignalsBlocker b(m_colorSelector);
m_colorSelector->setCurrentIndex(singleLayer ? activeNode->colorLabelIndex() : -1);
......@@ -678,6 +691,7 @@ void LayerBox::slotContextMenuRequested(const QPoint &pos, const QModelIndex &in
}
addActionToMenu(&menu, "selectopaque");
}
}
menu.exec(pos);
......@@ -725,6 +739,12 @@ void LayerBox::slotPropertiesClicked()
}
}
void LayerBox::slotChangeCloneSourceClicked()
{
if (!m_canvas) return;
m_nodeManager->changeCloneSource();
}
void LayerBox::slotCompositeOpChanged(int index)
{
Q_UNUSED(index);
......
......@@ -103,6 +103,7 @@ private Q_SLOTS:
void slotRaiseClicked();
void slotLowerClicked();
void slotPropertiesClicked();
void slotChangeCloneSourceClicked();
void slotCompositeOpChanged(int index);
void slotOpacityChanged();
......@@ -162,6 +163,7 @@ private:
QVector<KisAction*> m_actions;
KisAction* m_removeAction;
KisAction* m_propertiesAction;
KisAction* m_changeCloneSourceAction;
KisSignalCompressor m_thumbnailCompressor;
KisSignalCompressor m_colorLabelCompressor;
KisSignalCompressor m_thumbnailSizeCompressor;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment