From 47647bbb9a9a3cd3150705e09cc1f84a5e9f0be6 Mon Sep 17 00:00:00 2001 From: Yaroslav <43380144+MrIkso@users.noreply.github.com> Date: Sun, 4 May 2025 22:17:00 +0300 Subject: [PATCH] feat(gui): add Preview Tab Feature (PR #2474) * feat(gui): created simple preview mode (#756) * feat(gui): fixed opening code preview tab, if open from available tabs * fix(gui): rollback mouse events for tree --- .../java/jadx/gui/settings/JadxSettings.java | 10 +++ .../src/main/java/jadx/gui/ui/MainWindow.java | 11 +++- .../java/jadx/gui/ui/tab/TabBlueprint.java | 20 ++++-- .../java/jadx/gui/ui/tab/TabComponent.java | 12 +++- .../java/jadx/gui/ui/tab/TabsController.java | 66 +++++++++++++++---- .../resources/i18n/Messages_de_DE.properties | 1 + .../resources/i18n/Messages_en_US.properties | 1 + .../resources/i18n/Messages_es_ES.properties | 1 + .../resources/i18n/Messages_id_ID.properties | 1 + .../resources/i18n/Messages_ko_KR.properties | 1 + .../resources/i18n/Messages_pt_BR.properties | 1 + .../resources/i18n/Messages_ru_RU.properties | 1 + .../resources/i18n/Messages_zh_CN.properties | 1 + .../resources/i18n/Messages_zh_TW.properties | 1 + 14 files changed, 106 insertions(+), 22 deletions(-) diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java index 4fe755ba2..4861edc7d 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java @@ -104,6 +104,7 @@ public class JadxSettings extends JadxCLIArgs { private boolean showHeapUsageBar = false; private boolean alwaysSelectOpened = false; + private boolean enablePreviewTab = false; private boolean useAlternativeFileDialog = false; private Map windowPos = new HashMap<>(); @@ -319,6 +320,15 @@ public class JadxSettings extends JadxCLIArgs { partialSync(settings -> settings.alwaysSelectOpened = alwaysSelectOpened); } + public boolean isEnablePreviewTab() { + return enablePreviewTab; + } + + public void setEnablePreviewTab(boolean enablePreviewTab) { + this.enablePreviewTab = enablePreviewTab; + partialSync(settings -> settings.enablePreviewTab = enablePreviewTab); + } + public boolean isUseAlternativeFileDialog() { return useAlternativeFileDialog; } 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 27dbb4f75..f6e644481 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -878,11 +878,11 @@ public class MainWindow extends JFrame { JResource res = (JResource) obj; ResourceFile resFile = res.getResFile(); if (resFile != null && JResource.isSupportedForView(resFile.getType())) { - tabsController.selectTab(res); + tabsController.selectTab(res, true); return true; } } else if (obj instanceof JNode) { - tabsController.codeJump((JNode) obj); + tabsController.codeJump((JNode) obj, true); return true; } } catch (Exception e) { @@ -1062,6 +1062,12 @@ public class MainWindow extends JFrame { flatPkgMenuItem = new JCheckBoxMenuItem(NLS.str("menu.flatten"), Icons.FLAT_PKG); flatPkgMenuItem.setState(isFlattenPackage); + JCheckBoxMenuItem enablePreviewTabMenuItem = new JCheckBoxMenuItem(NLS.str("menu.enable_preview_tab")); + enablePreviewTabMenuItem.setState(settings.isEnablePreviewTab()); + enablePreviewTabMenuItem.addActionListener(event -> { + settings.setEnablePreviewTab(!settings.isEnablePreviewTab()); + }); + JCheckBoxMenuItem heapUsageBarMenuItem = new JCheckBoxMenuItem(NLS.str("menu.heapUsageBar")); heapUsageBarMenuItem.setState(settings.isShowHeapUsageBar()); heapUsageBarMenuItem.addActionListener(event -> { @@ -1153,6 +1159,7 @@ public class MainWindow extends JFrame { view.add(quickTabsAction.makeCheckBoxMenuItem()); view.add(flatPkgMenuItem); view.addSeparator(); + view.add(enablePreviewTabMenuItem); view.add(syncAction); view.add(alwaysSelectOpened); view.addSeparator(); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/tab/TabBlueprint.java b/jadx-gui/src/main/java/jadx/gui/ui/tab/TabBlueprint.java index 1ccf6d50d..da402c970 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/tab/TabBlueprint.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/tab/TabBlueprint.java @@ -9,6 +9,7 @@ public class TabBlueprint { private boolean pinned; private boolean bookmarked; private boolean hidden; + private boolean previewTab; public TabBlueprint(JNode node) { this.node = Objects.requireNonNull(node); @@ -50,6 +51,14 @@ public class TabBlueprint { this.hidden = hidden; } + public boolean isPreviewTab() { + return previewTab; + } + + public void setPreviewTab(boolean previewTab) { + this.previewTab = previewTab; + } + @Override public final boolean equals(Object o) { if (this == o) { @@ -68,10 +77,11 @@ public class TabBlueprint { @Override public String toString() { - return "TabBlueprint{node=" + node - + ", bookmarked=" + bookmarked - + ", pinned=" + pinned - + ", hidden=" + hidden - + '}'; + return "TabBlueprint{" + "node=" + + node + ", pinned=" + + pinned + ", bookmarked=" + + bookmarked + ", hidden=" + + hidden + ", previewTab=" + + previewTab + '}'; } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/tab/TabComponent.java b/jadx-gui/src/main/java/jadx/gui/ui/tab/TabComponent.java index 4ebc1e424..55eda883f 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/tab/TabComponent.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/tab/TabComponent.java @@ -75,7 +75,7 @@ public class TabComponent extends JPanel { icon = new OverlayIcon(node.getIcon()); label = new NodeLabel(buildTabTitle(node), node.disableHtml()); - label.setFont(getLabelFont()); + makeLabelFont(); String toolTip = contentPanel.getTabTooltip(); if (toolTip != null) { setToolTipText(toolTip); @@ -174,6 +174,16 @@ public class TabComponent extends JPanel { tabsController.setTabBookmarked(getNode(), bookmarked); } + private void makeLabelFont() { + boolean previewTab = getBlueprint().isPreviewTab(); + if (previewTab) { + Font newLabelFont = new Font(label.getFont().getName(), Font.ITALIC, label.getFont().getSize()); + label.setFont(newLabelFont); + } else { + label.setFont(getLabelFont()); + } + } + private void addListenerForDnd() { if (tabbedPane.getDnd() == null) { return; diff --git a/jadx-gui/src/main/java/jadx/gui/ui/tab/TabsController.java b/jadx-gui/src/main/java/jadx/gui/ui/tab/TabsController.java index d4156b157..ba579cad0 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/tab/TabsController.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/tab/TabsController.java @@ -32,7 +32,6 @@ public class TabsController { private final List listeners = new ArrayList<>(); private boolean forceClose; - private @Nullable TabBlueprint selectedTab; public TabsController(MainWindow mainWindow) { @@ -57,15 +56,20 @@ public class TabsController { } public TabBlueprint openTab(JNode node) { - return openTab(node, false); + return openTab(node, false, false); } public TabBlueprint openTab(JNode node, boolean hidden) { + return openTab(node, hidden, false); + } + + public TabBlueprint openTab(JNode node, boolean hidden, boolean preview) { TabBlueprint blueprint = getTabByNode(node); if (blueprint == null) { TabBlueprint newBlueprint = new TabBlueprint(node); - tabsMap.put(node, newBlueprint); newBlueprint.setHidden(hidden); + newBlueprint.setPreviewTab(preview); + tabsMap.put(node, newBlueprint); listeners.forEach(l -> l.onTabOpen(newBlueprint)); if (hidden) { listeners.forEach(l -> l.onTabVisibilityChange(newBlueprint)); @@ -76,20 +80,45 @@ public class TabsController { return blueprint; } + public TabBlueprint previewTab(JNode node) { + TabBlueprint blueprint = getPreviewTab(); + if (blueprint != null) { + closeTabForce(blueprint); + } + + blueprint = openTab(node, false, true); + + return blueprint; + } + public void selectTab(JNode node) { + selectTab(node, false); + } + + public void selectTab(JNode node, boolean fromTree) { if (selectedTab != null && selectedTab.getNode() == node) { // already selected return; } - TabBlueprint blueprint = openTab(node); - selectedTab = blueprint; - listeners.forEach(l -> l.onTabSelect(blueprint)); + if (mainWindow.getSettings().isEnablePreviewTab() && fromTree) { + selectedTab = previewTab(node); + } else { + selectedTab = openTab(node); + } + listeners.forEach(l -> l.onTabSelect(selectedTab)); } /** * Jump to node definition */ public void codeJump(JNode node) { + codeJump(node, false); + } + + /** + * Jump to node definition + */ + public void codeJump(JNode node, boolean fromTree) { JClass parentCls = node.getJParent(); if (parentCls != null) { JavaClass cls = node.getJParent().getCls(); @@ -97,23 +126,23 @@ public class TabsController { JavaClass codeParent = cls.getTopParentClass(); if (!Objects.equals(codeParent, origTopCls)) { JClass jumpCls = mainWindow.getCacheObject().getNodeCache().makeFrom(codeParent); - loadCodeWithUIAction(jumpCls, () -> jumpToInnerClass(node, codeParent, jumpCls)); + loadCodeWithUIAction(jumpCls, () -> jumpToInnerClass(node, codeParent, jumpCls, fromTree)); return; } } // Not an inline node, jump normally if (node.getPos() > 0) { - codeJump(new JumpPosition(node)); + codeJump(new JumpPosition(node), fromTree); return; } if (node.getRootClass() == null) { // not a class, select tab without position scroll - selectTab(node); + selectTab(node, fromTree); return; } // node need loading - loadCodeWithUIAction(node.getRootClass(), () -> codeJump(new JumpPosition(node))); + loadCodeWithUIAction(node.getRootClass(), () -> codeJump(new JumpPosition(node), fromTree)); } private void loadCodeWithUIAction(JClass cls, Runnable action) { @@ -129,12 +158,12 @@ public class TabsController { /** * Search and jump to original node in jumpCls */ - private void jumpToInnerClass(JNode node, JavaClass codeParent, JClass jumpCls) { + private void jumpToInnerClass(JNode node, JavaClass codeParent, JClass jumpCls, boolean fromTree) { codeParent.getCodeInfo().getCodeMetadata().searchDown(0, (pos, ann) -> { if (ann.getAnnType() == ICodeAnnotation.AnnType.DECLARATION) { ICodeNodeRef declNode = ((NodeDeclareRef) ann).getNode(); if (declNode.equals(node.getJavaNode().getCodeNodeRef())) { - codeJump(new JumpPosition(jumpCls, pos)); + codeJump(new JumpPosition(jumpCls, pos), fromTree); return true; } } @@ -142,13 +171,17 @@ public class TabsController { }); } + public void codeJump(JumpPosition pos) { + codeJump(pos, false); + } + /** * Prefer {@link TabsController#codeJump(JNode)} method */ - public void codeJump(JumpPosition pos) { + public void codeJump(JumpPosition pos, boolean fromTree) { JumpPosition currentPosition = mainWindow.getTabbedPane().getCurrentPosition(); if (selectedTab == null || selectedTab.getNode() != pos.getNode()) { - selectTab(pos.getNode()); + selectTab(pos.getNode(), fromTree); } listeners.forEach(l -> l.onTabCodeJump(selectedTab, currentPosition, pos)); } @@ -298,6 +331,11 @@ public class TabsController { .collect(Collectors.toUnmodifiableList()); } + public TabBlueprint getPreviewTab() { + return tabsMap.values().stream() + .filter(TabBlueprint::isPreviewTab).findFirst().orElse(null); + } + public void restoreEditorViewState(EditorViewState viewState) { JNode node = viewState.getNode(); TabBlueprint blueprint = openTab(node, viewState.isHidden()); 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 032c1cb88..2f4fdff9d 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties @@ -7,6 +7,7 @@ menu.no_recent_projects=Keine aktuellen Projekte menu.preferences=Einstellungen menu.sync=Mit Editor synchronisieren menu.flatten=Codepaket erweitern +#menu.enable_preview_tab=Enable Preview Tab menu.heapUsageBar=Speicherverbrauchsleiste anzeigen menu.alwaysSelectOpened=Immer geöffnete Datei/Klasse auswählen menu.dock_log=Dock-Protokollanzeige 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 dfc8e4112..829807aba 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -7,6 +7,7 @@ menu.no_recent_projects=No recent projects menu.preferences=Preferences menu.sync=Select in Tree menu.flatten=Use Flatten Packages +menu.enable_preview_tab=Enable Preview Tab menu.heapUsageBar=Show Memory Usage Bar menu.alwaysSelectOpened=Auto Select in Tree menu.dock_log=Dock Log Viewer 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 f5fb4c93f..d90653cf4 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties @@ -7,6 +7,7 @@ menu.view=Vista menu.preferences=Preferencias menu.sync=Sincronizar con el editor menu.flatten=Mostrar paquetes en vista plana +#menu.enable_preview_tab=Enable Preview Tab #menu.heapUsageBar=Show memory usage bar #menu.alwaysSelectOpened=Always Select Opened File/Class #menu.dock_log=Dock log viewer diff --git a/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties b/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties index 0e70ea335..9948d11ae 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties @@ -7,6 +7,7 @@ menu.no_recent_projects=Tidak ada proyek terbaru menu.preferences=Preferensi menu.sync=Sinkronisasi dengan editor menu.flatten=Tampilkan paket yang diratakan +#menu.enable_preview_tab=Enable Preview Tab menu.heapUsageBar=Tampilkan penggunaan memori menu.alwaysSelectOpened=Selalu Pilih Berkas/Kelas yang Terbuka menu.dock_log=Kaitkan pemantau log 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 732f55299..7ea352f45 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties @@ -7,6 +7,7 @@ menu.no_recent_projects=최근 프로젝트 없음 menu.preferences=설정 menu.sync=에디터와 동기화 menu.flatten=플랫 패키지 표시 +#menu.enable_preview_tab=Enable Preview Tab menu.heapUsageBar=메모리 사용량 표시 menu.alwaysSelectOpened=항상 열린 파일/클래스 선택 #menu.dock_log=Dock log viewer diff --git a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties index ffacb1a83..407d39d62 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties @@ -7,6 +7,7 @@ menu.no_recent_projects=Nenhum projeto recente menu.preferences=Preferências menu.sync=Sincronizar com editor menu.flatten=Mostrar pacotes achatados +#menu.enable_preview_tab=Enable Preview Tab menu.heapUsageBar=Mostrar uso de memória menu.alwaysSelectOpened=Sempre selecionar arquivo/classe aberta #menu.dock_log=Dock log viewer diff --git a/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties b/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties index 18a435864..bcf5b4c84 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties @@ -7,6 +7,7 @@ menu.no_recent_projects=Нет недавно открытых проектов menu.preferences=Параметры menu.sync=Синхр. файлы с редактором menu.flatten=Плоская структура пакетов +#menu.enable_preview_tab=Enable Preview Tab menu.heapUsageBar=Использование ОЗУ menu.alwaysSelectOpened=Выбирать открытый файл/класс menu.dock_log=Просмотр логов в панели 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 97d5bbd94..819704690 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties @@ -7,6 +7,7 @@ menu.no_recent_projects=没有最近的项目 menu.preferences=首选项 menu.sync=与编辑器同步 menu.flatten=展开显示代码包 +#menu.enable_preview_tab=Enable Preview Tab menu.heapUsageBar=显示内存使用栏 menu.alwaysSelectOpened=始终选中打开的文件/类 menu.dock_log=停靠日志查看器 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 cb50beb96..7552ea6d8 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties @@ -7,6 +7,7 @@ menu.no_recent_projects=無近期專案 menu.preferences=選項 menu.sync=與編輯器同步 menu.flatten=展開顯示套件 +#menu.enable_preview_tab=Enable Preview Tab menu.heapUsageBar=顯示記憶體使用率條 menu.alwaysSelectOpened=總是選擇已開啟的檔案/類別 menu.dock_log=固定記錄檔檢視器