Lock document in Tx() ctor (part of #2430)

This already fixes a lot of possible problems that can happen when a
script is running and modifying some part of a sprite that is being
backed up in a background thread.

We still need some work to being able to lock a sprite two or more
times in the same thread to write it. E.g. an app.transaction() should
lock the sprite for write access, but the script transaction function
could call a command, and that command could use a ContextWriter to
lock the sprite again. At the moment this is not possible because we
need a re-entrant RWLock implementation.
This commit is contained in:
David Capello 2023-12-12 21:34:30 -03:00
parent e87fdbb3af
commit 8722c8ec16
79 changed files with 279 additions and 212 deletions

View File

@ -130,7 +130,7 @@ void AddColorCommand::onExecute(Context* ctx)
if (document) {
frame_t frame = writer.frame();
Tx tx(writer.context(), friendlyName(), ModifyDocument);
Tx tx(writer, friendlyName(), ModifyDocument);
tx(new cmd::SetPalette(sprite, frame, newPalette.get()));
tx.commit();
}

View File

@ -55,7 +55,7 @@ void BackgroundFromLayerCommand::onExecute(Context* context)
Doc* document(writer.document());
{
Tx tx(writer.context(), friendlyName());
Tx tx(writer, friendlyName());
tx(new cmd::BackgroundFromLayer(static_cast<LayerImage*>(writer.layer())));
tx.commit();
}

View File

@ -385,7 +385,7 @@ void CanvasSizeCommand::onExecute(Context* context)
ContextWriter writer(reader);
Doc* doc = writer.document();
Sprite* sprite = writer.sprite();
Tx tx(writer.context(), "Canvas Size");
Tx tx(writer, "Canvas Size");
DocApi api = doc->getApi(tx);
api.cropSprite(sprite, bounds, params.trimOutside());
tx.commit();

View File

@ -73,7 +73,7 @@ void CelOpacityCommand::onExecute(Context* context)
return;
{
Tx tx(writer.context(), "Set Cel Opacity");
Tx tx(writer, "Set Cel Opacity");
// TODO the range of selected cels should be in app::Site.
DocRange range;

View File

@ -218,7 +218,7 @@ private:
newUserData != m_cel->data()->userData()))) {
try {
ContextWriter writer(UIContext::instance());
Tx tx(writer.context(), "Set Cel Properties");
Tx tx(writer, "Set Cel Properties");
DocRange range;
if (m_range.enabled()) {

View File

@ -48,7 +48,7 @@ void ClearCelCommand::onExecute(Context* context)
Doc* document(writer.document());
bool nonEditableLayers = false;
{
Tx tx(writer.context(), "Clear Cel");
Tx tx(writer, "Clear Cel");
const Site* site = writer.site();
if (site->inTimeline() &&

View File

@ -72,7 +72,7 @@ void CropSpriteCommand::onExecute(Context* context)
bounds = m_bounds;
{
Tx tx(writer.context(), "Sprite Crop");
Tx tx(writer, "Sprite Crop");
document->getApi(tx).cropSprite(sprite, bounds);
tx.commit();
}
@ -119,7 +119,7 @@ void AutocropSpriteCommand::onExecute(Context* context)
Doc* document(writer.document());
Sprite* sprite(writer.sprite());
{
Tx tx(writer.context(), onGetFriendlyName());
Tx tx(writer, onGetFriendlyName());
document->getApi(tx).trimSprite(sprite, m_byGrid);
tx.commit();
}

View File

@ -44,7 +44,7 @@ void DeselectMaskCommand::onExecute(Context* context)
ContextWriter writer(context);
Doc* document(writer.document());
{
Tx tx(writer.context(), "Deselect", DoesntModifyDocument);
Tx tx(writer, "Deselect", DoesntModifyDocument);
tx(new cmd::DeselectMask(document));
tx.commit();
}

View File

@ -49,7 +49,7 @@ void DuplicateLayerCommand::onExecute(Context* context)
Doc* document = writer.document();
{
Tx tx(writer.context(), "Layer Duplication");
Tx tx(writer, "Layer Duplication");
LayerImage* sourceLayer = static_cast<LayerImage*>(writer.layer());
DocApi api = document->getApi(tx);
api.duplicateLayerAfter(sourceLayer,

View File

@ -81,7 +81,7 @@ void FillCommand::onExecute(Context* ctx)
color = color_utils::color_for_layer(pref.colorBar.fgColor(), layer);
{
Tx tx(writer.context(), "Fill Selection with Foreground Color");
Tx tx(writer, "Fill Selection with Foreground Color");
{
ExpandCelCanvas expand(
site, layer,

View File

@ -57,7 +57,7 @@ void FlattenLayersCommand::onExecute(Context* context)
ContextWriter writer(context);
Sprite* sprite = writer.sprite();
{
Tx tx(writer.context(), "Flatten Layers");
Tx tx(writer, "Flatten Layers");
// TODO the range of selected layers should be in app::Site.
DocRange range;

View File

@ -120,7 +120,7 @@ void FlipCommand::onExecute(Context* ctx)
ContextWriter writer(ctx);
Doc* document = writer.document();
Sprite* sprite = writer.sprite();
Tx tx(ctx, friendlyName());
Tx tx(writer, friendlyName());
DocApi api = document->getApi(tx);
Mask* mask = document->mask();

View File

@ -127,7 +127,7 @@ void FramePropertiesCommand::onExecute(Context* context)
int newMsecs = window.frlen()->textInt();
ContextWriter writer(reader);
Tx tx(writer.context(), "Frame Duration");
Tx tx(writer, "Frame Duration");
DocApi api = writer.document()->getApi(tx);
for (frame_t frame : selFrames)

View File

@ -91,7 +91,7 @@ void FrameTagPropertiesCommand::onExecute(Context* context)
return;
ContextWriter writer(reader);
Tx tx(writer.context(), friendlyName());
Tx tx(writer, friendlyName());
Tag* tag = const_cast<Tag*>(foundTag);
std::string name = window.nameValue();

View File

@ -73,7 +73,7 @@ protected:
const Mask* mask = doc->mask();
gfx::Rect newGrid = mask->bounds();
Tx tx(writer.context(), friendlyName(), ModifyDocument);
Tx tx(writer, friendlyName(), ModifyDocument);
tx(new cmd::SetGridBounds(writer.sprite(), newGrid));
tx.commit();
@ -125,7 +125,7 @@ void GridSettingsCommand::onExecute(Context* context)
bounds.h = std::max(bounds.h, 1);
ContextWriter writer(context);
Tx tx(context, friendlyName(), ModifyDocument);
Tx tx(writer, friendlyName(), ModifyDocument);
tx(new cmd::SetGridBounds(site.sprite(), bounds));
tx.commit();

View File

@ -512,8 +512,9 @@ void ImportSpriteSheetCommand::onExecute(Context* context)
// The following steps modify the sprite, so we wrap all
// operations in a undo-transaction.
ContextWriter writer(context);
Tx tx(
writer.context(), Strings::import_sprite_sheet_title(), ModifyDocument);
Tx tx(writer,
Strings::import_sprite_sheet_title(),
ModifyDocument);
DocApi api = document->getApi(tx);
// Add the layer in the sprite.

View File

@ -93,7 +93,7 @@ void InvertMaskCommand::onExecute(Context* context)
mask->intersect(sprite->bounds());
// Set the new mask
Tx tx(writer.context(), "Mask Invert", DoesntModifyDocument);
Tx tx(writer, "Mask Invert", DoesntModifyDocument);
tx(new cmd::SetMask(document, mask.get()));
tx.commit();

View File

@ -51,7 +51,7 @@ void LayerFromBackgroundCommand::onExecute(Context* context)
ContextWriter writer(context);
Doc* document(writer.document());
{
Tx tx(writer.context(), friendlyName());
Tx tx(writer, friendlyName());
tx(new cmd::LayerFromBackground(writer.layer()));
tx.commit();
}

View File

@ -69,7 +69,7 @@ void LayerOpacityCommand::onExecute(Context* context)
return;
{
Tx tx(writer.context(), "Set Layer Opacity");
Tx tx(writer, "Set Layer Opacity");
// TODO the range of selected frames should be in app::Site.
SelectedLayers selLayers;

View File

@ -265,7 +265,7 @@ private:
newBlendMode != static_cast<LayerImage*>(m_layer)->blendMode()))))) {
try {
ContextWriter writer(UIContext::instance());
Tx tx(writer.context(), "Set Layer Properties");
Tx tx(writer, "Set Layer Properties");
DocRange range;
if (m_range.enabled())
@ -395,7 +395,7 @@ private:
tileset->matchFlags() != tilesetInfo.matchFlags ||
tilesetInfo.tsi != tilemap->tilesetIndex()) {
ContextWriter writer(UIContext::instance());
Tx tx(writer.context(), "Set Tileset Properties");
Tx tx(writer, "Set Tileset Properties");
// User changed tilemap's tileset
if (tilesetInfo.tsi != tilemap->tilesetIndex()) {
tileset = tilemap->sprite()->tilesets()->get(tilesetInfo.tsi);

View File

@ -57,7 +57,7 @@ void LinkCelsCommand::onExecute(Context* context)
if (!site.inTimeline())
return;
Tx tx(writer.context(), friendlyName());
Tx tx(writer, friendlyName());
for (Layer* layer : site.selectedLayers()) {
if (!layer->isImage())

View File

@ -77,7 +77,7 @@ void LoadMaskCommand::onExecute(Context* context)
{
ContextWriter writer(reader);
Doc* document = writer.document();
Tx tx(writer.context(),
Tx tx(writer,
Strings::load_selection_title(),
DoesntModifyDocument);
tx(new cmd::SetMask(document, mask.get()));

View File

@ -49,7 +49,7 @@ void MaskAllCommand::onExecute(Context* context)
Mask newMask;
newMask.replace(sprite->bounds());
Tx tx(writer.context(), "Select All", DoesntModifyDocument);
Tx tx(writer, "Select All", DoesntModifyDocument);
tx(new cmd::SetMask(document, &newMask));
document->resetTransformation();
tx.commit();

View File

@ -187,7 +187,7 @@ void MaskByColorCommand::onExecute(Context* context)
Doc* document(writer.document());
if (apply) {
Tx tx(writer.context(), "Mask by Color", DoesntModifyDocument);
Tx tx(writer, "Mask by Color", DoesntModifyDocument);
std::unique_ptr<Mask> mask(generateMask(*document->mask(),
sprite, image, xpos, ypos,
m_selMode->selectionMode()));

View File

@ -81,7 +81,7 @@ void MaskContentCommand::onExecute(Context* context)
newMask.replace(cel->bounds());
}
Tx tx(writer.context(), "Select Content", DoesntModifyDocument);
Tx tx(writer, "Select Content", DoesntModifyDocument);
tx(new cmd::SetMask(document, &newMask));
document->resetTransformation();
tx.commit();

View File

@ -79,7 +79,7 @@ void MergeDownLayerCommand::onExecute(Context* context)
LayerImage* src_layer = static_cast<LayerImage*>(writer.layer());
Layer* dst_layer = src_layer->getPrevious();
Tx tx(writer.context(), friendlyName(), ModifyDocument);
Tx tx(writer, friendlyName(), ModifyDocument);
for (frame_t frpos = 0; frpos<sprite->totalFrames(); ++frpos) {
// Get frames

View File

@ -138,7 +138,7 @@ void ModifySelectionCommand::onExecute(Context* context)
}
// Set the new mask
Tx tx(writer.context(),
Tx tx(writer,
friendlyName(),
DoesntModifyDocument);
tx(new cmd::SetMask(document, mask.get()));

View File

@ -87,7 +87,7 @@ void MoveMaskCommand::onExecute(Context* context)
ContextWriter writer(context);
Doc* document(writer.document());
{
Tx tx(writer.context(), "Move Selection", DoesntModifyDocument);
Tx tx(writer, "Move Selection", DoesntModifyDocument);
gfx::Point pt = document->mask()->bounds().origin();
document->getApi(tx).setMaskPosition(pt.x+delta.x, pt.y+delta.y);
tx.commit();
@ -102,7 +102,7 @@ void MoveMaskCommand::onExecute(Context* context)
ContextWriter writer(context);
if (writer.cel()) {
// Rotate content
Tx tx(writer.context(), "Shift Pixels");
Tx tx(writer, "Shift Pixels");
tx(new cmd::ShiftMaskedCel(writer.cel(), delta.x, delta.y));
tx.commit();
}

View File

@ -117,7 +117,7 @@ void NewBrushCommand::onQuickboxEnd(Editor* editor, const gfx::Rect& rect, ui::M
if (writer.cel()) {
gfx::Rect canvasRect = (rect & writer.cel()->bounds());
if (!canvasRect.isEmpty()) {
Tx tx(writer.context(), "Clear");
Tx tx(writer, "Clear");
tx(new cmd::ClearRect(writer.cel(), canvasRect));
tx.commit();
}

View File

@ -102,7 +102,7 @@ void NewFrameCommand::onExecute(Context* context)
#endif
{
Tx tx(writer.context(), friendlyName());
Tx tx(writer, friendlyName());
DocApi api = document->getApi(tx);
switch (m_content) {

View File

@ -74,7 +74,7 @@ void NewFrameTagCommand::onExecute(Context* context)
{
ContextWriter writer(reader);
Tx tx(writer.context(), friendlyName());
Tx tx(writer, friendlyName());
tx(new cmd::AddTag(writer.sprite(), tag.get()));
tag.release();
tx.commit();

View File

@ -274,9 +274,8 @@ void NewLayerCommand::onExecute(Context* context)
Layer* layer = nullptr;
{
Tx tx(
writer.context(),
fmt::format(Strings::commands_NewLayer(), layerPrefix()));
Tx tx(writer,
fmt::format(Strings::commands_NewLayer(), layerPrefix()));
DocApi api = document->getApi(tx);
bool afterBackground = false;
@ -428,7 +427,7 @@ void NewLayerCommand::onExecute(Context* context)
#ifdef ENABLE_UI
// Paste new layer from clipboard
else if (params().fromClipboard() && layer->isImage()) {
context->clipboard()->paste(context, false);
context->clipboard()->paste(writer, false);
if (layer->isReference()) {
if (Cel* cel = layer->cel(site.frame())) {

View File

@ -782,7 +782,7 @@ public:
m_context->activeDocument()->sprite() &&
m_context->activeDocument()->sprite()->gridBounds() != gridBounds()) {
ContextWriter writer(m_context);
Tx tx(m_context, Strings::commands_GridSettings(), ModifyDocument);
Tx tx(writer, Strings::commands_GridSettings(), ModifyDocument);
tx(new cmd::SetGridBounds(writer.sprite(), gridBounds()));
tx.commit();
}

View File

@ -78,7 +78,7 @@ void PaletteSizeCommand::onExecute(Context* context)
palette.resize(std::clamp(ncolors, 1, std::numeric_limits<int>::max()));
ContextWriter writer(reader);
Tx tx(context, "Palette Size", ModifyDocument);
Tx tx(writer, "Palette Size", ModifyDocument);
tx(new cmd::SetPalette(writer.sprite(), frame, &palette));
tx.commit();
}

View File

@ -53,7 +53,7 @@ void RemoveFrameCommand::onExecute(Context* context)
Doc* document(writer.document());
Sprite* sprite(writer.sprite());
{
Tx tx(writer.context(), "Remove Frame");
Tx tx(writer, "Remove Frame");
DocApi api = document->getApi(tx);
const Site* site = writer.site();
if (site->inTimeline() &&

View File

@ -77,7 +77,7 @@ void RemoveFrameTagCommand::onExecute(Context* context)
if (!foundTag)
return;
Tx tx(writer.context(), friendlyName());
Tx tx(writer, friendlyName());
tx(new cmd::RemoveTag(sprite, foundTag));
tx.commit();

View File

@ -139,7 +139,7 @@ void RemoveLayerCommand::onExecute(Context* context)
Doc* document(writer.document());
Sprite* sprite(writer.sprite());
{
Tx tx(writer.context(), "Remove Layer");
Tx tx(writer, "Remove Layer");
DocApi api = document->getApi(tx);
// We need to remove all the tilesets after the tilemaps are deleted
// and in descending tileset index order, otherwise the tileset indexes

View File

@ -101,7 +101,7 @@ void RemoveSliceCommand::onExecute(Context* context)
ContextWriter writer(reader);
Doc* document(writer.document());
Sprite* sprite(writer.sprite());
Tx tx(writer.context(), "Remove Slice");
Tx tx(writer, "Remove Slice");
for (auto slice : slicesToDelete.iterateAs<Slice>()) {
ASSERT(slice);

View File

@ -53,7 +53,7 @@ void ReselectMaskCommand::onExecute(Context* context)
ContextWriter writer(context);
Doc* document(writer.document());
{
Tx tx(writer.context(), "Reselect", DoesntModifyDocument);
Tx tx(writer, "Reselect", DoesntModifyDocument);
tx(new cmd::ReselectMask(document));
tx.commit();
}

View File

@ -101,7 +101,7 @@ void SelectTileCommand::onExecute(Context* ctx)
}
// Set the new mask
Tx tx(writer.context(),
Tx tx(writer,
friendlyName(),
DoesntModifyDocument);
tx(new cmd::SetMask(doc, mask.get()));

View File

@ -107,14 +107,14 @@ void SetLoopSectionCommand::onExecute(Context* ctx)
loopTag = create_loop_tag(begin, end);
ContextWriter writer(ctx);
Tx tx(writer.context(), "Add Loop");
Tx tx(writer, "Add Loop");
tx(new cmd::AddTag(sprite, loopTag));
tx.commit();
}
else if (loopTag->fromFrame() != begin ||
loopTag->toFrame() != end) {
ContextWriter writer(ctx);
Tx tx(writer.context(), "Set Loop Range");
Tx tx(writer, "Set Loop Range");
tx(new cmd::SetTagRange(loopTag, begin, end));
tx.commit();
}
@ -126,7 +126,7 @@ void SetLoopSectionCommand::onExecute(Context* ctx)
else {
if (loopTag) {
ContextWriter writer(ctx);
Tx tx(writer.context(), "Remove Loop");
Tx tx(writer, "Remove Loop");
tx(new cmd::RemoveTag(sprite, loopTag));
tx.commit();
}

View File

@ -37,7 +37,7 @@ void SetPaletteCommand::onExecute(Context* context)
ContextWriter writer(context);
if (writer.document()) {
Tx tx(writer.context(), "Set Palette");
Tx tx(writer, "Set Palette");
writer.document()->getApi(tx)
.setPalette(writer.sprite(), writer.frame(), m_palette);
tx.commit();

View File

@ -91,7 +91,7 @@ void SlicePropertiesCommand::onExecute(Context* context)
{
const SliceWindow::Mods mods = window.modifiedFields();
ContextWriter writer(reader);
Tx tx(writer.context(), "Slice Properties");
Tx tx(writer, "Slice Properties");
for (Slice* slice : slices.iterateAs<Slice>()) {
// Change name

View File

@ -96,10 +96,11 @@ public:
private:
void onDuplicate(const doc::Tileset* tileset)
{
auto sprite = tileset->sprite();
auto tilesetClone = Tileset::MakeCopyCopyingImages(tileset);
Tx tx(fmt::format(Strings::commands_TilesetDuplicate()));
tx(new cmd::AddTileset(tileset->sprite(), tilesetClone));
Tx tx(sprite, fmt::format(Strings::commands_TilesetDuplicate()));
tx(new cmd::AddTileset(sprite, tilesetClone));
tx.commit();
TilesetDuplicated(tilesetClone);
@ -121,8 +122,9 @@ private:
return;
}
Tx tx(fmt::format(Strings::commands_TilesetDelete()));
tx(new cmd::RemoveTileset(tileset->sprite(), tsi));
auto sprite = tileset->sprite();
Tx tx(sprite, fmt::format(Strings::commands_TilesetDelete()));
tx(new cmd::RemoveTileset(sprite, tsi));
tx.commit();
TilesetDeleted(this);
@ -351,7 +353,7 @@ void SpritePropertiesCommand::onExecute(Context* context)
ContextWriter writer(context);
Sprite* sprite(writer.sprite());
Tx tx(writer.context(), Strings::sprite_properties_assign_color_profile());
Tx tx(writer, Strings::sprite_properties_assign_color_profile());
tx(new cmd::AssignColorProfile(
sprite, colorSpaces[selectedColorProfile]->gfxColorSpace()));
tx.commit();
@ -364,7 +366,7 @@ void SpritePropertiesCommand::onExecute(Context* context)
ContextWriter writer(context);
Sprite* sprite(writer.sprite());
Tx tx(writer.context(), Strings::sprite_properties_convert_color_profile());
Tx tx(writer, Strings::sprite_properties_convert_color_profile());
tx(new cmd::ConvertColorProfile(
sprite, colorSpaces[selectedColorProfile]->gfxColorSpace()));
tx.commit();
@ -394,7 +396,7 @@ void SpritePropertiesCommand::onExecute(Context* context)
if (index != sprite->transparentColor() ||
pixelRatio != sprite->pixelRatio() ||
newUserData != sprite->userData()) {
Tx tx(writer.context(), Strings::sprite_properties_change_sprite_props());
Tx tx(writer, Strings::sprite_properties_change_sprite_props());
DocApi api = writer.document()->getApi(tx);
if (index != sprite->transparentColor())

View File

@ -47,7 +47,7 @@ void UnlinkCelCommand::onExecute(Context* context)
Doc* document(writer.document());
bool nonEditableLayers = false;
{
Tx tx(writer.context(), "Unlink Cel");
Tx tx(writer, "Unlink Cel");
const Site* site = writer.site();
if (site->inTimeline() &&

View File

@ -180,7 +180,7 @@ void ConvertLayerCommand::onExecute(Context* ctx)
ContextWriter writer(ctx);
Doc* document(writer.document());
{
Tx tx(ctx, friendlyName());
Tx tx(writer, friendlyName());
switch (params().to()) {

View File

@ -353,7 +353,7 @@ void FilterManagerImpl::initTransaction()
{
ASSERT(!m_tx);
m_writer.reset(new ContextWriter(m_reader));
m_tx.reset(new Tx(m_writer->context(),
m_tx.reset(new Tx(*m_writer,
m_filter->getName(),
ModifyDocument));
}

View File

@ -55,7 +55,7 @@ protected:
if (!writer.palette())
return;
Tx tx(writer.context(), friendlyName(), ModifyDocument);
Tx tx(writer, friendlyName(), ModifyDocument);
const int beforeIndex = params().before();
int currentEntry = picks.firstPick();

View File

@ -57,7 +57,7 @@ protected:
if (picks.picks() == 0)
return;
Tx tx(writer.context(), onGetFriendlyName(), ModifyDocument);
Tx tx(writer, onGetFriendlyName(), ModifyDocument);
const int beforeIndex = params().before();
int currentEntry = picks.firstPick();

View File

@ -52,7 +52,7 @@ TEST_F(BasicDocApiTest, RestackLayerBefore)
{
EXPECT_EQ(layer1, root->firstLayer());
{
Tx tx(&ctx, "");
Tx tx(sprite, "");
// Do nothing
doc->getApi(tx).restackLayerBefore(layer1, layer1->parent(), layer1);
EXPECT_EQ(layer1, root->firstLayer());
@ -63,7 +63,7 @@ TEST_F(BasicDocApiTest, RestackLayerBefore)
EXPECT_EQ(layer1, root->firstLayer());
{
Tx tx(&ctx, "");
Tx tx(sprite, "");
doc->getApi(tx).restackLayerBefore(layer1, layer3->parent(), layer3);
EXPECT_EQ(layer2, root->firstLayer());
EXPECT_EQ(layer1, root->firstLayer()->getNext());
@ -73,7 +73,7 @@ TEST_F(BasicDocApiTest, RestackLayerBefore)
EXPECT_EQ(layer1, root->firstLayer());
{
Tx tx(&ctx, "");
Tx tx(sprite, "");
doc->getApi(tx).restackLayerBefore(layer1, layer1->parent(), nullptr);
EXPECT_EQ(layer2, root->firstLayer());
EXPECT_EQ(layer3, root->firstLayer()->getNext());
@ -86,7 +86,7 @@ TEST_F(BasicDocApiTest, RestackLayerAfter)
{
EXPECT_EQ(layer1, root->firstLayer());
{
Tx tx(&ctx, "");
Tx tx(sprite, "");
// Do nothing
doc->getApi(tx).restackLayerAfter(layer1, layer1->parent(), layer1);
EXPECT_EQ(layer1, root->firstLayer());
@ -97,7 +97,7 @@ TEST_F(BasicDocApiTest, RestackLayerAfter)
EXPECT_EQ(layer1, root->firstLayer());
{
Tx tx(&ctx, "");
Tx tx(sprite, "");
doc->getApi(tx).restackLayerAfter(layer1, layer3->parent(), layer3);
EXPECT_EQ(layer2, root->firstLayer());
EXPECT_EQ(layer3, root->firstLayer()->getNext());
@ -107,7 +107,7 @@ TEST_F(BasicDocApiTest, RestackLayerAfter)
EXPECT_EQ(layer1, root->firstLayer());
{
Tx tx(&ctx, "");
Tx tx(sprite, "");
doc->getApi(tx).restackLayerAfter(layer3, layer3->parent(), nullptr);
EXPECT_EQ(layer3, root->firstLayer());
EXPECT_EQ(layer1, root->firstLayer()->getNext());
@ -132,7 +132,7 @@ TEST_F(BasicDocApiTest, MoveCel)
// Create a copy for later comparison.
std::unique_ptr<Image> expectedImage(Image::createCopy(image1));
Tx tx(&ctx, "");
Tx tx(sprite, "");
doc->getApi(tx).moveCel(
layer1, frame_t(0),
layer2, frame_t(1));

View File

@ -333,7 +333,7 @@ static DocRange drop_range_op(
const app::Context* context = static_cast<app::Context*>(doc->context());
const ContextReader reader(context);
ContextWriter writer(reader);
Tx tx(writer.context(), undoLabel, ModifyDocument);
Tx tx(writer, undoLabel, ModifyDocument);
DocApi api = doc->getApi(tx);
// TODO Try to add the range with just one call to DocApi
@ -490,7 +490,7 @@ void reverse_frames(Doc* doc, const DocRange& range)
const app::Context* context = static_cast<app::Context*>(doc->context());
const ContextReader reader(context);
ContextWriter writer(reader);
Tx tx(writer.context(), "Reverse Frames");
Tx tx(writer, "Reverse Frames");
DocApi api = doc->getApi(tx);
Sprite* sprite = doc->sprite();
LayerList layers;

View File

@ -13,6 +13,7 @@
#include "app/commands/commands.h"
#include "app/commands/params.h"
#include "app/context.h"
#include "app/context_access.h"
#include "app/doc.h"
#include "app/doc_access.h"
#include "app/i18n/strings.h"
@ -152,8 +153,22 @@ int App_transaction(lua_State* L)
}
if (lua_isfunction(L, index)) {
Tx tx(label); // Create a new transaction so it exists in the whole
// duration of the argument function call.
app::Context* ctx = App::instance()->context();
if (!ctx)
return luaL_error(L, "no context");
// Create a new transaction so it exists in the whole duration of
// the argument function call.
#if 0
// TODO To be able to lock the document in the transaction we have
// to create a re-entrant RWLock implementation to allow to call
// commands (creating sub-ContextWriters) inside the
// app.transaction()
ContextWriter writer(ctx);
Tx tx(writer, label);
#else
Tx tx(Tx::DontLockDoc, ctx, ctx->activeDocument(), label);
#endif
lua_pushvalue(L, -1);
if (lua_pcall(L, 0, LUA_MULTRET, 0) == LUA_OK)

View File

@ -110,8 +110,8 @@ int Cel_set_frame(lua_State* L)
if (cel->frame() == frame)
return 0;
Tx tx;
Doc* doc = static_cast<Doc*>(cel->document());
Tx tx(doc);
DocApi api = doc->getApi(tx);
api.moveCel(cel->layer(), cel->frame(),
cel->layer(), frame);
@ -125,7 +125,7 @@ int Cel_set_image(lua_State* L)
auto srcImage = get_image_from_arg(L, 2);
ImageRef newImage(Image::createCopy(srcImage));
Tx tx;
Tx tx(cel->sprite());
tx(new cmd::ReplaceImage(cel->sprite(),
cel->imageRef(),
newImage));
@ -137,7 +137,7 @@ int Cel_set_position(lua_State* L)
{
auto cel = get_docobj<Cel>(L, 1);
const gfx::Point pos = convert_args_into_point(L, 2);
Tx tx;
Tx tx(cel->sprite());
tx(new cmd::SetCelPosition(cel, pos.x, pos.y));
tx.commit();
return 0;
@ -146,7 +146,7 @@ int Cel_set_position(lua_State* L)
int Cel_set_opacity(lua_State* L)
{
auto cel = get_docobj<Cel>(L, 1);
Tx tx;
Tx tx(cel->sprite());
tx(new cmd::SetCelOpacity(cel, lua_tointeger(L, 2)));
tx.commit();
return 0;
@ -155,7 +155,7 @@ int Cel_set_opacity(lua_State* L)
int Cel_set_zIndex(lua_State* L)
{
auto cel = get_docobj<Cel>(L, 1);
Tx tx;
Tx tx(cel->sprite());
tx(new cmd::SetCelZIndex(cel, lua_tointeger(L, 2)));
tx.commit();
return 0;

View File

@ -104,7 +104,7 @@ int Frame_set_duration(lua_State* L)
auto obj = get_obj<FrameObj>(L, 1);
auto sprite = obj->sprite(L);
double duration = lua_tonumber(L, 2) * 1000.0;
Tx tx;
Tx tx(sprite);
tx(new cmd::SetFrameDuration(sprite, obj->frame, int(duration)));
tx.commit();
return 1;

View File

@ -311,14 +311,7 @@ int Image_drawImage(lua_State* L)
Image* dst = obj->image(L);
const Image* src = sprite->image(L);
// If the destination image is not related to a sprite, we just draw
// the source image without undo information.
if (obj->cel(L) == nullptr) {
doc::blend_image(dst, src,
pos.x, pos.y,
opacity, blendMode);
}
else {
if (auto cel = obj->cel(L)) {
gfx::Rect bounds(0, 0, src->size().w, src->size().h);
buf.reset(new doc::ImageBuffer);
ImageRef tmp_src(
@ -329,12 +322,19 @@ int Image_drawImage(lua_State* L)
// TODO Use something similar to doc::algorithm::shrink_bounds2()
// but we need something that does the render and compares
// the minimal modified area.
Tx tx;
Tx tx(cel->sprite());
tx(new cmd::CopyRegion(
dst, tmp_src.get(), gfx::Region(bounds),
gfx::Point(pos.x + bounds.x, pos.y + bounds.y)));
tx.commit();
}
// If the destination image is not related to a sprite, we just draw
// the source image without undo information.
else {
doc::blend_image(dst, src,
pos.x, pos.y,
opacity, blendMode);
}
return 0;
}
@ -349,13 +349,8 @@ int Image_drawSprite(lua_State* L)
ASSERT(dst);
ASSERT(sprite);
// If the destination image is not related to a sprite, we just draw
// the source image without undo information.
if (obj->cel(L) == nullptr) {
render_sprite(dst, sprite, frame, pos.x, pos.y);
}
else {
Tx tx;
if (auto cel = obj->cel(L)) {
Tx tx(cel->sprite());
ImageRef tmp(Image::createCopy(dst));
render_sprite(tmp.get(), sprite, frame, pos.x, pos.y);
@ -369,6 +364,11 @@ int Image_drawSprite(lua_State* L)
tx.commit();
}
// If the destination image is not related to a sprite, we just draw
// the source image without undo information.
else {
render_sprite(dst, sprite, frame, pos.x, pos.y);
}
return 0;
}
@ -571,7 +571,7 @@ int Image_resize(lua_State* L)
// If the destination image is not related to a sprite, we just draw
// the source image without undo information.
if (cel) {
Tx tx;
Tx tx(cel->sprite());
resize_cel_image(tx, cel, scale, method,
gfx::PointF(pivot));
tx.commit();
@ -627,14 +627,14 @@ int Image_flip(lua_State* L)
if (lua_isinteger(L, 2))
flipType = (doc::algorithm::FlipType)lua_tointeger(L, 2);
if (obj->cel(L) == nullptr) {
doc::algorithm::flip_image(img, img->bounds(), flipType);
}
else {
Tx tx;
if (auto cel = obj->cel(L)) {
Tx tx(cel->sprite());
tx(new cmd::FlipImage(img, img->bounds(), flipType));
tx.commit();
}
else {
doc::algorithm::flip_image(img, img->bounds(), flipType);
}
return 0;
}

View File

@ -250,7 +250,7 @@ int Layer_set_name(lua_State* L)
auto layer = get_docobj<Layer>(L, 1);
const char* name = lua_tostring(L, 2);
if (name) {
Tx tx;
Tx tx(layer->sprite());
tx(new cmd::SetLayerName(layer, name));
tx.commit();
}
@ -262,7 +262,7 @@ int Layer_set_opacity(lua_State* L)
auto layer = get_docobj<Layer>(L, 1);
const int opacity = lua_tointeger(L, 2);
if (layer->isImage()) {
Tx tx;
Tx tx(layer->sprite());
tx(new cmd::SetLayerOpacity(static_cast<LayerImage*>(layer), opacity));
tx.commit();
}
@ -274,7 +274,7 @@ int Layer_set_blendMode(lua_State* L)
auto layer = get_docobj<Layer>(L, 1);
auto blendMode = app::script::BlendMode(lua_tointeger(L, 2));
if (layer->isImage()) {
Tx tx;
Tx tx(layer->sprite());
tx(new cmd::SetLayerBlendMode(static_cast<LayerImage*>(layer),
base::convert_to<doc::BlendMode>(blendMode)));
tx.commit();
@ -313,7 +313,7 @@ int Layer_set_stackIndex(lua_State* L)
return 0;
Doc* doc = static_cast<Doc*>(layer->sprite()->document());
Tx tx;
Tx tx(doc);
DocApi(doc, tx).restackLayerBefore(layer, parent, beforeThis);
tx.commit();
return 0;
@ -381,7 +381,7 @@ int Layer_set_parent(lua_State* L)
if (parent) {
Doc* doc = static_cast<Doc*>(layer->sprite()->document());
Tx tx;
Tx tx(doc);
DocApi(doc, tx).restackLayerAfter(
layer, parent, parent->lastLayer());
tx.commit();
@ -402,7 +402,7 @@ int Layer_set_tileset(lua_State* L)
tsi = lua_tointeger(L, 2);
if (tsi != tilemap->tilesetIndex()) {
Tx tx;
Tx tx(layer->sprite());
tx(new cmd::SetLayerTileset(tilemap, tsi));
tx.commit();
}

View File

@ -165,7 +165,7 @@ int Palette_resize(lua_State* L)
newPal.resize(ncolors);
if (*pal != newPal) {
Tx tx;
Tx tx(sprite);
tx(new cmd::SetPalette(sprite, pal->frame(), &newPal));
tx.commit();
}
@ -212,7 +212,7 @@ int Palette_setColor(lua_State* L)
Palette newPal(*pal);
newPal.setEntry(i, docColor);
Tx tx;
Tx tx(sprite);
tx(new cmd::SetPalette(sprite, pal->frame(), &newPal));
tx.commit();
}

View File

@ -126,8 +126,8 @@ int Properties_newindex(lua_State* L)
// TODO add Object::sprite() member function
//if (obj->sprite()) {
if (App::instance()->context()->activeDocument()) {
Tx tx;
if (auto doc = App::instance()->context()->activeDocument()) {
Tx tx(doc); // TODO propObj might not be member of "doc"
if (propObj->ti != doc::notile) {
tx(new cmd::SetTileDataProperty(static_cast<doc::Tileset*>(obj),
propObj->ti, propObj->extID, field,
@ -165,8 +165,8 @@ int Properties_call(lua_State* L)
// TODO add Object::sprite() member function
//if (obj->sprite()) {
if (App::instance()->context()->activeDocument()) {
Tx tx;
if (auto doc = App::instance()->context()->activeDocument()) {
Tx tx(doc); // TODO propObj might not be member of "doc"
if (propObj->ti != doc::notile) {
tx(new cmd::SetTileDataProperties(static_cast<doc::Tileset*>(obj),
propObj->ti, extID, std::move(newProperties)));

View File

@ -117,7 +117,7 @@ int Selection_deselect(lua_State* L)
ASSERT(doc);
if (doc->isMaskVisible()) {
Tx tx;
Tx tx(doc);
tx(new cmd::DeselectMask(doc));
tx.commit();
}
@ -180,7 +180,7 @@ int Selection_op(lua_State* L, OpMask opMask, OpRect opRect)
Mask newMask;
opMask(newMask, *mask, *otherMask);
Tx tx;
Tx tx(sprite);
tx(new cmd::SetMask(doc, &newMask));
tx.commit();
}
@ -198,7 +198,7 @@ int Selection_op(lua_State* L, OpMask opMask, OpRect opRect)
Mask newMask;
opRect(newMask, *mask, bounds);
Tx tx;
Tx tx(sprite);
tx(new cmd::SetMask(doc, &newMask));
tx.commit();
}
@ -223,7 +223,7 @@ int Selection_selectAll(lua_State* L)
Mask newMask;
newMask.replace(sprite->bounds());
Tx tx;
Tx tx(doc);
tx(new cmd::SetMask(doc, &newMask));
tx.commit();
}
@ -290,7 +290,7 @@ int Selection_set_origin(lua_State* L)
if (auto sprite = obj->sprite(L)) {
Doc* doc = static_cast<Doc*>(sprite->document());
if (doc->isMaskVisible()) {
Tx tx;
Tx tx(doc);
doc->getApi(tx).setMaskPosition(pt.x, pt.y);
tx.commit();
}

View File

@ -83,7 +83,7 @@ int Slice_set_name(lua_State* L)
auto slice = get_docobj<Slice>(L, 1);
const char* name = lua_tostring(L, 2);
if (name) {
Tx tx;
Tx tx(slice->sprite());
tx(new cmd::SetSliceName(slice, name));
tx.commit();
}
@ -98,7 +98,7 @@ int Slice_set_bounds(lua_State* L)
if (const SliceKey* srcKey = slice->getByFrame(0))
key = *srcKey;
key.setBounds(bounds);
Tx tx;
Tx tx(slice->sprite());
tx(new cmd::SetSliceKey(slice, 0, key));
tx.commit();
return 0;
@ -112,7 +112,7 @@ int Slice_set_center(lua_State* L)
if (const SliceKey* srcKey = slice->getByFrame(0))
key = *srcKey;
key.setCenter(center);
Tx tx;
Tx tx(slice->sprite());
tx(new cmd::SetSliceKey(slice, 0, key));
tx.commit();
return 0;
@ -126,7 +126,7 @@ int Slice_set_pivot(lua_State* L)
if (const SliceKey* srcKey = slice->getByFrame(0))
key = *srcKey;
key.setPivot(pivot);
Tx tx;
Tx tx(slice->sprite());
tx(new cmd::SetSliceKey(slice, 0, key));
tx.commit();
return 0;

View File

@ -201,7 +201,7 @@ int Sprite_crop(lua_State* L)
}
if (!bounds.isEmpty()) {
Tx tx;
Tx tx(doc);
DocApi(doc, tx).cropSprite(sprite, bounds);
tx.commit();
}
@ -285,7 +285,7 @@ int Sprite_loadPalette(lua_State* L)
Doc* doc = static_cast<Doc*>(sprite->document());
std::unique_ptr<doc::Palette> palette(load_palette(absFn.c_str()));
if (palette) {
Tx tx;
Tx tx(doc);
// TODO Merge this with the code in LoadPaletteCommand
doc->getApi(tx).setPalette(sprite, 0, palette.get());
tx.commit();
@ -301,7 +301,7 @@ int Sprite_setPalette(lua_State* L)
if (sprite && pal) {
Doc* doc = static_cast<Doc*>(sprite->document());
Tx tx;
Tx tx(doc);
doc->getApi(tx).setPalette(sprite, 0, pal);
tx.commit();
}
@ -312,7 +312,7 @@ int Sprite_assignColorSpace(lua_State* L)
{
auto sprite = get_docobj<Sprite>(L, 1);
auto cs = get_obj<gfx::ColorSpace>(L, 2);
Tx tx;
Tx tx(sprite);
tx(new cmd::AssignColorProfile(
sprite, base::make_ref<gfx::ColorSpace>(*cs)));
tx.commit();
@ -323,7 +323,7 @@ int Sprite_convertColorSpace(lua_State* L)
{
auto sprite = get_docobj<Sprite>(L, 1);
auto cs = get_obj<gfx::ColorSpace>(L, 2);
Tx tx;
Tx tx(sprite);
tx(new cmd::ConvertColorProfile(
sprite, base::make_ref<gfx::ColorSpace>(*cs)));
tx.commit();
@ -338,7 +338,7 @@ int Sprite_flatten(lua_State* L)
for (auto layer : sprite->root()->layers())
range.selectLayer(layer);
Tx tx;
Tx tx(sprite);
tx(new cmd::FlattenLayers(sprite, range.selectedLayers(), true));
tx.commit();
return 0;
@ -349,7 +349,7 @@ int Sprite_newLayer(lua_State* L)
auto sprite = get_docobj<Sprite>(L, 1);
doc::Layer* newLayer = new doc::LayerImage(sprite);
Tx tx;
Tx tx(sprite);
tx(new cmd::AddLayer(sprite->root(), newLayer, sprite->root()->lastLayer()));
tx.commit();
@ -362,7 +362,7 @@ int Sprite_newGroup(lua_State* L)
auto sprite = get_docobj<Sprite>(L, 1);
doc::Layer* newGroup = new doc::LayerGroup(sprite);
Tx tx;
Tx tx(sprite);
tx(new cmd::AddLayer(sprite->root(), newGroup, sprite->root()->lastLayer()));
tx.commit();
@ -388,7 +388,7 @@ int Sprite_deleteLayer(lua_State* L)
if (layer) {
if (sprite != layer->sprite())
return luaL_error(L, "the layer doesn't belong to the sprite");
Tx tx;
Tx tx(sprite);
tx(new cmd::RemoveLayer(layer));
tx.commit();
return 0;
@ -412,7 +412,7 @@ int Sprite_newFrame(lua_State* L)
Doc* doc = static_cast<Doc*>(sprite->document());
Tx tx;
Tx tx(doc);
doc->getApi(tx).addFrame(sprite, copyThis);
tx.commit();
@ -432,7 +432,7 @@ int Sprite_newEmptyFrame(lua_State* L)
Doc* doc = static_cast<Doc*>(sprite->document());
Tx tx;
Tx tx(doc);
DocApi(doc, tx).addEmptyFrame(sprite, frame);
tx.commit();
@ -449,7 +449,7 @@ int Sprite_deleteFrame(lua_State* L)
Doc* doc = static_cast<Doc*>(sprite->document());
Tx tx;
Tx tx(doc);
doc->getApi(tx).removeFrame(sprite, frame);
tx.commit();
return 0;
@ -479,7 +479,7 @@ int Sprite_newCel(lua_State* L)
cel = layer->cel(frame);
ASSERT(cel);
Tx tx;
Tx tx(doc);
DocApi api = doc->getApi(tx);
api.clearCel(layer, frame);
if (srcImage) {
@ -499,7 +499,7 @@ int Sprite_newCel(lua_State* L)
cel = new Cel(frame, image);
cel->setPosition(pos);
Tx tx;
Tx tx(doc);
DocApi api = doc->getApi(tx);
if (layer->cel(frame))
api.clearCel(layer, frame);
@ -526,7 +526,7 @@ int Sprite_deleteCel(lua_State* L)
}
if (cel) {
Tx tx;
Tx tx(sprite);
tx(new cmd::ClearCel(cel));
tx.commit();
return 0;
@ -543,7 +543,7 @@ int Sprite_newTag(lua_State* L)
auto to = get_frame_number_from_arg(L, 3);
auto tag = new doc::Tag(from, to);
Tx tx;
Tx tx(sprite);
tx(new cmd::AddTag(sprite, tag));
tx.commit();
@ -563,7 +563,7 @@ int Sprite_deleteTag(lua_State* L)
if (tag) {
if (sprite != tag->owner()->sprite())
return luaL_error(L, "the tag doesn't belong to the sprite");
Tx tx;
Tx tx(sprite);
tx(new cmd::RemoveTag(sprite, tag));
tx.commit();
return 0;
@ -582,7 +582,7 @@ int Sprite_newSlice(lua_State* L)
if (!bounds.isEmpty())
slice->insert(0, doc::SliceKey(bounds));
Tx tx;
Tx tx(sprite);
tx(new cmd::AddSlice(sprite, slice));
tx.commit();
@ -602,7 +602,7 @@ int Sprite_deleteSlice(lua_State* L)
if (slice) {
if (sprite != slice->owner()->sprite())
return luaL_error(L, "the slice doesn't belong to the sprite");
Tx tx;
Tx tx(sprite);
tx(new cmd::RemoveSlice(sprite, slice));
tx.commit();
return 0;
@ -663,7 +663,7 @@ int Sprite_newTileset(lua_State* L)
tileset = new Tileset(sprite, grid, ntiles);
}
Tx tx;
Tx tx(sprite);
tx(new cmd::AddTileset(sprite, tileset));
tx.commit();
@ -686,7 +686,7 @@ int Sprite_deleteTileset(lua_State* L)
if (tileset && tsi >= 0) {
if (sprite != tileset->sprite())
return luaL_error(L, "the tileset doesn't belong to the sprite");
Tx tx;
Tx tx(sprite);
// Set the tileset from all layers that are using it
for (auto layer : sprite->allLayers()) {
@ -725,7 +725,7 @@ int Sprite_newTile(lua_State* L)
return luaL_error(L, "index must be equal to or greater than 1");
}
ts->insert(ti, ts->makeEmptyTile());
Tx tx;
Tx tx(sprite);
tx(new cmd::AddTile(ts, ti));
tx.commit();
push_tile(L, ts, ti);
@ -749,7 +749,7 @@ int Sprite_deleteTile(lua_State* L)
return luaL_error(L, "tile index = 0 cannot be removed");
if (ti < 0 || ti >= ts->size())
return luaL_error(L, "index out of bounds");
Tx tx;
Tx tx(sprite);
tx(new cmd::RemoveTile(ts, ti));
tx.commit();
push_tile(L, ts, ti);
@ -902,7 +902,7 @@ int Sprite_set_transparentColor(lua_State* L)
{
auto sprite = get_docobj<Sprite>(L, 1);
const int index = lua_tointeger(L, 2);
Tx tx;
Tx tx(sprite);
tx(new cmd::SetTransparentColor(sprite, index));
tx.commit();
return 0;
@ -920,7 +920,7 @@ int Sprite_set_width(lua_State* L)
{
auto sprite = get_docobj<Sprite>(L, 1);
const int width = lua_tointeger(L, 2);
Tx tx;
Tx tx(sprite);
tx(new cmd::SetSpriteSize(sprite, width, sprite->height()));
tx.commit();
return 0;
@ -930,7 +930,7 @@ int Sprite_set_height(lua_State* L)
{
auto sprite = get_docobj<Sprite>(L, 1);
const int height = lua_tointeger(L, 2);
Tx tx;
Tx tx(sprite);
tx(new cmd::SetSpriteSize(sprite, sprite->width(), height));
tx.commit();
return 0;
@ -941,7 +941,7 @@ int Sprite_set_selection(lua_State* L)
auto sprite = get_docobj<Sprite>(L, 1);
const auto mask = get_mask_from_arg(L, 2);
Doc* doc = static_cast<Doc*>(sprite->document());
Tx tx;
Tx tx(sprite);
tx(new cmd::SetMask(doc, mask));
tx.commit();
return 0;
@ -965,7 +965,7 @@ int Sprite_set_gridBounds(lua_State* L)
{
auto sprite = get_docobj<Sprite>(L, 1);
const gfx::Rect bounds = convert_args_into_rect(L, 2);
Tx tx;
Tx tx(sprite);
tx(new cmd::SetGridBounds(sprite, bounds));
tx.commit();
return 0;
@ -982,7 +982,7 @@ int Sprite_set_pixelRatio(lua_State* L)
{
auto sprite = get_docobj<Sprite>(L, 1);
const gfx::Size pixelRatio = convert_args_into_size(L, 2);
Tx tx;
Tx tx(sprite);
tx(new cmd::SetPixelRatio(sprite, pixelRatio));
tx.commit();
return 0;
@ -1006,7 +1006,7 @@ int Sprite_set_tileManagementPlugin(lua_State* L)
value = p;
if (sprite->tileManagementPlugin() != value) {
Tx tx;
Tx tx(sprite);
tx(new cmd::SetSpriteTileManagementPlugin(sprite, value));
tx.commit();
}

View File

@ -96,7 +96,7 @@ int Tag_set_fromFrame(lua_State* L)
{
auto tag = get_docobj<Tag>(L, 1);
const auto fromFrame = get_frame_number_from_arg(L, 2);
Tx tx;
Tx tx(tag->sprite());
tx(new cmd::SetTagRange(tag, fromFrame,
std::max(fromFrame, tag->toFrame())));
tx.commit();
@ -107,7 +107,7 @@ int Tag_set_toFrame(lua_State* L)
{
auto tag = get_docobj<Tag>(L, 1);
const auto toFrame = get_frame_number_from_arg(L, 2);
Tx tx;
Tx tx(tag->sprite());
tx(new cmd::SetTagRange(tag,
std::min(tag->fromFrame(), toFrame),
toFrame));
@ -120,7 +120,7 @@ int Tag_set_name(lua_State* L)
auto tag = get_docobj<Tag>(L, 1);
const char* name = lua_tostring(L, 2);
if (name) {
Tx tx;
Tx tx(tag->sprite());
tx(new cmd::SetTagName(tag, name));
tx.commit();
}
@ -131,7 +131,7 @@ int Tag_set_aniDir(lua_State* L)
{
auto tag = get_docobj<Tag>(L, 1);
const int aniDir = lua_tointeger(L, 2);
Tx tx;
Tx tx(tag->sprite());
tx(new cmd::SetTagAniDir(tag, (doc::AniDir)aniDir));
tx.commit();
return 0;
@ -141,7 +141,7 @@ int Tag_set_repeats(lua_State* L)
{
auto tag = get_docobj<Tag>(L, 1);
const int repeat = lua_tointeger(L, 2);
Tx tx;
Tx tx(tag->sprite());
tx(new cmd::SetTagRepeat(tag, repeat));
tx.commit();
return 0;

View File

@ -60,7 +60,7 @@ int Tile_set_image(lua_State* L)
ImageRef newImage(Image::createCopy(srcImage));
if (ts && ts->sprite()) {
Tx tx;
Tx tx(ts->sprite());
tx(new cmd::ReplaceImage(ts->sprite(),
ts->get(tile->ti),
newImage));
@ -125,8 +125,8 @@ int Tile_set_data(lua_State* L)
doc::UserData ud = ts->getTileData(tile->ti);
ud.setText(text);
if (ts->sprite()) { // TODO use transaction in this sprite
Tx tx;
if (ts->sprite()) {
Tx tx(ts->sprite());
tx(new cmd::SetTileData(ts, tile->ti, ud));
tx.commit();
}
@ -148,8 +148,8 @@ int Tile_set_color(lua_State* L)
doc::UserData ud = ts->getTileData(tile->ti);
ud.setColor(docColor);
if (ts->sprite()) { // TODO use transaction in this sprite
Tx tx;
if (ts->sprite()) {
Tx tx(ts->sprite());
tx(new cmd::SetTileData(ts, tile->ti, ud));
tx.commit();
}
@ -168,7 +168,7 @@ int Tile_set_properties(lua_State* L)
auto newProperties = get_value_from_lua<doc::UserData::Properties>(L, 2);
if (ts->sprite()) {
Tx tx;
Tx tx(ts->sprite());
tx(new cmd::SetTileDataProperties(ts, tile->ti,
std::string(),
std::move(newProperties)));

View File

@ -68,7 +68,7 @@ int Tileset_set_name(lua_State* L)
{
auto tileset = get_docobj<Tileset>(L, 1);
if (const char* newName = lua_tostring(L, 2)) {
Tx tx;
Tx tx(tileset->sprite());
tx(new cmd::SetTilesetName(tileset, newName));
tx.commit();
}
@ -93,7 +93,7 @@ int Tileset_set_baseIndex(lua_State* L)
{
auto tileset = get_docobj<Tileset>(L, 1);
int i = lua_tointeger(L, 2);
Tx tx;
Tx tx(tileset->sprite());
tx(new cmd::SetTilesetBaseIndex(tileset, i));
tx.commit();
return 0;

View File

@ -63,13 +63,12 @@ int UserData_get_properties(lua_State* L) {
template<typename T>
int UserData_set_text(lua_State* L) {
auto obj = get_docobj<T>(L, 1);
auto spr = obj->sprite();
const char* text = lua_tostring(L, 2);
auto wud = get_WithUserData<T>(obj);
UserData ud = wud->userData();
ud.setText(text ? std::string(text): std::string());
if (spr) {
Tx tx;
if (auto spr = obj->sprite()) {
Tx tx(spr);
tx(new cmd::SetUserData(wud, ud, static_cast<Doc*>(spr->document())));
tx.commit();
}
@ -82,13 +81,12 @@ int UserData_set_text(lua_State* L) {
template<typename T>
int UserData_set_color(lua_State* L) {
auto obj = get_docobj<T>(L, 1);
auto spr = obj->sprite();
doc::color_t docColor = convert_args_into_pixel_color(L, 2, doc::IMAGE_RGB);
auto wud = get_WithUserData<T>(obj);
UserData ud = wud->userData();
ud.setColor(docColor);
if (spr) {
Tx tx;
if (auto spr = obj->sprite()) {
Tx tx(spr);
tx(new cmd::SetUserData(wud, ud, static_cast<Doc*>(spr->document())));
tx.commit();
}
@ -103,8 +101,8 @@ int UserData_set_properties(lua_State* L) {
auto obj = get_docobj<T>(L, 1);
auto wud = get_WithUserData<T>(obj);
auto newProperties = get_value_from_lua<doc::UserData::Properties>(L, 2);
if (obj->sprite()) {
Tx tx;
if (auto spr = obj->sprite()) {
Tx tx(spr);
tx(new cmd::SetUserDataProperties(wud,
std::string(),
std::move(newProperties)));

View File

@ -17,7 +17,7 @@ SpriteJob::SpriteJob(const ContextReader& reader, const char* jobName)
, m_writer(reader, 500)
, m_document(m_writer.document())
, m_sprite(m_writer.sprite())
, m_tx(m_writer.context(), jobName, ModifyDocument)
, m_tx(m_writer, jobName, ModifyDocument)
{
}

View File

@ -11,8 +11,11 @@
#include "app/app.h"
#include "app/context.h"
#include "app/context_access.h"
#include "app/doc.h"
#include "app/doc_access.h"
#include "app/transaction.h"
#include "doc/sprite.h"
#include <stdexcept>
@ -22,36 +25,76 @@ namespace app {
// transaction in the context.
class Tx {
public:
enum LockAction {
DocIsLocked, // The doc is locked to be written
LockDoc, // We have to lock the doc in Tx() ctor to write it
DontLockDoc, // In case that we are going for a step-by-step
// transaction (e.g. ToolLoop or PixelsMovement)
};
static constexpr const char* kDefaultTransactionName = "Transaction";
Tx(Context* ctx,
Tx() = delete;
Tx(const LockAction lockAction,
Context* ctx,
Doc* doc,
const std::string& label = kDefaultTransactionName,
const Modification mod = ModifyDocument)
{
m_doc = ctx->activeDocument();
m_doc = doc;
if (!m_doc)
throw std::runtime_error("No active document to execute a transaction");
throw std::runtime_error("No document to execute a transaction");
m_transaction = m_doc->transaction();
if (m_transaction)
if (m_transaction) {
m_owner = false;
}
else {
if (lockAction == LockDoc) {
if (!m_doc->writeLock(500))
throw CannotWriteDocException();
m_locked = true;
}
m_transaction = new Transaction(ctx, m_doc, label, mod);
m_doc->setTransaction(m_transaction);
m_owner = true;
}
}
// Use the default App context
Tx(const std::string& label = kDefaultTransactionName,
public:
Tx(Doc* doc,
const std::string& label = kDefaultTransactionName,
const Modification mod = ModifyDocument)
: Tx(App::instance()->context(), label, mod) {
: Tx(LockDoc, doc->context(), doc, label, mod)
{
}
Tx(doc::Sprite* spr,
const std::string& label = kDefaultTransactionName,
const Modification mod = ModifyDocument)
: Tx(static_cast<Doc*>(spr->document()), label, mod)
{
}
// Use active document of the given context
Tx(ContextWriter& writer,
const std::string& label = kDefaultTransactionName,
const Modification mod = ModifyDocument)
: Tx(DocIsLocked,
writer.context(),
writer.document(), label, mod)
{
}
~Tx() {
if (m_owner) {
m_doc->setTransaction(nullptr);
delete m_transaction;
if (m_locked)
m_doc->unlock();
}
}
@ -84,7 +127,8 @@ namespace app {
private:
Doc* m_doc;
Transaction* m_transaction;
bool m_owner; // Owner of the transaction
bool m_locked = false; // The doc was locked here
bool m_owner = false; // Owner of the transaction
};
} // namespace app

View File

@ -892,7 +892,7 @@ void ColorBar::onRemapPalButtonClick()
if (sprite) {
ASSERT(sprite->pixelFormat() == IMAGE_INDEXED);
Tx tx(writer.context(), "Remap Colors", ModifyDocument);
Tx tx(writer, "Remap Colors", ModifyDocument);
bool remapPixels = true;
std::vector<ImageRef> images;
@ -1003,7 +1003,7 @@ void ColorBar::onRemapTilesButtonClick()
return;
}
Tx tx(writer.context(), Strings::color_bar_remap_tiles(), ModifyDocument);
Tx tx(writer, Strings::color_bar_remap_tiles(), ModifyDocument);
if (!existMapToEmpty &&
remap.isInvertible(usedTiles)) {
tx(new cmd::RemapTilemaps(tileset, remap));
@ -1090,7 +1090,7 @@ void ColorBar::setPalette(const doc::Palette* newPalette, const std::string& act
frame_t frame = writer.frame();
if (sprite &&
newPalette->countDiff(sprite->palette(frame), nullptr, nullptr)) {
Tx tx(writer.context(), actionText, ModifyDocument);
Tx tx(writer, actionText, ModifyDocument);
tx(new cmd::SetPalette(sprite, frame, newPalette));
tx.commit();
}
@ -1110,7 +1110,7 @@ void ColorBar::setTransparentIndex(int index)
sprite->pixelFormat() == IMAGE_INDEXED &&
int(sprite->transparentColor()) != index) {
// TODO merge this code with SpritePropertiesCommand
Tx tx(writer.context(), "Set Transparent Color");
Tx tx(writer, "Set Transparent Color");
DocApi api = writer.document()->getApi(tx);
api.setSpriteTransparentColor(sprite, index);
tx.commit();
@ -1209,7 +1209,7 @@ void ColorBar::onTilesViewClearTiles(const doc::PalettePicks& _picks)
if (sprite) {
auto tileset = m_tilesView.tileset();
Tx tx(writer.context(), "Clear Tiles", ModifyDocument);
Tx tx(writer, "Clear Tiles", ModifyDocument);
for (int ti=int(picks.size())-1; ti>=0; --ti) {
if (picks[ti])
tx(new cmd::RemoveTile(tileset, ti));
@ -1240,7 +1240,7 @@ void ColorBar::onTilesViewResize(const int newSize)
if (sprite) {
auto tileset = m_tilesView.tileset();
Tx tx(writer.context(), Strings::color_bar_resize_tiles(), ModifyDocument);
Tx tx(writer, Strings::color_bar_resize_tiles(), ModifyDocument);
if (tileset->size() < newSize) {
for (doc::tile_index ti=tileset->size(); ti<newSize; ++ti) {
ImageRef img = tileset->makeEmptyTile();
@ -1281,7 +1281,7 @@ void ColorBar::onTilesViewDragAndDrop(doc::Tileset* tileset,
Context* ctx = UIContext::instance();
InlineCommandExecution inlineCmd(ctx);
ContextWriter writer(ctx, 500);
Tx tx(writer.context(), Strings::color_bar_drag_and_drop_tiles(), ModifyDocument);
Tx tx(writer, Strings::color_bar_drag_and_drop_tiles(), ModifyDocument);
if (isCopy)
copy_tiles_in_tileset(tx, tileset, picks, currentEntry, beforeIndex);
else
@ -1880,7 +1880,7 @@ void ColorBar::updateCurrentSpritePalette(const char* operationName)
cmd.release()->execute(UIContext::instance());
}
else {
Tx tx(writer.context(), operationName, ModifyDocument);
Tx tx(writer, operationName, ModifyDocument);
// If tx() fails it will delete the cmd anyway, so we can
// release the unique pointer here.
tx(cmd.release());

View File

@ -594,7 +594,8 @@ bool DocView::onPaste(Context* ctx)
auto clipboard = ctx->clipboard();
if (clipboard->format() == ClipboardFormat::Image ||
clipboard->format() == ClipboardFormat::Tilemap) {
clipboard->paste(ctx, true);
ContextWriter writer(ctx);
clipboard->paste(writer, true);
return true;
}
else
@ -630,7 +631,7 @@ bool DocView::onClear(Context* ctx)
// TODO This code is similar to clipboard::cut()
{
Tx tx(writer.context(), "Clear");
Tx tx(writer, "Clear");
const bool deselectMask =
(visibleMask &&
!Preferences::instance().selection.keepSelectionAfterClear());

View File

@ -153,7 +153,7 @@ bool MovingCelState::onMouseUp(Editor* editor, MouseMessage* msg)
if (modified) {
{
ContextWriter writer(m_reader, 1000);
Tx tx(writer.context(), "Cel Movement", ModifyDocument);
Tx tx(writer, "Cel Movement", ModifyDocument);
DocApi api = document->getApi(tx);
gfx::Point intOffset = intCelOffset();

View File

@ -82,7 +82,7 @@ EditorState::LeaveAction MovingSelectionState::onLeaveState(Editor* editor, Edit
else {
{
ContextWriter writer(UIContext::instance(), 1000);
Tx tx(writer.context(), "Move Selection Edges", DoesntModifyDocument);
Tx tx(writer, "Move Selection Edges", DoesntModifyDocument);
tx(new cmd::SetMaskPosition(doc, newOrigin));
tx.commit();
}

View File

@ -55,7 +55,7 @@ bool MovingSliceState::onMouseUp(Editor* editor, MouseMessage* msg)
{
{
ContextWriter writer(UIContext::instance(), 1000);
Tx tx(writer.context(), "Slice Movement", ModifyDocument);
Tx tx(writer, "Slice Movement", ModifyDocument);
for (const auto& item : m_items) {
item.slice->insert(m_frame, item.oldKey);

View File

@ -119,7 +119,8 @@ PixelsMovement::PixelsMovement(
: m_reader(context)
, m_site(site)
, m_document(site.document())
, m_tx(context, operationName)
, m_tx(Tx::DontLockDoc, context,
context->activeDocument(), operationName)
, m_isDragging(false)
, m_adjustPivot(false)
, m_handle(NoHandle)

View File

@ -477,7 +477,9 @@ public:
const bool saveLastPoint)
: ToolLoopBase(editor, site, grid, params)
, m_context(context)
, m_tx(m_context,
, m_tx(Tx::DontLockDoc,
m_context,
m_context->activeDocument(),
m_tool->getText().c_str(),
((m_ink->isSelection() ||
m_ink->isEyedropper() ||

View File

@ -1387,7 +1387,8 @@ bool Timeline::onProcessMessage(Message* msg)
if (tag) {
if ((m_state == STATE_RESIZING_TAG_LEFT && tag->fromFrame() != m_resizeTagData.from) ||
(m_state == STATE_RESIZING_TAG_RIGHT && tag->toFrame() != m_resizeTagData.to)) {
Tx tx(UIContext::instance(), Strings::commands_FrameTagProperties());
ContextWriter writer(UIContext::instance());
Tx tx(writer, Strings::commands_FrameTagProperties());
tx(new cmd::SetTagRange(
tag,
(m_state == STATE_RESIZING_TAG_LEFT ? m_resizeTagData.from: tag->fromFrame()),
@ -4389,7 +4390,9 @@ bool Timeline::onPaste(Context* ctx)
m_redrawMarchingAntsOnly = false;
invalidate();
}
clipboard->paste(ctx, true);
ContextWriter writer(ctx);
clipboard->paste(writer, true);
return true;
}
else

View File

@ -362,7 +362,7 @@ void Clipboard::cut(ContextWriter& writer)
else {
// TODO This code is similar to DocView::onClear()
{
Tx tx(writer.context(), "Cut");
Tx tx(writer, "Cut");
Site site = writer.context()->activeSite();
CelList cels;
if (site.range().enabled()) {
@ -461,10 +461,10 @@ void Clipboard::copyPalette(const Palette* palette,
m_data->picks = picks;
}
void Clipboard::paste(Context* ctx,
void Clipboard::paste(ContextWriter& writer,
const bool interactive)
{
Site site = ctx->activeSite();
const Site site = *writer.site();
Doc* dstDoc = site.document();
if (!dstDoc)
return;
@ -531,7 +531,7 @@ void Clipboard::paste(Context* ctx,
if (!dstLayer || !dstLayer->isImage())
return;
Tx tx(ctx, "Paste Image");
Tx tx(writer, "Paste Image");
DocApi api = dstDoc->getApi(tx);
Cel* dstCel = api.addCel(
static_cast<LayerImage*>(dstLayer), site.frame(),
@ -611,7 +611,7 @@ void Clipboard::paste(Context* ctx,
break;
}
Tx tx(ctx, "Paste Cels");
Tx tx(writer, "Paste Cels");
DocApi api = dstDoc->getApi(tx);
// Add extra frames if needed
@ -671,7 +671,7 @@ void Clipboard::paste(Context* ctx,
break;
}
Tx tx(ctx, "Paste Frames");
Tx tx(writer, "Paste Frames");
DocApi api = dstDoc->getApi(tx);
auto srcLayers = srcSpr->allBrowsableLayers();
@ -714,7 +714,7 @@ void Clipboard::paste(Context* ctx,
if (srcDoc->colorMode() != dstDoc->colorMode())
throw std::runtime_error("You cannot copy layers of document with different color modes");
Tx tx(ctx, "Paste Layers");
Tx tx(writer, "Paste Layers");
DocApi api = dstDoc->getApi(tx);
// Remove children if their parent is selected so we only

View File

@ -74,7 +74,8 @@ namespace app {
const doc::Tileset* tileset);
void copyPalette(const doc::Palette* palette,
const doc::PalettePicks& picks);
void paste(Context* ctx, const bool interactive);
void paste(ContextWriter& writer,
const bool interactive);
doc::ImageRef getImage(doc::Palette* palette);

View File

@ -121,7 +121,7 @@ void select_layer_boundaries(Layer* layer,
}
}
Tx tx(writer.context(), "Select Layer Boundaries", DoesntModifyDocument);
Tx tx(writer, "Select Layer Boundaries", DoesntModifyDocument);
tx(new cmd::SetMask(doc, &newMask));
tx.commit();

View File

@ -105,7 +105,7 @@ namespace doc {
void setColorSpace(const gfx::ColorSpaceRef& colorSpace);
// This method is only required/used for the template functions app::script::UserData_set_text/color.
const Sprite* sprite() const { return this; }
Sprite* sprite() const { return const_cast<Sprite*>(this); }
// Returns true if the sprite has a background layer and it's visible
bool isOpaque() const;