From eb07b693a33121032b3a32a09fecc7ac88098f62 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 22 Nov 2024 15:52:12 +0100 Subject: [PATCH] Improve confirm option how to handle unsaved changes - Clarified button labels of confirmation dialogs. - Added option to save, discard or cancel to all confirmation dialogs unless cancelling is not an available option. - Improved internal handling of cancelled operations (e.g. no needless output of exception stacktraces) --- .../infinity/exceptions/AbortException.java | 43 +++++++ src/org/infinity/gui/ChildFrame.java | 6 + src/org/infinity/gui/StructViewer.java | 4 +- .../gui/converter/BamFilterBaseOutput.java | 3 + .../infinity/resource/ResourceFactory.java | 106 +++++++++++------- .../infinity/resource/bcs/BafResource.java | 4 +- .../infinity/resource/bcs/BcsResource.java | 9 +- .../resource/graphics/BamResource.java | 4 +- .../resource/graphics/PltResource.java | 4 +- .../resource/graphics/PseudoBamDecoder.java | 3 +- .../infinity/resource/mus/MusResource.java | 4 +- .../resource/other/UnknownResource.java | 4 +- .../infinity/resource/sav/SavResource.java | 24 +++- .../resource/text/PlainTextResource.java | 4 +- src/org/infinity/util/StringTable.java | 17 +-- src/org/infinity/util/TriState.java | 89 +++++++++++++++ 16 files changed, 259 insertions(+), 69 deletions(-) create mode 100644 src/org/infinity/exceptions/AbortException.java create mode 100644 src/org/infinity/util/TriState.java diff --git a/src/org/infinity/exceptions/AbortException.java b/src/org/infinity/exceptions/AbortException.java new file mode 100644 index 000000000..2d8b7823c --- /dev/null +++ b/src/org/infinity/exceptions/AbortException.java @@ -0,0 +1,43 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.exceptions; + +/** + * Thrown to indicate that the current operation was intentionally aborted. + */ +public class AbortException extends Exception { + /** Constructs an {@code AbortException} with no detail message. */ + public AbortException() { + super(); + } + + /** + * Constructs an {@code AbortException} with the specified detail message. + * + * @param message the detail message. + */ + public AbortException(String message) { + super(message); + } + + /** + * Constructs an {@code AbortException} with the specified detail message and cause. + * + * @param message the detail message. + * @param cause the cause for this exception to be thrown. + */ + public AbortException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Constructs an {@code AbortException} with a cause but no detail message. + * + * @param cause the cause for this exception to be thrown. + */ + public AbortException(Throwable cause) { + super(cause); + } +} diff --git a/src/org/infinity/gui/ChildFrame.java b/src/org/infinity/gui/ChildFrame.java index 27b62a550..0a3d218c2 100644 --- a/src/org/infinity/gui/ChildFrame.java +++ b/src/org/infinity/gui/ChildFrame.java @@ -27,6 +27,7 @@ import javax.swing.WindowConstants; import org.infinity.NearInfinity; +import org.infinity.exceptions.AbortException; import org.infinity.gui.menu.BrowserMenuBar; import org.infinity.resource.AbstractStruct; import org.infinity.resource.Closeable; @@ -236,6 +237,9 @@ public void actionPerformed(ActionEvent e) { if (!ChildFrame.this.windowClosing(false)) { return; } + } catch (AbortException e2) { + Logger.debug(e2); + return; } catch (Exception e2) { Logger.error(e2); return; @@ -411,6 +415,8 @@ private static void closeWindow(ChildFrame frame, WindowEvent event) { frame.close(); } frame.dispose(); + } catch (AbortException e) { + Logger.debug(e); } catch (Exception e) { Logger.error(e); } diff --git a/src/org/infinity/gui/StructViewer.java b/src/org/infinity/gui/StructViewer.java index eaa8c64ce..b634da854 100644 --- a/src/org/infinity/gui/StructViewer.java +++ b/src/org/infinity/gui/StructViewer.java @@ -550,11 +550,11 @@ public void actionPerformed(ActionEvent event) { WindowBlocker.blockWindow(wnd, false); } } else if (buttonPanel.getControlByType(ButtonPanel.Control.SAVE) == event.getSource()) { - if (ResourceFactory.saveResource((Resource) struct, getTopLevelAncestor())) { + if (ResourceFactory.saveResource((Resource) struct, getTopLevelAncestor()).isTrue()) { struct.setStructChanged(false); } } else if (buttonPanel.getControlByType(ButtonPanel.Control.SAVE_AS) == event.getSource()) { - if (ResourceFactory.saveResourceAs((Resource) struct, getTopLevelAncestor())) { + if (ResourceFactory.saveResourceAs((Resource) struct, getTopLevelAncestor()).isTrue()) { struct.setStructChanged(false); } } else if (buttonPanel.getControlByType(ButtonPanel.Control.EXPORT_BUTTON) == event.getSource()) { diff --git a/src/org/infinity/gui/converter/BamFilterBaseOutput.java b/src/org/infinity/gui/converter/BamFilterBaseOutput.java index 5247f99ff..55aa74cc3 100644 --- a/src/org/infinity/gui/converter/BamFilterBaseOutput.java +++ b/src/org/infinity/gui/converter/BamFilterBaseOutput.java @@ -9,6 +9,7 @@ import java.nio.file.Path; import java.util.Arrays; +import org.infinity.exceptions.AbortException; import org.infinity.resource.graphics.DxtEncoder; import org.infinity.resource.graphics.PseudoBamDecoder; import org.infinity.util.Logger; @@ -86,6 +87,8 @@ public static boolean convertBam(ConvertToBam converter, Path outFileName, Pseud try { return decoder.exportBamV2(outFileName, dxtType, pvrzIndex, BamOptionsDialog.getOverwritePvrzIndices(), converter.getProgressMonitor(), converter.getProgressMonitorStage()); + } catch (AbortException e) { + Logger.debug(e); } catch (Exception e) { Logger.error(e); throw e; diff --git a/src/org/infinity/resource/ResourceFactory.java b/src/org/infinity/resource/ResourceFactory.java index 3c9a341c3..1ae6a1078 100644 --- a/src/org/infinity/resource/ResourceFactory.java +++ b/src/org/infinity/resource/ResourceFactory.java @@ -37,6 +37,7 @@ import org.infinity.datatype.SecTypeBitmap; import org.infinity.datatype.Song2daBitmap; import org.infinity.datatype.Summon2daBitmap; +import org.infinity.exceptions.AbortException; import org.infinity.gui.ChildFrame; import org.infinity.gui.IdsBrowser; import org.infinity.gui.menu.BrowserMenuBar; @@ -85,7 +86,14 @@ import org.infinity.resource.video.WbmResource; import org.infinity.resource.wed.WedResource; import org.infinity.resource.wmp.WmpResource; -import org.infinity.util.*; +import org.infinity.util.CreMapCache; +import org.infinity.util.DynamicArray; +import org.infinity.util.IdsMapCache; +import org.infinity.util.Logger; +import org.infinity.util.Misc; +import org.infinity.util.Platform; +import org.infinity.util.StaticSimpleXorDecryptor; +import org.infinity.util.TriState; import org.infinity.util.io.FileEx; import org.infinity.util.io.FileManager; import org.infinity.util.io.StreamUtils; @@ -677,19 +685,27 @@ public static void saveCopyOfResource(ResourceEntry entry) { } } - public static boolean saveResource(Resource resource, Component parent) { + public static TriState saveResource(Resource resource, Component parent) { + return saveResource(resource, parent, false); + } + + public static TriState saveResource(Resource resource, Component parent, boolean overwrite) { if (getInstance() != null) { - return getInstance().saveResourceInternal(resource, parent); + return getInstance().saveResourceInternal(resource, parent, overwrite); } else { - return false; + return TriState.FALSE; } } - public static boolean saveResourceAs(Resource resource, Component parent) { + public static TriState saveResourceAs(Resource resource, Component parent) { + return saveResourceAs(resource, parent, false); + } + + public static TriState saveResourceAs(Resource resource, Component parent, boolean overwrite) { if (getInstance() != null) { - return getInstance().saveResourceAsInternal(resource, parent); + return getInstance().saveResourceAsInternal(resource, parent, overwrite); } else { - return false; + return TriState.FALSE; } } @@ -703,7 +719,7 @@ public static boolean saveResourceAs(Resource resource, Component parent) { * * @throws HeadlessException if {@link GraphicsEnvironment#isHeadless} returns {@code true} * @throws NullPointerException If any argument is {@code null} - * @throws Exception If save will be cancelled + * @throws AbortException If save will be cancelled */ public static void closeResource(Resource resource, ResourceEntry entry, JComponent parent) throws Exception { final Path output; @@ -725,7 +741,7 @@ public static void closeResource(Resource resource, ResourceEntry entry, JCompon * * @throws HeadlessException if {@link GraphicsEnvironment#isHeadless} returns {@code true} * @throws NullPointerException If {@code resource} or {@code parent} is {@code null} - * @throws Exception If save will be cancelled + * @throws AbortException If save will be cancelled */ public static void closeResource(Resource resource, Path output, JComponent parent) throws Exception { if (output != null) { @@ -733,9 +749,11 @@ public static void closeResource(Resource resource, Path output, JComponent pare final int result = JOptionPane.showOptionDialog(parent, "Save changes to " + output + '?', "Resource changed", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null, options, options[0]); if (result == JOptionPane.YES_OPTION) { - saveResource(resource, parent.getTopLevelAncestor()); + if (saveResource(resource, parent.getTopLevelAncestor()) == TriState.UNDEFINED) { + throw new AbortException("Save aborted"); + } } else if (result != JOptionPane.NO_OPTION) { - throw new Exception("Save aborted"); + throw new AbortException("Save aborted"); } } } @@ -1539,27 +1557,27 @@ private void saveCopyOfResourceInternal(ResourceEntry entry) { } } - private boolean saveResourceAsInternal(Resource resource, Component parent) { + private TriState saveResourceAsInternal(Resource resource, Component parent, boolean overwrite) { final Path outFile = getExportFileDialogInternal(parent, resource.getResourceEntry().getResourceName(), true); if (outFile != null) { - return saveResourceInternal(resource, parent, outFile); + return saveResourceInternal(resource, parent, outFile, overwrite); } else { - return false; + return TriState.FALSE; } } - private boolean saveResourceInternal(Resource resource, Component parent) { - return saveResourceInternal(resource, parent, null); + private TriState saveResourceInternal(Resource resource, Component parent, boolean overwrite) { + return saveResourceInternal(resource, parent, null, overwrite); } - private boolean saveResourceInternal(Resource resource, Component parent, Path outFile) { + private TriState saveResourceInternal(Resource resource, Component parent, Path outFile, boolean overwrite) { if (!(resource instanceof Writeable)) { JOptionPane.showMessageDialog(parent, "Resource not savable", "Error", JOptionPane.ERROR_MESSAGE); - return false; + return TriState.FALSE; } final ResourceEntry entry = resource.getResourceEntry(); if (entry == null) { - return false; + return TriState.FALSE; } Path outPath; @@ -1575,7 +1593,7 @@ private boolean saveResourceInternal(Resource resource, Component parent, Path o JOptionPane.showMessageDialog(parent, "Unable to create override folder.", "Error", JOptionPane.ERROR_MESSAGE); Logger.error(e); - return false; + return TriState.FALSE; } } outPath = FileManager.query(overridePath, entry.getResourceName()); @@ -1592,7 +1610,7 @@ private boolean saveResourceInternal(Resource resource, Component parent, Path o JOptionPane.showMessageDialog(parent, "Unable to create folder: " + outPath.getParent(), "Error", JOptionPane.ERROR_MESSAGE); Logger.error(e); - return false; + return TriState.FALSE; } } } @@ -1601,24 +1619,34 @@ private boolean saveResourceInternal(Resource resource, Component parent, Path o if (FileEx.create(outPath).exists()) { outPath = outPath.toAbsolutePath(); - String[] options = { "Overwrite", "Cancel" }; - if (JOptionPane.showOptionDialog(parent, outPath + " exists. Overwrite?", "Save resource", - JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null, options, options[0]) == 0) { - if (BrowserMenuBar.getInstance().getOptions().backupOnSave()) { - try { - Path bakPath = outPath.getParent().resolve(outPath.getFileName() + ".bak"); - if (FileEx.create(bakPath).isFile()) { - Files.delete(bakPath); - } - if (!FileEx.create(bakPath).exists()) { - Files.move(outPath, bakPath); + final int result; + if (overwrite) { + result = JOptionPane.YES_OPTION; + } else { + String[] options = { "Overwrite", "Discard", "Cancel" }; + result = JOptionPane.showOptionDialog(parent, outPath + " exists. Overwrite?", "Save resource", + JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null, options, options[0]); + } + switch (result) { + case JOptionPane.YES_OPTION: // Overwrite + if (BrowserMenuBar.getInstance().getOptions().backupOnSave()) { + try { + Path bakPath = outPath.getParent().resolve(outPath.getFileName() + ".bak"); + if (FileEx.create(bakPath).isFile()) { + Files.delete(bakPath); + } + if (!FileEx.create(bakPath).exists()) { + Files.move(outPath, bakPath); + } + } catch (IOException e) { + Logger.error(e); } - } catch (IOException e) { - Logger.error(e); } - } - } else { - return false; + break; + case JOptionPane.NO_OPTION: // Discard + return TriState.FALSE; + default: // Cancel + return TriState.UNDEFINED; } } @@ -1627,7 +1655,7 @@ private boolean saveResourceInternal(Resource resource, Component parent, Path o } catch (IOException e) { JOptionPane.showMessageDialog(parent, "Error while saving " + entry, "Error", JOptionPane.ERROR_MESSAGE); Logger.error(e); - return false; + return TriState.FALSE; } JOptionPane.showMessageDialog(parent, "File saved to \"" + outPath.toAbsolutePath() + '\"', "Save complete", @@ -1649,6 +1677,6 @@ private boolean saveResourceInternal(Resource resource, Component parent, Path o } else if (entry.getResourceName().equalsIgnoreCase(SecTypeBitmap.getTableName())) { SecTypeBitmap.resetTypeTable(); } - return true; + return TriState.TRUE; } } diff --git a/src/org/infinity/resource/bcs/BafResource.java b/src/org/infinity/resource/bcs/BafResource.java index 81bbe0637..7b5119b4b 100644 --- a/src/org/infinity/resource/bcs/BafResource.java +++ b/src/org/infinity/resource/bcs/BafResource.java @@ -467,9 +467,9 @@ private void save(boolean interactive) { } final boolean result; if (interactive) { - result = ResourceFactory.saveResourceAs(this, panel.getTopLevelAncestor()); + result = ResourceFactory.saveResourceAs(this, panel.getTopLevelAncestor()).isTrue(); } else { - result = ResourceFactory.saveResource(this, panel.getTopLevelAncestor()); + result = ResourceFactory.saveResource(this, panel.getTopLevelAncestor()).isTrue(); } if (result) { bSave.setEnabled(false); diff --git a/src/org/infinity/resource/bcs/BcsResource.java b/src/org/infinity/resource/bcs/BcsResource.java index 3adbf093e..e64d4afae 100644 --- a/src/org/infinity/resource/bcs/BcsResource.java +++ b/src/org/infinity/resource/bcs/BcsResource.java @@ -39,6 +39,7 @@ import javax.swing.filechooser.FileFilter; import javax.swing.text.BadLocationException; +import org.infinity.exceptions.AbortException; import org.infinity.gui.ButtonPanel; import org.infinity.gui.ButtonPopupMenu; import org.infinity.gui.DataMenuItem; @@ -360,11 +361,11 @@ public void close() throws Exception { if (result == JOptionPane.YES_OPTION) { ((JButton) bpDecompile.getControlByType(CTRL_COMPILE)).doClick(); if (bpDecompile.getControlByType(CTRL_ERRORS).isEnabled()) { - throw new Exception("Save aborted"); + throw new AbortException("Save aborted"); } ResourceFactory.saveResource(this, panel.getTopLevelAncestor()); } else if (result == JOptionPane.CANCEL_OPTION || result == JOptionPane.CLOSED_OPTION) { - throw new Exception("Save aborted"); + throw new AbortException("Save aborted"); } } else if (codeChanged) { ResourceFactory.closeResource(this, entry, panel); @@ -779,9 +780,9 @@ private void save(boolean interactive) { } final boolean result; if (interactive) { - result = ResourceFactory.saveResourceAs(this, panel.getTopLevelAncestor()); + result = ResourceFactory.saveResourceAs(this, panel.getTopLevelAncestor()).isTrue(); } else { - result = ResourceFactory.saveResource(this, panel.getTopLevelAncestor()); + result = ResourceFactory.saveResource(this, panel.getTopLevelAncestor()).isTrue(); } if (result) { bSave.setEnabled(false); diff --git a/src/org/infinity/resource/graphics/BamResource.java b/src/org/infinity/resource/graphics/BamResource.java index 7120f0090..f6e433de0 100644 --- a/src/org/infinity/resource/graphics/BamResource.java +++ b/src/org/infinity/resource/graphics/BamResource.java @@ -242,11 +242,11 @@ public void actionPerformed(ActionEvent event) { } else if (buttonPanel.getControlByType(ButtonPanel.Control.FIND_REFERENCES) == event.getSource()) { searchReferences(panelMain.getTopLevelAncestor()); } else if (buttonPanel.getControlByType(ButtonPanel.Control.SAVE) == event.getSource()) { - if (ResourceFactory.saveResource(this, panelMain.getTopLevelAncestor())) { + if (ResourceFactory.saveResource(this, panelMain.getTopLevelAncestor()).isTrue()) { setRawModified(false); } } else if (buttonPanel.getControlByType(ButtonPanel.Control.SAVE_AS) == event.getSource()) { - if (ResourceFactory.saveResourceAs(this, panelMain.getTopLevelAncestor())) { + if (ResourceFactory.saveResourceAs(this, panelMain.getTopLevelAncestor()).isTrue()) { setRawModified(false); } } else if (event.getSource() == timer) { diff --git a/src/org/infinity/resource/graphics/PltResource.java b/src/org/infinity/resource/graphics/PltResource.java index 833876f88..db5164413 100644 --- a/src/org/infinity/resource/graphics/PltResource.java +++ b/src/org/infinity/resource/graphics/PltResource.java @@ -292,11 +292,11 @@ public void actionPerformed(ActionEvent e) { JOptionPane.ERROR_MESSAGE); } } else if (buttonPanel.getControlByType(ButtonPanel.Control.SAVE) == e.getSource()) { - if (ResourceFactory.saveResource(this, panelMain.getTopLevelAncestor())) { + if (ResourceFactory.saveResource(this, panelMain.getTopLevelAncestor()).isTrue()) { setRawModified(false); } } else if (buttonPanel.getControlByType(ButtonPanel.Control.SAVE_AS) == e.getSource()) { - if (ResourceFactory.saveResourceAs(this, panelMain.getTopLevelAncestor())) { + if (ResourceFactory.saveResourceAs(this, panelMain.getTopLevelAncestor()).isTrue()) { setRawModified(false); } } diff --git a/src/org/infinity/resource/graphics/PseudoBamDecoder.java b/src/org/infinity/resource/graphics/PseudoBamDecoder.java index 05066987d..5daa3fddc 100644 --- a/src/org/infinity/resource/graphics/PseudoBamDecoder.java +++ b/src/org/infinity/resource/graphics/PseudoBamDecoder.java @@ -28,6 +28,7 @@ import javax.swing.ProgressMonitor; +import org.infinity.exceptions.AbortException; import org.infinity.gui.converter.ConvertToPvrz; import org.infinity.resource.Profile; import org.infinity.util.BinPack2D; @@ -1325,7 +1326,7 @@ private boolean createPvrzPages(Path path, DxtEncoder.DxtType dxtType, List