From f2fae91887fedd2c9597f8f13ba6b64686cdd2e9 Mon Sep 17 00:00:00 2001 From: Elvis Alistar Date: Fri, 8 Aug 2025 13:13:01 -0600 Subject: [PATCH 1/6] Only commit pending changes when the document is dirty --- Pinta/Actions/File/CloseDocumentAction.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Pinta/Actions/File/CloseDocumentAction.cs b/Pinta/Actions/File/CloseDocumentAction.cs index 309f78bd26..f5f84a84c6 100644 --- a/Pinta/Actions/File/CloseDocumentAction.cs +++ b/Pinta/Actions/File/CloseDocumentAction.cs @@ -57,16 +57,16 @@ void IActionHandler.Uninitialize () actions.File.Close.Activated -= Activated; } - private void Activated (object sender, EventArgs e) - { - // Commit any pending changes - tools.Commit (); - - // If it's not dirty, just close it - if (!workspace.ActiveDocument.IsDirty) { - workspace.CloseActiveDocument (actions); - return; - } + private void Activated (object sender, EventArgs e) + { + // If the document is already clean, close it without committing. + if (!workspace.ActiveDocument.IsDirty) { + workspace.CloseActiveDocument (actions); + return; + } + + // Commit any pending changes before prompting to save/discard. + tools.Commit (); string heading = Translations.GetString ( "Save changes to image \"{0}\" before closing?", From 83ea0a79d7cb58a3063ba13e3ad7749cb2bf7704 Mon Sep 17 00:00:00 2001 From: Elvis Alistar Date: Fri, 8 Aug 2025 13:15:14 -0600 Subject: [PATCH 2/6] Fix format --- Pinta/Actions/File/CloseDocumentAction.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Pinta/Actions/File/CloseDocumentAction.cs b/Pinta/Actions/File/CloseDocumentAction.cs index f5f84a84c6..fdb6ee6748 100644 --- a/Pinta/Actions/File/CloseDocumentAction.cs +++ b/Pinta/Actions/File/CloseDocumentAction.cs @@ -57,16 +57,16 @@ void IActionHandler.Uninitialize () actions.File.Close.Activated -= Activated; } - private void Activated (object sender, EventArgs e) - { - // If the document is already clean, close it without committing. - if (!workspace.ActiveDocument.IsDirty) { - workspace.CloseActiveDocument (actions); - return; - } - - // Commit any pending changes before prompting to save/discard. - tools.Commit (); + private void Activated (object sender, EventArgs e) + { + // If the document is already clean, close it without committing. + if (!workspace.ActiveDocument.IsDirty) { + workspace.CloseActiveDocument (actions); + return; + } + + // Commit any pending changes before prompting to save/discard. + tools.Commit (); string heading = Translations.GetString ( "Save changes to image \"{0}\" before closing?", From 9fe48ac94e0b789dc37c2f61209c6ce7701a6014 Mon Sep 17 00:00:00 2001 From: Elvis Alistar Date: Sat, 9 Aug 2025 16:31:21 -0600 Subject: [PATCH 3/6] Remove unnecessary call to commit since the tool would have already done that and this prevents adding an additional Finalize history item --- Pinta/Actions/File/CloseDocumentAction.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Pinta/Actions/File/CloseDocumentAction.cs b/Pinta/Actions/File/CloseDocumentAction.cs index fdb6ee6748..c06dac6582 100644 --- a/Pinta/Actions/File/CloseDocumentAction.cs +++ b/Pinta/Actions/File/CloseDocumentAction.cs @@ -59,15 +59,12 @@ void IActionHandler.Uninitialize () private void Activated (object sender, EventArgs e) { - // If the document is already clean, close it without committing. + // If it's not dirty, just close it if (!workspace.ActiveDocument.IsDirty) { workspace.CloseActiveDocument (actions); return; } - // Commit any pending changes before prompting to save/discard. - tools.Commit (); - string heading = Translations.GetString ( "Save changes to image \"{0}\" before closing?", workspace.ActiveDocument.DisplayName); From 87c57b688cf2c381732e690df77f1cd2e3ae34df Mon Sep 17 00:00:00 2001 From: Elvis Alistar Date: Sat, 9 Aug 2025 17:35:47 -0600 Subject: [PATCH 4/6] Avoid having shapes finalized if a document close operation is cancelled --- Pinta.Core/Managers/WorkspaceManager.cs | 14 +++++++++++--- Pinta/MainWindow.cs | 2 +- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Pinta.Core/Managers/WorkspaceManager.cs b/Pinta.Core/Managers/WorkspaceManager.cs index 34700fe2ff..1bb3975b7f 100644 --- a/Pinta.Core/Managers/WorkspaceManager.cs +++ b/Pinta.Core/Managers/WorkspaceManager.cs @@ -383,12 +383,13 @@ public void SetActiveDocument ( } internal void SetActiveDocumentInternal ( - ToolManager tools, - Document document) + ToolManager tools, + Document document, + bool suppressCommit = false) { // Work around a case where we closed a document but haven't updated // the active_document_index yet and it points to the closed document - if (HasOpenDocuments && active_document_index != -1 && open_documents.Count > active_document_index) + if (!suppressCommit && HasOpenDocuments && active_document_index != -1 && open_documents.Count > active_document_index) tools.Commit (); int index = open_documents.IndexOf (document); @@ -397,6 +398,13 @@ internal void SetActiveDocumentInternal ( OnActiveDocumentChanged (EventArgs.Empty); } + public void SetActiveDocumentForClose (Document document) + { + // Sets the active document in preparation for closing it. This avoids committing + // tool state when switching, so canceling the close will not finalize editable shapes, for example. + SetActiveDocumentInternal (PintaCore.Tools, document, suppressCommit: true); + } + private void OnActiveDocumentChanged (EventArgs _) { ActiveDocumentChanged?.Invoke (this, EventArgs.Empty); diff --git a/Pinta/MainWindow.cs b/Pinta/MainWindow.cs index ab101acf3a..f57e5b43c1 100644 --- a/Pinta/MainWindow.cs +++ b/Pinta/MainWindow.cs @@ -150,7 +150,7 @@ private void DockNotebook_TabClosed (object? sender, TabClosedEventArgs e) if (PintaCore.Workspace.OpenDocuments.IndexOf (view.Document) < 0) return; - PintaCore.Actions.Window.SetActiveDocument (view.Document); + PintaCore.Workspace.SetActiveDocumentForClose (view.Document); PintaCore.Actions.File.Close.Activate (); if (PintaCore.Workspace.OpenDocuments.IndexOf (view.Document) < 0) From e8a281e3c849cc6fad12a54655e8630c7019b1ee Mon Sep 17 00:00:00 2001 From: Elvis Alistar Date: Sat, 9 Aug 2025 18:07:18 -0600 Subject: [PATCH 5/6] Do not leak shape edits from one document tab to another --- Pinta.Docking/DockNotebook.cs | 14 +++++--- .../Widgets/Canvas/PintaCanvas.cs | 5 +++ .../Editable/EditEngines/BaseEditEngine.cs | 36 +++++++++++++++++-- Pinta/MainWindow.cs | 5 ++- 4 files changed, 53 insertions(+), 7 deletions(-) diff --git a/Pinta.Docking/DockNotebook.cs b/Pinta.Docking/DockNotebook.cs index 3ac16d80f9..eb2b634557 100644 --- a/Pinta.Docking/DockNotebook.cs +++ b/Pinta.Docking/DockNotebook.cs @@ -45,6 +45,7 @@ public sealed class DockNotebook : Gtk.Box private readonly Adw.TabView tab_view; private readonly Adw.TabBar tab_bar; private readonly HashSet items = []; + public bool IsClosingTab { get; private set; } public DockNotebook () { @@ -95,12 +96,17 @@ private bool TabView_OnClosePage (Adw.TabView _, Adw.TabView.ClosePageSignalArgs TabClosedEventArgs close_args = new (item); - TabClosed?.Invoke (this, close_args); + IsClosingTab = true; + try { + TabClosed?.Invoke (this, close_args); - tab_view.ClosePageFinish (page, !close_args.Cancel); + tab_view.ClosePageFinish (page, !close_args.Cancel); - if (!close_args.Cancel) - items.Remove (item); + if (!close_args.Cancel) + items.Remove (item); + } finally { + IsClosingTab = false; + } // Prevent the default close handler from running. return Gdk.Constants.EVENT_STOP; diff --git a/Pinta.Gui.Widgets/Widgets/Canvas/PintaCanvas.cs b/Pinta.Gui.Widgets/Widgets/Canvas/PintaCanvas.cs index 90c4d07dd0..b2a125a05d 100644 --- a/Pinta.Gui.Widgets/Widgets/Canvas/PintaCanvas.cs +++ b/Pinta.Gui.Widgets/Widgets/Canvas/PintaCanvas.cs @@ -256,6 +256,11 @@ private void DrawHandles (Gtk.Snapshot snapshot, Graphene.Rect canvasViewBounds) if (tool is null) return; + // Only draw tool handles on the canvas for the active document. + // This prevents handles from one document appearing on another tab's canvas. + if (!ReferenceEquals (document, PintaCore.Workspace.ActiveDocumentOrDefault)) + return; + snapshot.PushClip (canvasViewBounds); foreach (IToolHandle control in tool.Handles.Where (c => c.Active)) { diff --git a/Pinta.Tools/Editable/EditEngines/BaseEditEngine.cs b/Pinta.Tools/Editable/EditEngines/BaseEditEngine.cs index efc41111b5..453b37e439 100644 --- a/Pinta.Tools/Editable/EditEngines/BaseEditEngine.cs +++ b/Pinta.Tools/Editable/EditEngines/BaseEditEngine.cs @@ -177,9 +177,40 @@ public ShapeEngine? ActiveShapeEngine { //Helps to keep track of the first modification on a shape after the mouse is clicked, to prevent unnecessary history items. protected bool clicked_without_modifying = false; - //Stores the editable shape data. + // Stores editable shape data per document; the static SEngines reference is + // rebound on active-document changes to point at the current document's list. + private static readonly Dictionary> shapes_by_document = new (); + + // Stores the editable shape data for the currently active document. public static Collection SEngines = []; + static BaseEditEngine () + { + // Initialize mapping for the current active document if available. + if (PintaCore.Workspace.ActiveDocumentOrDefault is Document doc) + SEngines = GetShapesForDocument (doc); + + PintaCore.Workspace.ActiveDocumentChanged += (_, __) => { + if (PintaCore.Workspace.ActiveDocumentOrDefault is Document active) + SEngines = GetShapesForDocument (active); + }; + + PintaCore.Workspace.DocumentClosed += (_, e) => { + // Clean up mapping for closed documents. + if (e.Document is Document closed) + shapes_by_document.Remove (closed); + }; + } + + private static Collection GetShapesForDocument (Document doc) + { + if (!shapes_by_document.TryGetValue (doc, out var shapes)) { + shapes = []; + shapes_by_document[doc] = shapes; + } + return shapes; + } + #region ToolbarEventHandlers protected virtual void BrushMinusButtonClickedEvent (object? o, EventArgs args) @@ -1455,7 +1486,8 @@ protected void CalculateModifiedCurrentPoint () /// protected void ResetShapes () { - SEngines = []; + // Clear the current document's editable shape data. + SEngines.Clear (); //The fields are modified instead of the properties here because a redraw call is undesired (for speed/efficiency). SelectedPointIndex = -1; diff --git a/Pinta/MainWindow.cs b/Pinta/MainWindow.cs index f57e5b43c1..5c197dc146 100644 --- a/Pinta/MainWindow.cs +++ b/Pinta/MainWindow.cs @@ -162,7 +162,10 @@ private void DockNotebook_TabClosed (object? sender, TabClosedEventArgs e) private void DockNotebook_ActiveTabChanged (object? sender, EventArgs e) { - var item = canvas_pad.Notebook.ActiveItem; + if (canvas_pad.Notebook.IsClosingTab) + return; + + var item = canvas_pad.Notebook.ActiveItem; if (item == null) return; From d71f0ad1440cd69c25dff802dd62d956390aff9f Mon Sep 17 00:00:00 2001 From: Elvis Alistar Date: Sat, 9 Aug 2025 18:11:59 -0600 Subject: [PATCH 6/6] Fix format --- Pinta/MainWindow.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Pinta/MainWindow.cs b/Pinta/MainWindow.cs index 5c197dc146..5bc0a15d9d 100644 --- a/Pinta/MainWindow.cs +++ b/Pinta/MainWindow.cs @@ -162,10 +162,10 @@ private void DockNotebook_TabClosed (object? sender, TabClosedEventArgs e) private void DockNotebook_ActiveTabChanged (object? sender, EventArgs e) { - if (canvas_pad.Notebook.IsClosingTab) - return; + if (canvas_pad.Notebook.IsClosingTab) + return; - var item = canvas_pad.Notebook.ActiveItem; + var item = canvas_pad.Notebook.ActiveItem; if (item == null) return;