Merge pull request #1019 from SiboVG/issue-136

[fixes #136] Use warning dialog upon decal source file error
This commit is contained in:
Joe Pfeiffer 2022-01-28 15:14:40 -07:00 committed by GitHub
commit 4332732ade
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 288 additions and 53 deletions

View File

@ -2028,6 +2028,8 @@ DecalModel.lbl.choose = From file...
ExportDecalDialog.title = Export Decal
ExportDecalDialog.decalList.lbl = Decal
ExportDecalDialog.exception = Unable to write decal to file ''{0}''
ExportDecalDialog.source.title = No decal source file
ExportDecalDialog.source.exception = Could not find decal source file ''{0}''.<br><br>Would you like to look for this file?
! Component Preset Chooser Dialog
ComponentPresetChooserDialog.title = Choose component preset

View File

@ -6,6 +6,7 @@ import java.io.IOException;
import java.io.InputStream;
import net.sf.openrocket.util.ChangeSource;
import net.sf.openrocket.util.DecalNotFoundException;
/**
* Interface to handle image files for decals
@ -26,18 +27,42 @@ public interface DecalImage extends ChangeSource, Comparable<DecalImage> {
* @throws FileNotFoundException
* @throws IOException
*/
public InputStream getBytes() throws FileNotFoundException, IOException;
public InputStream getBytes() throws FileNotFoundException, IOException, DecalNotFoundException;
/**
* exports an image into the File
* @param file The File handler object
* @throws IOException
*/
public void exportImage(File file) throws IOException;
public void exportImage(File file) throws IOException, DecalNotFoundException;
/**
* wake up call to listeners
* @param source The source of the wake up call
*/
public void fireChangeEvent(Object source);
/**
* Get the decal file on which the DecalImage is based
* @return decal source file
*/
public File getDecalFile();
/**
* Set the decal file on which the DecalImage is based
* @param file decal source file
*/
public void setDecalFile(File file);
/**
* Checks whether this DecalImage should be ignored when saving the OpenRocket document.
* @return true if DecalImage should be ignored, false if should be saved
*/
public boolean isIgnored();
/**
* Sets the flag to know whether this DecalImage should be ignored when saving the OpenRocket document.
* @param ignored true if DecalImage should be ignored, false if should be saved
*/
public void setIgnored(boolean ignored);
}

View File

