diff options
| author | 2024-04-06 14:06:40 +0300 | |
|---|---|---|
| committer | 2024-04-06 12:06:40 +0100 | |
| commit | 3030b646ae58cc9448721f429ab5591fab4ca897 (patch) | |
| tree | 041281fb190b011114d66d5f8f77cd2dd1e91f70 /enigma-swing | |
| parent | Add Vineflower decompiler (#541) (diff) | |
| download | enigma-3030b646ae58cc9448721f429ab5591fab4ca897.tar.gz enigma-3030b646ae58cc9448721f429ab5591fab4ca897.tar.xz enigma-3030b646ae58cc9448721f429ab5591fab4ca897.zip | |
Add file extensions to open and save dialogs (#532)
* Add file extensions to Save As dialog
* Include leading dots in file extensions for simplicity
* Add file extensions to open mappings dialogs
* Remove unused tinyMappingsFileChooser
* Use the same file chooser for all mapping IO
* Fix NPE by using Enigma directories as the default mapping format
Fixes #533.
* Fix code style
* Allow .mappings extension for single Enigma files
* gradlew.bat
---------
Co-authored-by: NebelNidas <nebelnidas@gmail.com>
Diffstat (limited to 'enigma-swing')
4 files changed, 147 insertions, 22 deletions
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java index 11013153..05146d4a 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java | |||
| @@ -63,6 +63,7 @@ import cuchaz.enigma.gui.panels.IdentifierPanel; | |||
| 63 | import cuchaz.enigma.gui.panels.ObfPanel; | 63 | import cuchaz.enigma.gui.panels.ObfPanel; |
| 64 | import cuchaz.enigma.gui.panels.StructurePanel; | 64 | import cuchaz.enigma.gui.panels.StructurePanel; |
| 65 | import cuchaz.enigma.gui.renderer.MessageListCellRenderer; | 65 | import cuchaz.enigma.gui.renderer.MessageListCellRenderer; |
| 66 | import cuchaz.enigma.gui.util.ExtensionFileFilter; | ||
| 66 | import cuchaz.enigma.gui.util.GuiUtil; | 67 | import cuchaz.enigma.gui.util.GuiUtil; |
| 67 | import cuchaz.enigma.gui.util.LanguageUtil; | 68 | import cuchaz.enigma.gui.util.LanguageUtil; |
| 68 | import cuchaz.enigma.gui.util.ScaleUtil; | 69 | import cuchaz.enigma.gui.util.ScaleUtil; |
| @@ -117,8 +118,7 @@ public class Gui { | |||
| 117 | private final JLabel connectionStatusLabel = new JLabel(); | 118 | private final JLabel connectionStatusLabel = new JLabel(); |
| 118 | 119 | ||
| 119 | public final JFileChooser jarFileChooser = new JFileChooser(); | 120 | public final JFileChooser jarFileChooser = new JFileChooser(); |
| 120 | public final JFileChooser tinyMappingsFileChooser = new JFileChooser(); | 121 | public final JFileChooser mappingsFileChooser = new JFileChooser(); |
| 121 | public final JFileChooser enigmaMappingsFileChooser = new JFileChooser(); | ||
| 122 | public final JFileChooser exportSourceFileChooser = new JFileChooser(); | 122 | public final JFileChooser exportSourceFileChooser = new JFileChooser(); |
| 123 | public final JFileChooser exportJarFileChooser = new JFileChooser(); | 123 | public final JFileChooser exportJarFileChooser = new JFileChooser(); |
| 124 | public SearchDialog searchDialog; | 124 | public SearchDialog searchDialog; |
| @@ -147,10 +147,6 @@ public class Gui { | |||
| 147 | 147 | ||
| 148 | private void setupUi() { | 148 | private void setupUi() { |
| 149 | this.jarFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); | 149 | this.jarFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); |
| 150 | this.tinyMappingsFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); | ||
| 151 | |||
| 152 | this.enigmaMappingsFileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); | ||
| 153 | this.enigmaMappingsFileChooser.setAcceptAllFileFilterUsed(false); | ||
| 154 | 150 | ||
| 155 | this.exportSourceFileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); | 151 | this.exportSourceFileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); |
| 156 | this.exportSourceFileChooser.setAcceptAllFileFilterUsed(false); | 152 | this.exportSourceFileChooser.setAcceptAllFileFilterUsed(false); |
| @@ -322,7 +318,7 @@ public class Gui { | |||
| 322 | } | 318 | } |
| 323 | 319 | ||
| 324 | public void setMappingsFile(Path path) { | 320 | public void setMappingsFile(Path path) { |
| 325 | this.enigmaMappingsFileChooser.setSelectedFile(path != null ? path.toFile() : null); | 321 | this.mappingsFileChooser.setSelectedFile(path != null ? path.toFile() : null); |
| 326 | updateUiState(); | 322 | updateUiState(); |
| 327 | } | 323 | } |
| 328 | 324 | ||
| @@ -436,8 +432,10 @@ public class Gui { | |||
| 436 | } | 432 | } |
| 437 | 433 | ||
| 438 | public CompletableFuture<Void> saveMapping() { | 434 | public CompletableFuture<Void> saveMapping() { |
| 439 | if (this.enigmaMappingsFileChooser.getSelectedFile() != null || this.enigmaMappingsFileChooser.showSaveDialog(this.mainWindow.frame()) == JFileChooser.APPROVE_OPTION) { | 435 | ExtensionFileFilter.setupFileChooser(this.mappingsFileChooser, this.controller.getLoadedMappingFormat()); |
| 440 | return this.controller.saveMappings(this.enigmaMappingsFileChooser.getSelectedFile().toPath()); | 436 | |
| 437 | if (this.mappingsFileChooser.getSelectedFile() != null || this.mappingsFileChooser.showSaveDialog(this.mainWindow.frame()) == JFileChooser.APPROVE_OPTION) { | ||
| 438 | return this.controller.saveMappings(ExtensionFileFilter.getSavePath(this.mappingsFileChooser)); | ||
| 441 | } | 439 | } |
| 442 | 440 | ||
| 443 | return CompletableFuture.completedFuture(null); | 441 | return CompletableFuture.completedFuture(null); |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java index c5799019..ad10abfc 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java | |||
| @@ -99,7 +99,7 @@ public class GuiController implements ClientPacketHandler { | |||
| 99 | private IndexTreeBuilder indexTreeBuilder; | 99 | private IndexTreeBuilder indexTreeBuilder; |
| 100 | 100 | ||
| 101 | private Path loadedMappingPath; | 101 | private Path loadedMappingPath; |
| 102 | private MappingFormat loadedMappingFormat; | 102 | private MappingFormat loadedMappingFormat = MappingFormat.ENIGMA_DIRECTORY; |
| 103 | 103 | ||
| 104 | private ClassHandleProvider chp; | 104 | private ClassHandleProvider chp; |
| 105 | 105 | ||
| @@ -180,6 +180,10 @@ public class GuiController implements ClientPacketHandler { | |||
| 180 | chp.invalidateJavadoc(); | 180 | chp.invalidateJavadoc(); |
| 181 | } | 181 | } |
| 182 | 182 | ||
| 183 | public MappingFormat getLoadedMappingFormat() { | ||
| 184 | return loadedMappingFormat; | ||
| 185 | } | ||
| 186 | |||
| 183 | public CompletableFuture<Void> saveMappings(Path path) { | 187 | public CompletableFuture<Void> saveMappings(Path path) { |
| 184 | return saveMappings(path, loadedMappingFormat); | 188 | return saveMappings(path, loadedMappingFormat); |
| 185 | } | 189 | } |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java index 30e35861..35129799 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java | |||
| @@ -34,6 +34,7 @@ import cuchaz.enigma.gui.dialog.CreateServerDialog; | |||
| 34 | import cuchaz.enigma.gui.dialog.FontDialog; | 34 | import cuchaz.enigma.gui.dialog.FontDialog; |
| 35 | import cuchaz.enigma.gui.dialog.SearchDialog; | 35 | import cuchaz.enigma.gui.dialog.SearchDialog; |
| 36 | import cuchaz.enigma.gui.dialog.StatsDialog; | 36 | import cuchaz.enigma.gui.dialog.StatsDialog; |
| 37 | import cuchaz.enigma.gui.util.ExtensionFileFilter; | ||
| 37 | import cuchaz.enigma.gui.util.GuiUtil; | 38 | import cuchaz.enigma.gui.util.GuiUtil; |
| 38 | import cuchaz.enigma.gui.util.LanguageUtil; | 39 | import cuchaz.enigma.gui.util.LanguageUtil; |
| 39 | import cuchaz.enigma.gui.util.ScaleUtil; | 40 | import cuchaz.enigma.gui.util.ScaleUtil; |
| @@ -175,7 +176,7 @@ public class MenuBar { | |||
| 175 | 176 | ||
| 176 | this.jarCloseItem.setEnabled(jarOpen); | 177 | this.jarCloseItem.setEnabled(jarOpen); |
| 177 | this.openMappingsMenu.setEnabled(jarOpen); | 178 | this.openMappingsMenu.setEnabled(jarOpen); |
| 178 | this.saveMappingsItem.setEnabled(jarOpen && this.gui.enigmaMappingsFileChooser.getSelectedFile() != null && connectionState != ConnectionState.CONNECTED); | 179 | this.saveMappingsItem.setEnabled(jarOpen && this.gui.mappingsFileChooser.getSelectedFile() != null && connectionState != ConnectionState.CONNECTED); |
| 179 | this.saveMappingsAsMenu.setEnabled(jarOpen); | 180 | this.saveMappingsAsMenu.setEnabled(jarOpen); |
| 180 | this.closeMappingsItem.setEnabled(jarOpen); | 181 | this.closeMappingsItem.setEnabled(jarOpen); |
| 181 | this.reloadMappingsItem.setEnabled(jarOpen); | 182 | this.reloadMappingsItem.setEnabled(jarOpen); |
| @@ -250,7 +251,7 @@ public class MenuBar { | |||
| 250 | } | 251 | } |
| 251 | 252 | ||
| 252 | private void onSaveMappingsClicked() { | 253 | private void onSaveMappingsClicked() { |
| 253 | this.gui.getController().saveMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath()); | 254 | this.gui.getController().saveMappings(this.gui.mappingsFileChooser.getSelectedFile().toPath()); |
| 254 | } | 255 | } |
| 255 | 256 | ||
| 256 | private void openMappingsDiscardPrompt(Runnable then) { | 257 | private void openMappingsDiscardPrompt(Runnable then) { |
| @@ -422,12 +423,13 @@ public class MenuBar { | |||
| 422 | private static void addOpenMappingsMenuEntry(String text, MappingFormat format, boolean mappingIo, JMenu openMappingsMenu, Gui gui) { | 423 | private static void addOpenMappingsMenuEntry(String text, MappingFormat format, boolean mappingIo, JMenu openMappingsMenu, Gui gui) { |
| 423 | JMenuItem item = new JMenuItem(text); | 424 | JMenuItem item = new JMenuItem(text); |
| 424 | item.addActionListener(event -> { | 425 | item.addActionListener(event -> { |
| 425 | gui.enigmaMappingsFileChooser.setCurrentDirectory(new File(UiConfig.getLastSelectedDir())); | 426 | ExtensionFileFilter.setupFileChooser(gui.mappingsFileChooser, format); |
| 427 | gui.mappingsFileChooser.setCurrentDirectory(new File(UiConfig.getLastSelectedDir())); | ||
| 426 | 428 | ||
| 427 | if (gui.enigmaMappingsFileChooser.showOpenDialog(gui.getFrame()) == JFileChooser.APPROVE_OPTION) { | 429 | if (gui.mappingsFileChooser.showOpenDialog(gui.getFrame()) == JFileChooser.APPROVE_OPTION) { |
| 428 | File selectedFile = gui.enigmaMappingsFileChooser.getSelectedFile(); | 430 | File selectedFile = gui.mappingsFileChooser.getSelectedFile(); |
| 429 | gui.getController().openMappings(format, selectedFile.toPath(), mappingIo); | 431 | gui.getController().openMappings(format, selectedFile.toPath(), mappingIo); |
| 430 | UiConfig.setLastSelectedDir(gui.enigmaMappingsFileChooser.getCurrentDirectory().toString()); | 432 | UiConfig.setLastSelectedDir(gui.mappingsFileChooser.getCurrentDirectory().toString()); |
| 431 | } | 433 | } |
| 432 | }); | 434 | }); |
| 433 | openMappingsMenu.add(item); | 435 | openMappingsMenu.add(item); |
| @@ -453,15 +455,18 @@ public class MenuBar { | |||
| 453 | private static void addSaveMappingsAsMenuEntry(String text, MappingFormat format, boolean mappingIo, JMenu saveMappingsAsMenu, JMenuItem saveMappingsItem, Gui gui) { | 455 | private static void addSaveMappingsAsMenuEntry(String text, MappingFormat format, boolean mappingIo, JMenu saveMappingsAsMenu, JMenuItem saveMappingsItem, Gui gui) { |
| 454 | JMenuItem item = new JMenuItem(text); | 456 | JMenuItem item = new JMenuItem(text); |
| 455 | item.addActionListener(event -> { | 457 | item.addActionListener(event -> { |
| 456 | // TODO: Use a specific file chooser for it | 458 | JFileChooser fileChooser = gui.mappingsFileChooser; |
| 457 | if (gui.enigmaMappingsFileChooser.getCurrentDirectory() == null) { | 459 | ExtensionFileFilter.setupFileChooser(fileChooser, format); |
| 458 | gui.enigmaMappingsFileChooser.setCurrentDirectory(new File(UiConfig.getLastSelectedDir())); | 460 | |
| 461 | if (fileChooser.getCurrentDirectory() == null) { | ||
| 462 | fileChooser.setCurrentDirectory(new File(UiConfig.getLastSelectedDir())); | ||
| 459 | } | 463 | } |
| 460 | 464 | ||
| 461 | if (gui.enigmaMappingsFileChooser.showSaveDialog(gui.getFrame()) == JFileChooser.APPROVE_OPTION) { | 465 | if (fileChooser.showSaveDialog(gui.getFrame()) == JFileChooser.APPROVE_OPTION) { |
| 462 | gui.getController().saveMappings(gui.enigmaMappingsFileChooser.getSelectedFile().toPath(), format, mappingIo); | 466 | Path savePath = ExtensionFileFilter.getSavePath(fileChooser); |
| 467 | gui.getController().saveMappings(savePath, format, mappingIo); | ||
| 463 | saveMappingsItem.setEnabled(true); | 468 | saveMappingsItem.setEnabled(true); |
| 464 | UiConfig.setLastSelectedDir(gui.enigmaMappingsFileChooser.getCurrentDirectory().toString()); | 469 | UiConfig.setLastSelectedDir(fileChooser.getCurrentDirectory().toString()); |
| 465 | } | 470 | } |
| 466 | }); | 471 | }); |
| 467 | saveMappingsAsMenu.add(item); | 472 | saveMappingsAsMenu.add(item); |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/util/ExtensionFileFilter.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/ExtensionFileFilter.java new file mode 100644 index 00000000..a514cae5 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/ExtensionFileFilter.java | |||
| @@ -0,0 +1,118 @@ | |||
| 1 | package cuchaz.enigma.gui.util; | ||
| 2 | |||
| 3 | import java.io.File; | ||
| 4 | import java.nio.file.Path; | ||
| 5 | import java.util.List; | ||
| 6 | import java.util.Locale; | ||
| 7 | import java.util.StringJoiner; | ||
| 8 | |||
| 9 | import javax.swing.JFileChooser; | ||
| 10 | import javax.swing.filechooser.FileFilter; | ||
| 11 | |||
| 12 | import cuchaz.enigma.translation.mapping.serde.MappingFormat; | ||
| 13 | import cuchaz.enigma.utils.I18n; | ||
| 14 | |||
| 15 | public final class ExtensionFileFilter extends FileFilter { | ||
| 16 | private final String formatName; | ||
| 17 | private final List<String> extensions; | ||
| 18 | |||
| 19 | /** | ||
| 20 | * Constructs an {@code ExtensionFileFilter}. | ||
| 21 | * | ||
| 22 | * @param formatName the human-readable name of the file format | ||
| 23 | * @param extensions the file extensions with their leading dots (e.g. {@code .txt}) | ||
| 24 | */ | ||
| 25 | public ExtensionFileFilter(String formatName, List<String> extensions) { | ||
| 26 | this.formatName = formatName; | ||
| 27 | this.extensions = extensions; | ||
| 28 | } | ||
| 29 | |||
| 30 | public List<String> getExtensions() { | ||
| 31 | return extensions; | ||
| 32 | } | ||
| 33 | |||
| 34 | @Override | ||
| 35 | public boolean accept(File f) { | ||
| 36 | // Always accept directories so the user can see them. | ||
| 37 | if (f.isDirectory()) { | ||
| 38 | return true; | ||
| 39 | } | ||
| 40 | |||
| 41 | for (String extension : extensions) { | ||
| 42 | if (f.getName().endsWith(extension)) { | ||
| 43 | return true; | ||
| 44 | } | ||
| 45 | } | ||
| 46 | |||
| 47 | return false; | ||
| 48 | } | ||
| 49 | |||
| 50 | @Override | ||
| 51 | public String getDescription() { | ||
| 52 | var joiner = new StringJoiner(", "); | ||
| 53 | |||
| 54 | for (String extension : extensions) { | ||
| 55 | joiner.add("*" + extension); | ||
| 56 | } | ||
| 57 | |||
| 58 | return I18n.translateFormatted("menu.file.mappings.file_filter", formatName, joiner.toString()); | ||
| 59 | } | ||
| 60 | |||
| 61 | /** | ||
| 62 | * Sets up a file chooser with a mapping format. This method resets the choosable filters, | ||
| 63 | * and adds and selects a new filter based on the provided mapping format. | ||
| 64 | * | ||
| 65 | * @param fileChooser the mapping format | ||
| 66 | */ | ||
| 67 | public static void setupFileChooser(JFileChooser fileChooser, MappingFormat format) { | ||
| 68 | // Remove previous custom filters. | ||
| 69 | fileChooser.resetChoosableFileFilters(); | ||
| 70 | |||
| 71 | if (format.getFileType().isDirectory()) { | ||
| 72 | fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); | ||
| 73 | } else { | ||
| 74 | fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); | ||
| 75 | String formatName = I18n.translate("mapping_format." + format.name().toLowerCase(Locale.ROOT)); | ||
| 76 | var filter = new ExtensionFileFilter(formatName, format.getFileType().extensions()); | ||
| 77 | // Add our new filter to the list... | ||
| 78 | fileChooser.addChoosableFileFilter(filter); | ||
| 79 | // ...and choose it as the default. | ||
| 80 | fileChooser.setFileFilter(filter); | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | /** | ||
| 85 | * Fixes a missing file extension in a save file path when the selected filter | ||
| 86 | * is an {@code ExtensionFileFilter}. | ||
| 87 | * | ||
| 88 | * @param fileChooser the file chooser to check | ||
| 89 | * @return the fixed path | ||
| 90 | */ | ||
| 91 | public static Path getSavePath(JFileChooser fileChooser) { | ||
| 92 | Path savePath = fileChooser.getSelectedFile().toPath(); | ||
| 93 | |||
| 94 | if (fileChooser.getFileFilter() instanceof ExtensionFileFilter extensionFilter) { | ||
| 95 | // Check that the file name ends with the extension. | ||
| 96 | String fileName = savePath.getFileName().toString(); | ||
| 97 | boolean hasExtension = false; | ||
| 98 | |||
| 99 | for (String extension : extensionFilter.getExtensions()) { | ||
| 100 | if (fileName.endsWith(extension)) { | ||
| 101 | hasExtension = true; | ||
| 102 | break; | ||
| 103 | } | ||
| 104 | } | ||
| 105 | |||
| 106 | if (!hasExtension) { | ||
| 107 | String defaultExtension = extensionFilter.getExtensions().get(0); | ||
| 108 | // If not, add the extension. | ||
| 109 | savePath = savePath.resolveSibling(fileName + defaultExtension); | ||
| 110 | // Store the adjusted file, so that it shows up properly | ||
| 111 | // the next time this dialog is used. | ||
| 112 | fileChooser.setSelectedFile(savePath.toFile()); | ||
| 113 | } | ||
| 114 | } | ||
| 115 | |||
| 116 | return savePath; | ||
| 117 | } | ||
| 118 | } | ||