Commit d592d520 authored by Dmitry Kazakov's avatar Dmitry Kazakov

Implemented multinode editing of layer properties

Now you can edit any property of a group of layers using Properties
dialog. Near to every field there is a checkbox showing if the value
will be applied to your layers. If you disable the checkbox, the
properties of the layers will be restored back.

For boolean properties, like "Visible" or "Locked" there are tristated
checkboxes. They have the following meaning:

checked --- all the layers have the property enabled
unchecked --- all the layers have the property disabled
partially-checked --- the layers have mixed property values. They are kept
                      unchanged.

CC:kimageshop@kde.org
parent 30c07ba8
......@@ -25,7 +25,7 @@
#include "kis_node_model.h"
class KisLayerPropertiesIcons
class KRITAIMAGE_EXPORT KisLayerPropertiesIcons
{
public:
KisLayerPropertiesIcons();
......
......@@ -133,6 +133,7 @@ set(kritaui_LIB_SRCS
kis_view_plugin.cpp
kis_canvas_controls_manager.cpp
kis_tooltip_manager.cpp
kis_multinode_property.cpp
kisexiv2/kis_exif_io.cpp
kisexiv2/kis_exiv2.cpp
kisexiv2/kis_iptc_io.cpp
......@@ -187,7 +188,6 @@ set(kritaui_LIB_SRCS
tool/strokes/freehand_stroke.cpp
tool/strokes/kis_painter_based_stroke_strategy.cpp
tool/strokes/kis_filter_stroke_strategy.cpp
widgets/kis_channelflags_widget.cpp
widgets/kis_cmb_composite.cc
widgets/kis_cmb_contour.cpp
widgets/kis_cmb_gradient.cpp
......
......@@ -21,6 +21,8 @@
#include <QList>
#include <QCheckBox>
#include <QScopedPointer>
#include "kis_types.h"
#include <KoDialog.h>
......@@ -53,36 +55,28 @@ class KisDlgLayerProperties : public KoDialog
Q_OBJECT
public:
KisDlgLayerProperties(KisLayerSP layer, KisViewManager *view, KisDocument *doc, QWidget *parent = 0, const char *name = 0, Qt::WFlags f = 0);
KisDlgLayerProperties(KisNodeList nodes, KisViewManager *view, QWidget *parent = 0, const char *name = 0, Qt::WFlags f = 0);
virtual ~KisDlgLayerProperties();
private:
protected Q_SLOTS:
void updatePreview();
bool haveChanges() const;
QString getName() const;
qint32 getOpacity() const;
QString getCompositeOp() const;
void slotCompositeOpValueChangedInternally();
void slotCompositeOpValueChangedExternally();
/**
* @return a bit array of channel flags in the order in which the
* channels appear in the pixel, not in the list of KoChannelInfo
* objects from the colorspace.
*/
QBitArray getChannelFlags() const;
void slotOpacityValueChangedInternally();
void slotOpacityValueChangedExternally();
protected Q_SLOTS:
void slotNameChanged(const QString &);
void applyNewProperties();
void cleanPreviewChanges();
void kickTimer();
void updatePreview();
void adjustSize();
void slotNameValueChangedInternally();
void slotNameValueChangedExternally();
private:
void slotPropertyValueChangedInternally();
void slotFlagsValueChangedInternally();
private:
struct Private;
Private * const d;
const QScopedPointer<Private> d;
};
#endif // KIS_DLG_LAYER_PROPERTIES_H_
......
......@@ -2,28 +2,24 @@
<ui version="4.0">
<class>WdgLayerProperties</class>
<widget class="QWidget" name="WdgLayerProperties">
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,1,0">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>740</width>
<height>514</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QFormLayout" name="formLayout">
<layout class="QGridLayout" name="gridLayout" rowstretch="1,1,1,1,1">
<item row="0" column="0">
<widget class="QLabel" name="textLabel1">
<widget class="QCheckBox" name="chkName"/>
</item>
<item row="0" column="1">
<widget class="QLabel" name="lblName">
<property name="text">
<string>&amp;Name:</string>
</property>
......@@ -35,7 +31,7 @@
</property>
</widget>
</item>
<item row="0" column="1">
<item row="0" column="2">
<widget class="QLineEdit" name="editName">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
......@@ -46,7 +42,10 @@
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="textLabel3">
<widget class="QCheckBox" name="chkOpacity"/>
</item>
<item row="1" column="1">
<widget class="QLabel" name="lblOpacity">
<property name="text">
<string>&amp;Opacity:</string>
</property>
......@@ -58,8 +57,8 @@
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="KisSliderSpinBox" name="intOpacity">
<item row="1" column="2">
<widget class="KisSliderSpinBox" name="intOpacity" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
......@@ -69,7 +68,10 @@
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="textLabel4">
<widget class="QCheckBox" name="chkCompositeOp"/>
</item>
<item row="2" column="1">
<widget class="QLabel" name="lblCompositeOp">
<property name="text">
<string>Composite mode:</string>
</property>
......@@ -78,7 +80,7 @@
</property>
</widget>
</item>
<item row="2" column="1">
<item row="2" column="2">
<widget class="KisCompositeOpComboBox" name="cmbComposite">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
......@@ -88,8 +90,14 @@
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="lblColorSpaces">
<item row="3" column="1">
<widget class="QLabel" name="lblColorSpaceTitle">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Color space:</string>
</property>
......@@ -98,15 +106,27 @@
</property>
</widget>
</item>
<item row="3" column="1">
<item row="3" column="2">
<widget class="QLabel" name="lblColorSpace">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string comment="KDE::DoNotExtract">TextLabel</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="lblProfiles">
<item row="4" column="1">
<widget class="QLabel" name="lblProfileTitle">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Profile:</string>
</property>
......@@ -115,8 +135,14 @@
</property>
</widget>
</item>
<item row="4" column="1">
<item row="4" column="2">
<widget class="QLabel" name="lblProfile">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string comment="KDE::DoNotExtract">TextLabel</string>
</property>
......@@ -130,6 +156,43 @@
</item>
</layout>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>10</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="grpProperties">
<property name="title">
<string>Properties</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="lineActiveChannels">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="grpActiveChannels">
<property name="toolTip">
......@@ -150,24 +213,17 @@
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QCheckBox" name="checkBoxPreview">
<property name="text">
<string>&amp;Preview</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
......
......@@ -457,7 +457,9 @@ void KisLayerManager::layerProperties()
}
} else { // If layer == normal painting layer, vector layer, or group layer
KisDlgLayerProperties *dialog = new KisDlgLayerProperties(layer, m_view, m_view->document());
QList<KisNodeSP> 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();
......
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_multinode_property.h"
/******************************************************************/
/* MultinodePropertyConnectorInterface */
/******************************************************************/
MultinodePropertyConnectorInterface::~MultinodePropertyConnectorInterface()
{
}
void MultinodePropertyConnectorInterface::connectValueChangedSignal(const QObject *receiver, const char *method, Qt::ConnectionType type) {
connect(this, SIGNAL(sigValueChanged()), receiver, method, type);
notifyValueChanged();
}
void MultinodePropertyConnectorInterface::notifyValueChanged() {
emit sigValueChanged();
}
/******************************************************************/
/* MultinodePropertyBaseConnector */
/******************************************************************/
MultinodePropertyBaseConnector::MultinodePropertyBaseConnector(KisMultinodePropertyInterface *parent)
: m_parent(parent)
{
}
void MultinodePropertyBaseConnector::connectIgnoreCheckBox(QCheckBox *ignoreBox) {
m_ignoreBox = ignoreBox;
if (!m_parent->isIgnored() && !m_parent->savedValuesDiffer()) {
m_ignoreBox->setEnabled(false);
m_ignoreBox->setChecked(true);
if (m_parent->haveTheOnlyNode()) {
m_ignoreBox->setVisible(false);
}
} else {
connect(m_ignoreBox, SIGNAL(stateChanged(int)), SLOT(slotIgnoreCheckBoxChanged(int)));
m_ignoreBox->setEnabled(true);
m_ignoreBox->setChecked(!m_parent->isIgnored());
}
}
void MultinodePropertyBaseConnector::slotIgnoreCheckBoxChanged(int state) {
m_parent->setIgnored(state != Qt::Checked);
}
void MultinodePropertyBaseConnector::notifyIgnoreChanged() {
if (!m_ignoreBox) return;
if (m_ignoreBox->isChecked() != !m_parent->isIgnored()) {
m_ignoreBox->setChecked(!m_parent->isIgnored());
}
}
/******************************************************************/
/* KisMultinodePropertyInterface */
/******************************************************************/
KisMultinodePropertyInterface::KisMultinodePropertyInterface()
{
}
KisMultinodePropertyInterface::~KisMultinodePropertyInterface()
{
}
This diff is collapsed.
......@@ -191,6 +191,10 @@ public:
state while in stasis */
bool stateInStasis;
bool operator==(const Property &rhs) const {
return rhs.name == name;
}
/// Default constructor. Use if you want to assign the members manually.
Property(): isMutable( false ) { }
......
......@@ -203,10 +203,18 @@ set(kis_asl_layer_style_serializer_test_SRCS kis_asl_layer_style_serializer_test
kde4_add_unit_test(KisAslLayerStyleSerializerTest TESTNAME krita-ui-KisAslLayerStyleSerializerTest ${kis_asl_layer_style_serializer_test_SRCS})
target_link_libraries(KisAslLayerStyleSerializerTest kritaui kritaimage ${QT_QTTEST_LIBRARY})
########### next target ###############
set(kis_node_juggler_compressed_test_SRCS kis_node_juggler_compressed_test.cpp ../../sdk/tests/testutil.cpp)
kde4_add_unit_test(KisNodeJugglerCompressedTest TESTNAME krita-image-BaseNodeTest ${kis_node_juggler_compressed_test_SRCS})
target_link_libraries(KisNodeJugglerCompressedTest kritaimage kritaui Qt5::Test)
########### next target ###############
set(kis_multinode_property_test_SRCS kis_multinode_property_test.cpp)
kde4_add_unit_test(KisMultinodePropertyTest TESTNAME krita-image-BaseNodeTest ${kis_multinode_property_test_SRCS})
target_link_libraries(KisMultinodePropertyTest kritaimage kritaui Qt5::Test)
if (HAVE_OPENGL)
########### next target ###############
......
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* 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_multinode_property_test.h"
#include <QTest>
#include "testutil.h"
#include <KoCompositeOpRegistry.h>
#include "kis_multinode_property.h"
void KisMultinodePropertyTest::test()
{
TestUtil::MaskParent p;
KisPaintLayerSP layer1 = p.layer;
KisPaintLayerSP layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8);
KisPaintLayerSP layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8);
KisNodeList nodes;
nodes << layer1;
nodes << layer2;
nodes << layer3;
{
QScopedPointer<QCheckBox> box(new QCheckBox("test ignore"));
KisMultinodeCompositeOpProperty prop(nodes);
prop.connectIgnoreCheckBox(box.data());
QCOMPARE(prop.isIgnored(), false);
QCOMPARE(prop.value(), COMPOSITE_OVER);
QCOMPARE(box->isEnabled(), false);
QCOMPARE(box->isChecked(), true);
prop.setValue(COMPOSITE_ALPHA_DARKEN);
QCOMPARE(prop.value(), COMPOSITE_ALPHA_DARKEN);
QCOMPARE(box->isEnabled(), false);
QCOMPARE(box->isChecked(), true);
QCOMPARE(layer1->compositeOpId(), COMPOSITE_ALPHA_DARKEN);
QCOMPARE(layer2->compositeOpId(), COMPOSITE_ALPHA_DARKEN);
QCOMPARE(layer3->compositeOpId(), COMPOSITE_ALPHA_DARKEN);
prop.setIgnored(true);
QCOMPARE(layer1->compositeOpId(), COMPOSITE_OVER);
QCOMPARE(layer2->compositeOpId(), COMPOSITE_OVER);
QCOMPARE(layer3->compositeOpId(), COMPOSITE_OVER);
QCOMPARE(prop.value(), COMPOSITE_OVER);
QCOMPARE(box->isEnabled(), false);
QCOMPARE(box->isChecked(), false);
prop.setIgnored(false);
QCOMPARE(layer1->compositeOpId(), COMPOSITE_OVER);
QCOMPARE(layer2->compositeOpId(), COMPOSITE_OVER);
QCOMPARE(layer3->compositeOpId(), COMPOSITE_OVER);
QCOMPARE(prop.value(), COMPOSITE_OVER);
QCOMPARE(box->isEnabled(), false);
QCOMPARE(box->isChecked(), true);
}
layer1->setCompositeOpId(COMPOSITE_ALPHA_DARKEN);
layer2->setCompositeOpId(COMPOSITE_OVER);
layer3->setCompositeOpId(COMPOSITE_OVER);
{
QScopedPointer<QCheckBox> box(new QCheckBox("test ignore"));
KisMultinodeCompositeOpProperty prop(nodes);
prop.connectIgnoreCheckBox(box.data());
QCOMPARE(prop.isIgnored(), true);
QCOMPARE(layer1->compositeOpId(), COMPOSITE_ALPHA_DARKEN);
QCOMPARE(layer2->compositeOpId(), COMPOSITE_OVER);
QCOMPARE(layer3->compositeOpId(), COMPOSITE_OVER);
QCOMPARE(prop.value(), COMPOSITE_ALPHA_DARKEN);
QCOMPARE(box->isEnabled(), true);
QCOMPARE(box->isChecked(), false);
prop.setIgnored(false);
QCOMPARE(layer1->compositeOpId(), COMPOSITE_ALPHA_DARKEN);
QCOMPARE(layer2->compositeOpId(), COMPOSITE_ALPHA_DARKEN);
QCOMPARE(layer3->compositeOpId(), COMPOSITE_ALPHA_DARKEN);
QCOMPARE(prop.value(), COMPOSITE_ALPHA_DARKEN);
QCOMPARE(box->isEnabled(), true);
QCOMPARE(box->isChecked(), true);
prop.setValue(COMPOSITE_OVER);
QCOMPARE(layer1->compositeOpId(), COMPOSITE_OVER);
QCOMPARE(layer2->compositeOpId(), COMPOSITE_OVER);
QCOMPARE(layer3->compositeOpId(), COMPOSITE_OVER);
QCOMPARE(prop.value(), COMPOSITE_OVER);
QCOMPARE(box->isEnabled(), true);
QCOMPARE(box->isChecked(), true);
prop.setIgnored(true);
QCOMPARE(layer1->compositeOpId(), COMPOSITE_ALPHA_DARKEN);
QCOMPARE(layer2->compositeOpId(), COMPOSITE_OVER);
QCOMPARE(layer3->compositeOpId(), COMPOSITE_OVER);
QCOMPARE(prop.value(), COMPOSITE_ALPHA_DARKEN);
QCOMPARE(box->isEnabled(), true);
QCOMPARE(box->isChecked(), false);
}
}
QTEST_MAIN(KisMultinodePropertyTest)
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* 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
......@@ -15,57 +15,17 @@
* 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_CHANNELFLAGS_WIDGET_H
#define KIS_CHANNELFLAGS_WIDGET_H
#include <QList>
#include <QCheckBox>
#include "kritaui_export.h"
#ifndef __KIS_MULTINODE_PROPERTY_TEST_H
#define __KIS_MULTINODE_PROPERTY_TEST_H
#include <QScrollArea>
#include <QVector>
#include <QtTest/QtTest>
class QBitArray;
class KoColorSpace;
/**
* A simple widget that shows a list of checkboxes in a scrollable
* area. The checkboxes correspond to the channels in a colorspace and
* are in the order the channels are packed in the pixel. The
* resulting QBitArray can be used to decide which channels to modify
* or not for filters, composition and eveything else.
*/
class KRITAUI_EXPORT KisChannelFlagsWidget : public QScrollArea
class KisMultinodePropertyTest : public QObject
{
Q_OBJECT
public:
KisChannelFlagsWidget(const KoColorSpace *colorSpace, QWidget * parent = 0);
~KisChannelFlagsWidget();
public:
/**
* Set the channelflags -- they are supposed to be in pixel order.
*/
void setChannelFlags(const QBitArray & channelFlags);
/**
* retrieve the channel flags, in pixel order.
*/
QBitArray channelFlags() const;
Q_SIGNALS:
void channelSelectionChanced();
private:
QList<QCheckBox*> m_channelChecks;
private Q_SLOTS:
void test();
};
#endif
#endif /* __KIS_MULTINODE_PROPERTY_TEST_H */
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* 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 "widgets/kis_channelflags_widget.h"
#include <QBitArray>
#include <QScrollArea>
#include <QWidget>
#include <QCheckBox>
#include <QVBoxLayout>
#include <klocalizedstring.h>
#include <KoChannelInfo.h>
#include <KoColorSpace.h>
#include <kis_debug.h>
KisChannelFlagsWidget::KisChannelFlagsWidget(const KoColorSpace * colorSpace, QWidget * parent)
: QScrollArea(parent)
{
setObjectName("KisChannelFlagsWidget");
setToolTip(i18n("Check the active channels in this layer. Only these channels will be affected by any operation."));
QWidget * w = new QWidget();
setBackgroundRole(QPalette::Window);
QVBoxLayout * vbl = new QVBoxLayout();
for (int i = 0; i < colorSpace->channels().size(); ++i) {
KoChannelInfo * channel = colorSpace->channels().at(i);
QCheckBox * bx = new QCheckBox(channel->name(), w);
connect(bx, SIGNAL(toggled(bool)), SIGNAL(channelSelectionChanced()));
bx->setCheckState(Qt::Checked);
vbl->addWidget(bx);
m_channelChecks.append(bx);
}
w->setLayout(vbl);
setWidget(w);
}
KisChannelFlagsWidget::~KisChannelFlagsWidget()
{
}
void KisChannelFlagsWidget::setChannelFlags(const QBitArray & cf)
{
dbgUI << "KisChannelFlagsWidget::setChannelFlags " << cf.isEmpty();
if (cf.isEmpty()) return;