@ -16,7 +16,10 @@ import net.sf.openrocket.util.StateChangeListener;
public class ResourceDecalImage implements DecalImage {
/** File path to the image*/
final String resource;
private String resource;
// Flag to check whether this DecalImage should be ignored for saving
private boolean ignored = false;
/**
* main constructor, stores the file path given
@ -64,4 +67,27 @@ public class ResourceDecalImage implements DecalImage {
return getName().compareTo(o.getName());
}
@Override
public void setDecalFile(File file) {
if (file != null) {
this.resource = file.getAbsolutePath();
}
}
@Override
public boolean isIgnored() {
return this.ignored;
}
@Override
public void setIgnored(boolean ignored) {
this.ignored = ignored;
}
@Override
public File getDecalFile() {
return new File(resource);
}
}

View File

@ -6,6 +6,7 @@ import java.io.InputStream;
import net.sf.openrocket.util.AbstractChangeSource;
import net.sf.openrocket.util.ChangeSource;
import net.sf.openrocket.util.DecalNotFoundException;
/**
*
@ -39,7 +40,7 @@ public abstract class Attachment extends AbstractChangeSource implements Compara
* @throws FileNotFoundException
* @throws IOException
*/
public abstract InputStream getBytes() throws FileNotFoundException, IOException;
public abstract InputStream getBytes() throws FileNotFoundException, IOException, DecalNotFoundException;
/**
* {@inheritDoc}

View File

@ -20,8 +20,10 @@ import java.util.regex.Pattern;
import net.sf.openrocket.appearance.DecalImage;
import net.sf.openrocket.document.attachments.FileSystemAttachment;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.ChangeSource;
import net.sf.openrocket.util.DecalNotFoundException;
import net.sf.openrocket.util.FileUtils;
import net.sf.openrocket.util.StateChangeListener;
@ -91,7 +93,7 @@ public class DecalRegistry {
decalName = makeUniqueName(location.getName());
d = new DecalImageImpl(decalName, attachment);
d.setFileSystemLocation(location);
d.setDecalFile(location);
registeredDecals.put(decalName, d);
return d;
@ -121,7 +123,10 @@ public class DecalRegistry {
private final Attachment delegate;
private String name;
private File fileSystemLocation;
private File decalFile;
private final Translator trans = Application.getTranslator();
// Flag to check whether this DecalImage should be ignored for saving
private boolean ignored = false;
private DecalImageImpl(String name, Attachment delegate) {
this.name = name;
@ -151,10 +156,13 @@ public class DecalRegistry {
* @throws IOException
*/
@Override
public InputStream getBytes() throws FileNotFoundException, IOException {
public InputStream getBytes() throws FileNotFoundException, IOException, DecalNotFoundException {
// First check if the decal is located on the file system
File exportedFile = getFileSystemLocation();
File exportedFile = getDecalFile();
if (exportedFile != null) {
if (!exportedFile.exists()) {
throw new DecalNotFoundException(exportedFile.getAbsolutePath(), this);
}
InputStream rawIs = new FileInputStream(exportedFile);
try {
byte[] bytes = FileUtils.readBytes(rawIs);
@ -164,38 +172,45 @@ public class DecalRegistry {
}
}
try {
return delegate.getBytes();
} catch (DecalNotFoundException decex) {
throw new DecalNotFoundException(delegate.getName(), this);
}
}
@Override
public void exportImage(File file) throws IOException {
try {
InputStream is = getBytes();
public void exportImage(File file) throws IOException, DecalNotFoundException {
InputStream is;
is = getBytes();
OutputStream os = new BufferedOutputStream(new FileOutputStream(file));
FileUtils.copy(is, os);
is.close();
os.close();
this.fileSystemLocation = file;
} catch (IOException iex) {
throw new BugException(iex);
}
}
/**
*
* @return
*/
File getFileSystemLocation() {
return fileSystemLocation;
public File getDecalFile() {
return decalFile;
}
void setFileSystemLocation(File fileSystemLocation) {
this.fileSystemLocation = fileSystemLocation;
public void setDecalFile(File file) {
this.decalFile = file;
}
@Override
public boolean isIgnored() {
return this.ignored;
}
@Override
public void setIgnored(boolean ignored) {
this.ignored = ignored;
}
@Override
@ -211,7 +226,7 @@ public class DecalRegistry {
@Override
protected DecalImageImpl clone() {
DecalImageImpl clone = new DecalImageImpl(this.delegate);
clone.fileSystemLocation = this.fileSystemLocation;
clone.decalFile = this.decalFile;
return clone;
}
@ -237,7 +252,7 @@ public class DecalRegistry {
private DecalImageImpl findDecalForFile(File file) {
for (DecalImageImpl d : registeredDecals.values()) {
if (file.equals(d.getFileSystemLocation())) {
if (file.equals(d.getDecalFile())) {
return d;
}
}

View File

@ -7,6 +7,7 @@ import java.io.IOException;
import java.io.InputStream;
import net.sf.openrocket.document.Attachment;
import net.sf.openrocket.util.DecalNotFoundException;
/**
*
@ -41,7 +42,7 @@ public class FileSystemAttachment extends Attachment {
* creates the stream based on the location passed while building
*/
@Override
public InputStream getBytes() throws IOException {
public InputStream getBytes() throws DecalNotFoundException, IOException {
return new FileInputStream(location);
}

View File

@ -1,7 +1,6 @@
package net.sf.openrocket.document.attachments;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
@ -9,6 +8,7 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import net.sf.openrocket.document.Attachment;
import net.sf.openrocket.util.DecalNotFoundException;
import net.sf.openrocket.util.FileUtils;
public class ZipFileAttachment extends Attachment {
@ -21,7 +21,7 @@ public class ZipFileAttachment extends Attachment {
}
@Override
public InputStream getBytes() throws IOException {
public InputStream getBytes() throws DecalNotFoundException, IOException {
String name = getName();
ZipInputStream zis = new ZipInputStream(zipFileLocation.openStream());
@ -35,7 +35,7 @@ public class ZipFileAttachment extends Attachment {
}
entry = zis.getNextEntry();
}
throw new FileNotFoundException("Unable to locate decal for name " + name);
throw new DecalNotFoundException(name, null);
} finally {
zis.close();
}

View File

@ -8,11 +8,12 @@ import java.io.InputStream;
import java.io.OutputStream;
import net.sf.openrocket.document.Attachment;
import net.sf.openrocket.util.DecalNotFoundException;
import net.sf.openrocket.util.FileUtils;
public abstract class AttachmentUtils {
public static void exportAttachment(Attachment a, File outFile) throws IOException {
public static void exportAttachment(Attachment a, File outFile) throws IOException, DecalNotFoundException {
InputStream is = a.getBytes();
OutputStream os = new BufferedOutputStream(new FileOutputStream(outFile));

View File

@ -22,6 +22,7 @@ import net.sf.openrocket.file.openrocket.OpenRocketSaver;
import net.sf.openrocket.file.rocksim.export.RocksimSaver;
import net.sf.openrocket.rocketcomponent.InsideColorComponent;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.util.DecalNotFoundException;
import net.sf.openrocket.util.MathUtil;
public class GeneralRocketSaver {
@ -52,7 +53,7 @@ public class GeneralRocketSaver {
* @param document the document to save.
* @throws IOException in case of an I/O error.
*/
public final void save(File dest, OpenRocketDocument document) throws IOException {
public final void save(File dest, OpenRocketDocument document) throws IOException, DecalNotFoundException {
save(dest, document, document.getDefaultStorageOptions());
}
@ -64,7 +65,7 @@ public class GeneralRocketSaver {
* @param options the storage options.
* @throws IOException in case of an I/O error.
*/
public final void save(File dest, OpenRocketDocument document, StorageOptions options) throws IOException {
public final void save(File dest, OpenRocketDocument document, StorageOptions options) throws IOException, DecalNotFoundException {
save(dest, document, options, null);
}
@ -76,7 +77,7 @@ public class GeneralRocketSaver {
* @param progress a SavingProgress object used to provide progress information
* @throws IOException in case of an I/O error.
*/
public final void save(File dest, OpenRocketDocument doc, SavingProgress progress) throws IOException {
public final void save(File dest, OpenRocketDocument doc, SavingProgress progress) throws IOException, DecalNotFoundException {
save(dest, doc, doc.getDefaultStorageOptions(), progress);
}
@ -89,7 +90,7 @@ public class GeneralRocketSaver {
* @param progress a SavingProgress object used to provide progress information
* @throws IOException in case of an I/O error.
*/
public final void save(File dest, OpenRocketDocument doc, StorageOptions opts, SavingProgress progress) throws IOException {
public final void save(File dest, OpenRocketDocument doc, StorageOptions opts, SavingProgress progress) throws IOException, DecalNotFoundException {
// This method is the core operational method. It saves the document into a new (hopefully unique)
// file, then if the save is successful, it will copy the file over the old one.
@ -105,6 +106,9 @@ public class GeneralRocketSaver {
}
try {
save(dest.getName(), s, doc, opts);
} catch (DecalNotFoundException decex) {
temporaryNewFile.delete();
throw decex;
} finally {
s.close();
}
@ -145,7 +149,7 @@ public class GeneralRocketSaver {
}
}
private void save(String fileName, OutputStream output, OpenRocketDocument document, StorageOptions options) throws IOException {
private void save(String fileName, OutputStream output, OpenRocketDocument document, StorageOptions options) throws IOException, DecalNotFoundException {
// For now, we don't save decal information in ROCKSIM files, so don't do anything
// which follows.
@ -181,7 +185,7 @@ public class GeneralRocketSaver {
saveAllPartsZipFile(output, document, options, usedDecals);
}
public void saveAllPartsZipFile(OutputStream output, OpenRocketDocument document, StorageOptions options, Set<DecalImage> decals) throws IOException {
public void saveAllPartsZipFile(OutputStream output, OpenRocketDocument document, StorageOptions options, Set<DecalImage> decals) throws IOException, DecalNotFoundException {
// Open a zip stream to write to.
ZipOutputStream zos = new ZipOutputStream(output);
@ -196,8 +200,11 @@ public class GeneralRocketSaver {
zos.closeEntry();
// Now we write out all the decal images files.
for (DecalImage image : decals) {
if (image.isIgnored()) {
image.setIgnored(false);
continue;
}
String name = image.getName();
ZipEntry decal = new ZipEntry(name);

View File

@ -0,0 +1,43 @@
package net.sf.openrocket.util;
import net.sf.openrocket.appearance.DecalImage;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.startup.Application;
import java.text.MessageFormat;
/**
* Exception for decals without a valid source file.
*
* @author Sibo Van Gool <sibo.vangool@hotmail.com>
*/
public class DecalNotFoundException extends Exception {
private final DecalImage decal;
private final Translator trans = Application.getTranslator();
/**
* Exception for decals without a valid source file.
* @param message the file path or decal name of the faulty DecalImage
* @param decal DecalImage that has an issue with its source file
*/
public DecalNotFoundException(String message, DecalImage decal) {
super(message);
this.decal = decal;
}
/**
* Get the DecalImage that was the cause of this DecalNotFoundException.
* @return DecalImage that had an issue with its source file
*/
public DecalImage getDecal() {
return decal;
}
/**
* Automatically combine the exception message with the DecalImage file name.
*/
@Override
public String getMessage() {
return MessageFormat.format(trans.get("ExportDecalDialog.source.exception"), super.getMessage());
}
}

View File

@ -0,0 +1,50 @@
package net.sf.openrocket.gui.dialogs;
import net.sf.openrocket.gui.util.MessageWidthUtil;
import net.sf.openrocket.gui.util.SwingPreferences;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.DecalNotFoundException;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import java.awt.Component;
import java.io.File;
/**
* Dialog for handling a DecalNotFoundException.
*
* @author Sibo Van Gool <sibo.vangool@hotmail.com>
*/
public abstract class DecalNotFoundDialog {
/**
* Show a yes/no dialog telling the user that a certain decal source file can't be found and asking whether he/she
* wants to look for that file. If prompted yes, a FileChooser opens up to select the file. If a file is selected,
* the source file of the decal, present in <decex>, will be replaced with the selected file.
* @param parent parent window for the pop-up windows
* @param decex exception containing the decal file path as message and DecalImage
* @return true if the decal has been replaced/found, false if the decal source file issue did not get fixed by the user
*/
public static boolean showDialog(Component parent, DecalNotFoundException decex) {
Translator trans = Application.getTranslator();
// Show 'look up file" yes/no dialog
String message = MessageWidthUtil.setMessageWidth(decex.getMessage(), 400);
int resultYesNo = JOptionPane.showConfirmDialog(parent, message,
trans.get("ExportDecalDialog.source.title"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
int resultFileChooser = JFileChooser.CANCEL_OPTION;
// Look for the file
if (resultYesNo == JOptionPane.YES_OPTION) {
JFileChooser chooser = new JFileChooser();
chooser.setCurrentDirectory(((SwingPreferences) Application.getPreferences()).getDefaultDirectory());
resultFileChooser = chooser.showOpenDialog(parent);
if (resultFileChooser == JFileChooser.APPROVE_OPTION) {
File file = chooser.getSelectedFile();
decex.getDecal().setDecalFile(file);
}
}
return (resultYesNo == JOptionPane.YES_OPTION) && (resultFileChooser == JFileChooser.APPROVE_OPTION);
}
}

View File

@ -60,8 +60,11 @@ import javax.swing.tree.DefaultTreeSelectionModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import net.sf.openrocket.appearance.DecalImage;
import net.sf.openrocket.gui.dialogs.DecalNotFoundDialog;
import net.sf.openrocket.gui.widgets.SelectColorButton;
import net.sf.openrocket.rocketcomponent.AxialStage;
import net.sf.openrocket.util.DecalNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -1418,14 +1421,31 @@ public class BasicFrame extends JFrame {
}
}
try {
StorageOptions options = new StorageOptions();
options.setFileType(StorageOptions.FileType.ROCKSIM);
return saveRocksimFile(file, options);
}
/**
* Perform the actual saving of the Rocksim file
* @param file file to be stored
* @param options storage options to use
* @return true if the file was written
*/
private boolean saveRocksimFile(File file, StorageOptions options) {
try {
ROCKET_SAVER.save(file, document, options);
// Do not update the save state of the document.
return true;
} catch (IOException e) {
return false;
} catch (DecalNotFoundException decex) {
DecalImage decal = decex.getDecal();
// Check if the user replaced the source file, if not, just ignore the faulty decal on the next save
if (!DecalNotFoundDialog.showDialog(null, decex) && decal != null) {
decal.setIgnored(true);
}
return saveRocksimFile(file, options); // Resave
}
}
@ -1509,6 +1529,16 @@ public class BasicFrame extends JFrame {
"An I/O error occurred while saving:",
e.getMessage() }, "Saving failed", JOptionPane.ERROR_MESSAGE);
return false;
}
else if (cause instanceof DecalNotFoundException) {
DecalNotFoundException decex = (DecalNotFoundException) cause;
DecalImage decal = decex.getDecal();
// Check if the user replaced the source file, if not, just ignore the faulty decal on the next save
if (!DecalNotFoundDialog.showDialog(null, decex) && decal != null) {
decal.setIgnored(true);
}
return saveAsOpenRocket(file); // Resave
} else {
Reflection.handleWrappedException(e);
}

View File

@ -20,10 +20,12 @@ import javax.swing.JPanel;
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.appearance.DecalImage;
import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.gui.dialogs.DecalNotFoundDialog;
import net.sf.openrocket.gui.util.FileHelper;
import net.sf.openrocket.gui.util.SwingPreferences;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.DecalNotFoundException;
@SuppressWarnings("serial")
public class ExportDecalDialog extends JDialog {
@ -102,6 +104,8 @@ public class ExportDecalDialog extends JDialog {
try {
decal.exportImage(selectedFile);
} catch (DecalNotFoundException e) {
DecalNotFoundDialog.showDialog(this, e);
} catch (IOException iex) {
String message = MessageFormat.format(trans.get("ExportDecalDialog.exception"), selectedFile.getAbsoluteFile());
JOptionPane.showMessageDialog(this, message, "", JOptionPane.ERROR_MESSAGE);

View File

@ -11,6 +11,7 @@ import net.sf.openrocket.appearance.DecalImage;
import net.sf.openrocket.arch.SystemInfo;
import net.sf.openrocket.arch.SystemInfo.Platform;
import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.gui.dialogs.DecalNotFoundDialog;
import net.sf.openrocket.gui.dialogs.EditDecalDialog;
import net.sf.openrocket.gui.watcher.FileWatcher;
import net.sf.openrocket.gui.watcher.WatchEvent;
@ -21,6 +22,7 @@ import net.sf.openrocket.rocketcomponent.InsideColorComponentHandler;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import com.google.inject.Inject;
import net.sf.openrocket.util.DecalNotFoundException;
public class EditDecalHelper {
@ -83,7 +85,7 @@ public class EditDecalHelper {
//First Check preferences
if (usageCount == 1 && (sysPrefSet || isSnapConfined)) {
String commandLine = isSnapConfined ? "xdg-open %%" : prefs.getDecalEditorCommandLine();
launchEditor(prefs.isDecalEditorPreferenceSystem(), commandLine, decal);
launchEditor(parent, prefs.isDecalEditorPreferenceSystem(), commandLine, decal);
return decal;
}
@ -121,7 +123,7 @@ public class EditDecalHelper {
decal = makeDecalUniqueInside(doc, component, decal);
}
launchEditor(useSystemEditor, commandLine, decal);
launchEditor(parent, useSystemEditor, commandLine, decal);
return decal;
@ -154,7 +156,7 @@ public class EditDecalHelper {
return newImage;
}
private void launchEditor(boolean useSystemEditor, String commandTemplate, final DecalImage decal) throws EditDecalHelperException {
private void launchEditor(Window parent, boolean useSystemEditor, String commandTemplate, final DecalImage decal) throws EditDecalHelperException {
String decalId = decal.getName();
// Create Temp File.
@ -188,6 +190,11 @@ public class EditDecalHelper {
} catch (IOException ioex) {
String message = MessageFormat.format(trans.get("EditDecalHelper.createFileException"), tmpFile.getAbsoluteFile());
throw new EditDecalHelperException(message, ioex);
} catch (DecalNotFoundException decex) {
if (DecalNotFoundDialog.showDialog(parent, decex)) {
launchEditor(parent, useSystemEditor, commandTemplate, decal);
}
return;
}

View File

@ -0,0 +1,19 @@
package net.sf.openrocket.gui.util;
/**
* Helper class for setting the message width for e.g. JOptionPane pop-ups using HTML formatting.
*
* @author Sibo Van Gool <sibo.vangool@hotmail.com>
*/
public abstract class MessageWidthUtil {
/**
* Set the message width of <message> to <px> pixels using HTML formatting.
* @param message message to be formatted
* @param px width of the message in pixels
* @return HTML-formatted message
*
*/
public static String setMessageWidth(String message, int px) {
return String.format("<html><body><p style='width: %dpx'>%s</p></body></html>", px, message);
}
}

View File

@ -9,6 +9,8 @@ import net.sf.openrocket.document.StorageOptions;
import net.sf.openrocket.file.GeneralRocketLoader;
import net.sf.openrocket.file.GeneralRocketSaver;
import net.sf.openrocket.file.RocketLoadException;
import net.sf.openrocket.gui.dialogs.DecalNotFoundDialog;
import net.sf.openrocket.util.DecalNotFoundException;
/**
* Utility that loads Rocksim file formats and saves them in ORK format.
@ -64,6 +66,8 @@ public class RocksimConverter {
System.err.println("ERROR: Error loading '" + inputFile + "': " + e.getMessage());
} catch (IOException e) {
System.err.println("ERROR: Error saving '" + outputFile + "': " + e.getMessage());
} catch (DecalNotFoundException decex) {
DecalNotFoundDialog.showDialog(null, decex);
}
}