Commit 4b48ec63 authored by Dmitry Kazakov's avatar Dmitry Kazakov

Fixed deselected selections

Now a selection cannot be "deselected". It either exists or does not exist.
When we deselect the global selection the corresponding mask is set
not-active and is left in the layer stack. When we need to "Reselect", we
just search for the last non-active mask and activate it.

These changes need testing!

BUG:294216
parent 649971d8
......@@ -38,24 +38,10 @@ KisDeselectGlobalSelectionCommand::~KisDeselectGlobalSelectionCommand()
void KisDeselectGlobalSelectionCommand::redo()
{
m_oldDeselectedSelection = m_image->deselectedGlobalSelection();
m_image->setDeselectedGlobalSelection(m_image->globalSelection());
if (!m_newSelection) {
m_image->setGlobalSelection();
m_newSelection = m_image->globalSelection();
m_newSelection->getOrCreatePixelSelection()->select(m_image->bounds());
m_newSelection->setDeselected(true);
} else
m_image->setGlobalSelection(m_newSelection);
m_image->undoAdapter()->emitSelectionChanged();
m_image->deselectGlobalSelection();
}
void KisDeselectGlobalSelectionCommand::undo()
{
m_image->setGlobalSelection(m_image->deselectedGlobalSelection());
m_image->setDeselectedGlobalSelection(m_oldDeselectedSelection);
m_image->undoAdapter()->emitSelectionChanged();
m_image->reselectGlobalSelection();
}
......@@ -41,8 +41,6 @@ public:
private:
KisImageWSP m_image;
KisSelectionSP m_newSelection;
KisSelectionSP m_oldDeselectedSelection;
};
#endif
......@@ -38,18 +38,17 @@ KisReselectGlobalSelectionCommand::~KisReselectGlobalSelectionCommand()
void KisReselectGlobalSelectionCommand::redo()
{
m_oldSelection = m_image->globalSelection();
m_image->setGlobalSelection(m_image->deselectedGlobalSelection());
m_image->setDeselectedGlobalSelection(0);
m_canReselect = m_image->canReselectGlobalSelection();
m_image->undoAdapter()->emitSelectionChanged();
if(m_canReselect) {
m_image->reselectGlobalSelection();
}
}
void KisReselectGlobalSelectionCommand::undo()
{
m_image->setDeselectedGlobalSelection(m_image->globalSelection());
m_image->setGlobalSelection(m_oldSelection);
m_image->undoAdapter()->emitSelectionChanged();
if(m_canReselect) {
m_image->deselectGlobalSelection();
}
}
......@@ -40,7 +40,7 @@ public:
private:
KisImageWSP m_image;
KisSelectionSP m_oldSelection;
bool m_canReselect;
};
......
......@@ -26,18 +26,11 @@
#include "kis_selection_mask.h"
#include "kis_pixel_selection.h"
KisSetGlobalSelectionCommand::KisSetGlobalSelectionCommand(KisImageWSP image, KUndo2Command * parent, KisSelectionSP selection) :
KUndo2Command(parent)
, m_image(image)
KisSetGlobalSelectionCommand::KisSetGlobalSelectionCommand(KisImageWSP image, KisSelectionSP selection)
: m_image(image)
{
m_oldSelection = m_image->globalSelection();
m_image->setGlobalSelection(selection);
m_newSelection = m_image->globalSelection();
}
KisSetGlobalSelectionCommand::~KisSetGlobalSelectionCommand()
{
m_newSelection = selection;
}
void KisSetGlobalSelectionCommand::redo()
......@@ -47,10 +40,10 @@ void KisSetGlobalSelectionCommand::redo()
void KisSetGlobalSelectionCommand::undo()
{
if (m_oldSelection)
m_image->setGlobalSelection(m_oldSelection);
else
m_image->removeGlobalSelection();
m_image->undoAdapter()->emitSelectionChanged();
m_image->setGlobalSelection(m_oldSelection);
}
KisSetEmptyGlobalSelectionCommand::KisSetEmptyGlobalSelectionCommand(KisImageWSP image)
: KisSetGlobalSelectionCommand(image, new KisSelection(new KisDefaultBounds(image)))
{
}
......@@ -22,7 +22,10 @@
#include <kundo2command.h>
#include "kis_types.h"
/// The command for setting the global selection
/**
* This command sets the global selection of the image. No saving
* of the previous selection for "Reselect" action happens
*/
class KRITAIMAGE_EXPORT KisSetGlobalSelectionCommand : public KUndo2Command
{
......@@ -30,11 +33,10 @@ public:
/**
* Constructor
* @param image the image to set the global selection on
* @param parent the parent command
* @param selection the selection that will be set a global selection, if 0 a new selection will be created
* @param selection the selection that will be set a global selection,
* null selection will remove the selection
*/
KisSetGlobalSelectionCommand(KisImageWSP image, KUndo2Command * parent = 0, KisSelectionSP selection = 0);
virtual ~KisSetGlobalSelectionCommand();
KisSetGlobalSelectionCommand(KisImageWSP image, KisSelectionSP selection);
virtual void redo();
virtual void undo();
......@@ -45,4 +47,14 @@ private:
KisSelectionSP m_oldSelection;
};
/**
* Sets initial selection for the image. Nothing is selected,
* but the defaultBounds are set properly
*/
class KRITAIMAGE_EXPORT KisSetEmptyGlobalSelectionCommand : public KisSetGlobalSelectionCommand
{
public:
KisSetEmptyGlobalSelectionCommand(KisImageWSP image);
};
#endif //KIS_SET_GLOBAL_SELECTION_COMMAND_H
......@@ -196,65 +196,67 @@ void KisImage::nodeChanged(KisNode* node)
KisSelectionSP KisImage::globalSelection() const
{
KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask();
if (selectionMask) {
return selectionMask->selection();
}
else {
return 0;
}
return selectionMask ? selectionMask->selection() : 0;
}
void KisImage::setGlobalSelection(KisSelectionSP globalSelection)
{
KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask();
if (!selectionMask) {
selectionMask = new KisSelectionMask(this);
selectionMask->setActive(true);
bool success = addNode(selectionMask, m_d->rootLayer, 0);
Q_ASSERT(success);
if (!success) {
warnKrita << "Could not creaste global selection mask!";
if(!globalSelection) {
if(selectionMask) {
removeNode(selectionMask);
}
}
if (globalSelection) {
selectionMask->setSelection(globalSelection);
}
else {
selectionMask->setSelection(new KisSelection(new KisDefaultBounds(this)));
if(!selectionMask) {
selectionMask = new KisSelectionMask(this);
addNode(selectionMask);
selectionMask->setActive(true);
}
selectionMask->setSelection(globalSelection);
Q_ASSERT(m_d->rootLayer->childCount() > 0);
Q_ASSERT(m_d->rootLayer->selectionMask());
}
Q_ASSERT(m_d->rootLayer->childCount() > 0);
Q_ASSERT(m_d->rootLayer->selectionMask());
m_d->legacyUndoAdapter->emitSelectionChanged();
}
void KisImage::removeGlobalSelection()
void KisImage::deselectGlobalSelection()
{
KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask();
if (selectionMask) {
removeNode(selectionMask);
if(selectionMask) {
selectionMask->setActive(false);
}
m_d->legacyUndoAdapter->emitSelectionChanged();
}
KisSelectionSP KisImage::deselectedGlobalSelection()
bool KisImage::canReselectGlobalSelection()
{
KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask();
if (selectionMask) {
return selectionMask->deselectedSelection();
}
else {
return 0;
}
return deselectedMask();
}
void KisImage::setDeselectedGlobalSelection(KisSelectionSP selection)
void KisImage::reselectGlobalSelection()
{
KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask();
if (!selectionMask) {
setGlobalSelection();
selectionMask = m_d->rootLayer->selectionMask();
KisSelectionMaskSP mask = deselectedMask();
if(mask) {
mask->setActive(true);
}
Q_ASSERT(selectionMask);
selectionMask->setDeselectedSelection(selection);
m_d->legacyUndoAdapter->emitSelectionChanged();
}
KisSelectionMaskSP KisImage::deselectedMask()
{
// Get a list of non-active masks
KoProperties properties;
properties.setProperty("active", false);
QList<KisNodeSP> masks = root()->childNodes(QStringList("KisSelectionMask"), properties);
return masks.size() > 0 ?
static_cast<KisSelectionMask*>(masks.last().data()) : 0;
}
KisBackgroundSP KisImage::backgroundPattern() const
......
......@@ -480,6 +480,13 @@ public:
KisImageSignalRouter* signalRouter();
/**
* Returns whether we can reselect current global selection
*
* \see reselectGlobalSelection()
*/
bool canReselectGlobalSelection();
signals:
/**
......@@ -598,29 +605,29 @@ private:
friend class KisDeselectGlobalSelectionCommand;
friend class KisReselectGlobalSelectionCommand;
friend class KisSetGlobalSelectionCommand;
friend class KisPixelSelectionTest;
friend class KisImageTest;
/**
* Replaces the current global selection with globalSelection. If
* globalSelection is empty, a new selection object will be
* created that is by default completely deselected.
* \p globalSelection is empty, removes the selection object, so that
* \ref globalSelection() will return 0 after that.
*/
void setGlobalSelection(KisSelectionSP globalSelection = 0);
void setGlobalSelection(KisSelectionSP globalSelection);
/**
* Removes the global selection.
* Deselects current global selection.
* \ref globalSelection() will return 0 after that.
*/
void removeGlobalSelection();
void deselectGlobalSelection();
/**
* @return the deselected global selection or 0 if no global selection was deselected
* Reselects current deselected selection
*
* \see deselectGlobalSelection()
*/
KisSelectionSP deselectedGlobalSelection();
void reselectGlobalSelection();
/**
* Set deselected global selection
*/
void setDeselectedGlobalSelection(KisSelectionSP selection);
KisSelectionMaskSP deselectedMask();
private:
class KisImagePrivate;
......
......@@ -347,14 +347,10 @@ void KisPainter::bitBltWithFixedSelection(qint32 dstX, qint32 dstY,
quint8* srcBytes = new quint8[srcWidth * srcHeight * d->device->pixelSize()];
srcDev->readBytes(srcBytes, srcX, srcY, srcWidth, srcHeight);
/* This checks whether there is nothing selected.
When there is nothing selected, execute the IF block.
When there is something selected, execute the ELSE block.
Note that this IF-ELSE block is not redundant.
d->selection must be located first in the if statement, because
if it is null and not checked first, d->selection->isDeselected()
will call unreferenced memory and crash. */
if (!(d->selection && !d->selection->isDeselected())) {
/*
* This checks whether there is nothing selected.
*/
if (!d->selection) {
/* As there's nothing selected, blit to dstBytes (intermediary bit array),
ignoring d->selection (the user selection)*/
d->paramInfo.dstRowStart = dstBytes;
......@@ -746,7 +742,6 @@ void KisPainter::bltFixed(qint32 dstX, qint32 dstY,
d->paramInfo.rows = srcHeight;
d->paramInfo.cols = srcWidth;
// TODO: use the d->selection && !isDeselected() combo
if (d->selection) {
/* d->selection is a KisPaintDevice, so first a readBytes is performed to
get the area of interest... */
......@@ -807,8 +802,7 @@ void KisPainter::bltFixedWithFixedSelection(qint32 dstX, qint32 dstY,
quint8* dstBytes = new quint8[srcWidth * srcHeight * d->device->pixelSize()];
d->device->readBytes(dstBytes, dstX, dstY, srcWidth, srcHeight);
// Check bitBltWithFixedSelection for an explanation of this if-check
if (!(d->selection && !d->selection->isDeselected())) {
if (!d->selection) {
/* As there's nothing selected, blit to dstBytes (intermediary bit array),
ignoring d->selection (the user selection)*/
d->paramInfo.dstRowStart = dstBytes;
......
......@@ -27,8 +27,7 @@
struct KisSelection::Private {
Private()
: isDeselected(false),
isVisible(true),
: isVisible(true),
shapeSelection(0)
{
}
......@@ -36,7 +35,6 @@ struct KisSelection::Private {
// used for forwarding setDirty signals only
KisNodeWSP parentNode;
bool isDeselected; // true if the selection is empty, no pixels are selected
bool isVisible; //false is the selection decoration should not be displayed
KisDefaultBoundsBaseSP defaultBounds;
KisPixelSelectionSP projection;
......@@ -72,7 +70,6 @@ KisSelection::KisSelection(const KisSelection& rhs)
: KisShared(),
m_d(new Private)
{
m_d->isDeselected = rhs.m_d->isDeselected;
m_d->isVisible = rhs.m_d->isVisible;
m_d->defaultBounds = rhs.m_d->defaultBounds;
m_d->parentNode = 0; // not supposed to be shared
......@@ -100,7 +97,6 @@ KisSelection::KisSelection(const KisSelection& rhs)
KisSelection &KisSelection::operator=(const KisSelection &rhs)
{
if (&rhs != this) {
m_d->isDeselected = rhs.m_d->isDeselected;
m_d->isVisible = rhs.m_d->isVisible;
m_d->defaultBounds = rhs.m_d->defaultBounds;
m_d->parentNode = 0; // not supposed to be shared
......@@ -258,16 +254,6 @@ void KisSelection::updateProjection()
}
}
void KisSelection::setDeselected(bool deselected)
{
m_d->isDeselected = deselected;
}
bool KisSelection::isDeselected()
{
return m_d->isDeselected;
}
void KisSelection::setVisible(bool visible)
{
m_d->isVisible = visible;
......
......@@ -131,9 +131,6 @@ public:
void updateProjection(const QRect& rect);
void updateProjection();
void setDeselected(bool deselected);
bool isDeselected();
void setVisible(bool visible);
bool isVisible();
......
......@@ -18,11 +18,13 @@
*/
#include "kis_selection_mask.h"
#include "kis_image.h"
#include "kis_layer.h"
#include "kis_selection.h"
#include <KoColorSpaceRegistry.h>
#include <KoColorSpace.h>
#include "kis_fill_painter.h"
#include "kis_image.h"
#include <KoCompositeOp.h>
#include "kis_node_visitor.h"
#include "kis_processing_visitor.h"
......@@ -33,16 +35,22 @@ struct KisSelectionMask::Private
{
public:
KisImageWSP image;
KisSelectionSP deselectedSelection;
};
KisSelectionMask::KisSelectionMask(KisImageWSP image)
: KisMask("selection")
, m_d(new Private())
{
setActive(true);
setActive(false);
m_d->image = image;
m_d->deselectedSelection = 0;
}
KisSelectionMask::KisSelectionMask(const KisSelectionMask& rhs)
: KisMask(rhs)
, m_d(new Private())
{
setActive(false);
m_d->image = rhs.image();
}
KisSelectionMask::~KisSelectionMask()
......@@ -57,13 +65,6 @@ bool KisSelectionMask::allowAsChild(KisNodeSP node) const
}
KisSelectionMask::KisSelectionMask(const KisSelectionMask& rhs)
: KisMask(rhs)
, m_d(new Private())
{
m_d->image=rhs.image();
}
void KisSelectionMask::setSelection(KisSelectionSP selection)
{
if (selection) {
......@@ -94,16 +95,6 @@ void KisSelectionMask::accept(KisProcessingVisitor &visitor, KisUndoAdapter *und
return visitor.visit(this, undoAdapter);
}
KisSelectionSP KisSelectionMask::deselectedSelection()
{
return m_d->deselectedSelection;
}
void KisSelectionMask::setDeselectedSelection(KisSelectionSP selection)
{
m_d->deselectedSelection = selection;
}
KoDocumentSectionModel::PropertyList KisSelectionMask::sectionModelProperties() const
{
KoDocumentSectionModel::PropertyList l = KisBaseNode::sectionModelProperties();
......@@ -134,7 +125,21 @@ bool KisSelectionMask::active() const
void KisSelectionMask::setActive(bool active)
{
//the change needs to be done by the manager to deactivate current active selectionMask
emit changeActivity(this,active);
KisImageWSP image = this->image();
KisLayerSP parentLayer = dynamic_cast<KisLayer*>(parent().data());
if(active && parentLayer) {
KisSelectionMaskSP activeMask = parentLayer->selectionMask();
if(activeMask) {
activeMask->setActive(false);
}
}
nodeProperties().setProperty("active", active);
if(image) {
image->nodeChanged(this);
image->undoAdapter()->emitSelectionChanged();
}
}
......@@ -62,16 +62,6 @@ public:
bool accept(KisNodeVisitor &v);
void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter);
/**
* @return the deselected selection or 0 if no selection was deselected
*/
KisSelectionSP deselectedSelection();
/**
* Set deselected selection
*/
void setDeselectedSelection(KisSelectionSP selection);
virtual KoDocumentSectionModel::PropertyList sectionModelProperties() const;
virtual void setSectionModelProperties(const KoDocumentSectionModel::PropertyList &properties);
......@@ -79,9 +69,6 @@ public:
bool active() const;
void setActive(bool active);
signals:
void changeActivity(KisSelectionMask* mask, bool active);
private:
KisImageWSP image() const;
......
......@@ -28,12 +28,7 @@ KisSelectionTransactionData::KisSelectionTransactionData(const QString& name, Ki
KisTransactionData(name, selection->getOrCreatePixelSelection().data(), parent)
, m_image(image)
, m_selection(selection)
, m_wasDeselected(selection->isDeselected())
{
if (m_selection->isDeselected()) {
m_selection->getOrCreatePixelSelection()->clear();
m_selection->setDeselected(false);
}
}
KisSelectionTransactionData::~KisSelectionTransactionData()
......@@ -45,6 +40,7 @@ void KisSelectionTransactionData::redo()
KisTransactionData::redo();
m_selection->setDirty(m_image->bounds());
m_selection->updateProjection();
m_image->undoAdapter()->emitSelectionChanged();
}
......@@ -53,6 +49,6 @@ void KisSelectionTransactionData::undo()
KisTransactionData::undo();
m_selection->setDirty(m_image->bounds());
m_selection->updateProjection();
m_selection->setDeselected(m_wasDeselected);
m_image->undoAdapter()->emitSelectionChanged();
}
......@@ -46,7 +46,6 @@ public:
private:
KisImageWSP m_image;
KisSelectionSP m_selection;
bool m_wasDeselected;
};
#endif /* KIS_SELECTION_TRANSACTION_DATA_H_ */
......@@ -93,11 +93,9 @@ void KisRecordedFilterAction::play(KisNodeSP node, const KisPlayInfo& _info, KoU
KisImageWSP image = _info.image();
r1 = r1.intersected(image->bounds());
if (layer && layer->selectionMask()) {
r1 = r1.intersected(layer->selectionMask()->exactBounds());
if (layer && layer->selection()) {
r1 = r1.intersected(layer->selection()->selectedExactRect());
}
if (image->globalSelection())
r1 = r1.intersected(image->globalSelection()->selectedExactRect());
d->filter->process(dev, r1, kfc, _updater);
node->setDirty(r1);
......
......@@ -87,5 +87,55 @@ void KisImageTest::testConvertImageColorSpace()
image->refreshGraph();
}
void KisImageTest::testGlobalSelection()
{
const KoColorSpace *cs8 = KoColorSpaceRegistry::instance()->rgb8();
KisImageSP image = new KisImage(0, 1000, 1000, cs8, "stest");
QCOMPARE(image->globalSelection(), KisSelectionSP(0));
QCOMPARE(image->canReselectGlobalSelection(), false);
QCOMPARE(image->root()->childCount(), 0U);
KisSelectionSP selection1 = new KisSelection(new KisDefaultBounds(image));
KisSelectionSP selection2 = new KisSelection(new KisDefaultBounds(image));
image->setGlobalSelection(selection1);
QCOMPARE(image->globalSelection(), selection1);
QCOMPARE(image->canReselectGlobalSelection(), false);
QCOMPARE(image->root()->childCount(), 1U);
image->setGlobalSelection(selection2);
QCOMPARE(image->globalSelection(), selection2);
QCOMPARE(image->canReselectGlobalSelection(), false);
QCOMPARE(image->root()->childCount(), 1U);
image->deselectGlobalSelection();
QCOMPARE(image->globalSelection(), KisSelectionSP(0));
QCOMPARE(image->canReselectGlobalSelection(), true);
QCOMPARE(image->root()->childCount(), 1U);
image->reselectGlobalSelection();
QCOMPARE(image->globalSelection(), selection2);
QCOMPARE(image->canReselectGlobalSelection(), false);
QCOMPARE(image->root()->childCount(), 1U);
// mixed deselecting/setting/reselecting
image->deselectGlobalSelection();
QCOMPARE(image->globalSelection(), KisSelectionSP(0));
QCOMPARE(image->canReselectGlobalSelection(), true);
QCOMPARE(image->root()->childCount(), 1U);
image->setGlobalSelection(selection1);
QCOMPARE(image->globalSelection(), selection1);
QCOMPARE(image->canReselectGlobalSelection(), true);
QCOMPARE(image->root()->childCount(), 2U);
image->reselectGlobalSelection();