diff --git a/jadx-gui/src/main/java/jadx/gui/ui/CodePanel.java b/jadx-gui/src/main/java/jadx/gui/ui/CodePanel.java index 00633231a..bed12fa1b 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/CodePanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/CodePanel.java @@ -15,6 +15,8 @@ import org.fife.ui.rtextarea.RTextScrollPane; class CodePanel extends JPanel { + private static final long serialVersionUID = 5310536092010045565L; + private final TabbedPane codePanel; private final JClass jClass; private final SearchBar searchBar; diff --git a/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java b/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java index 8566f9d7a..9d827d7fc 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java @@ -1,14 +1,18 @@ package jadx.gui.ui; import jadx.gui.treemodel.JClass; +import jadx.gui.utils.NLS; import jadx.gui.utils.Utils; import javax.swing.BorderFactory; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JLabel; +import javax.swing.JMenuItem; import javax.swing.JPanel; +import javax.swing.JPopupMenu; import javax.swing.JTabbedPane; +import javax.swing.SwingUtilities; import javax.swing.plaf.basic.BasicButtonUI; import java.awt.Component; import java.awt.FlowLayout; @@ -16,51 +20,98 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; -import java.util.HashMap; +import java.awt.event.MouseWheelEvent; +import java.awt.event.MouseWheelListener; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; class TabbedPane extends JTabbedPane { + private static final long serialVersionUID = -8833600618794570904L; + private static final ImageIcon ICON_CLOSE = Utils.openIcon("cross"); private static final ImageIcon ICON_CLOSE_INACTIVE = Utils.openIcon("cross_grayed"); private final MainWindow mainWindow; - private final Map openTabs = new HashMap(); + private final Map openTabs = new LinkedHashMap(); TabbedPane(MainWindow window) { mainWindow = window; setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); + + addMouseWheelListener(new MouseWheelListener() { + public void mouseWheelMoved(MouseWheelEvent e) { + int direction = e.getWheelRotation(); + int index = getSelectedIndex(); + int maxIndex = getTabCount() - 1; + if ((index == 0 && direction < 0) + || (index == maxIndex && direction > 0)) { + index = maxIndex - index; + } else { + index += direction; + } + setSelectedIndex(index); + } + }); + } + + MainWindow getMainWindow() { + return mainWindow; + } + + void showCode(final JClass cls, final int line) { + final CodePanel codePanel = getCodePanel(cls); + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + setSelectedComponent(codePanel); + CodeArea codeArea = codePanel.getCodeArea(); + codeArea.scrollToLine(line); + codeArea.requestFocus(); + } + }); } private void addCodePanel(CodePanel codePanel) { - add(codePanel); openTabs.put(codePanel.getCls(), codePanel); + add(codePanel); } private void closeCodePanel(CodePanel codePanel) { - remove(codePanel); openTabs.remove(codePanel.getCls()); + remove(codePanel); } - void showCode(JClass cls, int line) { + private CodePanel getCodePanel(JClass cls) { CodePanel panel = openTabs.get(cls); if (panel == null) { panel = new CodePanel(this, cls); addCodePanel(panel); setTabComponentAt(indexOfComponent(panel), makeTabComponent(panel)); } + return panel; + } - setSelectedComponent(panel); - CodeArea codeArea = panel.getCodeArea(); - codeArea.scrollToLine(line); - codeArea.requestFocus(); + private CodePanel getCodePanel(int index) { + Component component = getComponent(index); + if (component instanceof CodePanel) { + return (CodePanel) component; + } + return null; + } + + CodePanel getSelectedCodePanel() { + return (CodePanel) getSelectedComponent(); } private Component makeTabComponent(final CodePanel codePanel) { JClass cls = codePanel.getCls(); String name = cls.getCls().getFullName(); - JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 3, 0)); + + final JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 3, 0)); panel.setOpaque(false); final JLabel label = new JLabel(name); @@ -87,8 +138,11 @@ class TabbedPane extends JTabbedPane { panel.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { - if (e.getButton() == MouseEvent.BUTTON2) { + if (SwingUtilities.isMiddleMouseButton(e)) { closeCodePanel(codePanel); + } else if (SwingUtilities.isRightMouseButton(e)) { + JPopupMenu menu = createTabPopupMenu(codePanel); + menu.show(panel, e.getX(), e.getY()); } else { // TODO: make correct event delegation to tabbed pane setSelectedComponent(codePanel); @@ -102,11 +156,65 @@ class TabbedPane extends JTabbedPane { return panel; } - CodePanel getSelectedCodePanel() { - return (CodePanel) getSelectedComponent(); - } + private JPopupMenu createTabPopupMenu(final CodePanel codePanel) { + JPopupMenu menu = new JPopupMenu(); - MainWindow getMainWindow() { - return mainWindow; + JMenuItem closeTab = new JMenuItem(NLS.str("tabs.close")); + closeTab.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + closeCodePanel(codePanel); + } + }); + menu.add(closeTab); + + if (openTabs.size() > 1) { + JMenuItem closeOther = new JMenuItem(NLS.str("tabs.closeOthers")); + closeOther.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + List codePanels = new ArrayList(openTabs.values()); + for (CodePanel panel : codePanels) { + if (panel != codePanel) { + closeCodePanel(panel); + } + } + } + }); + menu.add(closeOther); + + JMenuItem closeAll = new JMenuItem(NLS.str("tabs.closeAll")); + closeAll.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + List codePanels = new ArrayList(openTabs.values()); + for (CodePanel panel : codePanels) { + closeCodePanel(panel); + } + } + }); + menu.add(closeAll); + menu.addSeparator(); + + CodePanel selectedCodePanel = getSelectedCodePanel(); + for (final Map.Entry entry : openTabs.entrySet()) { + final CodePanel cp = entry.getValue(); + if (cp == selectedCodePanel) { + continue; + } + JClass jClass = entry.getKey(); + final String clsName = jClass.getCls().getFullName(); + JMenuItem item = new JMenuItem(clsName); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + setSelectedComponent(cp); + } + }); + item.setIcon(jClass.getIcon()); + menu.add(item); + } + } + return menu; } } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/Utils.java b/jadx-gui/src/main/java/jadx/gui/utils/Utils.java index 58c8908d3..1f71bdead 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/Utils.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/Utils.java @@ -4,7 +4,7 @@ import jadx.core.dex.info.AccessInfo; import jadx.core.dex.instructions.args.ArgType; import jadx.core.utils.exceptions.JadxRuntimeException; -import javax.swing.AbstractAction; +import javax.swing.Action; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JComponent; @@ -27,7 +27,7 @@ public class Utils { return new ImageIcon(resource); } - public static void addKeyBinding(JComponent comp, KeyStroke key, String id, AbstractAction action) { + public static void addKeyBinding(JComponent comp, KeyStroke key, String id, Action action) { comp.getInputMap().put(key, id); comp.getActionMap().put(id, action); } 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 068746b4e..938e5f912 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -16,3 +16,7 @@ search.regex=Regex search.match_case=Match Case search.whole_word=Whole word search.find=Find + +tabs.close=Close +tabs.closeOthers=Close Others +tabs.closeAll=Close All