diff --git a/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java b/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java index bf887198c45..0d6fbea8562 100644 --- a/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java +++ b/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java @@ -138,9 +138,17 @@ protected TaskStatus doInBackground() throws Exception { task.onDone(this); // treat UI task operations as part of the task to not mix with others UiUtils.uiRunAndWait(() -> { - progressPane.setVisible(false); - task.onFinish(this); + try { + progressPane.setVisible(false); + task.onFinish(this); + } catch (Throwable e) { + LOG.error("Task onFinish failed", e); + status = TaskStatus.ERROR; + } }); + } catch (Throwable e) { + LOG.error("Task onDone failed", e); + status = TaskStatus.ERROR; } finally { taskComplete(id); progressPane.changeVisibility(this, false); diff --git a/jadx-gui/src/main/java/jadx/gui/logs/LogPanel.java b/jadx-gui/src/main/java/jadx/gui/logs/LogPanel.java index d0770b2d2f9..35802ec73e2 100644 --- a/jadx-gui/src/main/java/jadx/gui/logs/LogPanel.java +++ b/jadx-gui/src/main/java/jadx/gui/logs/LogPanel.java @@ -23,7 +23,7 @@ import jadx.gui.treemodel.JNode; import jadx.gui.ui.MainWindow; import jadx.gui.ui.codearea.AbstractCodeArea; -import jadx.gui.ui.panel.ContentPanel; +import jadx.gui.ui.tab.TabBlueprint; import jadx.gui.utils.NLS; public class LogPanel extends JPanel { @@ -141,9 +141,9 @@ private void registerLogListener(LogOptions logOptions) { } private @Nullable String getCurrentScriptName() { - ContentPanel selectedCodePanel = mainWindow.getTabbedPane().getSelectedContentPanel(); - if (selectedCodePanel != null) { - JNode node = selectedCodePanel.getNode(); + TabBlueprint selectedTab = mainWindow.getTabsController().getSelectedTab(); + if (selectedTab != null) { + JNode node = selectedTab.getNode(); if (node instanceof JInputScript) { return node.getName(); } diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/context/GuiPluginContext.java b/jadx-gui/src/main/java/jadx/gui/plugins/context/GuiPluginContext.java index 9b9f9a5ab0b..eaa4f1b7d5d 100644 --- a/jadx-gui/src/main/java/jadx/gui/plugins/context/GuiPluginContext.java +++ b/jadx-gui/src/main/java/jadx/gui/plugins/context/GuiPluginContext.java @@ -179,7 +179,7 @@ public boolean open(ICodeNodeRef ref) { return false; } - commonContext.getMainWindow().getTabbedPane().codeJump(node); + commonContext.getMainWindow().getTabsController().codeJump(node); return true; } diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkManager.java b/jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkManager.java index 6b864dd9311..5b9e8af62d9 100644 --- a/jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkManager.java +++ b/jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkManager.java @@ -114,7 +114,7 @@ private void loadReport() { root.replaceCustomNode(quarkNode); root.update(); mainWindow.reloadTree(); - mainWindow.getTabbedPane().showNode(quarkNode); + mainWindow.getTabsController().selectTab(quarkNode); } catch (Exception e) { UiUtils.errorMessage(mainWindow, "Failed to load Quark report."); LOG.error("Failed to load Quark report.", e); diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkReportPanel.java b/jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkReportPanel.java index 18213572de0..313fe8ecc9b 100644 --- a/jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkReportPanel.java +++ b/jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkReportPanel.java @@ -117,7 +117,7 @@ public void mouseClicked(MouseEvent event) { Object node = getNodeUnderMouse(tree, event); if (node instanceof MethodTreeNode) { JMethod method = ((MethodTreeNode) node).getJMethod(); - tabbedPane.codeJump(method); + tabbedPane.getTabsController().codeJump(method); } } } @@ -159,7 +159,7 @@ private String buildHeader() { @Override public void loadSettings() { - Font settingsFont = getTabbedPane().getMainWindow().getSettings().getFont(); + Font settingsFont = getMainWindow().getSettings().getFont(); this.font = settingsFont.deriveFont(settingsFont.getSize2D() + 1.f); this.boldFont = font.deriveFont(Font.BOLD); header.setFont(font); @@ -279,7 +279,7 @@ public MutableTreeNode resolveMethod(String descr) { String[] parts = removeQuotes(descr).split(" ", 3); String cls = Utils.cleanObjectName(parts[0].replace('$', '.')); String mth = parts[1] + parts[2].replace(" ", ""); - MainWindow mainWindow = getTabbedPane().getMainWindow(); + MainWindow mainWindow = getMainWindow(); JadxWrapper wrapper = mainWindow.getWrapper(); JavaClass javaClass = wrapper.searchJavaClassByRawName(cls); if (javaClass == null) { diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptCodeArea.java b/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptCodeArea.java index d7af18ae349..d088577ae62 100644 --- a/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptCodeArea.java +++ b/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptCodeArea.java @@ -25,8 +25,8 @@ public ScriptCodeArea(ContentPanel contentPanel, JInputScript node) { setCodeFoldingEnabled(true); setCloseCurlyBraces(true); - shortcutsController = contentPanel.getTabbedPane().getMainWindow().getShortcutsController(); - JadxSettings settings = contentPanel.getTabbedPane().getMainWindow().getSettings(); + shortcutsController = contentPanel.getMainWindow().getShortcutsController(); + JadxSettings settings = contentPanel.getMainWindow().getSettings(); autoCompletion = addAutoComplete(settings); } diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptContentPanel.java b/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptContentPanel.java index 5d60687fc6c..2da8a81403c 100644 --- a/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptContentPanel.java +++ b/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptContentPanel.java @@ -232,7 +232,7 @@ private void applySettings() { } private void showScriptLog() { - getTabbedPane().getMainWindow().showLogViewer(LogOptions.forScript(getNode().getName())); + getMainWindow().showLogViewer(LogOptions.forScript(getNode().getName())); } @Override diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java index aab77ba4912..88f7b18c984 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java @@ -40,8 +40,7 @@ public ICodeNodeRef getCodeNodeRef() { return null; } - @Nullable - public ContentPanel getContentPanel(TabbedPane tabbedPane) { + public @Nullable ContentPanel getContentPanel(TabbedPane tabbedPane) { return null; } 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 1d91d66061a..bebab60ebc4 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -139,6 +139,8 @@ import jadx.gui.ui.panel.JDebuggerPanel; import jadx.gui.ui.panel.ProgressPanel; import jadx.gui.ui.popupmenu.RecentProjectsMenuListener; +import jadx.gui.ui.tab.EditorSyncManager; +import jadx.gui.ui.tab.NavigationController; import jadx.gui.ui.tab.QuickTabsTree; import jadx.gui.ui.tab.TabbedPane; import jadx.gui.ui.tab.TabsController; @@ -195,6 +197,10 @@ public class MainWindow extends JFrame { private final transient CacheManager cacheManager; private final transient BackgroundExecutor backgroundExecutor; + private final TabsController tabsController; + private final NavigationController navController; + private final EditorSyncManager editorSyncManager; + private transient @NotNull JadxProject project; private transient JadxGuiAction newProjectAction; @@ -209,7 +215,6 @@ public class MainWindow extends JFrame { private JTree tree; private DefaultTreeModel treeModel; private JRoot treeRoot; - private TabsController tabsController; private TabbedPane tabbedPane; private HeapUsageBar heapUsageBar; private transient boolean treeReloading; @@ -238,7 +243,7 @@ public class MainWindow extends JFrame { private boolean loaded; private boolean settingsOpen = false; - private ShortcutsController shortcutsController; + private final ShortcutsController shortcutsController; private JadxMenuBar menuBar; private JMenu pluginsMenu; @@ -253,16 +258,19 @@ public MainWindow(JadxSettings settings) { this.renameMappings = new RenameMappingsGui(this); this.cacheManager = new CacheManager(settings); this.shortcutsController = new ShortcutsController(settings); + this.tabsController = new TabsController(this); + this.navController = new NavigationController(this); JadxEventQueue.register(); resetCache(); FontUtils.registerBundledFonts(); setEditorTheme(settings.getEditorThemePath()); initUI(); + this.editorSyncManager = new EditorSyncManager(this, tabbedPane); this.backgroundExecutor = new BackgroundExecutor(settings, progressPane); initMenuAndToolbar(); UiUtils.setWindowIcons(this); - shortcutsController.registerMouseEventListener(this); + this.shortcutsController.registerMouseEventListener(this); loadSettings(); update(); @@ -288,7 +296,7 @@ public void windowClosing(WindowEvent e) { private void processCommandLineArgs() { if (settings.getFiles().isEmpty()) { - tabbedPane.showNode(new StartPageNode()); + tabsController.selectTab(new StartPageNode()); } else { open(FileUtils.fileNamesToPaths(settings.getFiles()), this::handleSelectClassOption); } @@ -306,7 +314,7 @@ private void handleSelectClassOption() { NLS.str("error_dialog.title"), JOptionPane.ERROR_MESSAGE); return; } - tabbedPane.codeJump(cacheObject.getNodeCache().makeFrom(javaNode)); + tabsController.codeJump(cacheObject.getNodeCache().makeFrom(javaNode)); } } @@ -517,7 +525,7 @@ private void openProject(Path path, Runnable onFinish) { private void loadFiles(Runnable onFinish) { if (project.getFilePaths().isEmpty()) { - tabbedPane.showNode(new StartPageNode()); + tabsController.selectTab(new StartPageNode()); return; } AtomicReference wrapperException = new AtomicReference<>(); @@ -562,6 +570,8 @@ private void saveAll() { private void closeAll() { notifyLoadListeners(false); cancelBackgroundJobs(); + navController.reset(); + tabbedPane.reset(); clearTree(); resetCache(); LogCollector.getInstance().reset(); @@ -783,7 +793,6 @@ public void initTree() { } private void clearTree() { - tabbedPane.reset(); treeRoot = null; treeModel.setRoot(null); treeModel.reload(); @@ -864,15 +873,17 @@ private boolean nodeClickAction(@Nullable Object obj) { JResource res = (JResource) obj; ResourceFile resFile = res.getResFile(); if (resFile != null && JResource.isSupportedForView(resFile.getType())) { - return tabbedPane.showNode(res); + tabsController.selectTab(res); + return true; } } else if (obj instanceof JNode) { JNode node = (JNode) obj; if (node.getRootClass() != null) { - tabbedPane.codeJump(node); + tabsController.codeJump(node); return true; } - return tabbedPane.showNode(node); + tabsController.selectTab(node); + return true; } } catch (Exception e) { LOG.error("Content loading error", e); @@ -901,12 +912,8 @@ private JNode getJNodeUnderMouse(MouseEvent mouseEvent) { return null; } - public void syncWithEditor() { - ContentPanel selectedContentPanel = tabbedPane.getSelectedContentPanel(); - if (selectedContentPanel == null) { - return; - } - JNode node = selectedContentPanel.getNode(); + // TODO: extract tree component into new class + public void selectNodeInTree(JNode node) { if (node.getParent() == null && treeRoot != null) { // node not register in tree node = treeRoot.searchNode(node); @@ -962,7 +969,7 @@ public void goToMainActivity() { if (mainActivityClass == null) { throw new JadxRuntimeException("Failed to find main activity class: " + results.getMainActivity()); } - tabbedPane.codeJump(getCacheObject().getNodeCache().makeFrom(mainActivityClass)); + tabsController.codeJump(getCacheObject().getNodeCache().makeFrom(mainActivityClass)); } catch (Exception e) { LOG.error("Main activity not found", e); JOptionPane.showMessageDialog(MainWindow.this, @@ -992,7 +999,7 @@ public void goToApplication() { if (applicationClass == null) { throw new JadxRuntimeException("Failed to find application class: " + results.getApplication()); } - tabbedPane.codeJump(getCacheObject().getNodeCache().makeFrom(applicationClass)); + tabsController.codeJump(getCacheObject().getNodeCache().makeFrom(applicationClass)); } catch (Exception e) { LOG.error("Application not found", e); JOptionPane.showMessageDialog(MainWindow.this, @@ -1042,7 +1049,7 @@ private void initMenuAndToolbar() { alwaysSelectOpened.addActionListener(event -> { settings.setAlwaysSelectOpened(!settings.isAlwaysSelectOpened()); if (settings.isAlwaysSelectOpened()) { - this.syncWithEditor(); + this.editorSyncManager.sync(); } }); @@ -1061,7 +1068,7 @@ private void initMenuAndToolbar() { setQuickTabsVisibility(true); } - JadxGuiAction syncAction = new JadxGuiAction(ActionModel.SYNC, this::syncWithEditor); + JadxGuiAction syncAction = new JadxGuiAction(ActionModel.SYNC, this.editorSyncManager::sync); JadxGuiAction textSearchAction = new JadxGuiAction(ActionModel.TEXT_SEARCH, this::textSearch); JadxGuiAction clsSearchAction = new JadxGuiAction(ActionModel.CLASS_SEARCH, () -> SearchDialog.search(MainWindow.this, SearchDialog.SearchPreset.CLASS)); @@ -1085,10 +1092,10 @@ private void initMenuAndToolbar() { JadxGuiAction showLogAction = new JadxGuiAction(ActionModel.SHOW_LOG, () -> showLogViewer(LogOptions.current())); JadxGuiAction aboutAction = new JadxGuiAction(ActionModel.ABOUT, () -> new AboutDialog().setVisible(true)); - JadxGuiAction backAction = new JadxGuiAction(ActionModel.BACK, tabbedPane::navBack); - JadxGuiAction backVariantAction = new JadxGuiAction(ActionModel.BACK_V, tabbedPane::navBack); - JadxGuiAction forwardAction = new JadxGuiAction(ActionModel.FORWARD, tabbedPane::navForward); - JadxGuiAction forwardVariantAction = new JadxGuiAction(ActionModel.FORWARD_V, tabbedPane::navForward); + JadxGuiAction backAction = new JadxGuiAction(ActionModel.BACK, navController::navBack); + JadxGuiAction backVariantAction = new JadxGuiAction(ActionModel.BACK_V, navController::navBack); + JadxGuiAction forwardAction = new JadxGuiAction(ActionModel.FORWARD, navController::navForward); + JadxGuiAction forwardVariantAction = new JadxGuiAction(ActionModel.FORWARD_V, navController::navForward); JadxGuiAction quarkAction = new JadxGuiAction(ActionModel.QUARK, () -> new QuarkDialog(MainWindow.this).setVisible(true)); JadxGuiAction openDeviceAction = new JadxGuiAction(ActionModel.OPEN_DEVICE, @@ -1345,7 +1352,6 @@ public void treeWillCollapse(TreeExpansionEvent event) { leftPane.add(bottomPane, BorderLayout.PAGE_END); treeSplitPane.setLeftComponent(leftPane); - tabsController = new TabsController(this); tabbedPane = new TabbedPane(this, tabsController); tabbedPane.setMinimumSize(new Dimension(150, 150)); new TabDndController(tabbedPane, settings); @@ -1581,6 +1587,10 @@ public TabsController getTabsController() { return tabsController; } + public NavigationController getNavController() { + return navController; + } + public JadxSettings getSettings() { return settings; } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeArea.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeArea.java index 222c4212020..6756d24784e 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeArea.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeArea.java @@ -90,7 +90,7 @@ public AbstractCodeArea(ContentPanel contentPanel, JNode node) { applyEditableProperties(node); loadSettings(); - JadxSettings settings = contentPanel.getTabbedPane().getMainWindow().getSettings(); + JadxSettings settings = contentPanel.getMainWindow().getSettings(); setLineWrap(settings.isCodeAreaLineWrap()); ZoomActions.register(this, settings, this::loadSettings); @@ -147,7 +147,7 @@ protected void appendFoldingMenu(JPopupMenu popup) { } private void appendWrapLineMenu(JPopupMenu popupMenu) { - JadxSettings settings = contentPanel.getTabbedPane().getMainWindow().getSettings(); + JadxSettings settings = contentPanel.getMainWindow().getSettings(); popupMenu.addSeparator(); JCheckBoxMenuItem wrapItem = new JCheckBoxMenuItem(NLS.str("popup.line_wrap"), getLineWrap()); wrapItem.setAction(new AbstractAction(NLS.str("popup.line_wrap")) { @@ -378,7 +378,7 @@ public static void loadCommonSettings(MainWindow mainWindow, RSyntaxTextArea are } public void loadSettings() { - loadCommonSettings(contentPanel.getTabbedPane().getMainWindow(), this); + loadCommonSettings(contentPanel.getMainWindow(), this); } public void scrollToPos(int pos) { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java index d117378571e..414fcfd5442 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java @@ -87,7 +87,7 @@ private void navToDecl(Point point) { int offs = viewToModel2D(point); JNode node = getJNodeAtOffset(adjustOffsetForWordToken(offs)); if (node != null) { - contentPanel.getTabbedPane().codeJump(node); + contentPanel.getTabsController().codeJump(node); } } @@ -332,7 +332,7 @@ public void refreshClass() { } public MainWindow getMainWindow() { - return contentPanel.getTabbedPane().getMainWindow(); + return contentPanel.getMainWindow(); } public JadxWrapper getJadxWrapper() { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodePanel.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodePanel.java index 49601020375..ce4ff7eb5ec 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodePanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodePanel.java @@ -82,7 +82,7 @@ public void actionPerformed(ActionEvent e) { AbstractAction globalSearchAction = new AbstractAction(NLS.str("popup.search_global", "")) { @Override public void actionPerformed(ActionEvent e) { - MainWindow mainWindow = codeArea.getContentPanel().getTabbedPane().getMainWindow(); + MainWindow mainWindow = codeArea.getContentPanel().getMainWindow(); SearchDialog.searchText(mainWindow, codeArea.getSelectedText()); } }; diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/GoToDeclarationAction.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/GoToDeclarationAction.java index 3fc92b74373..4b1b4531c45 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/GoToDeclarationAction.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/GoToDeclarationAction.java @@ -12,6 +12,6 @@ public GoToDeclarationAction(CodeArea codeArea) { @Override public void runAction(JNode node) { - getCodeArea().getContentPanel().getTabbedPane().codeJump(node); + getCodeArea().getContentPanel().getTabsController().codeJump(node); } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/HexArea.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/HexArea.java index 4478c275ae6..479afe20c0a 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/HexArea.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/HexArea.java @@ -85,10 +85,10 @@ public void loadSettings() { } private void applyTheme() { - Font font = getContentPanel().getTabbedPane().getMainWindow().getSettings().getSmaliFont(); + Font font = getContentPanel().getMainWindow().getSettings().getSmaliFont(); setFont(font); - Theme theme = contentPanel.getTabbedPane().getMainWindow().getEditorTheme(); + Theme theme = contentPanel.getMainWindow().getEditorTheme(); if (hexPreviewPanel != null) { hexPreviewPanel.applyTheme(theme, font); } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/SmaliArea.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/SmaliArea.java index 7b381047176..7d21b1f47ce 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/SmaliArea.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/SmaliArea.java @@ -74,7 +74,7 @@ public final class SmaliArea extends AbstractCodeArea { @Override public void actionPerformed(ActionEvent e) { - JadxSettings settings = getContentPanel().getTabbedPane().getMainWindow().getSettings(); + JadxSettings settings = getContentPanel().getMainWindow().getSettings(); settings.setSmaliAreaShowBytecode(!settings.getSmaliAreaShowBytecode()); contentPanel.getTabbedPane().getTabs().forEach(v -> { if (v instanceof ClassCodeContentPanel) { @@ -127,7 +127,7 @@ private void switchModel() { } public void scrollToDebugPos(int pos) { - getContentPanel().getTabbedPane().getMainWindow() + getContentPanel().getMainWindow() .getSettings().setSmaliAreaShowBytecode(true); // don't sync when it's set programmatically. cbUseSmaliV2.setState(shouldUseSmaliPrinterV2()); if (!(model instanceof DebugModel)) { @@ -151,7 +151,7 @@ public Font getFontForTokenType(int type) { } private boolean shouldUseSmaliPrinterV2() { - return getContentPanel().getTabbedPane().getMainWindow().getSettings().getSmaliAreaShowBytecode(); + return getContentPanel().getMainWindow().getSettings().getSmaliAreaShowBytecode(); } private abstract class SmaliModel { @@ -177,7 +177,7 @@ void togglePosHighlight(int pos) { private class NormalModel extends SmaliModel { public NormalModel() { - Theme theme = getContentPanel().getTabbedPane().getMainWindow().getEditorTheme(); + Theme theme = getContentPanel().getMainWindow().getEditorTheme(); setSyntaxScheme(theme.scheme); setSyntaxEditingStyle(SYNTAX_STYLE_SMALI); } @@ -323,16 +323,16 @@ private class SmaliV2Style extends SyntaxScheme { public SmaliV2Style(SmaliArea smaliArea) { super(true); - curTheme = smaliArea.getContentPanel().getTabbedPane().getMainWindow().getEditorTheme(); + curTheme = smaliArea.getContentPanel().getMainWindow().getEditorTheme(); updateTheme(); } public Font getFont() { - return getContentPanel().getTabbedPane().getMainWindow().getSettings().getSmaliFont(); + return getContentPanel().getMainWindow().getSettings().getSmaliFont(); } public boolean refreshTheme() { - Theme theme = getContentPanel().getTabbedPane().getMainWindow().getEditorTheme(); + Theme theme = getContentPanel().getMainWindow().getEditorTheme(); boolean refresh = theme != curTheme; if (refresh) { curTheme = theme; diff --git a/jadx-gui/src/main/java/jadx/gui/ui/dialog/CommonSearchDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/dialog/CommonSearchDialog.java index 53dca461d44..8e0007d5d56 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/dialog/CommonSearchDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/dialog/CommonSearchDialog.java @@ -52,7 +52,7 @@ import jadx.gui.ui.MainWindow; import jadx.gui.ui.codearea.AbstractCodeArea; import jadx.gui.ui.panel.ProgressPanel; -import jadx.gui.ui.tab.TabbedPane; +import jadx.gui.ui.tab.TabsController; import jadx.gui.utils.CacheObject; import jadx.gui.utils.JNodeCache; import jadx.gui.utils.JumpPosition; @@ -67,7 +67,7 @@ public abstract class CommonSearchDialog extends JFrame { private static final Logger LOG = LoggerFactory.getLogger(CommonSearchDialog.class); private static final long serialVersionUID = 8939332306115370276L; - protected final transient TabbedPane tabbedPane; + protected final transient TabsController tabsController; protected final transient CacheObject cache; protected final transient MainWindow mainWindow; protected final transient Font codeFont; @@ -84,7 +84,7 @@ public abstract class CommonSearchDialog extends JFrame { public CommonSearchDialog(MainWindow mainWindow, String title) { this.mainWindow = mainWindow; - this.tabbedPane = mainWindow.getTabbedPane(); + this.tabsController = mainWindow.getTabsController(); this.cache = mainWindow.getCacheObject(); this.codeFont = mainWindow.getSettings().getFont(); this.windowTitle = title; @@ -145,9 +145,9 @@ protected void openSelectedItem() { protected void openItem(JNode node) { if (node instanceof JResSearchNode) { JumpPosition jmpPos = new JumpPosition(((JResSearchNode) node).getResNode(), node.getPos()); - tabbedPane.codeJump(jmpPos); + tabsController.codeJump(jmpPos); } else { - tabbedPane.codeJump(node); + tabsController.codeJump(node); } if (!mainWindow.getSettings().getKeepCommonDialogOpen()) { dispose(); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/panel/ContentPanel.java b/jadx-gui/src/main/java/jadx/gui/ui/panel/ContentPanel.java index 2825044b578..20f1e1ba4b9 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/panel/ContentPanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/panel/ContentPanel.java @@ -7,7 +7,9 @@ import jadx.gui.settings.JadxSettings; import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JNode; +import jadx.gui.ui.MainWindow; import jadx.gui.ui.tab.TabbedPane; +import jadx.gui.ui.tab.TabsController; public abstract class ContentPanel extends JPanel { @@ -27,6 +29,14 @@ public TabbedPane getTabbedPane() { return tabbedPane; } + public TabsController getTabsController() { + return tabbedPane.getTabsController(); + } + + public MainWindow getMainWindow() { + return tabbedPane.getMainWindow(); + } + public JNode getNode() { return node; } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/panel/HtmlPanel.java b/jadx-gui/src/main/java/jadx/gui/ui/panel/HtmlPanel.java index 735c253529d..d9117b7a10b 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/panel/HtmlPanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/panel/HtmlPanel.java @@ -34,7 +34,7 @@ public HtmlPanel(TabbedPane panel, JNode jnode) { @Override public void loadSettings() { - JadxSettings settings = getTabbedPane().getMainWindow().getSettings(); + JadxSettings settings = getMainWindow().getSettings(); textArea.setFont(settings.getFont()); } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/panel/JDebuggerPanel.java b/jadx-gui/src/main/java/jadx/gui/ui/panel/JDebuggerPanel.java index d98006f15fb..540f66faece 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/panel/JDebuggerPanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/panel/JDebuggerPanel.java @@ -445,7 +445,7 @@ public void resetUI() { } public void scrollToSmaliLine(JClass cls, int pos, boolean debugMode) { - SwingUtilities.invokeLater(() -> getMainWindow().getTabbedPane().smaliJump(cls, pos, debugMode)); + SwingUtilities.invokeLater(() -> getMainWindow().getTabsController().smaliJump(cls, pos, debugMode)); } public void resetAllDebuggingInfo() { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/tab/EditorSyncManager.java b/jadx-gui/src/main/java/jadx/gui/ui/tab/EditorSyncManager.java new file mode 100644 index 00000000000..1485a8e8d16 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/tab/EditorSyncManager.java @@ -0,0 +1,33 @@ +package jadx.gui.ui.tab; + +import jadx.gui.ui.MainWindow; +import jadx.gui.ui.panel.ContentPanel; + +public class EditorSyncManager implements ITabStatesListener { + private final MainWindow mainWindow; + private final TabbedPane tabbedPane; + + public EditorSyncManager(MainWindow mainWindow, TabbedPane tabbedPane) { + this.mainWindow = mainWindow; + this.tabbedPane = tabbedPane; + mainWindow.getTabsController().addListener(this); + } + + public void sync() { + ContentPanel selectedContentPanel = tabbedPane.getSelectedContentPanel(); + if (selectedContentPanel != null) { + mainWindow.selectNodeInTree(selectedContentPanel.getNode()); + } + } + + @Override + public void onTabSelect(TabBlueprint blueprint) { + if (mainWindow.getSettings().isAlwaysSelectOpened()) { + // verify that tab opened for this blueprint (some nodes don't open tab with content) + ContentPanel selectedContentPanel = tabbedPane.getSelectedContentPanel(); + if (selectedContentPanel != null && selectedContentPanel.getNode().equals(blueprint.getNode())) { + sync(); + } + } + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/tab/ITabStatesListener.java b/jadx-gui/src/main/java/jadx/gui/ui/tab/ITabStatesListener.java index bbc3cdcc03e..b99736b6faa 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/tab/ITabStatesListener.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/tab/ITabStatesListener.java @@ -3,27 +3,47 @@ import java.util.List; import jadx.gui.ui.codearea.EditorViewState; +import jadx.gui.utils.JumpPosition; public interface ITabStatesListener { - void onTabOpen(TabBlueprint blueprint); - void onTabSelect(TabBlueprint blueprint); + default void onTabOpen(TabBlueprint blueprint) { + } - void onTabClose(TabBlueprint blueprint); + default void onTabSelect(TabBlueprint blueprint) { + } - void onTabPositionFirst(TabBlueprint blueprint); + default void onTabCodeJump(TabBlueprint blueprint, JumpPosition position) { + } - void onTabPinChange(TabBlueprint blueprint); + default void onTabSmaliJump(TabBlueprint blueprint, int pos, boolean debugMode) { + } - void onTabBookmarkChange(TabBlueprint blueprint); + default void onTabClose(TabBlueprint blueprint) { + } - void onTabVisibilityChange(TabBlueprint blueprint); + default void onTabPositionFirst(TabBlueprint blueprint) { + } - void onTabRestore(TabBlueprint blueprint, EditorViewState viewState); + default void onTabPinChange(TabBlueprint blueprint) { + } - void onTabsRestoreDone(); + default void onTabBookmarkChange(TabBlueprint blueprint) { + } - void onTabsReorder(List blueprints); + default void onTabVisibilityChange(TabBlueprint blueprint) { + } + + default void onTabRestore(TabBlueprint blueprint, EditorViewState viewState) { + } + + default void onTabsRestoreDone() { + } + + default void onTabsReorder(List blueprints) { + } + + default void onTabSave(TabBlueprint blueprint, EditorViewState viewState) { + } - void onTabSave(TabBlueprint blueprint, EditorViewState viewState); } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/tab/LogTabStates.java b/jadx-gui/src/main/java/jadx/gui/ui/tab/LogTabStates.java new file mode 100644 index 00000000000..e47ebbc7495 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/tab/LogTabStates.java @@ -0,0 +1,81 @@ +package jadx.gui.ui.tab; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.gui.ui.codearea.EditorViewState; +import jadx.gui.utils.JumpPosition; + +/** + * Utility class to log events from TabsController by implementing ITabStatesListener. + */ +public class LogTabStates implements ITabStatesListener { + private static final Logger LOG = LoggerFactory.getLogger(LogTabStates.class); + + @Override + public void onTabBookmarkChange(TabBlueprint blueprint) { + LOG.debug("onTabBookmarkChange: blueprint={}", blueprint); + } + + @Override + public void onTabClose(TabBlueprint blueprint) { + LOG.debug("onTabClose: blueprint={}", blueprint); + } + + @Override + public void onTabCodeJump(TabBlueprint blueprint, JumpPosition position) { + LOG.debug("onTabCodeJump: blueprint={}, position={}", blueprint, position); + } + + @Override + public void onTabOpen(TabBlueprint blueprint) { + LOG.debug("onTabOpen: blueprint={}", blueprint); + } + + @Override + public void onTabPinChange(TabBlueprint blueprint) { + LOG.debug("onTabPinChange: blueprint={}", blueprint); + } + + @Override + public void onTabPositionFirst(TabBlueprint blueprint) { + LOG.debug("onTabPositionFirst: blueprint={}", blueprint); + } + + @Override + public void onTabRestore(TabBlueprint blueprint, EditorViewState viewState) { + LOG.debug("onTabRestore: blueprint={}, viewState={}", blueprint, viewState); + } + + @Override + public void onTabSave(TabBlueprint blueprint, EditorViewState viewState) { + LOG.debug("onTabSave: blueprint={}, viewState={}", blueprint, viewState); + } + + @Override + public void onTabSelect(TabBlueprint blueprint) { + LOG.debug("onTabSelect: blueprint={}", blueprint); + } + + @Override + public void onTabSmaliJump(TabBlueprint blueprint, int pos, boolean debugMode) { + LOG.debug("onTabSmaliJump: blueprint={}, pos={}, debugMode={}", blueprint, pos, debugMode); + } + + @Override + public void onTabsReorder(List blueprints) { + LOG.debug("onTabsReorder: blueprints={}", blueprints); + } + + @Override + public void onTabsRestoreDone() { + LOG.debug("onTabsRestoreDone"); + } + + @Override + public void onTabVisibilityChange(TabBlueprint blueprint) { + LOG.debug("onTabVisibilityChange: blueprint={}", blueprint); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/tab/NavigationController.java b/jadx-gui/src/main/java/jadx/gui/ui/tab/NavigationController.java new file mode 100644 index 00000000000..e3bc3c52ec4 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/tab/NavigationController.java @@ -0,0 +1,73 @@ +package jadx.gui.ui.tab; + +import org.jetbrains.annotations.Nullable; + +import jadx.gui.ui.MainWindow; +import jadx.gui.utils.JumpManager; +import jadx.gui.utils.JumpPosition; + +/** + * TODO: Save jumps history into project file to restore after reload or reopen + */ +public class NavigationController implements ITabStatesListener { + private final transient MainWindow mainWindow; + + private final transient JumpManager jumps = new JumpManager(); + + public NavigationController(MainWindow mainWindow) { + this.mainWindow = mainWindow; + mainWindow.getTabsController().addListener(this); + } + + public void navBack() { + if (jumps.size() > 1) { + jumps.updateCurPosition(mainWindow.getTabbedPane().getCurrentPosition()); + } + jump(jumps.getPrev()); + } + + public void navForward() { + if (jumps.size() > 1) { + jumps.updateCurPosition(mainWindow.getTabbedPane().getCurrentPosition()); + } + jump(jumps.getNext()); + } + + private void jump(@Nullable JumpPosition pos) { + if (pos != null) { + mainWindow.getTabsController().codeJump(pos); + } + } + + @Override + public void onTabCodeJump(TabBlueprint blueprint, JumpPosition position) { + if (position.equals(jumps.getCurrent())) { + // ignore self-initiated jumps + return; + } + saveCurrentPosition(); + jumps.addPosition(position); + } + + @Override + public void onTabSmaliJump(TabBlueprint blueprint, int pos, boolean debugMode) { + saveCurrentPosition(); + // TODO: save smali jump + } + + private void saveCurrentPosition() { + JumpPosition curPos = mainWindow.getTabbedPane().getCurrentPosition(); + if (curPos != null) { + jumps.addPosition(curPos); + } + } + + public void reset() { + jumps.reset(); + } + + public void dispose() { + reset(); + mainWindow.getTabsController().removeListener(this); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/tab/QuickTabsTree.java b/jadx-gui/src/main/java/jadx/gui/ui/tab/QuickTabsTree.java index 78b693c2e8a..c5da98539e6 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/tab/QuickTabsTree.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/tab/QuickTabsTree.java @@ -6,7 +6,6 @@ import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; -import java.util.List; import javax.swing.JPopupMenu; import javax.swing.JTree; @@ -20,7 +19,6 @@ import jadx.gui.treemodel.JNode; import jadx.gui.ui.MainWindow; -import jadx.gui.ui.codearea.EditorViewState; import jadx.gui.utils.UiUtils; public class QuickTabsTree extends JTree implements ITabStatesListener, TreeSelectionListener { @@ -180,11 +178,6 @@ public void onTabOpen(TabBlueprint blueprint) { } } - @Override - public void onTabSelect(TabBlueprint blueprint) { - - } - @Override public void onTabClose(TabBlueprint blueprint) { removeJNode(openParentNode, blueprint.getNode()); @@ -192,11 +185,6 @@ public void onTabClose(TabBlueprint blueprint) { removeJNode(bookmarkParentNode, blueprint.getNode()); } - @Override - public void onTabPositionFirst(TabBlueprint blueprint) { - - } - @Override public void onTabPinChange(TabBlueprint blueprint) { JNode node = blueprint.getNode(); @@ -227,26 +215,6 @@ public void onTabVisibilityChange(TabBlueprint blueprint) { } } - @Override - public void onTabRestore(TabBlueprint blueprint, EditorViewState viewState) { - - } - - @Override - public void onTabsRestoreDone() { - - } - - @Override - public void onTabsReorder(List blueprints) { - - } - - @Override - public void onTabSave(TabBlueprint blueprint, EditorViewState viewState) { - - } - private class Root extends DefaultMutableTreeNode { } 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 6059c48bd08..1ccf6d50d50 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 @@ -1,5 +1,7 @@ package jadx.gui.ui.tab; +import java.util.Objects; + import jadx.gui.treemodel.JNode; public class TabBlueprint { @@ -9,7 +11,7 @@ public class TabBlueprint { private boolean hidden; public TabBlueprint(JNode node) { - this.node = node; + this.node = Objects.requireNonNull(node); } public JNode getNode() { @@ -47,4 +49,29 @@ public boolean isHidden() { public void setHidden(boolean hidden) { this.hidden = hidden; } + + @Override + public final boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof TabBlueprint)) { + return false; + } + return node.equals(((TabBlueprint) o).node); + } + + @Override + public int hashCode() { + return node.hashCode(); + } + + @Override + public String toString() { + return "TabBlueprint{node=" + node + + ", bookmarked=" + bookmarked + + ", pinned=" + pinned + + ", hidden=" + hidden + + '}'; + } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/tab/TabbedPane.java b/jadx-gui/src/main/java/jadx/gui/ui/tab/TabbedPane.java index ba4404db9a7..5b731828485 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/tab/TabbedPane.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/tab/TabbedPane.java @@ -8,10 +8,10 @@ import java.awt.event.KeyEvent; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Objects; -import java.util.Optional; +import java.util.Set; import javax.swing.JTabbedPane; import javax.swing.SwingUtilities; @@ -20,10 +20,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import jadx.api.JavaClass; -import jadx.api.metadata.ICodeAnnotation; -import jadx.api.metadata.ICodeNodeRef; -import jadx.api.metadata.annotations.NodeDeclareRef; import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JNode; @@ -38,9 +34,7 @@ import jadx.gui.ui.panel.IViewStateSupport; import jadx.gui.ui.panel.ImagePanel; import jadx.gui.ui.tab.dnd.TabDndController; -import jadx.gui.utils.JumpManager; import jadx.gui.utils.JumpPosition; -import jadx.gui.utils.NLS; import jadx.gui.utils.UiUtils; public class TabbedPane extends JTabbedPane implements ITabStatesListener { @@ -52,8 +46,6 @@ public class TabbedPane extends JTabbedPane implements ITabStatesListener { private final transient TabsController controller; private final transient Map tabsMap = new HashMap<>(); - private final transient JumpManager jumps = new JumpManager(); - private transient ContentPanel curTab; private transient ContentPanel lastTab; @@ -212,63 +204,8 @@ public MainWindow getMainWindow() { return mainWindow; } - /** - * Jump to node definition - */ - public void codeJump(JNode node) { - JClass parentCls = node.getJParent(); - if (parentCls != null) { - JavaClass cls = node.getJParent().getCls(); - JavaClass origTopCls = cls.getOriginalTopParentClass(); - JavaClass codeParent = cls.getTopParentClass(); - if (!Objects.equals(codeParent, origTopCls)) { - JClass jumpCls = mainWindow.getCacheObject().getNodeCache().makeFrom(codeParent); - mainWindow.getBackgroundExecutor().execute( - NLS.str("progress.load"), - jumpCls::loadNode, // load code in background - status -> { - // search original node in jump class - 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)); - return true; - } - } - return null; - }); - }); - return; - } - } - - // Not an inline node, jump normally - if (node.getPos() != 0 || node.getRootClass() == null) { - codeJump(new JumpPosition(node)); - return; - } - // node need loading - mainWindow.getBackgroundExecutor().execute( - NLS.str("progress.load"), - () -> node.getRootClass().getCodeInfo(), // run heavy loading in background - status -> codeJump(new JumpPosition(node))); - } - - /** - * Prefer {@link TabbedPane#codeJump(JNode)} method - */ - public void codeJump(JumpPosition pos) { - saveJump(pos); - showCode(pos); - } - - private void saveJump(JumpPosition pos) { - JumpPosition curPos = getCurrentPosition(); - if (curPos != null) { - jumps.addPosition(curPos); - jumps.addPosition(pos); - } + public TabsController getTabsController() { + return controller; } private @Nullable ContentPanel showCode(JumpPosition jumpPos) { @@ -280,15 +217,6 @@ private void saveJump(JumpPosition pos) { return contentPanel; } - public boolean showNode(JNode node) { - final ContentPanel contentPanel = getContentPanel(node); - if (contentPanel == null) { - return false; - } - selectTab(contentPanel); - return true; - } - private void scrollToPos(ContentPanel contentPanel, int pos) { if (pos == 0) { LOG.warn("Ignore zero jump!", new JadxRuntimeException()); @@ -305,7 +233,7 @@ public void selectTab(ContentPanel contentPanel) { controller.selectTab(contentPanel.getNode()); } - public void smaliJump(JClass cls, int pos, boolean debugMode) { + private void smaliJump(JClass cls, int pos, boolean debugMode) { ContentPanel panel = getTabByNode(cls); if (panel == null) { panel = showCode(new JumpPosition(cls, 1)); @@ -325,8 +253,7 @@ public void smaliJump(JClass cls, int pos, boolean debugMode) { smaliArea.requestFocus(); } - @Nullable - public JumpPosition getCurrentPosition() { + public @Nullable JumpPosition getCurrentPosition() { ContentPanel selectedCodePanel = getSelectedContentPanel(); if (selectedCodePanel instanceof AbstractCodeContentPanel) { return ((AbstractCodeContentPanel) selectedCodePanel).getCodeArea().getCurrentPosition(); @@ -334,26 +261,6 @@ public JumpPosition getCurrentPosition() { return null; } - public void navBack() { - if (jumps.size() > 1) { - jumps.updateCurPosition(getCurrentPosition()); - } - JumpPosition pos = jumps.getPrev(); - if (pos != null) { - showCode(pos); - } - } - - public void navForward() { - if (jumps.size() > 1) { - jumps.updateCurPosition(getCurrentPosition()); - } - JumpPosition pos = jumps.getNext(); - if (pos != null) { - showCode(pos); - } - } - private void addContentPanel(ContentPanel contentPanel) { tabsMap.put(contentPanel.getNode(), contentPanel); int tabCount = getTabCount(); @@ -382,7 +289,15 @@ public List getTabs() { } public @Nullable TabComponent getTabComponentByNode(JNode node) { - Component component = getTabComponentAt(indexOfComponent(getTabByNode(node))); + ContentPanel contentPanel = getTabByNode(node); + if (contentPanel == null) { + return null; + } + int index = indexOfComponent(contentPanel); + if (index == -1) { + return null; + } + Component component = getTabComponentAt(index); if (!(component instanceof TabComponent)) { return null; } @@ -460,7 +375,6 @@ public void loadSettings() { public void reset() { closeAllTabs(); tabsMap.clear(); - jumps.reset(); curTab = null; lastTab = null; FocusManager.reset(); @@ -485,16 +399,30 @@ public void onTabOpen(TabBlueprint blueprint) { return; } ContentPanel newPanel = blueprint.getNode().getContentPanel(this); - FocusManager.listen(newPanel); - addContentPanel(newPanel); + if (newPanel != null) { + FocusManager.listen(newPanel); + addContentPanel(newPanel); + } } @Override public void onTabSelect(TabBlueprint blueprint) { ContentPanel contentPanel = getContentPanel(blueprint.getNode()); - setSelectedComponent(contentPanel); - if (mainWindow.getSettings().isAlwaysSelectOpened()) { - mainWindow.syncWithEditor(); + if (contentPanel != null) { + setSelectedComponent(contentPanel); + } + } + + @Override + public void onTabCodeJump(TabBlueprint blueprint, JumpPosition position) { + showCode(position); + } + + @Override + public void onTabSmaliJump(TabBlueprint blueprint, int pos, boolean debugMode) { + JNode node = blueprint.getNode(); + if (node instanceof JClass) { + smaliJump((JClass) node, pos, debugMode); } } @@ -564,25 +492,19 @@ public void onTabRestore(TabBlueprint blueprint, EditorViewState viewState) { } } - @Override - public void onTabsRestoreDone() { - } - @Override public void onTabsReorder(List blueprints) { - List newBlueprints = new ArrayList<>(); + List newBlueprints = new ArrayList<>(blueprints.size()); for (ContentPanel contentPanel : getTabs()) { - Optional blueprintFindResult = blueprints.stream() - .filter(b -> b.getNode() == contentPanel.getNode()) - .findFirst(); - if (blueprintFindResult.isPresent()) { - TabBlueprint blueprint = blueprintFindResult.get(); - blueprints.remove(blueprint); + TabBlueprint blueprint = controller.getTabByNode(contentPanel.getNode()); + if (blueprint != null) { newBlueprints.add(blueprint); } } // Add back hidden tabs - newBlueprints.addAll(blueprints); + Set set = new LinkedHashSet<>(blueprints); + newBlueprints.forEach(set::remove); + newBlueprints.addAll(set); blueprints.clear(); blueprints.addAll(newBlueprints); 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 15d7f6dcc75..75313991059 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 @@ -4,22 +4,34 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; import org.jetbrains.annotations.Nullable; - +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.api.JavaClass; +import jadx.api.metadata.ICodeAnnotation; +import jadx.api.metadata.ICodeNodeRef; +import jadx.api.metadata.annotations.NodeDeclareRef; +import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JNode; import jadx.gui.ui.MainWindow; import jadx.gui.ui.codearea.EditorViewState; +import jadx.gui.utils.JumpPosition; +import jadx.gui.utils.NLS; public class TabsController { - private final transient MainWindow mainWindow; + private static final Logger LOG = LoggerFactory.getLogger(TabsController.class); + + private final MainWindow mainWindow; private final Map tabsMap = new HashMap<>(); private final List listeners = new ArrayList<>(); private boolean forceClose; - private TabBlueprint selectedTab = null; + private @Nullable TabBlueprint selectedTab; public TabsController(MainWindow mainWindow) { this.mainWindow = mainWindow; @@ -57,7 +69,6 @@ public TabBlueprint openTab(JNode node, boolean hidden) { } blueprint = newBlueprint; } - setTabHiddenInternal(blueprint, hidden); return blueprint; } @@ -65,10 +76,68 @@ public TabBlueprint openTab(JNode node, boolean hidden) { public void selectTab(JNode node) { TabBlueprint blueprint = openTab(node); selectedTab = blueprint; - listeners.forEach(l -> l.onTabSelect(blueprint)); } + /** + * Jump to node definition + */ + public void codeJump(JNode node) { + JClass parentCls = node.getJParent(); + if (parentCls != null) { + JavaClass cls = node.getJParent().getCls(); + JavaClass origTopCls = cls.getOriginalTopParentClass(); + JavaClass codeParent = cls.getTopParentClass(); + if (!Objects.equals(codeParent, origTopCls)) { + JClass jumpCls = mainWindow.getCacheObject().getNodeCache().makeFrom(codeParent); + mainWindow.getBackgroundExecutor().execute( + NLS.str("progress.load"), + jumpCls::loadNode, // load code in background + status -> { + // search original node in jump class + 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)); + return true; + } + } + return null; + }); + }); + return; + } + } + + // Not an inline node, jump normally + if (node.getPos() != 0 || node.getRootClass() == null) { + codeJump(new JumpPosition(node)); + return; + } + // node need loading + mainWindow.getBackgroundExecutor().execute( + NLS.str("progress.load"), + () -> node.getRootClass().getCodeInfo(), // run heavy loading in background + status -> codeJump(new JumpPosition(node))); + } + + /** + * Prefer {@link TabsController#codeJump(JNode)} method + */ + public void codeJump(JumpPosition pos) { + if (selectedTab == null) { + LOG.warn("Cannot codeJump because selectedTab is null"); + return; + } + listeners.forEach(l -> l.onTabCodeJump(selectedTab, pos)); + } + + public void smaliJump(JClass cls, int pos, boolean debugMode) { + TabBlueprint blueprint = openTab(cls); + listeners.forEach(l -> l.onTabSmaliJump(blueprint, pos, debugMode)); + } + public void closeTab(JNode node) { closeTab(node, false); } @@ -219,6 +288,11 @@ public void restoreEditorViewState(EditorViewState viewState) { } public void notifyRestoreEditorViewStateDone() { + if (selectedTab == null && !tabsMap.isEmpty()) { + JNode node = tabsMap.values().iterator().next().getNode(); + LOG.warn("No active tab found, select {}", node); // TODO: find the reason of this issue + selectTab(node); + } listeners.forEach(ITabStatesListener::onTabsRestoreDone); } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/JumpManager.java b/jadx-gui/src/main/java/jadx/gui/utils/JumpManager.java index 077b924c03b..fba2c8f29d0 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/JumpManager.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/JumpManager.java @@ -43,8 +43,7 @@ private boolean ignoreJump(JumpPosition pos) { return pos.equals(current); } - @Nullable - private JumpPosition getCurrent() { + public @Nullable JumpPosition getCurrent() { if (currentPos >= 0 && currentPos < list.size()) { return list.get(currentPos); }