From e02434d13549652ffa6d418618a5485957e15cf0 Mon Sep 17 00:00:00 2001 From: Skylot Date: Mon, 25 Apr 2022 12:31:35 +0100 Subject: [PATCH] fix(gui): confirm directory loading on file open (#1462) --- .../src/main/java/jadx/gui/ui/MainWindow.java | 159 ++++++------------ .../java/jadx/gui/ui/dialog/FileDialog.java | 155 +++++++++++++++++ .../resources/i18n/Messages_de_DE.properties | 4 + .../resources/i18n/Messages_en_US.properties | 4 + .../resources/i18n/Messages_es_ES.properties | 4 + .../resources/i18n/Messages_ko_KR.properties | 4 + .../resources/i18n/Messages_zh_CN.properties | 4 + .../resources/i18n/Messages_zh_TW.properties | 4 + 8 files changed, 228 insertions(+), 110 deletions(-) create mode 100644 jadx-gui/src/main/java/jadx/gui/ui/dialog/FileDialog.java diff --git a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java index fa12bcece..ddbf5d81d 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -8,7 +8,6 @@ import java.awt.DisplayMode; import java.awt.Font; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; -import java.awt.HeadlessException; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.dnd.DnDConstants; @@ -46,8 +45,6 @@ import javax.swing.Action; import javax.swing.Box; import javax.swing.ImageIcon; import javax.swing.JCheckBoxMenuItem; -import javax.swing.JDialog; -import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMenuBar; @@ -67,7 +64,6 @@ import javax.swing.event.MenuEvent; import javax.swing.event.MenuListener; import javax.swing.event.TreeExpansionEvent; import javax.swing.event.TreeWillExpandListener; -import javax.swing.filechooser.FileNameExtensionFilter; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.DefaultTreeModel; @@ -88,6 +84,7 @@ import jadx.api.JavaNode; import jadx.api.ResourceFile; import jadx.api.plugins.utils.CommonFileUtils; import jadx.core.Jadx; +import jadx.core.utils.ListUtils; import jadx.core.utils.StringUtils; import jadx.core.utils.Utils; import jadx.core.utils.files.FileUtils; @@ -118,6 +115,7 @@ import jadx.gui.ui.codearea.AbstractCodeContentPanel; import jadx.gui.ui.codearea.EditorViewState; import jadx.gui.ui.dialog.ADBDialog; import jadx.gui.ui.dialog.AboutDialog; +import jadx.gui.ui.dialog.FileDialog; import jadx.gui.ui.dialog.LogViewerDialog; import jadx.gui.ui.dialog.RenameDialog; import jadx.gui.ui.dialog.SearchDialog; @@ -144,7 +142,6 @@ import jadx.gui.utils.search.TextSearchIndex; import static io.reactivex.internal.functions.Functions.EMPTY_RUNNABLE; import static jadx.gui.utils.FileUtils.fileNamesToPaths; -import static jadx.gui.utils.FileUtils.toPaths; import static javax.swing.KeyStroke.getKeyStroke; public class MainWindow extends JFrame { @@ -284,63 +281,22 @@ public class MainWindow extends JFrame { } public void openFileOrProject() { - String title = NLS.str("file.open_title"); - JFileChooser fileChooser = buildFileChooser(false, title); - int ret = fileChooser.showDialog(this, title); - if (ret == JFileChooser.APPROVE_OPTION) { - settings.setLastOpenFilePath(fileChooser.getCurrentDirectory().toPath()); - open(toPaths(fileChooser.getSelectedFiles())); + FileDialog fileDialog = new FileDialog(this, FileDialog.OpenMode.OPEN); + List openPaths = fileDialog.show(); + if (!openPaths.isEmpty()) { + settings.setLastOpenFilePath(fileDialog.getCurrentDir()); + open(openPaths); } } public void addFiles() { - String title = NLS.str("file.add_files_action"); - JFileChooser fileChooser = buildFileChooser(true, title); - int ret = fileChooser.showDialog(this, title); - if (ret == JFileChooser.APPROVE_OPTION) { - List paths = new ArrayList<>(wrapper.getOpenPaths()); - paths.addAll(toPaths(fileChooser.getSelectedFiles())); - open(paths); + FileDialog fileDialog = new FileDialog(this, FileDialog.OpenMode.ADD); + List addPaths = fileDialog.show(); + if (!addPaths.isEmpty()) { + open(ListUtils.distinctMergeSortedLists(addPaths, wrapper.getOpenPaths())); } } - private JFileChooser buildFileChooser(boolean addFiles, String toolTipText) { - String[] exts; - if (addFiles) { - exts = new String[] { "apk", "dex", "jar", "class", "smali", "zip", "aar", "arsc" }; - } else { - exts = new String[] { JadxProject.PROJECT_EXTENSION, "apk", "dex", "jar", "class", "smali", "zip", "aar", "arsc", "aab" }; - } - String description = "Supported files: (" + Utils.arrayToStr(exts) + ')'; - - JFileChooser fileChooser = new JFileChooser() { - @Override - protected JDialog createDialog(Component parent) throws HeadlessException { - JDialog dialog = super.createDialog(parent); - dialog.setLocationRelativeTo(null); - settings.loadWindowPos(dialog); - dialog.addWindowListener(new WindowAdapter() { - @Override - public void windowClosed(WindowEvent e) { - settings.saveWindowPos(dialog); - super.windowClosed(e); - } - }); - return dialog; - } - }; - fileChooser.setAcceptAllFileFilterUsed(true); - fileChooser.setFileFilter(new FileNameExtensionFilter(description, exts)); - fileChooser.setMultiSelectionEnabled(true); - fileChooser.setToolTipText(toolTipText); - fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); - Path currentDirectory = settings.getLastOpenFilePath(); - if (currentDirectory != null) { - fileChooser.setCurrentDirectory(currentDirectory.toFile()); - } - return fileChooser; - } - private void newProject() { if (!ensureProjectIsSaved()) { return; @@ -359,45 +315,35 @@ public class MainWindow extends JFrame { } private void saveProjectAs() { - JFileChooser fileChooser = new JFileChooser(); - fileChooser.setAcceptAllFileFilterUsed(true); - String[] exts = { JadxProject.PROJECT_EXTENSION }; - String description = "supported files: " + Arrays.toString(exts).replace('[', '(').replace(']', ')'); - fileChooser.setFileFilter(new FileNameExtensionFilter(description, exts)); - fileChooser.setToolTipText(NLS.str("file.save_project")); - Path currentDirectory = settings.getLastSaveProjectPath(); - if (currentDirectory != null) { - fileChooser.setCurrentDirectory(currentDirectory.toFile()); - } - if (this.project.getFilePaths().size() == 1) { + FileDialog fileDialog = new FileDialog(this, FileDialog.OpenMode.SAVE_PROJECT); + if (project.getFilePaths().size() == 1) { // If there is only one file loaded we suggest saving the jadx project file next to the loaded file Path loadedFile = this.project.getFilePaths().get(0); String fileName = loadedFile.getFileName() + "." + JadxProject.PROJECT_EXTENSION; - fileChooser.setSelectedFile(loadedFile.resolveSibling(fileName).toFile()); + fileDialog.setSelectedFile(loadedFile.resolveSibling(fileName)); } - int ret = fileChooser.showSaveDialog(mainPanel); - if (ret == JFileChooser.APPROVE_OPTION) { - settings.setLastSaveProjectPath(fileChooser.getCurrentDirectory().toPath()); - - Path path = fileChooser.getSelectedFile().toPath(); - if (!path.getFileName().toString().toLowerCase(Locale.ROOT).endsWith(JadxProject.PROJECT_EXTENSION)) { - path = path.resolveSibling(path.getFileName() + "." + JadxProject.PROJECT_EXTENSION); - } - - if (Files.exists(path)) { - int res = JOptionPane.showConfirmDialog( - this, - NLS.str("confirm.save_as_message", path.getFileName()), - NLS.str("confirm.save_as_title"), - JOptionPane.YES_NO_OPTION); - if (res == JOptionPane.NO_OPTION) { - return; - } - } - project.saveAs(path); - settings.addRecentProject(path); - update(); + List saveFiles = fileDialog.show(); + if (saveFiles.isEmpty()) { + return; } + settings.setLastSaveProjectPath(fileDialog.getCurrentDir()); + Path savePath = saveFiles.get(0); + if (!savePath.getFileName().toString().toLowerCase(Locale.ROOT).endsWith(JadxProject.PROJECT_EXTENSION)) { + savePath = savePath.resolveSibling(savePath.getFileName() + "." + JadxProject.PROJECT_EXTENSION); + } + if (Files.exists(savePath)) { + int res = JOptionPane.showConfirmDialog( + this, + NLS.str("confirm.save_as_message", savePath.getFileName()), + NLS.str("confirm.save_as_title"), + JOptionPane.YES_NO_OPTION); + if (res == JOptionPane.NO_OPTION) { + return; + } + } + project.saveAs(savePath); + settings.addRecentProject(savePath); + update(); } void open(List paths) { @@ -646,29 +592,22 @@ public class MainWindow extends JFrame { } private void saveAll(boolean export) { - JFileChooser fileChooser = new JFileChooser(); - fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); - fileChooser.setToolTipText(NLS.str("file.save_all_msg")); - - Path currentDirectory = settings.getLastSaveFilePath(); - if (currentDirectory != null) { - fileChooser.setCurrentDirectory(currentDirectory.toFile()); + FileDialog fileDialog = new FileDialog(this, FileDialog.OpenMode.EXPORT); + List saveDirs = fileDialog.show(); + if (saveDirs.isEmpty()) { + return; } - - int ret = fileChooser.showSaveDialog(mainPanel); - if (ret == JFileChooser.APPROVE_OPTION) { - JadxArgs decompilerArgs = wrapper.getArgs(); - decompilerArgs.setExportAsGradleProject(export); - if (export) { - decompilerArgs.setSkipSources(false); - decompilerArgs.setSkipResources(false); - } else { - decompilerArgs.setSkipSources(settings.isSkipSources()); - decompilerArgs.setSkipResources(settings.isSkipResources()); - } - settings.setLastSaveFilePath(fileChooser.getCurrentDirectory().toPath()); - backgroundExecutor.execute(new ExportTask(this, wrapper, fileChooser.getSelectedFile())); + JadxArgs decompilerArgs = wrapper.getArgs(); + decompilerArgs.setExportAsGradleProject(export); + if (export) { + decompilerArgs.setSkipSources(false); + decompilerArgs.setSkipResources(false); + } else { + decompilerArgs.setSkipSources(settings.isSkipSources()); + decompilerArgs.setSkipResources(settings.isSkipResources()); } + settings.setLastSaveFilePath(fileDialog.getCurrentDir()); + backgroundExecutor.execute(new ExportTask(this, wrapper, saveDirs.get(0).toFile())); } public void initTree() { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/dialog/FileDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/dialog/FileDialog.java new file mode 100644 index 000000000..4518bd8df --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/dialog/FileDialog.java @@ -0,0 +1,155 @@ +package jadx.gui.ui.dialog; + +import java.awt.Component; +import java.awt.HeadlessException; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.File; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import javax.swing.JDialog; +import javax.swing.JFileChooser; +import javax.swing.JOptionPane; +import javax.swing.filechooser.FileNameExtensionFilter; + +import org.jetbrains.annotations.Nullable; + +import jadx.core.utils.Utils; +import jadx.gui.settings.JadxProject; +import jadx.gui.ui.MainWindow; +import jadx.gui.utils.FileUtils; +import jadx.gui.utils.NLS; + +public class FileDialog { + + public enum OpenMode { + OPEN, ADD, SAVE_PROJECT, EXPORT + } + + private final MainWindow mainWindow; + + private boolean isOpen; + private String title; + private List fileExtList; + private int selectionMode = JFileChooser.FILES_AND_DIRECTORIES; + private @Nullable Path currentDir; + private @Nullable Path selectedFile; + + public FileDialog(MainWindow mainWindow, OpenMode mode) { + this.mainWindow = mainWindow; + initForMode(mode); + } + + public List show() { + FileChooser fileChooser = buildFileChooser(); + int ret = isOpen ? fileChooser.showOpenDialog(mainWindow) : fileChooser.showSaveDialog(mainWindow); + if (ret != JFileChooser.APPROVE_OPTION) { + return Collections.emptyList(); + } + currentDir = fileChooser.getCurrentDirectory().toPath(); + return FileUtils.toPaths(fileChooser.getSelectedFiles()); + } + + public Path getCurrentDir() { + return currentDir; + } + + public void setSelectedFile(Path path) { + this.selectedFile = path; + } + + private void initForMode(OpenMode mode) { + switch (mode) { + case OPEN: + case ADD: + fileExtList = new ArrayList<>(Arrays.asList("apk", "dex", "jar", "class", "smali", "zip", "aar", "arsc")); + if (mode == OpenMode.OPEN) { + fileExtList.addAll(Arrays.asList(JadxProject.PROJECT_EXTENSION, "aab")); + title = NLS.str("file.open_title"); + } else { + title = NLS.str("file.add_files_action"); + } + selectionMode = JFileChooser.FILES_AND_DIRECTORIES; + currentDir = mainWindow.getSettings().getLastOpenFilePath(); + isOpen = true; + break; + + case SAVE_PROJECT: + title = NLS.str("file.save_project"); + fileExtList = Collections.singletonList(JadxProject.PROJECT_EXTENSION); + selectionMode = JFileChooser.FILES_ONLY; + currentDir = mainWindow.getSettings().getLastSaveFilePath(); + isOpen = false; + break; + + case EXPORT: + title = NLS.str("file.save_all_msg"); + fileExtList = Collections.emptyList(); + selectionMode = JFileChooser.DIRECTORIES_ONLY; + currentDir = mainWindow.getSettings().getLastSaveFilePath(); + isOpen = false; + break; + } + } + + private FileChooser buildFileChooser() { + FileChooser fileChooser = new FileChooser(); + fileChooser.setToolTipText(title); + fileChooser.setFileSelectionMode(selectionMode); + fileChooser.setMultiSelectionEnabled(isOpen); + fileChooser.setAcceptAllFileFilterUsed(true); + if (!fileExtList.isEmpty()) { + String description = NLS.str("file_dialog.supported_files") + ": (" + Utils.listToString(fileExtList) + ')'; + fileChooser.setFileFilter(new FileNameExtensionFilter(description, fileExtList.toArray(new String[0]))); + } + if (currentDir != null) { + fileChooser.setCurrentDirectory(currentDir.toFile()); + } + if (selectedFile != null) { + fileChooser.setSelectedFile(selectedFile.toFile()); + } + return fileChooser; + } + + private class FileChooser extends JFileChooser { + @Override + protected JDialog createDialog(Component parent) throws HeadlessException { + JDialog dialog = super.createDialog(parent); + dialog.setTitle(title); + dialog.setLocationRelativeTo(null); + mainWindow.getSettings().loadWindowPos(dialog); + dialog.addWindowListener(new WindowAdapter() { + @Override + public void windowClosed(WindowEvent e) { + mainWindow.getSettings().saveWindowPos(dialog); + super.windowClosed(e); + } + }); + return dialog; + } + + @Override + public void approveSelection() { + if (selectionMode == FILES_AND_DIRECTORIES) { + File currentFile = getSelectedFile(); + if (currentFile.isDirectory()) { + int option = JOptionPane.showConfirmDialog( + mainWindow, + NLS.str("file_dialog.load_dir_confirm") + "\n " + currentFile, + NLS.str("file_dialog.load_dir_title"), + JOptionPane.YES_NO_OPTION); + if (option != JOptionPane.YES_OPTION) { + this.setCurrentDirectory(currentFile); + this.updateUI(); + return; + } + } + } + super.approveSelection(); + } + } +} diff --git a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties index a8f71f129..af06bc141 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties @@ -80,6 +80,10 @@ common_dialog.add=Hinzufügen common_dialog.update=Aktualisieren common_dialog.remove=Entfernen +#file_dialog.supported_files=Supported files +#file_dialog.load_dir_title=Load directory +#file_dialog.load_dir_confirm=Load all files from directory? + search_dialog.open=Öffnen search_dialog.cancel=Beenden search_dialog.open_by_name=Nach Text suchen: diff --git a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties index 5df00d9e1..47115fd8b 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -80,6 +80,10 @@ common_dialog.add=Add common_dialog.update=Update common_dialog.remove=Remove +file_dialog.supported_files=Supported files +file_dialog.load_dir_title=Load directory +file_dialog.load_dir_confirm=Load all files from directory? + search_dialog.open=Open search_dialog.cancel=Cancel search_dialog.open_by_name=Search for text: diff --git a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties index 98d057564..4abecd8f3 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties @@ -80,6 +80,10 @@ nav.forward=Adelante #common_dialog.update=Update #common_dialog.remove=Remove +#file_dialog.supported_files=Supported files +#file_dialog.load_dir_title=Load directory +#file_dialog.load_dir_confirm=Load all files from directory? + search_dialog.open=Abrir search_dialog.cancel=Cancelar search_dialog.open_by_name=Buscar texto: diff --git a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties index 09f3b59af..82ee2e495 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties @@ -80,6 +80,10 @@ common_dialog.add=추가 common_dialog.update=업데이트 common_dialog.remove=삭제 +#file_dialog.supported_files=Supported files +#file_dialog.load_dir_title=Load directory +#file_dialog.load_dir_confirm=Load all files from directory? + search_dialog.open=열기 search_dialog.cancel=취소 search_dialog.open_by_name=텍스트 검색 : diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties index baef645ea..97fe96f9b 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties @@ -80,6 +80,10 @@ common_dialog.add=添加 common_dialog.update=更新 common_dialog.remove=移除 +#file_dialog.supported_files=Supported files +#file_dialog.load_dir_title=Load directory +#file_dialog.load_dir_confirm=Load all files from directory? + search_dialog.open=转到 search_dialog.cancel=取消 search_dialog.open_by_name=搜索文本: diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties index 0a579df74..9e1a38a34 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties @@ -80,6 +80,10 @@ common_dialog.add=新增 common_dialog.update=更新 common_dialog.remove=移除 +#file_dialog.supported_files=Supported files +#file_dialog.load_dir_title=Load directory +#file_dialog.load_dir_confirm=Load all files from directory? + search_dialog.open=開啟 search_dialog.cancel=取消 search_dialog.open_by_name=搜尋文字: