diff --git a/krita/krita.qrc b/krita/krita.qrc
index 0479a25e9f..4e55340c89 100644
--- a/krita/krita.qrc
+++ b/krita/krita.qrc
@@ -1,75 +1,75 @@
data/kritarc
krita4.xmlgui
pics/broken-preset.png
pics/dark_layer-locked.png
pics/dark_layer-unlocked.png
pics/dark_novisible.svg
pics/dark_passthrough-disabled.png
pics/dark_passthrough-enabled.png
pics/dark_selection-mode_ants.png
pics/dark_selection-mode_invisible.png
pics/dark_selection-mode_mask.png
pics/dark_transparency-disabled.png
pics/dark_transparency-enabled.png
pics/dark_trim-to-image.png
pics/dark_visible.svg
pics/delete.png
pics/dirty-preset.svg
pics/height.png
pics/height_icon.png
pics/hi16-add_dialog.png
pics/hi16-palette_library.png
pics/icon-kritasketch-256.png
pics/layer-style-disabled.png
pics/layer-style-enabled.png
pics/light_layer-locked.png
pics/light_layer-unlocked.png
pics/light_novisible.svg
pics/light_passthrough-disabled.png
pics/light_passthrough-enabled.png
pics/light_selection-mode_ants.png
pics/light_selection-mode_invisible.png
pics/light_selection-mode_mask.png
pics/light_transparency-disabled.png
pics/light_transparency-enabled.png
pics/light_trim-to-image.png
pics/light_visible.svg
pics/linked.png
pics/local_selection_active.png
pics/local_selection_inactive.png
pics/mirrorAxis-HorizontalMove.png
pics/mirrorAxis-VerticalMove.png
pics/novisible.svg
pics/offset_horizontal.png
pics/offset_vertical.png
pics/ratio_icon.png
pics/select_pixel.png
pics/select_shape.png
pics/selection_add.png
- pics/selection_exclude.png
+ pics/selection_symmetric_difference.png
pics/selection_intersect.png
pics/selection_replace.png
pics/selection_subtract.png
pics/shade.png
pics/shear_horizontal.png
pics/shear_vertical.png
pics/sidebaricon.png
pics/tablet.png
pics/tool_screenshot.png
pics/transparency-locked.png
pics/transparency-unlocked.png
pics/unlinked.png
pics/visible.svg
pics/width.png
pics/width_icon.png
pics/workspace-chooser.png
diff --git a/krita/pics/selection_exclude.png b/krita/pics/selection_symmetric_difference.png
similarity index 100%
rename from krita/pics/selection_exclude.png
rename to krita/pics/selection_symmetric_difference.png
diff --git a/libs/image/KisSelectionTags.h b/libs/image/KisSelectionTags.h
index eab45119d2..a4021da2ce 100644
--- a/libs/image/KisSelectionTags.h
+++ b/libs/image/KisSelectionTags.h
@@ -1,36 +1,37 @@
/*
* Copyright (c) 2018 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 KISSELECTIONTAGS_H
#define KISSELECTIONTAGS_H
enum SelectionMode {
PIXEL_SELECTION = 0,
SHAPE_PROTECTION
};
enum SelectionAction {
SELECTION_REPLACE = 0,
SELECTION_ADD,
SELECTION_SUBTRACT,
SELECTION_INTERSECT,
+ SELECTION_SYMMETRICDIFFERENCE,
SELECTION_DEFAULT
};
#endif // KISSELECTIONTAGS_H
diff --git a/libs/image/kis_pixel_selection.cpp b/libs/image/kis_pixel_selection.cpp
index d887afb362..b6bf7ecc2e 100644
--- a/libs/image/kis_pixel_selection.cpp
+++ b/libs/image/kis_pixel_selection.cpp
@@ -1,568 +1,597 @@
/*
* Copyright (c) 2004 Boudewijn Rempt
* Copyright (c) 2007 Sven Langkamp
*
* 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_pixel_selection.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kis_layer.h"
#include "kis_debug.h"
#include "kis_image.h"
#include "kis_fill_painter.h"
#include "kis_outline_generator.h"
#include
#include "kis_lod_transform.h"
struct Q_DECL_HIDDEN KisPixelSelection::Private {
KisSelectionWSP parentSelection;
QPainterPath outlineCache;
bool outlineCacheValid;
QMutex outlineCacheMutex;
bool thumbnailImageValid;
QImage thumbnailImage;
QTransform thumbnailImageTransform;
QPoint lod0CachesOffset;
void invalidateThumbnailImage() {
thumbnailImageValid = false;
thumbnailImage = QImage();
thumbnailImageTransform = QTransform();
}
};
KisPixelSelection::KisPixelSelection(KisDefaultBoundsBaseSP defaultBounds, KisSelectionWSP parentSelection)
: KisPaintDevice(0, KoColorSpaceRegistry::instance()->alpha8(), defaultBounds)
, m_d(new Private)
{
m_d->outlineCacheValid = true;
m_d->invalidateThumbnailImage();
m_d->parentSelection = parentSelection;
}
KisPixelSelection::KisPixelSelection(const KisPixelSelection& rhs, KritaUtils::DeviceCopyMode copyMode)
: KisPaintDevice(rhs, copyMode)
, KisSelectionComponent(rhs)
, m_d(new Private)
{
// parent selection is not supposed to be shared
m_d->outlineCache = rhs.m_d->outlineCache;
m_d->outlineCacheValid = rhs.m_d->outlineCacheValid;
m_d->thumbnailImageValid = rhs.m_d->thumbnailImageValid;
m_d->thumbnailImage = rhs.m_d->thumbnailImage;
m_d->thumbnailImageTransform = rhs.m_d->thumbnailImageTransform;
}
KisPixelSelection::KisPixelSelection(const KisPaintDeviceSP copySource, KritaUtils::DeviceCopyMode copyMode, KisSelectionWSP parentSelection)
: KisPaintDevice(*copySource.data(), copyMode)
, m_d(new Private)
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8();
convertTo(cs);
m_d->parentSelection = parentSelection;
m_d->outlineCacheValid = true;
m_d->invalidateThumbnailImage();
}
KisSelectionComponent* KisPixelSelection::clone(KisSelection*)
{
return new KisPixelSelection(*this);
}
KisPixelSelection::~KisPixelSelection()
{
delete m_d;
}
const KoColorSpace *KisPixelSelection::compositionSourceColorSpace() const
{
return KoColorSpaceRegistry::instance()->
colorSpace(GrayAColorModelID.id(),
Integer8BitsColorDepthID.id(),
QString());
}
bool KisPixelSelection::read(QIODevice *stream)
{
bool retval = KisPaintDevice::read(stream);
m_d->outlineCacheValid = false;
m_d->invalidateThumbnailImage();
return retval;
}
void KisPixelSelection::select(const QRect & rc, quint8 selectedness)
{
QRect r = rc.normalized();
if (r.isEmpty()) return;
KisFillPainter painter(KisPaintDeviceSP(this));
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
painter.fillRect(r, KoColor(Qt::white, cs), selectedness);
if (m_d->outlineCacheValid) {
QPainterPath path;
path.addRect(r);
if (selectedness != MIN_SELECTED) {
m_d->outlineCache += path;
} else {
m_d->outlineCache -= path;
}
}
m_d->invalidateThumbnailImage();
}
void KisPixelSelection::applySelection(KisPixelSelectionSP selection, SelectionAction action)
{
switch (action) {
case SELECTION_REPLACE:
clear();
addSelection(selection);
break;
case SELECTION_ADD:
addSelection(selection);
break;
case SELECTION_SUBTRACT:
subtractSelection(selection);
break;
case SELECTION_INTERSECT:
intersectSelection(selection);
break;
+ case SELECTION_SYMMETRICDIFFERENCE:
+ symmetricdifferenceSelection(selection);
+ break;
default:
break;
}
}
void KisPixelSelection::copyAlphaFrom(KisPaintDeviceSP src, const QRect &processRect)
{
const KoColorSpace *srcCS = src->colorSpace();
KisSequentialConstIterator srcIt(src, processRect);
KisSequentialIterator dstIt(this, processRect);
while (srcIt.nextPixel() && dstIt.nextPixel()) {
const quint8 *srcPtr = srcIt.rawDataConst();
quint8 *alpha8Ptr = dstIt.rawData();
*alpha8Ptr = srcCS->opacityU8(srcPtr);
}
m_d->outlineCacheValid = false;
m_d->outlineCache = QPainterPath();
m_d->invalidateThumbnailImage();
}
void KisPixelSelection::addSelection(KisPixelSelectionSP selection)
{
QRect r = selection->selectedRect();
if (r.isEmpty()) return;
KisHLineIteratorSP dst = createHLineIteratorNG(r.x(), r.y(), r.width());
KisHLineConstIteratorSP src = selection->createHLineConstIteratorNG(r.x(), r.y(), r.width());
for (int i = 0; i < r.height(); ++i) {
do {
if (*src->oldRawData() + *dst->rawData() < MAX_SELECTED)
*dst->rawData() = *src->oldRawData() + *dst->rawData();
else
*dst->rawData() = MAX_SELECTED;
} while (src->nextPixel() && dst->nextPixel());
dst->nextRow();
src->nextRow();
}
m_d->outlineCacheValid &= selection->outlineCacheValid();
if (m_d->outlineCacheValid) {
m_d->outlineCache += selection->outlineCache();
}
m_d->invalidateThumbnailImage();
}
void KisPixelSelection::subtractSelection(KisPixelSelectionSP selection)
{
QRect r = selection->selectedRect();
if (r.isEmpty()) return;
KisHLineIteratorSP dst = createHLineIteratorNG(r.x(), r.y(), r.width());
KisHLineConstIteratorSP src = selection->createHLineConstIteratorNG(r.x(), r.y(), r.width());
for (int i = 0; i < r.height(); ++i) {
do {
if (*dst->rawData() - *src->oldRawData() > MIN_SELECTED)
*dst->rawData() = *dst->rawData() - *src->oldRawData();
else
*dst->rawData() = MIN_SELECTED;
} while (src->nextPixel() && dst->nextPixel());
dst->nextRow();
src->nextRow();
}
m_d->outlineCacheValid &= selection->outlineCacheValid();
if (m_d->outlineCacheValid) {
m_d->outlineCache -= selection->outlineCache();
}
m_d->invalidateThumbnailImage();
}
void KisPixelSelection::intersectSelection(KisPixelSelectionSP selection)
{
QRect r = selection->selectedRect().united(selectedRect());
if (r.isEmpty()) return;
KisHLineIteratorSP dst = createHLineIteratorNG(r.x(), r.y(), r.width());
KisHLineConstIteratorSP src = selection->createHLineConstIteratorNG(r.x(), r.y(), r.width());
for (int i = 0; i < r.height(); ++i) {
do {
*dst->rawData() = qMin(*dst->rawData(), *src->oldRawData());
} while (src->nextPixel() && dst->nextPixel());
dst->nextRow();
src->nextRow();
}
m_d->outlineCacheValid &= selection->outlineCacheValid();
if (m_d->outlineCacheValid) {
m_d->outlineCache &= selection->outlineCache();
}
m_d->invalidateThumbnailImage();
}
+void KisPixelSelection::symmetricdifferenceSelection(KisPixelSelectionSP selection)
+{
+ QRect r = selection->selectedRect().united(selectedRect());
+ if (r.isEmpty()) return;
+
+ KisHLineIteratorSP dst = createHLineIteratorNG(r.x(), r.y(), r.width());
+ KisHLineConstIteratorSP src = selection->createHLineConstIteratorNG(r.x(), r.y(), r.width());
+ for (int i = 0; i < r.height(); ++i) {
+
+ do {
+ *dst->rawData() = abs(*dst->rawData() - *src->oldRawData());
+ } while (src->nextPixel() && dst->nextPixel());
+
+ dst->nextRow();
+ src->nextRow();
+ }
+
+ m_d->outlineCacheValid &= selection->outlineCacheValid();
+
+ if (m_d->outlineCacheValid) {
+ m_d->outlineCache = (m_d->outlineCache | selection->outlineCache()) - (m_d->outlineCache & selection->outlineCache());
+ }
+
+ m_d->invalidateThumbnailImage();
+}
+
void KisPixelSelection::clear(const QRect & r)
{
if (*defaultPixel().data() != MIN_SELECTED) {
KisFillPainter painter(KisPaintDeviceSP(this));
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
painter.fillRect(r, KoColor(Qt::white, cs), MIN_SELECTED);
} else {
KisPaintDevice::clear(r);
}
if (m_d->outlineCacheValid) {
QPainterPath path;
path.addRect(r);
m_d->outlineCache -= path;
}
m_d->invalidateThumbnailImage();
}
void KisPixelSelection::clear()
{
setDefaultPixel(KoColor(Qt::transparent, colorSpace()));
KisPaintDevice::clear();
m_d->outlineCacheValid = true;
m_d->outlineCache = QPainterPath();
// Empty the thumbnail image. It is a valid state.
m_d->invalidateThumbnailImage();
m_d->thumbnailImageValid = true;
}
void KisPixelSelection::invert()
{
// Region is needed here (not exactBounds or extent), because
// unselected but existing pixels need to be inverted too
QRect rc = region().boundingRect();
if (!rc.isEmpty()) {
KisSequentialIterator it(this, rc);
while(it.nextPixel()) {
*(it.rawData()) = MAX_SELECTED - *(it.rawData());
}
}
quint8 defPixel = MAX_SELECTED - *defaultPixel().data();
setDefaultPixel(KoColor(&defPixel, colorSpace()));
if (m_d->outlineCacheValid) {
QPainterPath path;
path.addRect(defaultBounds()->bounds());
m_d->outlineCache = path - m_d->outlineCache;
}
m_d->invalidateThumbnailImage();
}
void KisPixelSelection::moveTo(const QPoint &pt)
{
const int lod = defaultBounds()->currentLevelOfDetail();
const QPoint lod0Point = !lod ? pt :
pt * KisLodTransform::lodToInvScale(lod);
const QPoint offset = lod0Point - m_d->lod0CachesOffset;
if (m_d->outlineCacheValid) {
m_d->outlineCache.translate(offset);
}
if (m_d->thumbnailImageValid) {
m_d->thumbnailImageTransform =
QTransform::fromTranslate(offset.x(), offset.y()) *
m_d->thumbnailImageTransform;
}
m_d->lod0CachesOffset = lod0Point;
KisPaintDevice::moveTo(pt);
}
bool KisPixelSelection::isTotallyUnselected(const QRect & r) const
{
if (*defaultPixel().data() != MIN_SELECTED)
return false;
QRect sr = selectedExactRect();
return ! r.intersects(sr);
}
QRect KisPixelSelection::selectedRect() const
{
return extent();
}
QRect KisPixelSelection::selectedExactRect() const
{
return exactBounds();
}
QVector KisPixelSelection::outline() const
{
QRect selectionExtent = selectedExactRect();
/**
* When the default pixel is not fully transarent, the
* exactBounds() return extent of the device instead. To make this
* value sane we should limit the calculated area by the bounds of
* the image.
*/
if (*defaultPixel().data() != MIN_SELECTED) {
selectionExtent &= defaultBounds()->bounds();
}
qint32 xOffset = selectionExtent.x();
qint32 yOffset = selectionExtent.y();
qint32 width = selectionExtent.width();
qint32 height = selectionExtent.height();
KisOutlineGenerator generator(colorSpace(), MIN_SELECTED);
// If the selection is small using a buffer is much faster
try {
quint8* buffer = new quint8[width*height];
readBytes(buffer, xOffset, yOffset, width, height);
QVector paths = generator.outline(buffer, xOffset, yOffset, width, height);
delete[] buffer;
return paths;
}
catch(const std::bad_alloc&) {
// Allocating so much memory failed, so we fall through to the slow option.
warnKrita << "KisPixelSelection::outline ran out of memory allocating" << width << "*" << height << "bytes.";
}
return generator.outline(this, xOffset, yOffset, width, height);
}
bool KisPixelSelection::isEmpty() const
{
return *defaultPixel().data() == MIN_SELECTED && selectedRect().isEmpty();
}
QPainterPath KisPixelSelection::outlineCache() const
{
QMutexLocker locker(&m_d->outlineCacheMutex);
return m_d->outlineCache;
}
void KisPixelSelection::setOutlineCache(const QPainterPath &cache)
{
QMutexLocker locker(&m_d->outlineCacheMutex);
m_d->outlineCache = cache;
m_d->outlineCacheValid = true;
m_d->thumbnailImageValid = false;
}
bool KisPixelSelection::outlineCacheValid() const
{
QMutexLocker locker(&m_d->outlineCacheMutex);
return m_d->outlineCacheValid;
}
void KisPixelSelection::invalidateOutlineCache()
{
QMutexLocker locker(&m_d->outlineCacheMutex);
m_d->outlineCacheValid = false;
m_d->thumbnailImageValid = false;
}
void KisPixelSelection::recalculateOutlineCache()
{
QMutexLocker locker(&m_d->outlineCacheMutex);
m_d->outlineCache = QPainterPath();
Q_FOREACH (const QPolygon &polygon, outline()) {
m_d->outlineCache.addPolygon(polygon);
/**
* The outline generation algorithm has a small bug, which
* results in the starting point be repeated twice in the
* beginning of the path, instead of being put to the
* end. Here we just explicitly close the path to workaround
* it.
*
* \see KisSelectionTest::testOutlineGeneration()
*/
m_d->outlineCache.closeSubpath();
}
m_d->outlineCacheValid = true;
}
bool KisPixelSelection::thumbnailImageValid() const
{
return m_d->thumbnailImageValid;
}
QImage KisPixelSelection::thumbnailImage() const
{
return m_d->thumbnailImage;
}
QTransform KisPixelSelection::thumbnailImageTransform() const
{
return m_d->thumbnailImageTransform;
}
QImage deviceToQImage(KisPaintDeviceSP device,
const QRect &rc,
const QColor &maskColor)
{
QImage image(rc.size(), QImage::Format_ARGB32);
QColor color = maskColor;
const qreal alphaScale = maskColor.alphaF();
KisSequentialConstIterator it(device, rc);
while(it.nextPixel()) {
quint8 value = (MAX_SELECTED - *(it.rawDataConst())) * alphaScale;
color.setAlpha(value);
QPoint pt(it.x(), it.y());
pt -= rc.topLeft();
image.setPixel(pt.x(), pt.y(), color.rgba());
}
return image;
}
void KisPixelSelection::recalculateThumbnailImage(const QColor &maskColor)
{
QRect rc = selectedExactRect();
const int maxPreviewSize = 2000;
if (rc.isEmpty()) {
m_d->thumbnailImageTransform = QTransform();
m_d->thumbnailImage = QImage();
return;
}
if (rc.width() > maxPreviewSize ||
rc.height() > maxPreviewSize) {
qreal factor = 1.0;
if (rc.width() > rc.height()) {
factor = qreal(maxPreviewSize) / rc.width();
} else {
factor = qreal(maxPreviewSize) / rc.height();
}
int newWidth = qRound(rc.width() * factor);
int newHeight = qRound(rc.height() * factor);
m_d->thumbnailImageTransform =
QTransform::fromScale(qreal(rc.width()) / newWidth,
qreal(rc.height()) / newHeight) *
QTransform::fromTranslate(rc.x(), rc.y());
KisPaintDeviceSP thumbDevice =
createThumbnailDevice(newWidth, newHeight, rc);
QRect thumbRect(0, 0, newWidth, newHeight);
m_d->thumbnailImage = deviceToQImage(thumbDevice, thumbRect, maskColor);
} else {
m_d->thumbnailImageTransform = QTransform::fromTranslate(rc.x(), rc.y());
m_d->thumbnailImage = deviceToQImage(this, rc, maskColor);
}
m_d->thumbnailImageValid = true;
}
void KisPixelSelection::setParentSelection(KisSelectionWSP selection)
{
m_d->parentSelection = selection;
}
KisSelectionWSP KisPixelSelection::parentSelection() const
{
return m_d->parentSelection;
}
void KisPixelSelection::renderToProjection(KisPaintDeviceSP projection)
{
renderToProjection(projection, selectedExactRect());
}
void KisPixelSelection::renderToProjection(KisPaintDeviceSP projection, const QRect& rc)
{
QRect updateRect = rc & selectedExactRect();
if (updateRect.isValid()) {
KisPainter::copyAreaOptimized(updateRect.topLeft(), KisPaintDeviceSP(this), projection, updateRect);
}
}
diff --git a/libs/image/kis_pixel_selection.h b/libs/image/kis_pixel_selection.h
index f65e773a6c..aab880c871 100644
--- a/libs/image/kis_pixel_selection.h
+++ b/libs/image/kis_pixel_selection.h
@@ -1,172 +1,177 @@
/*
* Copyright (c) 2004 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_PIXEL_SELECTION_H_
#define KIS_PIXEL_SELECTION_H_
#include
#include
#include "kis_types.h"
#include "kis_global.h"
#include "kis_paint_device.h"
#include "kis_selection_component.h"
#include "kis_selection.h"
#include
/**
* KisPixelSelection contains a byte-map representation of a layer, where
* the value of a byte signifies whether a corresponding pixel is selected, or not.
*/
class KRITAIMAGE_EXPORT KisPixelSelection : public KisPaintDevice, public KisSelectionComponent
{
public:
/**
* Create a new KisPixelSelection. This selection will not have a
* parent paint device.
*/
KisPixelSelection(KisDefaultBoundsBaseSP defaultBounds = KisDefaultBoundsBaseSP(), KisSelectionWSP parentSelection = KisSelectionWSP());
/**
* Copy the selection
*/
KisPixelSelection(const KisPixelSelection& rhs, KritaUtils::DeviceCopyMode copyMode = KritaUtils::CopySnapshot);
/**
* Create a new selection using the content of copySource as the mask.
*/
KisPixelSelection(const KisPaintDeviceSP copySource, KritaUtils::DeviceCopyMode copyMode = KritaUtils::CopySnapshot, KisSelectionWSP parentSelection = KisSelectionWSP());
~KisPixelSelection() override;
KisSelectionComponent* clone(KisSelection*) override;
const KoColorSpace* compositionSourceColorSpace() const override;
bool read(QIODevice *stream);
/**
* Fill the specified rect with the specified selectedness.
*/
void select(const QRect & r, quint8 selectedness = MAX_SELECTED);
/**
* Invert the total selection. This will also invert the default value
* of the selection paint device, from MIN_SELECTED to MAX_SELECTED or
* back.
*/
void invert();
/**
* Set the specified rect to MIN_SELECTED.
*/
void clear(const QRect & r);
/**
* Reset the entire selection. The selectedRect and selectedExactRect
* will be empty. The selection will be completely deselected.
*/
void clear() override;
/**
* Copies alpha channel form the specified \p src device
*/
void copyAlphaFrom(KisPaintDeviceSP src, const QRect &processRect);
/**
* Apply a selection to the selection using the specified selection mode
*/
void applySelection(KisPixelSelectionSP selection, SelectionAction action);
/// Tests if the rect is totally outside the selection
bool isTotallyUnselected(const QRect & r) const;
/**
* Rough, but fastish way of determining the area
* of the tiles used by the selection.
*/
QRect selectedRect() const;
/**
* Slow, but exact way of determining the rectangle
* that encloses the selection.
*/
QRect selectedExactRect() const;
/**
* @brief outline returns the outline of the current selection
* @return a vector of polygons that can be used to draw the outline
*/
QVector outline() const;
/**
* Overridden from KisPaintDevice to handle outline cache moves
*/
void moveTo(const QPoint& pt) override;
using KisPaintDevice::moveTo;
bool isEmpty() const override;
QPainterPath outlineCache() const override;
bool outlineCacheValid() const override;
void recalculateOutlineCache() override;
void setOutlineCache(const QPainterPath &cache);
void invalidateOutlineCache();
bool thumbnailImageValid() const;
QImage thumbnailImage() const;
QTransform thumbnailImageTransform() const;
void recalculateThumbnailImage(const QColor &maskColor);
void setParentSelection(KisSelectionWSP selection);
KisSelectionWSP parentSelection() const;
void renderToProjection(KisPaintDeviceSP projection) override;
void renderToProjection(KisPaintDeviceSP projection, const QRect& r) override;
private:
/**
* Add a selection
*/
void addSelection(KisPixelSelectionSP selection);
/**
* Subtracts a selection
*/
void subtractSelection(KisPixelSelectionSP selection);
/**
* Intersects a selection using min-T-norm for this.
*/
void intersectSelection(KisPixelSelectionSP selection);
+ /**
+ * Invert a selection or intersect with the inverse of a selection
+ */
+ void symmetricdifferenceSelection(KisPixelSelectionSP selection);
+
private:
// We don't want these methods to be used on selections:
using KisPaintDevice::extent;
using KisPaintDevice::exactBounds;
private:
struct Private;
Private * const m_d;
};
#endif // KIS_PIXEL_SELECTION_H_
diff --git a/libs/libkis/Selection.cpp b/libs/libkis/Selection.cpp
index 28783df6c9..150b971f34 100644
--- a/libs/libkis/Selection.cpp
+++ b/libs/libkis/Selection.cpp
@@ -1,322 +1,328 @@
/*
* Copyright (c) 2016 Boudewijn Rempt
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser 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 "Selection.h"
#include
#include "kis_iterator_ng.h"
#include
#include
#include
#include
#include
#include
#include
#include
struct Selection::Private {
Private() {}
KisSelectionSP selection;
};
Selection::Selection(KisSelectionSP selection, QObject *parent)
: QObject(parent)
, d(new Private)
{
d->selection = selection;
}
Selection::Selection(QObject *parent)
: QObject(parent)
, d(new Private)
{
d->selection = new KisSelection();
}
Selection::~Selection()
{
delete d;
}
bool Selection::operator==(const Selection &other) const
{
return (d->selection == other.d->selection);
}
bool Selection::operator!=(const Selection &other) const
{
return !(operator==(other));
}
int Selection::width() const
{
if (!d->selection) return 0;
return d->selection->selectedExactRect().width();
}
int Selection::height() const
{
if (!d->selection) return 0;
return d->selection->selectedExactRect().height();
}
int Selection::x() const
{
if (!d->selection) return 0;
int xPos = d->selection->x();
if (d->selection->hasPixelSelection()) {
xPos = d->selection->selectedExactRect().x();
}
return xPos;
}
int Selection::y() const
{
if (!d->selection) return 0;
int yPos = d->selection->y();
if (d->selection->hasPixelSelection()) {
yPos = d->selection->selectedExactRect().y();
}
return yPos;
}
void Selection::move(int x, int y)
{
if (!d->selection) return;
d->selection->pixelSelection()->moveTo(QPoint(x, y));
}
void Selection::clear()
{
if (!d->selection) return;
d->selection->clear();
}
void Selection::contract(int value)
{
if (!d->selection) return;
d->selection->pixelSelection()->select(QRect(x(), y(), width() - value, height() - value));
}
void Selection::copy(Node *node)
{
if (!node) return;
if (!d->selection) return;
if (node->node()->exactBounds().isEmpty()) return;
if (!node->node()->hasEditablePaintDevice()) return;
KisPaintDeviceSP dev = node->node()->paintDevice();
KisPaintDeviceSP clip = new KisPaintDevice(dev->colorSpace());
KisPaintDeviceSP selectionProjection = d->selection->projection();
const KoColorSpace *cs = clip->colorSpace();
const KoColorSpace *selCs = d->selection->projection()->colorSpace();
QRect rc = d->selection->selectedExactRect();
KisPainter::copyAreaOptimized(QPoint(), dev, clip, rc);
KisHLineIteratorSP layerIt = clip->createHLineIteratorNG(0, 0, rc.width());
KisHLineConstIteratorSP selectionIt = selectionProjection->createHLineIteratorNG(rc.x(), rc.y(), rc.width());
for (qint32 y = 0; y < rc.height(); y++) {
for (qint32 x = 0; x < rc.width(); x++) {
qreal dstAlpha = cs->opacityF(layerIt->rawData());
qreal sel = selCs->opacityF(selectionIt->oldRawData());
qreal newAlpha = sel * dstAlpha / (1.0 - dstAlpha + sel * dstAlpha);
float mask = newAlpha / dstAlpha;
cs->applyAlphaNormedFloatMask(layerIt->rawData(), &mask, 1);
layerIt->nextPixel();
selectionIt->nextPixel();
}
layerIt->nextRow();
selectionIt->nextRow();
}
KisClipboard::instance()->setClip(clip, rc.topLeft());
}
void Selection::cut(Node* node)
{
if (!node) return;
if (!d->selection) return;
if (node->node()->exactBounds().isEmpty()) return;
if (!node->node()->hasEditablePaintDevice()) return;
KisPaintDeviceSP dev = node->node()->paintDevice();
copy(node);
dev->clearSelection(d->selection);
node->node()->setDirty(d->selection->selectedExactRect());
}
void Selection::paste(Node *destination, int x, int y)
{
if (!destination) return;
if (!d->selection) return;
if (!KisClipboard::instance()->hasClip()) return;
KisPaintDeviceSP src = KisClipboard::instance()->clip(QRect(), false);
KisPaintDeviceSP dst = destination->node()->paintDevice();
if (!dst) return;
KisPainter::copyAreaOptimized(QPoint(x, y),
src,
dst,
src->exactBounds(),
d->selection);
destination->node()->setDirty();
}
void Selection::erode()
{
if (!d->selection) return;
KisErodeSelectionFilter esf;
QRect rc = esf.changeRect(d->selection->selectedExactRect());
esf.process(d->selection->pixelSelection(), rc);
}
void Selection::dilate()
{
if (!d->selection) return;
KisDilateSelectionFilter dsf;
QRect rc = dsf.changeRect(d->selection->selectedExactRect());
dsf.process(d->selection->pixelSelection(), rc);
}
void Selection::border(int xRadius, int yRadius)
{
if (!d->selection) return;
KisBorderSelectionFilter sf(xRadius, yRadius);
QRect rc = sf.changeRect(d->selection->selectedExactRect());
sf.process(d->selection->pixelSelection(), rc);
}
void Selection::feather(int radius)
{
if (!d->selection) return;
KisFeatherSelectionFilter fsf(radius);
QRect rc = fsf.changeRect(d->selection->selectedExactRect());
fsf.process(d->selection->pixelSelection(), rc);
}
void Selection::grow(int xradius, int yradius)
{
if (!d->selection) return;
KisGrowSelectionFilter gsf(xradius, yradius);
QRect rc = gsf.changeRect(d->selection->selectedExactRect());
gsf.process(d->selection->pixelSelection(), rc);
}
void Selection::shrink(int xRadius, int yRadius, bool edgeLock)
{
if (!d->selection) return;
KisShrinkSelectionFilter sf(xRadius, yRadius, edgeLock);
QRect rc = sf.changeRect(d->selection->selectedExactRect());
sf.process(d->selection->pixelSelection(), rc);
}
void Selection::smooth()
{
if (!d->selection) return;
KisSmoothSelectionFilter sf;
QRect rc = sf.changeRect(d->selection->selectedExactRect());
sf.process(d->selection->pixelSelection(), rc);
}
void Selection::invert()
{
if (!d->selection) return;
KisInvertSelectionFilter sf;
QRect rc = sf.changeRect(d->selection->selectedExactRect());
sf.process(d->selection->pixelSelection(), rc);
}
void Selection::resize(int w, int h)
{
if (!d->selection) return;
d->selection->pixelSelection()->select(QRect(x(), y(), w, h));
}
void Selection::select(int x, int y, int w, int h, int value)
{
if (!d->selection) return;
d->selection->pixelSelection()->select(QRect(x, y, w, h), value);
}
void Selection::selectAll(Node *node, int value)
{
if (!d->selection) return;
d->selection->pixelSelection()->select(node->node()->exactBounds(), value);
}
void Selection::replace(Selection *selection)
{
if (!d->selection) return;
d->selection->pixelSelection()->applySelection(selection->selection()->pixelSelection(), SELECTION_REPLACE);
}
void Selection::add(Selection *selection)
{
if (!d->selection) return;
d->selection->pixelSelection()->applySelection(selection->selection()->pixelSelection(), SELECTION_ADD);
}
void Selection::subtract(Selection *selection)
{
if (!d->selection) return;
d->selection->pixelSelection()->applySelection(selection->selection()->pixelSelection(), SELECTION_SUBTRACT);
}
void Selection::intersect(Selection *selection)
{
if (!d->selection) return;
d->selection->pixelSelection()->applySelection(selection->selection()->pixelSelection(), SELECTION_INTERSECT);
}
+void Selection::symmetricdifference(Selection *selection)
+{
+ if (!d->selection) return;
+ d->selection->pixelSelection()->applySelection(selection->selection()->pixelSelection(), SELECTION_SYMMETRICDIFFERENCE);
+}
+
QByteArray Selection::pixelData(int x, int y, int w, int h) const
{
QByteArray ba;
if (!d->selection) return ba;
KisPaintDeviceSP dev = d->selection->projection();
quint8 *data = new quint8[w * h];
dev->readBytes(data, x, y, w, h);
ba = QByteArray((const char*)data, (int)(w * h));
delete[] data;
return ba;
}
void Selection::setPixelData(QByteArray value, int x, int y, int w, int h)
{
if (!d->selection) return;
KisPixelSelectionSP dev = d->selection->pixelSelection();
if (!dev) return;
dev->writeBytes((const quint8*)value.constData(), x, y, w, h);
}
KisSelectionSP Selection::selection() const
{
return d->selection;
}
diff --git a/libs/libkis/Selection.h b/libs/libkis/Selection.h
index 961463b6df..ffbf96e2a4 100644
--- a/libs/libkis/Selection.h
+++ b/libs/libkis/Selection.h
@@ -1,245 +1,250 @@
/*
* Copyright (c) 2016 Boudewijn Rempt
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser 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 LIBKIS_SELECTION_H
#define LIBKIS_SELECTION_H
#include
#include "kritalibkis_export.h"
#include "libkis.h"
#include
/**
* Selection represents a selection on Krita. A selection is
* not necessarily associated with a particular Node or Image.
*
* @code
* from krita import *
*
* d = Application.activeDocument()
* n = d.activeNode()
* r = n.bounds()
* s = Selection()
* s.select(r.width() / 3, r.height() / 3, r.width() / 3, r.height() / 3, 255)
* s.cut(n)
* @endcode
*/
class KRITALIBKIS_EXPORT Selection : public QObject
{
Q_OBJECT
public:
/**
* For internal use only.
*/
Selection(KisSelectionSP selection, QObject *parent = 0);
/**
* Create a new, empty selection object.
*/
explicit Selection(QObject *parent = 0);
~Selection() override;
bool operator==(const Selection &other) const;
bool operator!=(const Selection &other) const;
public Q_SLOTS:
/**
* @return the width of the selection
*/
int width() const;
/**
* @return the height of the selection
*/
int height() const;
/**
* @return the left-hand position of the selection.
*/
int x() const;
/**
* @return the top position of the selection.
*/
int y() const;
/**
* Move the selection's top-left corner to the given coordinates.
*/
void move(int x, int y);
/**
* Make the selection entirely unselected.
*/
void clear();
/**
* Make the selection's width and height smaller by the given value.
* This will not move the selection's top-left position.
*/
void contract(int value);
/**
* @brief copy copies the area defined by the selection from the node to the clipboard.
* @param node the node from where the pixels will be copied.
*/
void copy(Node *node);
/**
* @brief cut erases the area defined by the selection from the node and puts a copy on the clipboard.
* @param node the node from which the selection will be cut.
*/
void cut(Node *node);
/**
* @brief paste pastes the content of the clipboard to the given node, limited by the area of the current
* selection.
* @param destination the node where the pixels will be written
* @param x: the x position at which the clip will be written
* @param y: the y position at which the clip will be written
*/
void paste(Node *destination, int x, int y);
/**
* Erode the selection with a radius of 1 pixel.
*/
void erode();
/**
* Dilate the selection with a radius of 1 pixel.
*/
void dilate();
/**
* Border the selection with the given radius.
*/
void border(int xRadius, int yRadius);
/**
* Feather the selection with the given radius.
*/
void feather(int radius);
/**
* Grow the selection with the given radius.
*/
void grow(int xradius, int yradius);
/**
* Shrink the selection with the given radius.
*/
void shrink(int xRadius, int yRadius, bool edgeLock);
/**
* Smooth the selection.
*/
void smooth();
/**
* Invert the selection.
*/
void invert();
/**
* Resize the selection to the given width and height. The top-left position will not be moved.
*/
void resize(int w, int h);
/**
* Select the given area. The value can be between 0 and 255; 0 is
* totally unselected, 255 is totally selected.
*/
void select(int x, int y, int w, int h, int value);
/**
* Select all pixels in the given node. The value can be between 0 and 255; 0 is
* totally unselected, 255 is totally selected.
*/
void selectAll(Node *node, int value);
/**
* Replace the current selection's selection with the one of the given selection.
*/
void replace(Selection *selection);
/**
* Add the given selection's selected pixels to the current selection.
*/
void add(Selection *selection);
/**
* Subtract the given selection's selected pixels from the current selection.
*/
void subtract(Selection *selection);
/**
* Intersect the given selection with this selection.
*/
void intersect(Selection *selection);
+ /**
+ * Intersect with the inverse of the given selection with this selection.
+ */
+ void symmetricdifference(Selection *selection);
+
/**
* @brief pixelData reads the given rectangle from the Selection's mask and returns it as a
* byte array. The pixel data starts top-left, and is ordered row-first.
*
* The byte array will contain one byte for every pixel, representing the selectedness. 0
* is totally unselected, 255 is fully selected.
*
* You can read outside the Selection's boundaries; those pixels will be unselected.
*
* The byte array is a copy of the original selection data.
* @param x x position from where to start reading
* @param y y position from where to start reading
* @param w row length to read
* @param h number of rows to read
* @return a QByteArray with the pixel data. The byte array may be empty.
*/
QByteArray pixelData(int x, int y, int w, int h) const;
/**
* @brief setPixelData writes the given bytes, of which there must be enough, into the
* Selection.
*
* @param value the byte array representing the pixels. There must be enough bytes available.
* Krita will take the raw pointer from the QByteArray and start reading, not stopping before
* (w * h) bytes are read.
*
* @param x the x position to start writing from
* @param y the y position to start writing from
* @param w the width of each row
* @param h the number of rows to write
*/
void setPixelData(QByteArray value, int x, int y, int w, int h);
private:
friend class Document;
friend class FilterLayer;
friend class FillLayer;
friend class SelectionMask;
KisSelectionSP selection() const;
struct Private;
Private *const d;
};
#endif // LIBKIS_SELECTION_H
diff --git a/libs/libqml/qml/panels/SelectPanel.qml b/libs/libqml/qml/panels/SelectPanel.qml
index 21d6e1f946..3e0f659380 100644
--- a/libs/libqml/qml/panels/SelectPanel.qml
+++ b/libs/libqml/qml/panels/SelectPanel.qml
@@ -1,520 +1,529 @@
/* This file is part of the KDE project
* Copyright (C) 2012 Arjen Hiemstra
*
* 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.
*/
import QtQuick 2.3
import org.krita.sketch 1.0
import org.krita.sketch.components 1.0
Panel {
id: base;
name: "Select";
colorSet: "selection";
actions: [
Button {
id: deselectButton;
width: height;
height: Constants.ToolbarButtonSize
image: Settings.theme.icon("select-deselect");
enabled: sketchView.selectionManager ? sketchView.selectionManager.havePixelsSelected : false;
onClicked: if (sketchView.selectionManager) sketchView.selectionManager.deselect();
},
Button {
id: reselectButton;
width: height;
height: Constants.ToolbarButtonSize
image: Settings.theme.icon("select-reselect");
onClicked: sketchView.selectionManager.reselect();
},
Item {
width: base.width - Constants.DefaultMargin - (Constants.ToolbarButtonSize * 3)
height: Constants.ToolbarButtonSize;
},
Button {
id: toggleShowSelectionButton;
property bool showSelection: sketchView.selectionManager ? sketchView.selectionManager.displaySelection : false;
width: height;
height: Constants.ToolbarButtonSize
image: showSelection ? Settings.theme.icon("select-show") : Settings.theme.icon("select-hide");
onClicked: sketchView.selectionManager.toggleDisplaySelection();
}
]
peekContents: Item {
id: peekItem;
anchors.fill: parent;
Item {
width: childrenRect.width;
height: peekItem.height / 3;
anchors {
horizontalCenter: parent.horizontalCenter;
top: parent.top;
}
Button {
id: selectRectangle;
anchors.verticalCenter: parent.verticalCenter;
image: Settings.theme.icon("select-rectangle");
width: Constants.ToolbarButtonSize * 0.8;
height: width;
onClicked: toolManager.requestToolChange("KisToolSelectRectangular");
checked: toolManager.currentTool !== null ? toolManager.currentTool.toolId() === "KisToolSelectRectangular" : false;
}
Button {
id: selectPolygon;
anchors.verticalCenter: parent.verticalCenter;
anchors.left: selectRectangle.right;
image: Settings.theme.icon("select-polygon");
width: Constants.ToolbarButtonSize * 0.8;
height: width;
onClicked: toolManager.requestToolChange("KisToolSelectPolygonal");
checked: toolManager.currentTool !== null ? toolManager.currentTool.toolId() === "KisToolSelectPolygonal" : false;
}
Button {
id: selectArea;
anchors.verticalCenter: parent.verticalCenter;
anchors.left: selectPolygon.right;
image: Settings.theme.icon("select-area");
width: Constants.ToolbarButtonSize * 0.8;
height: width;
onClicked: toolManager.requestToolChange("KisToolSelectContiguous");
checked: toolManager.currentTool !== null ? toolManager.currentTool.toolId() === "KisToolSelectContiguous" : false;
}
Button {
id: selectColor;
anchors.verticalCenter: parent.verticalCenter;
anchors.left: selectArea.right;
image: Settings.theme.icon("select-color");
width: Constants.ToolbarButtonSize * 0.8;
height: width;
onClicked: toolManager.requestToolChange("KisToolSelectSimilar");
checked: toolManager.currentTool !== null ? toolManager.currentTool.toolId() === "KisToolSelectSimilar" : false;
}
}
Item {
width: childrenRect.width;
height: childrenRect.height;
anchors.centerIn: parent;
Button {
id: selectReplace;
image: Settings.theme.icon("select-replace");
width: Constants.ToolbarButtonSize * 0.8;
height: width;
checked: (toolManager.currentTool && toolManager.currentTool.selectionAction === 0) ? true : false;
onClicked: if (toolManager.currentTool && toolManager.currentTool.selectionAction !== undefined) toolManager.currentTool.selectionAction = 0;
}
Button {
id: selectIntersect;
anchors.left: selectReplace.right;
image: Settings.theme.icon("select-intersect");
width: Constants.ToolbarButtonSize * 0.8;
height: width;
checked: (toolManager.currentTool && toolManager.currentTool.selectionAction === 3) ? true : false;
onClicked: if (toolManager.currentTool && toolManager.currentTool.selectionAction !== undefined) toolManager.currentTool.selectionAction = 3;
}
Button {
id: selectAdd;
anchors.left: selectIntersect.right;
image: Settings.theme.icon("select-add");
width: Constants.ToolbarButtonSize * 0.8;
height: width;
checked: (toolManager.currentTool && toolManager.currentTool.selectionAction === 1) ? true : false;
onClicked: if (toolManager.currentTool && toolManager.currentTool.selectionAction !== undefined) toolManager.currentTool.selectionAction = 1;
}
Button {
id: selectSub;
anchors.left: selectAdd.right;
image: Settings.theme.icon("select-sub");
width: Constants.ToolbarButtonSize * 0.8;
height: width;
checked: (toolManager.currentTool && toolManager.currentTool.selectionAction === 2) ? true : false;
onClicked: if (toolManager.currentTool && toolManager.currentTool.selectionAction !== undefined) toolManager.currentTool.selectionAction = 2;
}
+ Button {
+ id: selectSymmetricDifference;
+ anchors.left: selectSub.right;
+ image: Settings.theme.icon("select-symmetric-difference");
+ width: Constants.ToolbarButtonSize * 0.8;
+ height: width;
+ checked: (toolManager.currentTool && toolManager.currentTool.selectionAction === 4) ? true : false;
+ onClicked: if (toolManager.currentTool && toolManager.currentTool.selectionAction !== undefined) toolManager.currentTool.selectionAction = 4;
+ }
}
Item {
width: childrenRect.width;
height: peekItem.height / 3;
anchors {
horizontalCenter: parent.horizontalCenter;
bottom: parent.bottom;
}
Button {
id: selectAll;
anchors.verticalCenter: parent.verticalCenter;
anchors.margins: Constants.DefaultMargin;
text: "All";
textColor: Settings.theme.color("panels/selection/buttons/text");
color: Settings.theme.color("panels/selection/buttons/color");
border.width: 1;
border.color: Settings.theme.color("panels/selection/buttons/border");
radius: Constants.DefaultMargin;
width: Constants.IsLandscape ? Constants.GridWidth : (Constants.GridWidth * 2 / 3);// - Constants.DefaultMargin;
height: textSize + Constants.DefaultMargin * 2;
onClicked: sketchView.selectionManager.selectAll();
}
Button {
id: selectInvert;
anchors.verticalCenter: parent.verticalCenter;
anchors.left: selectAll.right;
anchors.margins: Constants.DefaultMargin;
text: "Invert";
textColor: Settings.theme.color("panels/selection/buttons/text");
color: Settings.theme.color("panels/selection/buttons/color");
border.width: 1;
border.color: Settings.theme.color("panels/selection/buttons/border");
radius: Constants.DefaultMargin;
width: Constants.IsLandscape ? Constants.GridWidth : (Constants.GridWidth * 2 / 3);// - Constants.DefaultMargin;
height: textSize + Constants.DefaultMargin * 2
onClicked: sketchView.selectionManager.invert();
}
/*Button {
id: selectOpaque;
anchors.verticalCenter: parent.verticalCenter;
anchors.left: selectInvert.right;
anchors.margins: Constants.DefaultMargin;
text: "Opaque";
textColor: "black";
color: "#63ffffff";
border.width: 1;
border.color: "silver";
radius: Constants.DefaultMargin;
width: (Constants.GridWidth * 2 / 3) - Constants.DefaultMargin;
height: textSize + Constants.DefaultMargin * 2
}*/
}
}
fullContents: Item {
anchors.fill: parent;
Flickable {
id: selectOptionsFullFlickable;
anchors.fill: parent
contentHeight: fullItem.height;
Item {
id: fullItem;
width: parent.width;
height: Constants.GridHeight * 9.5;
Item {
id: fullTopRow;
width: childrenRect.width;
height: Constants.GridHeight;
anchors {
horizontalCenter: parent.horizontalCenter;
top: parent.top;
}
Button {
id: selectRectangleFull;
anchors.verticalCenter: parent.verticalCenter;
image: Settings.theme.icon("select-rectangle");
width: Constants.ToolbarButtonSize * 0.8;
height: width;
onClicked: toolManager.requestToolChange("KisToolSelectRectangular");
checked: toolManager.currentTool !== null ? toolManager.currentTool.toolId() === "KisToolSelectRectangular" : false;
}
Button {
id: selectPolygonFull;
anchors.verticalCenter: parent.verticalCenter;
anchors.left: selectRectangleFull.right;
image: Settings.theme.icon("select-polygon")
width: Constants.ToolbarButtonSize * 0.8;
height: width;
onClicked: toolManager.requestToolChange("KisToolSelectPolygonal");
checked: toolManager.currentTool !== null ? toolManager.currentTool.toolId() === "KisToolSelectPolygonal" : false;
}
Button {
id: selectAreaFull;
anchors.verticalCenter: parent.verticalCenter;
anchors.left: selectPolygonFull.right;
image: Settings.theme.icon("select-area")
width: Constants.ToolbarButtonSize * 0.8;
height: width;
onClicked: toolManager.requestToolChange("KisToolSelectContiguous");
checked: toolManager.currentTool !== null ? toolManager.currentTool.toolId() === "KisToolSelectContiguous" : false;
}
Button {
id: selectColorFull;
anchors.verticalCenter: parent.verticalCenter;
anchors.left: selectAreaFull.right;
image: Settings.theme.icon("select-color")
width: Constants.ToolbarButtonSize * 0.8;
height: width;
onClicked: toolManager.requestToolChange("KisToolSelectSimilar");
checked: toolManager.currentTool !== null ? toolManager.currentTool.toolId() === "KisToolSelectSimilar" : false;
}
}
Label {
id: modeLabel
anchors {
left: parent.left;
leftMargin: Constants.DefaultMargin;
top: fullTopRow.bottom;
}
height: Constants.GridHeight / 2;
text: "Mode:";
font: Settings.theme.font("panelSection");
}
Item {
id: fullModeRow;
width: childrenRect.width;
height: Constants.GridHeight;
anchors.horizontalCenter: parent.horizontalCenter;
anchors.top: modeLabel.bottom;
Button {
id: selectReplaceFull;
image: Settings.theme.icon("select-replace")
width: Constants.ToolbarButtonSize * 0.8;
height: width;
checked: (toolManager.currentTool && toolManager.currentTool.selectionAction === 0) ? true : false;
onClicked: if (toolManager.currentTool && toolManager.currentTool.selectionAction !== undefined) toolManager.currentTool.selectionAction = 0;
}
Button {
id: selectIntersectFull;
anchors.left: selectReplaceFull.right;
image: Settings.theme.icon("select-intersect")
width: Constants.ToolbarButtonSize * 0.8;
height: width;
checked: (toolManager.currentTool && toolManager.currentTool.selectionAction === 3) ? true : false;
onClicked: if (toolManager.currentTool && toolManager.currentTool.selectionAction !== undefined) toolManager.currentTool.selectionAction = 3;
}
Button {
id: selectAddFull;
anchors.left: selectIntersectFull.right;
image: Settings.theme.icon("select-add")
width: Constants.ToolbarButtonSize * 0.8;
height: width;
checked: (toolManager.currentTool && toolManager.currentTool.selectionAction === 1) ? true : false;
onClicked: if (toolManager.currentTool && toolManager.currentTool.selectionAction !== undefined) toolManager.currentTool.selectionAction = 1;
}
Button {
id: selectSubFull;
anchors.left: selectAddFull.right;
image: Settings.theme.icon("select-sub")
width: Constants.ToolbarButtonSize * 0.8;
height: width;
checked: (toolManager.currentTool && toolManager.currentTool.selectionAction === 2) ? true : false;
onClicked: if (toolManager.currentTool && toolManager.currentTool.selectionAction !== undefined) toolManager.currentTool.selectionAction = 2;
}
}
Label {
id: fullSelectCommandsLabel;
anchors {
left: parent.left;
leftMargin: Constants.DefaultMargin;
top: fullModeRow.bottom;
}
height: Constants.GridHeight / 2;
text: "Select:";
font: Settings.theme.font("panelSection");
}
Item {
id: fullSelectCommands;
width: childrenRect.width;
height: Constants.GridHeight;
anchors {
horizontalCenter: parent.horizontalCenter;
top: fullSelectCommandsLabel.bottom;
}
Button {
id: selectAllFull;
anchors.verticalCenter: parent.verticalCenter;
anchors.margins: Constants.DefaultMargin;
text: "All";
textColor: Settings.theme.color("panels/selection/buttons/text");
color: Settings.theme.color("panels/selection/buttons/color");
border.width: 1;
border.color: Settings.theme.color("panels/selection/buttons/border");
radius: Constants.DefaultMargin;
width: Constants.IsLandscape ? Constants.GridWidth : (Constants.GridWidth * 2 / 3);// - Constants.DefaultMargin;
height: textSize + Constants.DefaultMargin * 2
onClicked: sketchView.selectionManager.selectAll();
}
Button {
id: selectInvertFull;
anchors.verticalCenter: parent.verticalCenter;
anchors.left: selectAllFull.right;
anchors.margins: Constants.DefaultMargin;
text: "Invert";
textColor: Settings.theme.color("panels/selection/buttons/text");
color: Settings.theme.color("panels/selection/buttons/color");
border.width: 1;
border.color: Settings.theme.color("panels/selection/buttons/border");
radius: Constants.DefaultMargin;
width: Constants.IsLandscape ? Constants.GridWidth : (Constants.GridWidth * 2 / 3);// - Constants.DefaultMargin;
height: textSize + Constants.DefaultMargin * 2
onClicked: sketchView.selectionManager.invert();
}
/*Button {
id: selectOpaqueFull;
anchors.verticalCenter: parent.verticalCenter;
anchors.left: selectInvertFull.right;
anchors.margins: Constants.DefaultMargin;
text: "Opaque";
textColor: "black";
color: "#63ffffff";
border.width: 1;
border.color: "silver";
radius: Constants.DefaultMargin;
width: (Constants.GridWidth * 2 / 3) - Constants.DefaultMargin;
height: textSize + Constants.DefaultMargin * 2
}*/
}
Label {
id: fullEditingLabel;
anchors {
left: parent.left;
leftMargin: Constants.DefaultMargin;
top: fullSelectCommands.bottom;
}
height: Constants.GridHeight / 2;
text: "Edit:";
font: Settings.theme.font("panelSection");
}
Column {
anchors {
top: fullEditingLabel.bottom;
left: parent.left;
right: parent.right;
}
spacing: Constants.DefaultMargin;
Item {
width: fullItem.width;
height: featherTxt.height;
RangeInput {
id: featherTxt;
anchors {
left: parent.left;
right: featherImg.left;
}
placeholder: "Feather";
min: 1; max: 500; decimals: 0;
useExponentialValue: true;
}
Image {
id: featherImg;
anchors {
right: parent.right;
rightMargin: Constants.DefaultMargin;
verticalCenter: featherTxt.verticalCenter;
}
height: parent.height - (Constants.DefaultMargin * 6);
width: height;
source: Settings.theme.icon("select-apply");
smooth: true;
MouseArea {
anchors.fill: parent;
onClicked: sketchView.selectionExtras.feather(featherTxt.value);
}
}
}
Item {
width: fullItem.width;
height: growTxt.height;
RangeInput {
id: growTxt;
anchors {
left: parent.left;
right: growImg.left;
}
placeholder: "Grow";
min: 1; max: 500; decimals: 0;
useExponentialValue: true;
}
Image {
id: growImg;
anchors {
right: parent.right;
rightMargin: Constants.DefaultMargin;
verticalCenter: growTxt.verticalCenter;
}
height: parent.height - (Constants.DefaultMargin * 6);
width: height;
source: Settings.theme.icon("select-apply");
smooth: true;
MouseArea {
anchors.fill: parent;
onClicked: sketchView.selectionExtras.grow(growTxt.value, growTxt.value);
}
}
}
Item {
width: fullItem.width;
height: borderTxt.height;
RangeInput {
id: borderTxt;
anchors {
left: parent.left;
right: borderImg.left;
}
placeholder: "Border";
min: 1; max: 500; decimals: 0;
useExponentialValue: true;
}
Image {
id: borderImg;
anchors {
right: parent.right;
rightMargin: Constants.DefaultMargin;
verticalCenter: borderTxt.verticalCenter;
}
height: parent.height - (Constants.DefaultMargin * 6);
width: height;
source: Settings.theme.icon("select-apply");
smooth: true;
MouseArea {
anchors.fill: parent;
onClicked: sketchView.selectionExtras.border(borderTxt.value, borderTxt.value);
}
}
}
Item {
width: fullItem.width;
height: shrinkTxt.height;
RangeInput {
id: shrinkTxt;
anchors {
left: parent.left;
right: shrinkImg.left;
}
placeholder: "Shrink";
min: 1; max: 500; decimals: 0;
useExponentialValue: true;
}
Image {
id: shrinkImg;
anchors {
right: parent.right;
rightMargin: Constants.DefaultMargin;
verticalCenter: shrinkTxt.verticalCenter;
}
height: parent.height - (Constants.DefaultMargin * 6);
width: height;
source: Settings.theme.icon("select-apply");
smooth: true;
MouseArea {
anchors.fill: parent;
onClicked: sketchView.selectionExtras.shrink(shrinkTxt.value, shrinkTxt.value, false);
}
}
}
}
}
}
ScrollDecorator { flickableItem: selectOptionsFullFlickable; }
}
}
diff --git a/libs/ui/forms/wdgselectionoptions.ui b/libs/ui/forms/wdgselectionoptions.ui
index 7231b4c46d..3cc3ce3820 100644
--- a/libs/ui/forms/wdgselectionoptions.ui
+++ b/libs/ui/forms/wdgselectionoptions.ui
@@ -1,195 +1,199 @@
WdgSelectionOptions
0
0
271
- 106
+ 110
-
-
- 0
-
-
- 0
-
-
- 0
-
-
- 0
-
-
- 0
-
+
-
0
-
Intersect
...
true
false
-
Vector Selection
...
true
false
-
Replace
...
true
true
false
-
Add
...
true
false
-
CrossCursor
Mode:
Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
Action:
Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
Pixel Selection
...
true
true
false
-
Subtract
...
true
false
+ -
+
+
+ Symmetric Difference
+
+
+ -1
+
+
+ ...
+
+
+ true
+
+
+ false
+
+
+
- -
+
-
Qt::Horizontal
0
10
-
Anti-aliasing
true
KoGroupButton
QToolButton
diff --git a/libs/ui/kis_selection_manager.cc b/libs/ui/kis_selection_manager.cc
index d7dc874755..0f963f5ee5 100644
--- a/libs/ui/kis_selection_manager.cc
+++ b/libs/ui/kis_selection_manager.cc
@@ -1,743 +1,746 @@
/*
* Copyright (c) 2004 Boudewijn Rempt
* Copyright (c) 2007 Sven Langkamp
*
* The outline algorithm uses the limn algorithm of fontutils by
* Karl Berry and Kathryn Hargreaves
*
* 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_selection_manager.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "KoCanvasController.h"
#include "KoChannelInfo.h"
#include "KoIntegerMaths.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kis_adjustment_layer.h"
#include "kis_node_manager.h"
#include "canvas/kis_canvas2.h"
#include "kis_config.h"
#include "kis_convolution_painter.h"
#include "kis_convolution_kernel.h"
#include "kis_debug.h"
#include "kis_fill_painter.h"
#include "kis_group_layer.h"
#include "kis_layer.h"
#include "kis_statusbar.h"
#include "kis_paint_device.h"
#include "kis_paint_layer.h"
#include "kis_painter.h"
#include "kis_transaction.h"
#include "kis_selection.h"
#include "kis_types.h"
#include "kis_canvas_resource_provider.h"
#include "kis_undo_adapter.h"
#include "kis_pixel_selection.h"
#include "flake/kis_shape_selection.h"
#include "commands/kis_selection_commands.h"
#include "kis_selection_mask.h"
#include "flake/kis_shape_layer.h"
#include "kis_selection_decoration.h"
#include "canvas/kis_canvas_decoration.h"
#include "kis_node_commands_adapter.h"
#include "kis_iterator_ng.h"
#include "kis_clipboard.h"
#include "KisViewManager.h"
#include "kis_selection_filters.h"
#include "kis_figure_painting_tool_helper.h"
#include "KisView.h"
#include "dialogs/kis_dlg_stroke_selection_properties.h"
#include "actions/kis_selection_action_factories.h"
#include "actions/KisPasteActionFactory.h"
#include "kis_action.h"
#include "kis_action_manager.h"
#include "operations/kis_operation_configuration.h"
//new
#include "kis_node_query_path.h"
#include "kis_tool_shape.h"
KisSelectionManager::KisSelectionManager(KisViewManager * view)
: m_view(view),
m_doc(0),
m_imageView(0),
m_adapter(new KisNodeCommandsAdapter(view)),
m_copy(0),
m_copyMerged(0),
m_cut(0),
m_paste(0),
m_pasteNew(0),
m_cutToNewLayer(0),
m_selectAll(0),
m_deselect(0),
m_clear(0),
m_reselect(0),
m_invert(0),
m_copyToNewLayer(0),
m_fillForegroundColor(0),
m_fillBackgroundColor(0),
m_fillPattern(0),
m_imageResizeToSelection(0),
m_selectionDecoration(0)
{
m_clipboard = KisClipboard::instance();
}
KisSelectionManager::~KisSelectionManager()
{
}
void KisSelectionManager::setup(KisActionManager* actionManager)
{
m_cut = actionManager->createStandardAction(KStandardAction::Cut, this, SLOT(cut()));
m_copy = actionManager->createStandardAction(KStandardAction::Copy, this, SLOT(copy()));
m_paste = actionManager->createStandardAction(KStandardAction::Paste, this, SLOT(paste()));
KisAction *action = actionManager->createAction("copy_sharp");
connect(action, SIGNAL(triggered()), this, SLOT(copySharp()));
action = actionManager->createAction("cut_sharp");
connect(action, SIGNAL(triggered()), this, SLOT(cutSharp()));
m_pasteNew = actionManager->createAction("paste_new");
connect(m_pasteNew, SIGNAL(triggered()), this, SLOT(pasteNew()));
m_pasteAt = actionManager->createAction("paste_at");
connect(m_pasteAt, SIGNAL(triggered()), this, SLOT(pasteAt()));
m_copyMerged = actionManager->createAction("copy_merged");
connect(m_copyMerged, SIGNAL(triggered()), this, SLOT(copyMerged()));
m_selectAll = actionManager->createAction("select_all");
connect(m_selectAll, SIGNAL(triggered()), this, SLOT(selectAll()));
m_deselect = actionManager->createAction("deselect");
connect(m_deselect, SIGNAL(triggered()), this, SLOT(deselect()));
m_clear = actionManager->createAction("clear");
connect(m_clear, SIGNAL(triggered()), SLOT(clear()));
m_reselect = actionManager->createAction("reselect");
connect(m_reselect, SIGNAL(triggered()), this, SLOT(reselect()));
m_invert = actionManager->createAction("invert_selection");
m_invert->setOperationID("invertselection");
actionManager->registerOperation(new KisInvertSelectionOperation);
m_copyToNewLayer = actionManager->createAction("copy_selection_to_new_layer");
connect(m_copyToNewLayer, SIGNAL(triggered()), this, SLOT(copySelectionToNewLayer()));
m_cutToNewLayer = actionManager->createAction("cut_selection_to_new_layer");
connect(m_cutToNewLayer, SIGNAL(triggered()), this, SLOT(cutToNewLayer()));
m_fillForegroundColor = actionManager->createAction("fill_selection_foreground_color");
connect(m_fillForegroundColor, SIGNAL(triggered()), this, SLOT(fillForegroundColor()));
m_fillBackgroundColor = actionManager->createAction("fill_selection_background_color");
connect(m_fillBackgroundColor, SIGNAL(triggered()), this, SLOT(fillBackgroundColor()));
m_fillPattern = actionManager->createAction("fill_selection_pattern");
connect(m_fillPattern, SIGNAL(triggered()), this, SLOT(fillPattern()));
m_fillForegroundColorOpacity = actionManager->createAction("fill_selection_foreground_color_opacity");
connect(m_fillForegroundColorOpacity, SIGNAL(triggered()), this, SLOT(fillForegroundColorOpacity()));
m_fillBackgroundColorOpacity = actionManager->createAction("fill_selection_background_color_opacity");
connect(m_fillBackgroundColorOpacity, SIGNAL(triggered()), this, SLOT(fillBackgroundColorOpacity()));
m_fillPatternOpacity = actionManager->createAction("fill_selection_pattern_opacity");
connect(m_fillPatternOpacity, SIGNAL(triggered()), this, SLOT(fillPatternOpacity()));
m_strokeShapes = actionManager->createAction("stroke_shapes");
connect(m_strokeShapes, SIGNAL(triggered()), this, SLOT(paintSelectedShapes()));
m_toggleDisplaySelection = actionManager->createAction("toggle_display_selection");
connect(m_toggleDisplaySelection, SIGNAL(triggered()), this, SLOT(toggleDisplaySelection()));
m_toggleDisplaySelection->setChecked(true);
m_imageResizeToSelection = actionManager->createAction("resizeimagetoselection");
connect(m_imageResizeToSelection, SIGNAL(triggered()), this, SLOT(imageResizeToSelection()));
action = actionManager->createAction("edit_selection");
connect(action, SIGNAL(triggered()), SLOT(editSelection()));
action = actionManager->createAction("convert_to_vector_selection");
connect(action, SIGNAL(triggered()), SLOT(convertToVectorSelection()));
action = actionManager->createAction("convert_to_raster_selection");
connect(action, SIGNAL(triggered()), SLOT(convertToRasterSelection()));
action = actionManager->createAction("convert_shapes_to_vector_selection");
connect(action, SIGNAL(triggered()), SLOT(convertShapesToVectorSelection()));
action = actionManager->createAction("convert_selection_to_shape");
connect(action, SIGNAL(triggered()), SLOT(convertToShape()));
m_toggleSelectionOverlayMode = actionManager->createAction("toggle-selection-overlay-mode");
connect(m_toggleSelectionOverlayMode, SIGNAL(triggered()), SLOT(slotToggleSelectionDecoration()));
m_strokeSelected = actionManager->createAction("stroke_selection");
connect(m_strokeSelected, SIGNAL(triggered()), SLOT(slotStrokeSelection()));
QClipboard *cb = QApplication::clipboard();
connect(cb, SIGNAL(dataChanged()), SLOT(clipboardDataChanged()));
}
void KisSelectionManager::setView(QPointerimageView)
{
if (m_imageView && m_imageView->canvasBase()) {
disconnect(m_imageView->canvasBase()->toolProxy(), SIGNAL(toolChanged(const QString&)), this, SLOT(clipboardDataChanged()));
KoSelection *selection = m_imageView->canvasBase()->globalShapeManager()->selection();
selection->disconnect(this, SLOT(shapeSelectionChanged()));
KisSelectionDecoration *decoration = qobject_cast(m_imageView->canvasBase()->decoration("selection").data());
if (decoration) {
disconnect(SIGNAL(currentSelectionChanged()), decoration);
}
m_imageView->image()->undoAdapter()->disconnect(this);
m_selectionDecoration = 0;
}
m_imageView = imageView;
if (m_imageView) {
connect(m_imageView->canvasBase()->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(shapeSelectionChanged()));
KisSelectionDecoration* decoration = qobject_cast(m_imageView->canvasBase()->decoration("selection").data());
if (!decoration) {
decoration = new KisSelectionDecoration(m_imageView);
decoration->setVisible(true);
m_imageView->canvasBase()->addDecoration(decoration);
}
m_selectionDecoration = decoration;
connect(this, SIGNAL(currentSelectionChanged()), decoration, SLOT(selectionChanged()));
connect(m_imageView->image()->undoAdapter(), SIGNAL(selectionChanged()), SLOT(selectionChanged()));
connect(m_imageView->canvasBase()->toolProxy(), SIGNAL(toolChanged(const QString&)), SLOT(clipboardDataChanged()));
}
}
void KisSelectionManager::clipboardDataChanged()
{
m_view->updateGUI();
}
bool KisSelectionManager::havePixelsSelected()
{
KisSelectionSP activeSelection = m_view->selection();
return activeSelection && !activeSelection->selectedRect().isEmpty();
}
bool KisSelectionManager::havePixelsInClipboard()
{
return m_clipboard->hasClip();
}
bool KisSelectionManager::haveShapesSelected()
{
if (m_view && m_view->canvasBase()) {
return m_view->canvasBase()->selectedShapesProxy()->selection()->count() > 0;
}
return false;
}
bool KisSelectionManager::haveShapesInClipboard()
{
KoSvgPaste paste;
return paste.hasShapes();
}
bool KisSelectionManager::haveAnySelectionWithPixels()
{
KisSelectionSP selection = m_view->selection();
return selection && selection->hasPixelSelection();
}
bool KisSelectionManager::haveShapeSelectionWithShapes()
{
KisSelectionSP selection = m_view->selection();
return selection && selection->hasShapeSelection();
}
bool KisSelectionManager::haveRasterSelectionWithPixels()
{
KisSelectionSP selection = m_view->selection();
return selection && selection->hasPixelSelection() && !selection->hasShapeSelection();
}
void KisSelectionManager::updateGUI()
{
Q_ASSERT(m_view);
Q_ASSERT(m_clipboard);
if (!m_view || !m_clipboard) return;
bool havePixelsSelected = this->havePixelsSelected();
bool havePixelsInClipboard = this->havePixelsInClipboard();
bool haveShapesSelected = this->haveShapesSelected();
bool haveShapesInClipboard = this->haveShapesInClipboard();
bool haveDevice = m_view->activeDevice();
KisLayerSP activeLayer = m_view->activeLayer();
KisImageWSP image = activeLayer ? activeLayer->image() : 0;
bool canReselect = image && image->canReselectGlobalSelection();
bool canDeselect = image && image->globalSelection();
m_clear->setEnabled(haveDevice || havePixelsSelected || haveShapesSelected);
m_cut->setEnabled(havePixelsSelected || haveShapesSelected);
m_copy->setEnabled(havePixelsSelected || haveShapesSelected);
m_paste->setEnabled(havePixelsInClipboard || haveShapesInClipboard);
m_pasteAt->setEnabled(havePixelsInClipboard || haveShapesInClipboard);
// FIXME: how about pasting shapes?
m_pasteNew->setEnabled(havePixelsInClipboard);
m_selectAll->setEnabled(true);
m_deselect->setEnabled(canDeselect);
m_reselect->setEnabled(canReselect);
// m_load->setEnabled(true);
// m_save->setEnabled(havePixelsSelected);
updateStatusBar();
emit signalUpdateGUI();
}
void KisSelectionManager::updateStatusBar()
{
if (m_view && m_view->statusBar()) {
m_view->statusBar()->setSelection(m_view->image());
}
}
void KisSelectionManager::selectionChanged()
{
m_view->updateGUI();
emit currentSelectionChanged();
}
void KisSelectionManager::cut()
{
KisCutCopyActionFactory factory;
factory.run(true, false, m_view);
}
void KisSelectionManager::copy()
{
KisCutCopyActionFactory factory;
factory.run(false, false, m_view);
}
void KisSelectionManager::cutSharp()
{
KisCutCopyActionFactory factory;
factory.run(true, true, m_view);
}
void KisSelectionManager::copySharp()
{
KisCutCopyActionFactory factory;
factory.run(false, true, m_view);
}
void KisSelectionManager::copyMerged()
{
KisCopyMergedActionFactory factory;
factory.run(m_view);
}
void KisSelectionManager::paste()
{
KisPasteActionFactory factory;
factory.run(false, m_view);
}
void KisSelectionManager::pasteAt()
{
KisPasteActionFactory factory;
factory.run(true, m_view);
}
void KisSelectionManager::pasteNew()
{
KisPasteNewActionFactory factory;
factory.run(m_view);
}
void KisSelectionManager::selectAll()
{
KisSelectAllActionFactory factory;
factory.run(m_view);
}
void KisSelectionManager::deselect()
{
KisDeselectActionFactory factory;
factory.run(m_view);
}
void KisSelectionManager::invert()
{
if(m_invert)
m_invert->trigger();
}
void KisSelectionManager::reselect()
{
KisReselectActionFactory factory;
factory.run(m_view);
}
#include
#include
void KisSelectionManager::editSelection()
{
KisSelectionSP selection = m_view->selection();
if (!selection) return;
KisAction *action = m_view->actionManager()->actionByName("show-global-selection-mask");
KIS_SAFE_ASSERT_RECOVER_RETURN(action);
if (!action->isChecked()) {
action->setChecked(true);
emit action->toggled(true);
emit action->triggered(true);
}
KisNodeSP node = selection->parentNode();
KIS_SAFE_ASSERT_RECOVER_RETURN(node);
m_view->nodeManager()->slotNonUiActivatedNode(node);
if (selection->hasShapeSelection()) {
KisShapeSelection *shapeSelection = dynamic_cast(selection->shapeSelection());
KIS_SAFE_ASSERT_RECOVER_RETURN(shapeSelection);
KoToolManager::instance()->switchToolRequested(KoInteractionTool_ID);
QList shapes = shapeSelection->shapes();
if (shapes.isEmpty()) {
KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "no shapes");
return;
}
Q_FOREACH (KoShape *shape, shapes) {
m_view->canvasBase()->selectedShapesProxy()->selection()->select(shape);
}
} else {
KoToolManager::instance()->switchToolRequested("KisToolTransform");
}
}
void KisSelectionManager::convertToVectorSelection()
{
KisSelectionToVectorActionFactory factory;
factory.run(m_view);
}
void KisSelectionManager::convertToRasterSelection()
{
KisSelectionToRasterActionFactory factory;
factory.run(m_view);
}
void KisSelectionManager::convertShapesToVectorSelection()
{
KisShapesToVectorSelectionActionFactory factory;
factory.run(m_view);
}
void KisSelectionManager::convertToShape()
{
KisSelectionToShapeActionFactory factory;
factory.run(m_view);
}
void KisSelectionManager::clear()
{
KisClearActionFactory factory;
factory.run(m_view);
}
void KisSelectionManager::fillForegroundColor()
{
KisFillActionFactory factory;
factory.run("fg", m_view);
}
void KisSelectionManager::fillBackgroundColor()
{
KisFillActionFactory factory;
factory.run("bg", m_view);
}
void KisSelectionManager::fillPattern()
{
KisFillActionFactory factory;
factory.run("pattern", m_view);
}
void KisSelectionManager::fillForegroundColorOpacity()
{
KisFillActionFactory factory;
factory.run("fg_opacity", m_view);
}
void KisSelectionManager::fillBackgroundColorOpacity()
{
KisFillActionFactory factory;
factory.run("bg_opacity", m_view);
}
void KisSelectionManager::fillPatternOpacity()
{
KisFillActionFactory factory;
factory.run("pattern_opacity", m_view);
}
void KisSelectionManager::copySelectionToNewLayer()
{
copy();
paste();
}
void KisSelectionManager::cutToNewLayer()
{
cut();
paste();
}
void KisSelectionManager::toggleDisplaySelection()
{
KIS_ASSERT_RECOVER_RETURN(m_selectionDecoration);
m_selectionDecoration->toggleVisibility();
m_toggleDisplaySelection->blockSignals(true);
m_toggleDisplaySelection->setChecked(m_selectionDecoration->visible());
m_toggleDisplaySelection->blockSignals(false);
emit displaySelectionChanged();
}
bool KisSelectionManager::displaySelection()
{
return m_toggleDisplaySelection->isChecked();
}
void KisSelectionManager::shapeSelectionChanged()
{
KoShapeManager* shapeManager = m_view->canvasBase()->globalShapeManager();
KoSelection * selection = shapeManager->selection();
QList selectedShapes = selection->selectedShapes();
KoShapeStrokeSP border(new KoShapeStroke(0, Qt::lightGray));
Q_FOREACH (KoShape* shape, shapeManager->shapes()) {
if (dynamic_cast(shape->parent())) {
if (selectedShapes.contains(shape))
shape->setStroke(border);
else
shape->setStroke(KoShapeStrokeSP());
}
}
m_view->updateGUI();
}
void KisSelectionManager::imageResizeToSelection()
{
KisImageResizeToSelectionActionFactory factory;
factory.run(m_view);
}
void KisSelectionManager::paintSelectedShapes()
{
KisImageWSP image = m_view->image();
if (!image) return;
KisLayerSP layer = m_view->activeLayer();
if (!layer) return;
QList shapes = m_view->canvasBase()->shapeManager()->selection()->selectedShapes();
KisPaintLayerSP paintLayer = new KisPaintLayer(image, i18n("Stroked Shapes"), OPACITY_OPAQUE_U8);
KUndo2MagicString actionName = kundo2_i18n("Stroke Shapes");
m_adapter->beginMacro(actionName);
m_adapter->addNode(paintLayer.data(), layer->parent().data(), layer.data());
KisFigurePaintingToolHelper helper(actionName,
image,
paintLayer.data(),
m_view->resourceProvider()->resourceManager(),
KisPainter::StrokeStyleBrush,
KisPainter::FillStyleNone);
Q_FOREACH (KoShape* shape, shapes) {
QTransform matrix = shape->absoluteTransformation(0) * QTransform::fromScale(image->xRes(), image->yRes());
QPainterPath mapedOutline = matrix.map(shape->outline());
helper.paintPainterPath(mapedOutline);
}
m_adapter->endMacro();
}
void KisSelectionManager::slotToggleSelectionDecoration()
{
KIS_ASSERT_RECOVER_RETURN(m_selectionDecoration);
KisSelectionDecoration::Mode mode =
m_selectionDecoration->mode() ?
KisSelectionDecoration::Ants : KisSelectionDecoration::Mask;
m_selectionDecoration->setMode(mode);
emit displaySelectionChanged();
}
bool KisSelectionManager::showSelectionAsMask() const
{
if (m_selectionDecoration) {
return m_selectionDecoration->mode() == KisSelectionDecoration::Mask;
}
return false;
}
void KisSelectionManager::slotStrokeSelection()
{
KisImageWSP image = m_view->image();
if (!image ) {
return;
}
KisNodeSP currentNode = m_view->resourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value();
bool isVectorLayer = false;
if (currentNode->inherits("KisShapeLayer")) {
isVectorLayer = true;
}
QPointer dlg = new KisDlgStrokeSelection(image, m_view, isVectorLayer);
if (dlg->exec() == QDialog::Accepted) {
StrokeSelectionOptions params = dlg->getParams();
if (params.brushSelected){
KisStrokeBrushSelectionActionFactory factory;
factory.run(m_view, params);
}
else {
KisStrokeSelectionActionFactory factory;
factory.run(m_view, params);
}
}
delete dlg;
}
#include "kis_image_barrier_locker.h"
#include "kis_selection_tool_helper.h"
void KisSelectionManager::selectOpaqueOnNode(KisNodeSP node, SelectionAction action)
{
KisImageSP image = m_view->image();
if (!m_view->blockUntilOperationsFinished(image)) {
return;
}
KUndo2MagicString actionName;
KisPixelSelectionSP tmpSel = KisPixelSelectionSP(new KisPixelSelection());
KisCanvas2 *canvas = m_view->canvasBase();
{
KisImageBarrierLocker locker(image);
KisPaintDeviceSP device = node->projection();
if (!device) device = node->paintDevice();
if (!device) device = node->original();
KIS_ASSERT_RECOVER_RETURN(canvas && device);
QRect rc = device->exactBounds();
if (rc.isEmpty()) return;
/**
* If there is nothing selected, just create a new selection
*/
if (!canvas->imageView()->selection()) {
action = SELECTION_REPLACE;
}
switch (action) {
case SELECTION_ADD:
actionName = kundo2_i18n("Select Opaque (Add)");
break;
case SELECTION_SUBTRACT:
actionName = kundo2_i18n("Select Opaque (Subtract)");
break;
case SELECTION_INTERSECT:
actionName = kundo2_i18n("Select Opaque (Intersect)");
break;
+ case SELECTION_SYMMETRICDIFFERENCE:
+ actionName = kundo2_i18n("Select Opaque (Symmetric Difference)");
+ break;
default:
actionName = kundo2_i18n("Select Opaque");
break;
}
qint32 x, y, w, h;
rc.getRect(&x, &y, &w, &h);
const KoColorSpace * cs = device->colorSpace();
KisHLineConstIteratorSP deviter = device->createHLineConstIteratorNG(x, y, w);
KisHLineIteratorSP selIter = tmpSel ->createHLineIteratorNG(x, y, w);
for (int row = y; row < h + y; ++row) {
do {
*selIter->rawData() = cs->opacityU8(deviter->oldRawData());
} while (deviter->nextPixel() && selIter->nextPixel());
deviter->nextRow();
selIter->nextRow();
}
}
KisSelectionToolHelper helper(canvas, actionName);
tmpSel->invalidateOutlineCache();
helper.selectPixelSelection(tmpSel, action);
}
diff --git a/libs/ui/tool/kis_selection_tool_config_widget_helper.cpp b/libs/ui/tool/kis_selection_tool_config_widget_helper.cpp
index c1f7022eb4..d9e8d84145 100644
--- a/libs/ui/tool/kis_selection_tool_config_widget_helper.cpp
+++ b/libs/ui/tool/kis_selection_tool_config_widget_helper.cpp
@@ -1,152 +1,158 @@
/*
* Copyright (c) 2011 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_selection_tool_config_widget_helper.h"
#include
#include "kis_selection_options.h"
#include "kis_canvas2.h"
#include "KisViewManager.h"
#include "kis_canvas_resource_provider.h"
#include "kis_signals_blocker.h"
#include
#include
KisSelectionToolConfigWidgetHelper::KisSelectionToolConfigWidgetHelper(const QString &windowTitle)
: m_optionsWidget(0),
m_windowTitle(windowTitle)
{
}
void KisSelectionToolConfigWidgetHelper::createOptionWidget(KisCanvas2 *canvas, const QString &toolId)
{
m_optionsWidget = new KisSelectionOptions(canvas);
Q_CHECK_PTR(m_optionsWidget);
m_optionsWidget->setObjectName(toolId + "option widget");
m_optionsWidget->setWindowTitle(m_windowTitle);
slotToolActivatedChanged(true);
// See https://bugs.kde.org/show_bug.cgi?id=316896
QWidget *specialSpacer = new QWidget(m_optionsWidget);
specialSpacer->setObjectName("SpecialSpacer");
specialSpacer->setFixedSize(0, 0);
m_optionsWidget->layout()->addWidget(specialSpacer);
connect(m_optionsWidget, &KisSelectionOptions::actionChanged,
this, &KisSelectionToolConfigWidgetHelper::slotWidgetActionChanged);
connect(m_optionsWidget, &KisSelectionOptions::modeChanged,
this, &KisSelectionToolConfigWidgetHelper::slotWidgetModeChanged);
connect(m_optionsWidget, &KisSelectionOptions::antiAliasSelectionChanged,
this, &KisSelectionToolConfigWidgetHelper::slotWidgetAntiAliasChanged);
m_optionsWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
m_optionsWidget->adjustSize();
}
KisSelectionOptions* KisSelectionToolConfigWidgetHelper::optionWidget() const
{
return m_optionsWidget;
}
SelectionMode KisSelectionToolConfigWidgetHelper::selectionMode() const
{
return m_selectionMode;
}
SelectionAction KisSelectionToolConfigWidgetHelper::selectionAction() const
{
return m_selectionAction;
}
bool KisSelectionToolConfigWidgetHelper::antiAliasSelection() const
{
return m_antiAliasSelection;
}
void KisSelectionToolConfigWidgetHelper::slotWidgetActionChanged(int action)
{
- if (action >= SELECTION_REPLACE && action <= SELECTION_INTERSECT) {
+ if (action >= SELECTION_REPLACE && action <= SELECTION_SYMMETRICDIFFERENCE) {
m_selectionAction = (SelectionAction)action;
KConfigGroup cfg = KSharedConfig::openConfig()->group("KisToolSelectBase");
cfg.writeEntry("selectionAction", action);
emit selectionActionChanged(action);
}
}
void KisSelectionToolConfigWidgetHelper::slotWidgetModeChanged(int mode)
{
m_selectionMode = (SelectionMode)mode;
KConfigGroup cfg = KSharedConfig::openConfig()->group("KisToolSelectBase");
cfg.writeEntry("selectionMode", mode);
}
void KisSelectionToolConfigWidgetHelper::slotWidgetAntiAliasChanged(bool value)
{
m_antiAliasSelection = value;
KConfigGroup cfg = KSharedConfig::openConfig()->group("KisToolSelectBase");
cfg.writeEntry("antiAliasSelection", value);
}
void KisSelectionToolConfigWidgetHelper::slotReplaceModeRequested()
{
m_optionsWidget->setAction(SELECTION_REPLACE);
slotWidgetActionChanged(m_optionsWidget->action());
}
void KisSelectionToolConfigWidgetHelper::slotAddModeRequested()
{
m_optionsWidget->setAction(SELECTION_ADD);
slotWidgetActionChanged(m_optionsWidget->action());
}
void KisSelectionToolConfigWidgetHelper::slotSubtractModeRequested()
{
m_optionsWidget->setAction(SELECTION_SUBTRACT);
slotWidgetActionChanged(m_optionsWidget->action());
}
void KisSelectionToolConfigWidgetHelper::slotIntersectModeRequested()
{
m_optionsWidget->setAction(SELECTION_INTERSECT);
slotWidgetActionChanged(m_optionsWidget->action());
}
+void KisSelectionToolConfigWidgetHelper::slotSymmetricDifferenceModeRequested()
+{
+ m_optionsWidget->setAction(SELECTION_SYMMETRICDIFFERENCE);
+ slotWidgetActionChanged(m_optionsWidget->action());
+}
+
void KisSelectionToolConfigWidgetHelper::slotToolActivatedChanged(bool isActivated)
{
if (!isActivated) return;
KConfigGroup cfg = KSharedConfig::openConfig()->group("KisToolSelectBase");
m_selectionAction = (SelectionAction)cfg.readEntry("selectionAction", (int)SELECTION_REPLACE);
m_selectionMode = (SelectionMode)cfg.readEntry("selectionMode", (int)SHAPE_PROTECTION);
m_antiAliasSelection = cfg.readEntry("antiAliasSelection", true);
KisSignalsBlocker b(m_optionsWidget);
m_optionsWidget->setAction(m_selectionAction);
m_optionsWidget->setMode(m_selectionMode);
m_optionsWidget->setAntiAliasSelection(m_antiAliasSelection);
}
diff --git a/libs/ui/tool/kis_selection_tool_config_widget_helper.h b/libs/ui/tool/kis_selection_tool_config_widget_helper.h
index acb631c905..1b0fa09793 100644
--- a/libs/ui/tool/kis_selection_tool_config_widget_helper.h
+++ b/libs/ui/tool/kis_selection_tool_config_widget_helper.h
@@ -1,73 +1,74 @@
/*
* Copyright (c) 2011 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_SELECTION_TOOL_CONFIG_WIDGET_HELPER_H
#define __KIS_SELECTION_TOOL_CONFIG_WIDGET_HELPER_H
#include
#include "kritaui_export.h"
#include "kis_selection.h"
#include "kis_canvas_resource_provider.h"
class QKeyEvent;
class KisCanvas2;
class KisSelectionOptions;
class KoCanvasResourceProvider;
class KRITAUI_EXPORT KisSelectionToolConfigWidgetHelper : public QObject
{
Q_OBJECT
public:
KisSelectionToolConfigWidgetHelper(const QString &windowTitle);
void createOptionWidget(KisCanvas2 *canvas, const QString &toolId);
KisSelectionOptions* optionWidget() const;
SelectionMode selectionMode() const;
SelectionAction selectionAction() const;
bool antiAliasSelection() const;
int action() const { return selectionAction(); }
Q_SIGNALS:
void selectionActionChanged(int newAction);
public Q_SLOTS:
void slotToolActivatedChanged(bool isActivated);
void slotWidgetActionChanged(int action);
void slotWidgetModeChanged(int mode);
void slotWidgetAntiAliasChanged(bool value);
void slotReplaceModeRequested();
void slotAddModeRequested();
void slotSubtractModeRequested();
void slotIntersectModeRequested();
+ void slotSymmetricDifferenceModeRequested();
private:
KisSelectionOptions* m_optionsWidget;
QString m_windowTitle;
SelectionMode m_selectionMode;
SelectionAction m_selectionAction;
bool m_antiAliasSelection = true;
};
#endif /* __KIS_SELECTION_TOOL_CONFIG_WIDGET_HELPER_H */
diff --git a/libs/ui/tool/kis_selection_tool_helper.cpp b/libs/ui/tool/kis_selection_tool_helper.cpp
index 109e9c4b10..2db9542a17 100644
--- a/libs/ui/tool/kis_selection_tool_helper.cpp
+++ b/libs/ui/tool/kis_selection_tool_helper.cpp
@@ -1,351 +1,354 @@
/*
* Copyright (c) 2007 Sven Langkamp
*
* 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_selection_tool_helper.h"
#include
#include
#include
#include "kis_pixel_selection.h"
#include "kis_shape_selection.h"
#include "kis_image.h"
#include "canvas/kis_canvas2.h"
#include "KisViewManager.h"
#include "kis_selection_manager.h"
#include "kis_transaction.h"
#include "commands/kis_selection_commands.h"
#include "kis_shape_controller.h"
#include
#include "kis_processing_applicator.h"
#include "commands_new/kis_transaction_based_command.h"
#include "kis_gui_context_command.h"
#include "kis_command_utils.h"
#include "commands/kis_deselect_global_selection_command.h"
#include "kis_algebra_2d.h"
#include "kis_config.h"
#include "kis_action_manager.h"
#include "kis_action.h"
#include
KisSelectionToolHelper::KisSelectionToolHelper(KisCanvas2* canvas, const KUndo2MagicString& name)
: m_canvas(canvas)
, m_name(name)
{
m_image = m_canvas->viewManager()->image();
}
KisSelectionToolHelper::~KisSelectionToolHelper()
{
}
struct LazyInitGlobalSelection : public KisTransactionBasedCommand {
LazyInitGlobalSelection(KisViewManager *view) : m_view(view) {}
KisViewManager *m_view;
KUndo2Command* paint() override {
return !m_view->selection() ?
new KisSetEmptyGlobalSelectionCommand(m_view->image()) : 0;
}
};
void KisSelectionToolHelper::selectPixelSelection(KisPixelSelectionSP selection, SelectionAction action)
{
KisViewManager* view = m_canvas->viewManager();
if (selection->selectedExactRect().isEmpty()) {
m_canvas->viewManager()->selectionManager()->deselect();
return;
}
KisProcessingApplicator applicator(view->image(),
0 /* we need no automatic updates */,
KisProcessingApplicator::SUPPORTS_WRAPAROUND_MODE,
KisImageSignalVector() << ModifiedSignal,
m_name);
applicator.applyCommand(new LazyInitGlobalSelection(view));
struct ApplyToPixelSelection : public KisTransactionBasedCommand {
ApplyToPixelSelection(KisViewManager *view,
KisPixelSelectionSP selection,
SelectionAction action) : m_view(view),
m_selection(selection),
m_action(action) {}
KisViewManager *m_view;
KisPixelSelectionSP m_selection;
SelectionAction m_action;
KUndo2Command* paint() override {
KisPixelSelectionSP pixelSelection = m_view->selection()->pixelSelection();
KIS_ASSERT_RECOVER(pixelSelection) { return 0; }
bool hasSelection = !pixelSelection->isEmpty();
KisSelectionTransaction transaction(pixelSelection);
- if (!hasSelection && m_action == SELECTION_SUBTRACT) {
+ if (!hasSelection && m_action == SELECTION_SYMMETRICDIFFERENCE) {
pixelSelection->invert();
}
pixelSelection->applySelection(m_selection, m_action);
QRect dirtyRect = m_view->image()->bounds();
- if (hasSelection && m_action != SELECTION_REPLACE && m_action != SELECTION_INTERSECT) {
+ if (hasSelection && m_action != SELECTION_REPLACE && m_action != SELECTION_SYMMETRICDIFFERENCE) {
dirtyRect = m_selection->selectedRect();
}
m_view->selection()->updateProjection(dirtyRect);
KUndo2Command *savedCommand = transaction.endAndTake();
pixelSelection->setDirty(dirtyRect);
if (m_view->selection()->selectedExactRect().isEmpty()) {
KisCommandUtils::CompositeCommand *cmd = new KisCommandUtils::CompositeCommand();
cmd->addCommand(savedCommand);
cmd->addCommand(new KisDeselectGlobalSelectionCommand(m_view->image()));
savedCommand = cmd;
}
return savedCommand;
}
};
applicator.applyCommand(new ApplyToPixelSelection(view, selection, action));
applicator.end();
}
void KisSelectionToolHelper::addSelectionShape(KoShape* shape, SelectionAction action)
{
QList shapes;
shapes.append(shape);
addSelectionShapes(shapes, action);
}
void KisSelectionToolHelper::addSelectionShapes(QList< KoShape* > shapes, SelectionAction action)
{
KisViewManager* view = m_canvas->viewManager();
if (view->image()->wrapAroundModePermitted()) {
view->showFloatingMessage(
i18n("Shape selection does not fully "
"support wraparound mode. Please "
"use pixel selection instead"),
KisIconUtils::loadIcon("selection-info"));
}
KisProcessingApplicator applicator(view->image(),
0 /* we need no automatic updates */,
KisProcessingApplicator::NONE,
KisImageSignalVector() << ModifiedSignal,
m_name);
applicator.applyCommand(new LazyInitGlobalSelection(view));
struct ClearPixelSelection : public KisTransactionBasedCommand {
ClearPixelSelection(KisViewManager *view) : m_view(view) {}
KisViewManager *m_view;
KUndo2Command* paint() override {
KisPixelSelectionSP pixelSelection = m_view->selection()->pixelSelection();
KIS_ASSERT_RECOVER(pixelSelection) { return 0; }
KisSelectionTransaction transaction(pixelSelection);
pixelSelection->clear();
return transaction.endAndTake();
}
};
if (action == SELECTION_REPLACE || action == SELECTION_DEFAULT) {
applicator.applyCommand(new ClearPixelSelection(view));
}
struct AddSelectionShape : public KisTransactionBasedCommand {
AddSelectionShape(KisViewManager *view, KoShape* shape, SelectionAction action)
: m_view(view),
m_shape(shape),
m_action(action) {}
KisViewManager *m_view;
KoShape* m_shape;
SelectionAction m_action;
KUndo2Command* paint() override {
KUndo2Command *resultCommand = 0;
KisSelectionSP selection = m_view->selection();
if (selection) {
KisShapeSelection * shapeSelection = static_cast(selection->shapeSelection());
if (shapeSelection) {
QList existingShapes = shapeSelection->shapes();
if (existingShapes.size() == 1) {
KoShape *currentShape = existingShapes.first();
QPainterPath path1 = currentShape->absoluteTransformation(0).map(currentShape->outline());
QPainterPath path2 = m_shape->absoluteTransformation(0).map(m_shape->outline());
QPainterPath path = path2;
switch (m_action) {
case SELECTION_DEFAULT:
case SELECTION_REPLACE:
path = path2;
break;
case SELECTION_INTERSECT:
path = path1 & path2;
break;
case SELECTION_ADD:
path = path1 | path2;
break;
case SELECTION_SUBTRACT:
path = path1 - path2;
break;
+ case SELECTION_SYMMETRICDIFFERENCE:
+ path = (path1 | path2) - (path1 & path2);
+ break;
}
KoShape *newShape = KoPathShape::createShapeFromPainterPath(path);
newShape->setUserData(new KisShapeSelectionMarker);
KUndo2Command *parentCommand = new KUndo2Command();
m_view->canvasBase()->shapeController()->removeShape(currentShape, parentCommand);
m_view->canvasBase()->shapeController()->addShape(newShape, 0, parentCommand);
resultCommand = parentCommand;
}
}
}
if (!resultCommand) {
/**
* Mark a shape that it belongs to a shape selection
*/
if(!m_shape->userData()) {
m_shape->setUserData(new KisShapeSelectionMarker);
}
resultCommand = m_view->canvasBase()->shapeController()->addShape(m_shape, 0);
}
return resultCommand;
}
};
Q_FOREACH (KoShape* shape, shapes) {
applicator.applyCommand(
new KisGuiContextCommand(new AddSelectionShape(view, shape, action), view));
}
applicator.end();
}
bool KisSelectionToolHelper::canShortcutToDeselect(const QRect &rect, SelectionAction action)
{
return rect.isEmpty() && (action == SELECTION_INTERSECT || action == SELECTION_REPLACE);
}
bool KisSelectionToolHelper::canShortcutToNoop(const QRect &rect, SelectionAction action)
{
return rect.isEmpty() && action == SELECTION_ADD;
}
bool KisSelectionToolHelper::tryDeselectCurrentSelection(const QRectF selectionViewRect, SelectionAction action)
{
bool result = false;
if (KisAlgebra2D::maxDimension(selectionViewRect) < KisConfig(true).selectionViewSizeMinimum() &&
- (action == SELECTION_INTERSECT || action == SELECTION_REPLACE)) {
+ (action == SELECTION_SYMMETRICDIFFERENCE || action == SELECTION_REPLACE)) {
// Queueing this action to ensure we avoid a race condition when unlocking the node system
QTimer::singleShot(0, m_canvas->viewManager()->selectionManager(), SLOT(deselect()));
result = true;
}
return result;
}
QMenu* KisSelectionToolHelper::getSelectionContextMenu(KisCanvas2* canvas)
{
QMenu *m_contextMenu = new QMenu();
KisActionManager * actionMan = canvas->viewManager()->actionManager();
m_contextMenu->addAction(actionMan->actionByName("deselect"));
m_contextMenu->addAction(actionMan->actionByName("invert"));
m_contextMenu->addAction(actionMan->actionByName("select_all"));
m_contextMenu->addSeparator();
m_contextMenu->addAction(actionMan->actionByName("cut_selection_to_new_layer"));
m_contextMenu->addAction(actionMan->actionByName("copy_selection_to_new_layer"));
m_contextMenu->addSeparator();
KisSelectionSP selection = canvas->viewManager()->selection();
if (selection && canvas->viewManager()->selectionEditable()) {
m_contextMenu->addAction(actionMan->actionByName("edit_selection"));
if (!selection->hasShapeSelection()) {
m_contextMenu->addAction(actionMan->actionByName("convert_to_vector_selection"));
} else {
m_contextMenu->addAction(actionMan->actionByName("convert_to_raster_selection"));
}
QMenu *transformMenu = m_contextMenu->addMenu(i18n("Transform"));
transformMenu->addAction(actionMan->actionByName("selectionscale"));
transformMenu->addAction(actionMan->actionByName("growselection"));
transformMenu->addAction(actionMan->actionByName("shrinkselection"));
transformMenu->addAction(actionMan->actionByName("borderselection"));
transformMenu->addAction(actionMan->actionByName("smoothselection"));
transformMenu->addAction(actionMan->actionByName("featherselection"));
transformMenu->addAction(actionMan->actionByName("stroke_selection"));
m_contextMenu->addSeparator();
}
m_contextMenu->addAction(actionMan->actionByName("resizeimagetoselection"));
m_contextMenu->addSeparator();
m_contextMenu->addAction(actionMan->actionByName("toggle_display_selection"));
m_contextMenu->addAction(actionMan->actionByName("show-global-selection-mask"));
return m_contextMenu;
}
SelectionMode KisSelectionToolHelper::tryOverrideSelectionMode(KisSelectionSP activeSelection, SelectionMode currentMode, SelectionAction currentAction) const
{
if (currentAction != SELECTION_DEFAULT && currentAction != SELECTION_REPLACE) {
if (activeSelection) {
currentMode = activeSelection->hasShapeSelection() ? SHAPE_PROTECTION : PIXEL_SELECTION;
}
}
return currentMode;
}
diff --git a/libs/ui/widgets/kis_selection_options.cc b/libs/ui/widgets/kis_selection_options.cc
index beda02c1f2..6d8ac5fb9b 100644
--- a/libs/ui/widgets/kis_selection_options.cc
+++ b/libs/ui/widgets/kis_selection_options.cc
@@ -1,169 +1,181 @@
/*
* Copyright (c) 2005 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_selection_options.h"
#include
#include
#include
#include
#include
#include
#include
#include "kis_types.h"
#include "kis_layer.h"
#include "kis_image.h"
#include "kis_selection.h"
#include "kis_paint_device.h"
#include "canvas/kis_canvas2.h"
#include "KisViewManager.h"
KisSelectionOptions::KisSelectionOptions(KisCanvas2 * /*canvas*/)
{
m_page = new WdgSelectionOptions(this);
Q_CHECK_PTR(m_page);
QVBoxLayout * l = new QVBoxLayout(this);
l->addWidget(m_page);
l->addSpacerItem(new QSpacerItem(0,0, QSizePolicy::Preferred, QSizePolicy::Expanding));
l->setContentsMargins(0,0,0,0);
m_mode = new QButtonGroup(this);
m_mode->addButton(m_page->pixel, PIXEL_SELECTION);
m_mode->addButton(m_page->shape, SHAPE_PROTECTION);
m_action = new QButtonGroup(this);
m_action->addButton(m_page->add, SELECTION_ADD);
m_action->addButton(m_page->subtract, SELECTION_SUBTRACT);
m_action->addButton(m_page->replace, SELECTION_REPLACE);
m_action->addButton(m_page->intersect, SELECTION_INTERSECT);
+ m_action->addButton(m_page->symmetricdifference, SELECTION_SYMMETRICDIFFERENCE);
m_page->pixel->setGroupPosition(KoGroupButton::GroupLeft);
m_page->shape->setGroupPosition(KoGroupButton::GroupRight);
m_page->pixel->setIcon(KisIconUtils::loadIcon("select_pixel"));
m_page->shape->setIcon(KisIconUtils::loadIcon("select_shape"));
m_page->add->setGroupPosition(KoGroupButton::GroupCenter);
m_page->subtract->setGroupPosition(KoGroupButton::GroupRight);
m_page->replace->setGroupPosition(KoGroupButton::GroupLeft);
m_page->intersect->setGroupPosition(KoGroupButton::GroupCenter);
+ m_page->symmetricdifference->setGroupPosition(KoGroupButton::GroupRight);
m_page->add->setIcon(KisIconUtils::loadIcon("selection_add"));
m_page->subtract->setIcon(KisIconUtils::loadIcon("selection_subtract"));
m_page->replace->setIcon(KisIconUtils::loadIcon("selection_replace"));
m_page->intersect->setIcon(KisIconUtils::loadIcon("selection_intersect"));
+ m_page->symmetricdifference->setIcon(KisIconUtils::loadIcon("selection_symmetric_difference"));
connect(m_mode, SIGNAL(buttonClicked(int)), this, SIGNAL(modeChanged(int)));
connect(m_action, SIGNAL(buttonClicked(int)), this, SIGNAL(actionChanged(int)));
connect(m_mode, SIGNAL(buttonClicked(int)), this, SLOT(hideActionsForSelectionMode(int)));
connect(m_page->chkAntiAliasing, SIGNAL(toggled(bool)), this, SIGNAL(antiAliasSelectionChanged(bool)));
}
KisSelectionOptions::~KisSelectionOptions()
{
}
int KisSelectionOptions::action()
{
return m_action->checkedId();
}
void KisSelectionOptions::setAction(int action) {
QAbstractButton* button = m_action->button(action);
KIS_SAFE_ASSERT_RECOVER_RETURN(button);
button->setChecked(true);
}
void KisSelectionOptions::setMode(int mode) {
QAbstractButton* button = m_mode->button(mode);
KIS_SAFE_ASSERT_RECOVER_RETURN(button);
button->setChecked(true);
hideActionsForSelectionMode(mode);
}
void KisSelectionOptions::setAntiAliasSelection(bool value)
{
m_page->chkAntiAliasing->setChecked(value);
}
void KisSelectionOptions::updateActionButtonToolTip(int action, const QKeySequence &shortcut)
{
const QString shortcutString = shortcut.toString(QKeySequence::NativeText);
QString toolTipText;
switch ((SelectionAction)action) {
case SELECTION_DEFAULT:
case SELECTION_REPLACE:
toolTipText = shortcutString.isEmpty() ?
i18nc("@info:tooltip", "Replace") :
i18nc("@info:tooltip", "Replace (%1)", shortcutString);
m_action->button(SELECTION_REPLACE)->setToolTip(toolTipText);
break;
case SELECTION_ADD:
toolTipText = shortcutString.isEmpty() ?
i18nc("@info:tooltip", "Add") :
i18nc("@info:tooltip", "Add (%1)", shortcutString);
m_action->button(SELECTION_ADD)->setToolTip(toolTipText);
break;
case SELECTION_SUBTRACT:
toolTipText = shortcutString.isEmpty() ?
i18nc("@info:tooltip", "Subtract") :
i18nc("@info:tooltip", "Subtract (%1)", shortcutString);
m_action->button(SELECTION_SUBTRACT)->setToolTip(toolTipText);
break;
case SELECTION_INTERSECT:
toolTipText = shortcutString.isEmpty() ?
i18nc("@info:tooltip", "Intersect") :
i18nc("@info:tooltip", "Intersect (%1)", shortcutString);
m_action->button(SELECTION_INTERSECT)->setToolTip(toolTipText);
+ break;
+
+ case SELECTION_SYMMETRICDIFFERENCE:
+ toolTipText = shortcutString.isEmpty() ?
+ i18nc("@info:tooltip", "Symmetric Difference") :
+ i18nc("@info:tooltip", "Symmetric Difference (%1)", shortcutString);
+
+ m_action->button(SELECTION_SYMMETRICDIFFERENCE)->setToolTip(toolTipText);
+
break;
}
}
//hide action buttons and antialiasing, if shape selection is active (actions currently don't work on shape selection)
void KisSelectionOptions::hideActionsForSelectionMode(int mode) {
const bool isPixelSelection = (mode == (int)PIXEL_SELECTION);
m_page->chkAntiAliasing->setVisible(isPixelSelection);
}
bool KisSelectionOptions::antiAliasSelection()
{
return m_page->chkAntiAliasing->isChecked();
}
void KisSelectionOptions::disableAntiAliasSelectionOption()
{
m_page->chkAntiAliasing->hide();
disconnect(m_page->pixel, SIGNAL(clicked()), m_page->chkAntiAliasing, SLOT(show()));
}
void KisSelectionOptions::disableSelectionModeOption()
{
m_page->lblMode->hide();
m_page->pixel->hide();
m_page->shape->hide();
}
diff --git a/plugins/extensions/pykrita/sip/krita/Selection.sip b/plugins/extensions/pykrita/sip/krita/Selection.sip
index c429b8e61e..7ea9cbca44 100644
--- a/plugins/extensions/pykrita/sip/krita/Selection.sip
+++ b/plugins/extensions/pykrita/sip/krita/Selection.sip
@@ -1,41 +1,42 @@
class Selection : QObject
{
%TypeHeaderCode
#include "Selection.h"
%End
Selection(const Selection & __0);
public:
Selection(QObject* parent /TransferThis/ = 0);
virtual ~Selection();
bool operator==(const Selection &other) const;
bool operator!=(const Selection &other) const;
public Q_SLOTS:
int width() const;
int height() const;
int x() const;
int y() const;
void move(int x, int y);
void clear();
void contract(int value);
void copy(Node *node);
void cut(Node *node);
void paste(Node *destination, int x, int y);
void erode();
void dilate();
void border(int xRadius, int yRadius);
void feather(int radius);
void grow(int xradius, int yradius);
void shrink(int xRadius, int yRadius, bool edgeLock);
void smooth();
void invert();
void resize(int w, int h);
void select(int x, int y, int w, int h, int value);
void selectAll(Node* node, int value);
void replace(Selection *selection);
void add(Selection *selection);
void subtract(Selection *selection);
void intersect(Selection *selection);
+ void symmetricdifference(Selection *selection);
QByteArray pixelData(int x, int y, int w, int h) const;
void setPixelData(QByteArray value, int x, int y, int w, int h);
private:
};
diff --git a/plugins/tools/selectiontools/kis_selection_modifier_mapper.cc b/plugins/tools/selectiontools/kis_selection_modifier_mapper.cc
index 419383b111..8896faffc5 100644
--- a/plugins/tools/selectiontools/kis_selection_modifier_mapper.cc
+++ b/plugins/tools/selectiontools/kis_selection_modifier_mapper.cc
@@ -1,124 +1,130 @@
/* This file is part of the KDE project
* Copyright (C) 2016 Michael Abrahams
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* This is a basic template to create selection tools from basic path based drawing tools.
* The template overrides the ability to execute alternate actions correctly.
* Modifier keys are overridden with the following behavior:
*
* Shift: add to selection
* Alt: subtract from selection
* Shift+Alt: intersect current selection
* Ctrl: replace selection
*
* Certain tools also use modifier keys to alter their behavior, e.g. forcing square proportions with the rectangle tool.
* The template enables the following rules for forwarding keys:
* 1) Any modifier keys held *when the tool is first activated* will determine the new selection method.
* 2) If the underlying tool *does not take modifier keys*, pressing modifier keys in the middle of a stroke will change the selection method. This applies to the lasso tool and polygon tool.
* 3) If the underlying tool *takes modifier keys,* they will always be forwarded to the underlying tool, and it is not possible to change the selection method in the middle of a stroke.
*/
#include "kis_selection.h"
#include "kis_selection_modifier_mapper.h"
#include "kis_config_notifier.h"
#include "kis_config.h"
Q_GLOBAL_STATIC(KisSelectionModifierMapper, s_instance);
// This numerically serializes modifier flags... let's keep it around for later.
#if 0
#include
QString QMOD_BINARY(Qt::KeyboardModifiers m)
{
return QString(std::bitset(m).to_string().c_str());
};
#endif
struct Q_DECL_HIDDEN KisSelectionModifierMapper::Private
{
SelectionAction map(Qt::KeyboardModifiers m);
void slotConfigChanged();
Qt::KeyboardModifiers replaceModifiers;
Qt::KeyboardModifiers intersectModifiers;
Qt::KeyboardModifiers addModifiers;
Qt::KeyboardModifiers subtractModifiers;
+ Qt::KeyboardModifiers symmetricdifferenceModifiers;
};
KisSelectionModifierMapper::KisSelectionModifierMapper()
: m_d(new Private)
{
connect(KisConfigNotifier::instance(),
SIGNAL(configChanged()),
SLOT(slotConfigChanged()));
slotConfigChanged();
}
KisSelectionModifierMapper::~KisSelectionModifierMapper()
{
}
KisSelectionModifierMapper *KisSelectionModifierMapper::instance()
{
return s_instance;
}
void KisSelectionModifierMapper::slotConfigChanged()
{
m_d->slotConfigChanged();
}
void KisSelectionModifierMapper::Private::slotConfigChanged()
{
KisConfig cfg(true);
if (!cfg.switchSelectionCtrlAlt()) {
replaceModifiers = Qt::ControlModifier;
intersectModifiers = (Qt::KeyboardModifiers)(Qt::AltModifier | Qt::ShiftModifier);
subtractModifiers = Qt::AltModifier;
+ symmetricdifferenceModifiers = (Qt::KeyboardModifiers)(Qt::ControlModifier | Qt::AltModifier);
} else {
replaceModifiers = Qt::AltModifier;
intersectModifiers = (Qt::KeyboardModifiers)(Qt::ControlModifier | Qt::ShiftModifier);
subtractModifiers = Qt::ControlModifier;
+ symmetricdifferenceModifiers = (Qt::KeyboardModifiers)(Qt::ShiftModifier | Qt::ControlModifier);
}
addModifiers = Qt::ShiftModifier;
}
SelectionAction KisSelectionModifierMapper::map(Qt::KeyboardModifiers m)
{
return s_instance->m_d->map(m);
}
SelectionAction KisSelectionModifierMapper::Private::map(Qt::KeyboardModifiers m)
{
SelectionAction newAction = SELECTION_DEFAULT;
if (m == replaceModifiers) {
newAction = SELECTION_REPLACE;
} else if (m == intersectModifiers) {
newAction = SELECTION_INTERSECT;
} else if (m == addModifiers) {
newAction = SELECTION_ADD;
} else if (m == subtractModifiers) {
newAction = SELECTION_SUBTRACT;
+ } else if (m == symmetricdifferenceModifiers) {
+ newAction = SELECTION_SYMMETRICDIFFERENCE;
}
+
return newAction;
}