Undo system for painterly paintops.
Emanuele Tamponi
emanuele at valinor.it
Wed Aug 22 23:17:09 CEST 2007
Hello,
I attached the patches that implements the method I thought for undoing
painterly actions.
Explanation of the problem:
Actually, KisTransaction handles only one paint device at once.
Painterly paintops change two paint devices at a time: the paint device
onto they're putting colors, and the painterly overlay where they write
painterly data.
Possible solution:
QUndoCommand provides
virtual bool mergeWith(const QUndoCommand *command)
that is used "to merge two commands with the same ids".
The default implementation returns false, so I implemented my own, this
way: I changed the d-pointer so that it contains
QMap<KisPaintDeviceSP, KisMementoSP> data
I used a standard way of iteration through the values of the map by
using foreach for undo(), redo() and undoNoUpdate().
Then mergeWith just calls
data.unite(transaction->m_private->data);
This works as expected only when the paint device of the command
parameter is different from the device of the destination
KisTransaction. And it's what we want because we don't want to merge two
KisTransactions for the same paint devices.
Anyway, it would not do anything evil when called with a command on the
same paint device, since in this case this unite is just ignored.
Next, I added a setSourceLayer(KisLayer *) method to KisPainter. It
dynamic_cast's it to a KisPaintLayer to have the "source layer" which
will be used by the paintop to get the "source paint device" which it
reads the color from, and the "source painterly overlay", that's similar
use.
Once setSourceLayer is called from the Tool. The KisPainter will decide
what to do with transactions. In particular:
- If a non-painterly paintop (that's: all paintops except mine for now)
is set (with setPaintOp), it will do nothing more than what it has
always done.
- If a painterly paintop is set, it has these possibilities:
- If m_transaction != 0, then it infers that the developer want to
keep track of the overlay too, and initializes m_overlayTransaction by
calling beginOverlayTransaction
- If m_transaction == 0, don't do a m_overlayTransaction.
Then the paintop, painterly or non-painterly, will do all his things
until KisPainter::endTransaction() is called. Then, again:
- If a non-painterly paintop is used, do nothing more.
- If a painterly paintop is used, and the transaction is not zero, then
merge the overlay transaction with the device transaction and return the
merged command
Advantages:
I liked this solution because it is completely transparent to the final
developer, who has not to bother with all this things.
For example:
(1)
Emanuele wants to implement a tool and want his tool to be usable with
painterly paintops.
To let his tool be used from painterly paintops, he has to:
- do *nothing* if his tool inherits from KisToolFreehand and uses its
initPaint and endPaint functions
- add m_painter->setSourceLayer(sourceLayer) before calling setPaintOp
- write the same old code to undo commands, without any extra call.
(2)
Bart wants to implement his own painterly paintop.
- It has just to do this check:
if (!painter()->sourceLayer())
return;
(because the paintop can't be used if there is no data to take from to
do bidirectionality...)
This means that almost all tools are painterly-paintops-ready, by just
adding only one line of code, and that all painterly paintops can check
if the current tool supports them by adding 2 lines of code.
If you agree, I can commit.
Bye,
Emanuele
-------------- next part --------------
Index: kis_painter.cc
===================================================================
--- kis_painter.cc (revisione 702430)
+++ kis_painter.cc (copia locale)
@@ -46,6 +46,7 @@
#include "kis_layer.h"
#include "kis_paint_device.h"
#include "kis_painter.h"
+#include "kis_painterly_overlay.h"
#include "KoColorSpace.h"
#include "kis_transaction.h"
#include "kis_types.h"
@@ -77,6 +78,7 @@
void KisPainter::init()
{
m_transaction = 0;
+ m_overlayTransaction = 0;
m_paintOp = 0;
m_brush = 0;
m_pattern= 0;
@@ -96,9 +98,9 @@
KisPainter::~KisPainter()
{
+ end();
m_brush = 0;
delete m_paintOp;
- end();
}
void KisPainter::begin(KisPaintDeviceSP device)
@@ -109,6 +111,8 @@
if (m_transaction)
delete m_transaction;
+ if (m_overlayTransaction)
+ delete m_overlayTransaction;
m_device = device;
m_colorSpace = device->colorSpace();
@@ -121,6 +125,30 @@
return endTransaction();
}
+void KisPainter::setSourceLayer(KisLayer *source)
+{
+ if (!source)
+ return;
+ m_sourceLayer = dynamic_cast<KisPaintLayer *>(source);
+
+ if (!m_sourceLayer)
+ return;
+
+ if (!m_sourceLayer->painterlyOverlay())
+ m_sourceLayer->createPainterlyOverlay();
+}
+
+void KisPainter::beginOverlayTransaction()
+{
+ Q_ASSERT(m_sourceLayer);
+ Q_ASSERT(m_sourceLayer->painterlyOverlay());
+
+ if (m_overlayTransaction)
+ delete m_overlayTransaction;
+ m_overlayTransaction = new KisTransaction("Overlay Auto Command", m_sourceLayer->painterlyOverlay());
+ Q_CHECK_PTR(m_overlayTransaction);
+}
+
void KisPainter::beginTransaction(const QString& customName)
{
if (m_transaction)
@@ -136,11 +164,19 @@
m_transaction = command;
}
-
QUndoCommand *KisPainter::endTransaction()
{
QUndoCommand *command = m_transaction;
+ if (m_transaction && m_overlayTransaction) {
+ if (paintOp() && paintOp()->painterly()) {
+ bool result = command->mergeWith(m_overlayTransaction);
+ Q_ASSERT(result);
+
+ delete m_overlayTransaction;
+ }
+ }
m_transaction = 0;
+ m_overlayTransaction = 0;
return command;
}
@@ -177,6 +213,12 @@
{
delete m_paintOp;
m_paintOp = paintOp;
+ if (m_paintOp->painterly() && m_transaction) {
+ if (m_sourceLayer && m_sourceLayer->painterlyOverlay())
+ beginOverlayTransaction();
+ else
+ kDebug() << "You didn't set the source layer - expect problems!";
+ }
}
void KisPainter::bitBlt(qint32 dx, qint32 dy,
-------------- next part --------------
Index: kis_painter.h
===================================================================
--- kis_painter.h (revisione 702430)
+++ kis_painter.h (copia locale)
@@ -71,6 +71,8 @@
// Implement KisProgressSubject
virtual void cancel() { m_cancelRequested = true; }
+ void beginOverlayTransaction();
+
public:
/**
* Start painting on the specified device. Not undoable.
@@ -504,7 +506,7 @@
// Returns the current fill color
KoColor fillColor() const { return m_fillColor; }
- void setSourceLayer(KisPaintLayer *source) { m_sourceLayer = source; }
+ void setSourceLayer(KisLayer *source);
KisPaintLayer *sourceLayer() { return m_sourceLayer; }
void setComplexColor(KisComplexColor *color) { m_complexColor = color; }
KisComplexColor *complexColor() { return m_complexColor; }
@@ -604,7 +606,8 @@
protected:
KisPaintDeviceSP m_device;
- KisTransaction *m_transaction;
+ KisTransaction *m_transaction;
+ KisTransaction *m_overlayTransaction;
QRegion m_dirtyRegion;
QRect m_dirtyRect;
-------------- next part --------------
Index: kis_transaction.cc
===================================================================
--- kis_transaction.cc (revisione 702349)
+++ kis_transaction.cc (copia locale)
@@ -15,6 +15,9 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
+#include <QMap>
+
#include "kis_transaction.h"
#include "kis_types.h"
@@ -26,8 +29,7 @@
class KisTransaction::Private {
public:
QString name;
- KisPaintDeviceSP device;
- KisMementoSP memento;
+ QMap<KisPaintDeviceSP, KisMementoSP> data;
bool firstRedo;
};
@@ -35,17 +37,16 @@
: QUndoCommand(name, parent)
, m_private( new Private() )
{
- m_private->device = device;
- m_private->memento = device->dataManager()->getMemento();
+ m_private->data.insert(device, device->dataManager()->getMemento());
m_private->firstRedo = true;
}
KisTransaction::~KisTransaction()
{
- if (m_private->memento) {
- // For debugging purposes
- m_private->memento->setInvalid();
- }
+ KisMementoSP memento;
+ foreach (memento, m_private->data)
+ if (memento)
+ memento->setInvalid();
delete m_private;
}
@@ -57,34 +58,51 @@
m_private->firstRedo = false;
return;
}
- Q_ASSERT(!m_private->memento.isNull());
- m_private->device->dataManager()->rollforward(m_private->memento);
-
- QRect rc;
- qint32 x, y, width, height;
- m_private->memento->extent(x,y,width,height);
- rc.setRect(x + m_private->device->x(), y + m_private->device->y(), width, height);
-
- m_private->device->setDirty( rc );
+ KisMementoSP memento;
+ KisPaintDeviceSP device;
+ foreach (memento, m_private->data) {
+ device = m_private->data.key(memento);
+ device->dataManager()->rollforward(memento);
+ QRect rc;
+ qint32 x, y, width, height;
+ memento->extent(x, y, width, height);
+ rc.setRect(x + device->x(), y + device->y(), width, height);
+ device->setDirty( rc );
+ }
}
void KisTransaction::undo()
{
- Q_ASSERT(!m_private->memento.isNull());
- m_private->device->dataManager()->rollback(m_private->memento);
+ KisMementoSP memento;
+ KisPaintDeviceSP device;
+ foreach (memento, m_private->data) {
+ device = m_private->data.key(memento);
+ device->dataManager()->rollback(memento);
+ QRect rc;
+ qint32 x, y, width, height;
+ memento->extent(x, y, width, height);
+ rc.setRect(x + device->x(), y + device->y(), width, height);
+ device->setDirty( rc );
+ }
+}
- QRect rc;
- qint32 x, y, width, height;
- m_private->memento->extent(x,y,width,height);
- rc.setRect(x + m_private->device->x(), y + m_private->device->y(), width, height);
+bool KisTransaction::mergeWith(const QUndoCommand *command)
+{
+ const KisTransaction *transaction = dynamic_cast<const KisTransaction *>(command);
+ if (!transaction)
+ return false;
- m_private->device->setDirty( rc );
+ m_private->data.unite(transaction->m_private->data);
+ return true;
}
void KisTransaction::undoNoUpdate()
{
- Q_ASSERT(!m_private->memento.isNull());
-
- m_private->device->dataManager()->rollback(m_private->memento);
+ KisMementoSP memento;
+ KisPaintDeviceSP device;
+ foreach (memento, m_private->data) {
+ device = m_private->data.key(memento);
+ device->dataManager()->rollback(memento);
+ }
}
-------------- next part --------------
Index: kis_transaction.h
===================================================================
--- kis_transaction.h (revisione 702349)
+++ kis_transaction.h (copia locale)
@@ -34,6 +34,7 @@
virtual void redo();
virtual void undo();
virtual void undoNoUpdate();
+ virtual bool mergeWith(const QUndoCommand *command);
private:
class Private;
Private * const m_private;
-------------- next part --------------
Index: kis_tool_freehand.cc
===================================================================
--- kis_tool_freehand.cc (revisione 702431)
+++ kis_tool_freehand.cc (copia locale)
@@ -238,14 +238,14 @@
m_painter = new KisPainter( m_target );
Q_CHECK_PTR(m_painter);
m_source = device;
+
m_painter->beginTransaction(m_transactionText);
m_painter->setPaintColor(currentFgColor());
m_painter->setBackgroundColor(currentBgColor());
m_painter->setBrush(currentBrush());
- if (dynamic_cast<KisPaintLayer *>(currentLayer().data()))
- m_painter->setSourceLayer(dynamic_cast<KisPaintLayer *>(currentLayer().data()));
m_painter->setComplexColor(currentComplexColor());
+ m_painter->setSourceLayer(currentLayer().data());
// if you're drawing on a temporary layer, the layer already sets this
if (m_paintIncremental) {
@@ -254,8 +254,8 @@
} else {
m_painter->setCompositeOp(device->colorSpace()->compositeOp(COMPOSITE_ALPHA_DARKEN));
m_painter->setOpacity( OPACITY_OPAQUE );
+ }
- }
m_previousTangent = QPointF(0,0);
m_previousDrag = QPointF(0,0);
/* kDebug(41007) <<"target:" << m_target <<"(" << m_target->name() <<" )"
More information about the kimageshop
mailing list