diff options
19 files changed, 962 insertions, 609 deletions
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java index cddedad7..cac0ea89 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java | |||
| @@ -14,9 +14,12 @@ package cuchaz.enigma.gui; | |||
| 14 | import java.awt.BorderLayout; | 14 | import java.awt.BorderLayout; |
| 15 | import java.awt.Container; | 15 | import java.awt.Container; |
| 16 | import java.awt.Point; | 16 | import java.awt.Point; |
| 17 | import java.awt.event.*; | 17 | import java.awt.event.ActionEvent; |
| 18 | import java.nio.file.Path; | 18 | import java.nio.file.Path; |
| 19 | import java.util.*; | 19 | import java.util.Collection; |
| 20 | import java.util.Collections; | ||
| 21 | import java.util.List; | ||
| 22 | import java.util.Set; | ||
| 20 | import java.util.concurrent.CompletableFuture; | 23 | import java.util.concurrent.CompletableFuture; |
| 21 | import java.util.function.Consumer; | 24 | import java.util.function.Consumer; |
| 22 | import java.util.function.Function; | 25 | import java.util.function.Function; |
| @@ -24,30 +27,24 @@ import java.util.function.Function; | |||
| 24 | import javax.annotation.Nullable; | 27 | import javax.annotation.Nullable; |
| 25 | import javax.swing.*; | 28 | import javax.swing.*; |
| 26 | import javax.swing.tree.DefaultMutableTreeNode; | 29 | import javax.swing.tree.DefaultMutableTreeNode; |
| 27 | import javax.swing.tree.DefaultTreeModel; | ||
| 28 | import javax.swing.tree.TreeNode; | 30 | import javax.swing.tree.TreeNode; |
| 29 | import javax.swing.tree.TreePath; | 31 | import javax.swing.tree.TreePath; |
| 30 | 32 | ||
| 31 | import com.google.common.collect.HashBiMap; | ||
| 32 | import com.google.common.collect.Lists; | 33 | import com.google.common.collect.Lists; |
| 33 | 34 | ||
| 34 | import cuchaz.enigma.Enigma; | 35 | import cuchaz.enigma.Enigma; |
| 35 | import cuchaz.enigma.EnigmaProfile; | 36 | import cuchaz.enigma.EnigmaProfile; |
| 36 | import cuchaz.enigma.analysis.*; | 37 | import cuchaz.enigma.analysis.EntryReference; |
| 37 | import cuchaz.enigma.classhandle.ClassHandle; | ||
| 38 | import cuchaz.enigma.gui.config.Themes; | 38 | import cuchaz.enigma.gui.config.Themes; |
| 39 | import cuchaz.enigma.gui.config.UiConfig; | 39 | import cuchaz.enigma.gui.config.UiConfig; |
| 40 | import cuchaz.enigma.gui.dialog.CrashDialog; | ||
| 41 | import cuchaz.enigma.gui.dialog.JavadocDialog; | 40 | import cuchaz.enigma.gui.dialog.JavadocDialog; |
| 42 | import cuchaz.enigma.gui.dialog.SearchDialog; | 41 | import cuchaz.enigma.gui.dialog.SearchDialog; |
| 43 | import cuchaz.enigma.gui.elements.*; | 42 | import cuchaz.enigma.gui.elements.*; |
| 44 | import cuchaz.enigma.gui.events.EditorActionListener; | ||
| 45 | import cuchaz.enigma.gui.panels.*; | 43 | import cuchaz.enigma.gui.panels.*; |
| 46 | import cuchaz.enigma.gui.renderer.CallsTreeCellRenderer; | ||
| 47 | import cuchaz.enigma.gui.renderer.ImplementationsTreeCellRenderer; | ||
| 48 | import cuchaz.enigma.gui.renderer.InheritanceTreeCellRenderer; | ||
| 49 | import cuchaz.enigma.gui.renderer.MessageListCellRenderer; | 44 | import cuchaz.enigma.gui.renderer.MessageListCellRenderer; |
| 50 | import cuchaz.enigma.gui.util.*; | 45 | import cuchaz.enigma.gui.util.GuiUtil; |
| 46 | import cuchaz.enigma.gui.util.LanguageUtil; | ||
| 47 | import cuchaz.enigma.gui.util.ScaleUtil; | ||
| 51 | import cuchaz.enigma.network.Message; | 48 | import cuchaz.enigma.network.Message; |
| 52 | import cuchaz.enigma.network.packet.MessageC2SPacket; | 49 | import cuchaz.enigma.network.packet.MessageC2SPacket; |
| 53 | import cuchaz.enigma.source.Token; | 50 | import cuchaz.enigma.source.Token; |
| @@ -55,293 +52,109 @@ import cuchaz.enigma.translation.mapping.EntryChange; | |||
| 55 | import cuchaz.enigma.translation.mapping.EntryRemapper; | 52 | import cuchaz.enigma.translation.mapping.EntryRemapper; |
| 56 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | 53 | import cuchaz.enigma.translation.representation.entry.ClassEntry; |
| 57 | import cuchaz.enigma.translation.representation.entry.Entry; | 54 | import cuchaz.enigma.translation.representation.entry.Entry; |
| 58 | import cuchaz.enigma.translation.representation.entry.FieldEntry; | ||
| 59 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 60 | import cuchaz.enigma.utils.I18n; | 55 | import cuchaz.enigma.utils.I18n; |
| 61 | import cuchaz.enigma.utils.validation.ParameterizedMessage; | 56 | import cuchaz.enigma.utils.validation.ParameterizedMessage; |
| 62 | import cuchaz.enigma.utils.validation.ValidationContext; | 57 | import cuchaz.enigma.utils.validation.ValidationContext; |
| 63 | 58 | ||
| 64 | public class Gui implements LanguageChangeListener { | 59 | public class Gui { |
| 65 | 60 | ||
| 66 | private final ObfPanel obfPanel; | 61 | private final MainWindow mainWindow = new MainWindow(Enigma.NAME); |
| 67 | private final DeobfPanel deobfPanel; | 62 | private final GuiController controller; |
| 68 | |||
| 69 | private final MenuBar menuBar; | ||
| 70 | 63 | ||
| 71 | // state | ||
| 72 | public History<EntryReference<Entry<?>, Entry<?>>> referenceHistory; | ||
| 73 | private ConnectionState connectionState; | 64 | private ConnectionState connectionState; |
| 74 | private boolean isJarOpen; | 65 | private boolean isJarOpen; |
| 75 | private final Set<EditableType> editableTypes; | 66 | private final Set<EditableType> editableTypes; |
| 76 | private boolean singleClassTree; | 67 | private boolean singleClassTree; |
| 77 | 68 | ||
| 78 | public JFileChooser jarFileChooser; | 69 | private final MenuBar menuBar; |
| 79 | public JFileChooser tinyMappingsFileChooser; | 70 | private final ObfPanel obfPanel; |
| 80 | public JFileChooser enigmaMappingsFileChooser; | 71 | private final DeobfPanel deobfPanel; |
| 81 | public JFileChooser exportSourceFileChooser; | 72 | private final IdentifierPanel infoPanel; |
| 82 | public JFileChooser exportJarFileChooser; | 73 | private final StructurePanel structurePanel; |
| 74 | private final InheritanceTree inheritanceTree; | ||
| 75 | private final ImplementationsTree implementationsTree; | ||
| 76 | private final CallsTree callsTree; | ||
| 77 | |||
| 78 | private final EditorTabbedPane editorTabbedPane; | ||
| 79 | |||
| 80 | private final JPanel classesPanel = new JPanel(new BorderLayout()); | ||
| 81 | private final JSplitPane splitClasses; | ||
| 82 | private final JTabbedPane tabs = new JTabbedPane(); | ||
| 83 | private final CollapsibleTabbedPane logTabs = new CollapsibleTabbedPane(JTabbedPane.BOTTOM); | ||
| 84 | private final JSplitPane logSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, tabs, logTabs); | ||
| 85 | private final JPanel centerPanel = new JPanel(new BorderLayout()); | ||
| 86 | private final JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, this.logSplit); | ||
| 87 | private final JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, this.classesPanel, splitRight); | ||
| 88 | |||
| 89 | private final DefaultListModel<String> userModel = new DefaultListModel<>(); | ||
| 90 | private final DefaultListModel<Message> messageModel = new DefaultListModel<>(); | ||
| 91 | private final JList<String> users = new JList<>(userModel); | ||
| 92 | private final JList<Message> messages = new JList<>(messageModel); | ||
| 93 | private final JPanel messagePanel = new JPanel(new BorderLayout()); | ||
| 94 | private final JScrollPane messageScrollPane = new JScrollPane(this.messages); | ||
| 95 | private final JTextField chatBox = new JTextField(); | ||
| 96 | |||
| 97 | private final JLabel connectionStatusLabel = new JLabel(); | ||
| 98 | |||
| 99 | public final JFileChooser jarFileChooser = new JFileChooser(); | ||
| 100 | public final JFileChooser tinyMappingsFileChooser = new JFileChooser(); | ||
| 101 | public final JFileChooser enigmaMappingsFileChooser = new JFileChooser(); | ||
| 102 | public final JFileChooser exportSourceFileChooser = new JFileChooser(); | ||
| 103 | public final JFileChooser exportJarFileChooser = new JFileChooser(); | ||
| 83 | public SearchDialog searchDialog; | 104 | public SearchDialog searchDialog; |
| 84 | private GuiController controller; | ||
| 85 | private JFrame frame; | ||
| 86 | private JPanel classesPanel; | ||
| 87 | private JSplitPane splitClasses; | ||
| 88 | private IdentifierPanel infoPanel; | ||
| 89 | private StructurePanel structurePanel; | ||
| 90 | private JTree inheritanceTree; | ||
| 91 | private JTree implementationsTree; | ||
| 92 | private JTree callsTree; | ||
| 93 | private JList<Token> tokens; | ||
| 94 | private JTabbedPane tabs; | ||
| 95 | |||
| 96 | private JSplitPane splitCenter; | ||
| 97 | private JSplitPane splitRight; | ||
| 98 | private JSplitPane logSplit; | ||
| 99 | private CollapsibleTabbedPane logTabs; | ||
| 100 | private JList<String> users; | ||
| 101 | private DefaultListModel<String> userModel; | ||
| 102 | private JScrollPane messageScrollPane; | ||
| 103 | private JList<Message> messages; | ||
| 104 | private DefaultListModel<Message> messageModel; | ||
| 105 | private JTextField chatBox; | ||
| 106 | |||
| 107 | private JPanel statusBar; | ||
| 108 | private JLabel connectionStatusLabel; | ||
| 109 | private JLabel statusLabel; | ||
| 110 | |||
| 111 | private final EditorTabPopupMenu editorTabPopupMenu; | ||
| 112 | private final DeobfPanelPopupMenu deobfPanelPopupMenu; | ||
| 113 | private final JTabbedPane openFiles; | ||
| 114 | private final HashBiMap<ClassEntry, EditorPanel> editors = HashBiMap.create(); | ||
| 115 | 105 | ||
| 116 | public Gui(EnigmaProfile profile, Set<EditableType> editableTypes) { | 106 | public Gui(EnigmaProfile profile, Set<EditableType> editableTypes) { |
| 117 | this.editableTypes = editableTypes; | 107 | this.editableTypes = editableTypes; |
| 108 | this.controller = new GuiController(this, profile); | ||
| 109 | this.structurePanel = new StructurePanel(this); | ||
| 110 | this.deobfPanel = new DeobfPanel(this); | ||
| 111 | this.infoPanel = new IdentifierPanel(this); | ||
| 112 | this.obfPanel = new ObfPanel(this); | ||
| 113 | this.menuBar = new MenuBar(this); | ||
| 114 | this.inheritanceTree = new InheritanceTree(this); | ||
| 115 | this.implementationsTree = new ImplementationsTree(this); | ||
| 116 | this.callsTree = new CallsTree(this); | ||
| 117 | this.editorTabbedPane = new EditorTabbedPane(this); | ||
| 118 | this.splitClasses = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, this.obfPanel, this.deobfPanel); | ||
| 118 | 119 | ||
| 119 | // init frame | 120 | this.setupUi(); |
| 120 | this.frame = new JFrame(Enigma.NAME); | ||
| 121 | final Container pane = this.frame.getContentPane(); | ||
| 122 | pane.setLayout(new BorderLayout()); | ||
| 123 | |||
| 124 | if (Boolean.parseBoolean(System.getProperty("enigma.catchExceptions", "true"))) { | ||
| 125 | // install a global exception handler to the event thread | ||
| 126 | CrashDialog.init(this.frame); | ||
| 127 | Thread.setDefaultUncaughtExceptionHandler((thread, t) -> { | ||
| 128 | t.printStackTrace(System.err); | ||
| 129 | if (!ExceptionIgnorer.shouldIgnore(t)) { | ||
| 130 | CrashDialog.show(t); | ||
| 131 | } | ||
| 132 | }); | ||
| 133 | } | ||
| 134 | 121 | ||
| 122 | LanguageUtil.addListener(this::retranslateUi); | ||
| 135 | Themes.addListener((lookAndFeel, boxHighlightPainters) -> SwingUtilities.updateComponentTreeUI(this.getFrame())); | 123 | Themes.addListener((lookAndFeel, boxHighlightPainters) -> SwingUtilities.updateComponentTreeUI(this.getFrame())); |
| 136 | 124 | ||
| 137 | this.controller = new GuiController(this, profile); | 125 | this.mainWindow.setVisible(true); |
| 126 | } | ||
| 138 | 127 | ||
| 139 | // init file choosers | 128 | private void setupUi() { |
| 140 | this.jarFileChooser = new JFileChooser(); | ||
| 141 | this.jarFileChooser.setDialogTitle(I18n.translate("menu.file.jar.open")); | ||
| 142 | this.jarFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); | 129 | this.jarFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); |
| 143 | |||
| 144 | this.tinyMappingsFileChooser = new JFileChooser(); | ||
| 145 | this.tinyMappingsFileChooser.setDialogTitle("Open tiny Mappings"); | ||
| 146 | this.tinyMappingsFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); | 130 | this.tinyMappingsFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); |
| 147 | 131 | ||
| 148 | this.enigmaMappingsFileChooser = new JFileChooser(); | ||
| 149 | this.enigmaMappingsFileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); | 132 | this.enigmaMappingsFileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); |
| 150 | this.enigmaMappingsFileChooser.setAcceptAllFileFilterUsed(false); | 133 | this.enigmaMappingsFileChooser.setAcceptAllFileFilterUsed(false); |
| 151 | 134 | ||
| 152 | this.exportSourceFileChooser = new JFileChooser(); | ||
| 153 | this.exportSourceFileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); | 135 | this.exportSourceFileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); |
| 154 | this.exportSourceFileChooser.setAcceptAllFileFilterUsed(false); | 136 | this.exportSourceFileChooser.setAcceptAllFileFilterUsed(false); |
| 155 | 137 | ||
| 156 | this.exportJarFileChooser = new JFileChooser(); | ||
| 157 | this.exportJarFileChooser.setDialogTitle(I18n.translate("menu.file.export.jar")); | ||
| 158 | this.exportJarFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); | 138 | this.exportJarFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); |
| 159 | 139 | ||
| 160 | this.obfPanel = new ObfPanel(this); | 140 | this.splitClasses.setResizeWeight(0.3); |
| 161 | this.deobfPanel = new DeobfPanel(this); | ||
| 162 | |||
| 163 | // set up classes panel (don't add the splitter yet) | ||
| 164 | splitClasses = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, this.obfPanel, this.deobfPanel); | ||
| 165 | splitClasses.setResizeWeight(0.3); | ||
| 166 | this.classesPanel = new JPanel(); | ||
| 167 | this.classesPanel.setLayout(new BorderLayout()); | ||
| 168 | this.classesPanel.setPreferredSize(ScaleUtil.getDimension(250, 0)); | 141 | this.classesPanel.setPreferredSize(ScaleUtil.getDimension(250, 0)); |
| 169 | 142 | ||
| 170 | // init info panel | ||
| 171 | infoPanel = new IdentifierPanel(this); | ||
| 172 | |||
| 173 | // init structure panel | ||
| 174 | this.structurePanel = new StructurePanel(this); | ||
| 175 | |||
| 176 | // init inheritance panel | ||
| 177 | inheritanceTree = new JTree(); | ||
| 178 | inheritanceTree.setModel(null); | ||
| 179 | inheritanceTree.setCellRenderer(new InheritanceTreeCellRenderer(this)); | ||
| 180 | inheritanceTree.setSelectionModel(new SingleTreeSelectionModel()); | ||
| 181 | inheritanceTree.setShowsRootHandles(true); | ||
| 182 | inheritanceTree.addMouseListener(new MouseAdapter() { | ||
| 183 | @Override | ||
| 184 | public void mouseClicked(MouseEvent event) { | ||
| 185 | if (event.getClickCount() >= 2 && event.getButton() == MouseEvent.BUTTON1) { | ||
| 186 | // get the selected node | ||
| 187 | TreePath path = inheritanceTree.getSelectionPath(); | ||
| 188 | if (path == null) { | ||
| 189 | return; | ||
| 190 | } | ||
| 191 | |||
| 192 | Object node = path.getLastPathComponent(); | ||
| 193 | if (node instanceof ClassInheritanceTreeNode) { | ||
| 194 | ClassInheritanceTreeNode classNode = (ClassInheritanceTreeNode) node; | ||
| 195 | controller.navigateTo(new ClassEntry(classNode.getObfClassName())); | ||
| 196 | } else if (node instanceof MethodInheritanceTreeNode) { | ||
| 197 | MethodInheritanceTreeNode methodNode = (MethodInheritanceTreeNode) node; | ||
| 198 | if (methodNode.isImplemented()) { | ||
| 199 | controller.navigateTo(methodNode.getMethodEntry()); | ||
| 200 | } | ||
| 201 | } | ||
| 202 | } | ||
| 203 | } | ||
| 204 | }); | ||
| 205 | |||
| 206 | JPanel inheritancePanel = new JPanel(); | ||
| 207 | inheritancePanel.setLayout(new BorderLayout()); | ||
| 208 | inheritancePanel.add(new JScrollPane(inheritanceTree)); | ||
| 209 | |||
| 210 | // init implementations panel | ||
| 211 | implementationsTree = new JTree(); | ||
| 212 | implementationsTree.setModel(null); | ||
| 213 | implementationsTree.setCellRenderer(new ImplementationsTreeCellRenderer(this)); | ||
| 214 | implementationsTree.setSelectionModel(new SingleTreeSelectionModel()); | ||
| 215 | implementationsTree.setShowsRootHandles(true); | ||
| 216 | implementationsTree.addMouseListener(new MouseAdapter() { | ||
| 217 | @Override | ||
| 218 | public void mouseClicked(MouseEvent event) { | ||
| 219 | if (event.getClickCount() >= 2 && event.getButton() == MouseEvent.BUTTON1) { | ||
| 220 | // get the selected node | ||
| 221 | TreePath path = implementationsTree.getSelectionPath(); | ||
| 222 | if (path == null) { | ||
| 223 | return; | ||
| 224 | } | ||
| 225 | |||
| 226 | Object node = path.getLastPathComponent(); | ||
| 227 | if (node instanceof ClassImplementationsTreeNode classNode) { | ||
| 228 | controller.navigateTo(classNode.getClassEntry()); | ||
| 229 | } else if (node instanceof MethodImplementationsTreeNode methodNode) { | ||
| 230 | controller.navigateTo(methodNode.getMethodEntry()); | ||
| 231 | } | ||
| 232 | } | ||
| 233 | } | ||
| 234 | }); | ||
| 235 | JPanel implementationsPanel = new JPanel(); | ||
| 236 | implementationsPanel.setLayout(new BorderLayout()); | ||
| 237 | implementationsPanel.add(new JScrollPane(implementationsTree)); | ||
| 238 | |||
| 239 | // init call panel | ||
| 240 | callsTree = new JTree(); | ||
| 241 | callsTree.setModel(null); | ||
| 242 | callsTree.setCellRenderer(new CallsTreeCellRenderer(this)); | ||
| 243 | callsTree.setSelectionModel(new SingleTreeSelectionModel()); | ||
| 244 | callsTree.setShowsRootHandles(true); | ||
| 245 | callsTree.addMouseListener(new MouseAdapter() { | ||
| 246 | @SuppressWarnings("unchecked") | ||
| 247 | @Override | ||
| 248 | public void mouseClicked(MouseEvent event) { | ||
| 249 | if (event.getClickCount() >= 2 && event.getButton() == MouseEvent.BUTTON1) { | ||
| 250 | // get the selected node | ||
| 251 | TreePath path = callsTree.getSelectionPath(); | ||
| 252 | if (path == null) { | ||
| 253 | return; | ||
| 254 | } | ||
| 255 | |||
| 256 | Object node = path.getLastPathComponent(); | ||
| 257 | if (node instanceof ReferenceTreeNode referenceNode) { | ||
| 258 | if (referenceNode.getReference() != null) { | ||
| 259 | controller.navigateTo(referenceNode.getReference()); | ||
| 260 | } else { | ||
| 261 | controller.navigateTo(referenceNode.getEntry()); | ||
| 262 | } | ||
| 263 | } | ||
| 264 | } | ||
| 265 | } | ||
| 266 | }); | ||
| 267 | tokens = new JList<>(); | ||
| 268 | tokens.setCellRenderer(new TokenListCellRenderer(controller)); | ||
| 269 | tokens.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); | ||
| 270 | tokens.setLayoutOrientation(JList.VERTICAL); | ||
| 271 | tokens.addMouseListener(new MouseAdapter() { | ||
| 272 | @Override | ||
| 273 | public void mouseClicked(MouseEvent event) { | ||
| 274 | if (event.getClickCount() == 2) { | ||
| 275 | Token selected = tokens.getSelectedValue(); | ||
| 276 | if (selected != null) { | ||
| 277 | openClass(controller.getTokenHandle().getRef()).navigateToToken(selected); | ||
| 278 | } | ||
| 279 | } | ||
| 280 | } | ||
| 281 | }); | ||
| 282 | tokens.setPreferredSize(ScaleUtil.getDimension(0, 200)); | ||
| 283 | tokens.setMinimumSize(ScaleUtil.getDimension(0, 200)); | ||
| 284 | JSplitPane callPanel = new JSplitPane( | ||
| 285 | JSplitPane.VERTICAL_SPLIT, | ||
| 286 | true, | ||
| 287 | new JScrollPane(callsTree), | ||
| 288 | new JScrollPane(tokens) | ||
| 289 | ); | ||
| 290 | callPanel.setResizeWeight(1); // let the top side take all the slack | ||
| 291 | callPanel.resetToPreferredSizes(); | ||
| 292 | |||
| 293 | editorTabPopupMenu = new EditorTabPopupMenu(this); | ||
| 294 | openFiles = new JTabbedPane(JTabbedPane.TOP, JTabbedPane.SCROLL_TAB_LAYOUT); | ||
| 295 | openFiles.addMouseListener(new MouseAdapter() { | ||
| 296 | @Override | ||
| 297 | public void mousePressed(MouseEvent e) { | ||
| 298 | if (SwingUtilities.isRightMouseButton(e)) { | ||
| 299 | int i = openFiles.getUI().tabForCoordinate(openFiles, e.getX(), e.getY()); | ||
| 300 | if (i != -1) { | ||
| 301 | editorTabPopupMenu.show(openFiles, e.getX(), e.getY(), EditorPanel.byUi(openFiles.getComponentAt(i))); | ||
| 302 | } | ||
| 303 | } | ||
| 304 | |||
| 305 | showStructure(getActiveEditor()); | ||
| 306 | } | ||
| 307 | }); | ||
| 308 | |||
| 309 | deobfPanelPopupMenu = new DeobfPanelPopupMenu(this); | ||
| 310 | deobfPanel.deobfClasses.addMouseListener(new MouseAdapter() { | ||
| 311 | @Override | ||
| 312 | public void mousePressed(MouseEvent e) { | ||
| 313 | if (SwingUtilities.isRightMouseButton(e)) { | ||
| 314 | deobfPanel.deobfClasses.setSelectionRow(deobfPanel.deobfClasses.getClosestRowForLocation(e.getX(), e.getY())); | ||
| 315 | int i = deobfPanel.deobfClasses.getRowForPath(deobfPanel.deobfClasses.getSelectionPath()); | ||
| 316 | if (i != -1) { | ||
| 317 | deobfPanelPopupMenu.show(deobfPanel.deobfClasses, e.getX(), e.getY()); | ||
| 318 | } | ||
| 319 | } | ||
| 320 | } | ||
| 321 | }); | ||
| 322 | |||
| 323 | // layout controls | 143 | // layout controls |
| 324 | JPanel centerPanel = new JPanel(); | 144 | Container workArea = this.mainWindow.workArea(); |
| 325 | centerPanel.setLayout(new BorderLayout()); | 145 | workArea.setLayout(new BorderLayout()); |
| 146 | |||
| 326 | centerPanel.add(infoPanel.getUi(), BorderLayout.NORTH); | 147 | centerPanel.add(infoPanel.getUi(), BorderLayout.NORTH); |
| 327 | centerPanel.add(openFiles, BorderLayout.CENTER); | 148 | centerPanel.add(this.editorTabbedPane.getUi(), BorderLayout.CENTER); |
| 328 | tabs = new JTabbedPane(); | 149 | |
| 329 | tabs.setPreferredSize(ScaleUtil.getDimension(250, 0)); | 150 | tabs.setPreferredSize(ScaleUtil.getDimension(250, 0)); |
| 330 | tabs.addTab(I18n.translate("info_panel.tree.structure"), structurePanel); | 151 | tabs.addTab(I18n.translate("info_panel.tree.structure"), structurePanel.getPanel()); |
| 331 | tabs.addTab(I18n.translate("info_panel.tree.inheritance"), inheritancePanel); | 152 | tabs.addTab(I18n.translate("info_panel.tree.inheritance"), inheritanceTree.getPanel()); |
| 332 | tabs.addTab(I18n.translate("info_panel.tree.implementations"), implementationsPanel); | 153 | tabs.addTab(I18n.translate("info_panel.tree.implementations"), implementationsTree.getPanel()); |
| 333 | tabs.addTab(I18n.translate("info_panel.tree.calls"), callPanel); | 154 | tabs.addTab(I18n.translate("info_panel.tree.calls"), callsTree.getPanel()); |
| 334 | logTabs = new CollapsibleTabbedPane(JTabbedPane.BOTTOM); | 155 | |
| 335 | userModel = new DefaultListModel<>(); | ||
| 336 | users = new JList<>(userModel); | ||
| 337 | messageModel = new DefaultListModel<>(); | ||
| 338 | messages = new JList<>(messageModel); | ||
| 339 | messages.setCellRenderer(new MessageListCellRenderer()); | 156 | messages.setCellRenderer(new MessageListCellRenderer()); |
| 340 | JPanel messagePanel = new JPanel(new BorderLayout()); | ||
| 341 | messageScrollPane = new JScrollPane(this.messages); | ||
| 342 | messagePanel.add(messageScrollPane, BorderLayout.CENTER); | ||
| 343 | JPanel chatPanel = new JPanel(new BorderLayout()); | 157 | JPanel chatPanel = new JPanel(new BorderLayout()); |
| 344 | chatBox = new JTextField(); | ||
| 345 | AbstractAction sendListener = new AbstractAction("Send") { | 158 | AbstractAction sendListener = new AbstractAction("Send") { |
| 346 | @Override | 159 | @Override |
| 347 | public void actionPerformed(ActionEvent e) { | 160 | public void actionPerformed(ActionEvent e) { |
| @@ -352,18 +165,17 @@ public class Gui implements LanguageChangeListener { | |||
| 352 | JButton chatSendButton = new JButton(sendListener); | 165 | JButton chatSendButton = new JButton(sendListener); |
| 353 | chatPanel.add(chatBox, BorderLayout.CENTER); | 166 | chatPanel.add(chatBox, BorderLayout.CENTER); |
| 354 | chatPanel.add(chatSendButton, BorderLayout.EAST); | 167 | chatPanel.add(chatSendButton, BorderLayout.EAST); |
| 168 | messagePanel.add(messageScrollPane, BorderLayout.CENTER); | ||
| 355 | messagePanel.add(chatPanel, BorderLayout.SOUTH); | 169 | messagePanel.add(chatPanel, BorderLayout.SOUTH); |
| 356 | logTabs.addTab(I18n.translate("log_panel.users"), new JScrollPane(this.users)); | 170 | logTabs.addTab(I18n.translate("log_panel.users"), new JScrollPane(this.users)); |
| 357 | logTabs.addTab(I18n.translate("log_panel.messages"), messagePanel); | 171 | logTabs.addTab(I18n.translate("log_panel.messages"), messagePanel); |
| 358 | logSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, tabs, logTabs); | ||
| 359 | logSplit.setResizeWeight(0.5); | 172 | logSplit.setResizeWeight(0.5); |
| 360 | logSplit.resetToPreferredSizes(); | 173 | logSplit.resetToPreferredSizes(); |
| 361 | splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, this.logSplit); | ||
| 362 | splitRight.setResizeWeight(1); // let the left side take all the slack | 174 | splitRight.setResizeWeight(1); // let the left side take all the slack |
| 363 | splitRight.resetToPreferredSizes(); | 175 | splitRight.resetToPreferredSizes(); |
| 364 | splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, this.classesPanel, splitRight); | ||
| 365 | splitCenter.setResizeWeight(0); // let the right side take all the slack | 176 | splitCenter.setResizeWeight(0); // let the right side take all the slack |
| 366 | pane.add(splitCenter, BorderLayout.CENTER); | 177 | |
| 178 | workArea.add(splitCenter, BorderLayout.CENTER); | ||
| 367 | 179 | ||
| 368 | // restore state | 180 | // restore state |
| 369 | int[] layout = UiConfig.getLayout(); | 181 | int[] layout = UiConfig.getLayout(); |
| @@ -374,56 +186,41 @@ public class Gui implements LanguageChangeListener { | |||
| 374 | this.logSplit.setDividerLocation(layout[3]); | 186 | this.logSplit.setDividerLocation(layout[3]); |
| 375 | } | 187 | } |
| 376 | 188 | ||
| 377 | // init menus | 189 | this.mainWindow.statusBar().addPermanentComponent(this.connectionStatusLabel); |
| 378 | this.menuBar = new MenuBar(this); | ||
| 379 | this.frame.setJMenuBar(this.menuBar.getUi()); | ||
| 380 | |||
| 381 | // init status bar | ||
| 382 | statusBar = new JPanel(new BorderLayout()); | ||
| 383 | statusBar.setBorder(BorderFactory.createLoweredBevelBorder()); | ||
| 384 | connectionStatusLabel = new JLabel(); | ||
| 385 | statusLabel = new JLabel(); | ||
| 386 | statusBar.add(statusLabel, BorderLayout.CENTER); | ||
| 387 | statusBar.add(connectionStatusLabel, BorderLayout.EAST); | ||
| 388 | pane.add(statusBar, BorderLayout.SOUTH); | ||
| 389 | 190 | ||
| 390 | // init state | 191 | // init state |
| 391 | setConnectionState(ConnectionState.NOT_CONNECTED); | 192 | setConnectionState(ConnectionState.NOT_CONNECTED); |
| 392 | onCloseJar(); | 193 | onCloseJar(); |
| 393 | 194 | ||
| 394 | this.frame.addWindowListener(new WindowAdapter() { | 195 | JFrame frame = this.mainWindow.frame(); |
| 395 | @Override | 196 | frame.addWindowListener(GuiUtil.onWindowClose(e -> this.close())); |
| 396 | public void windowClosing(WindowEvent event) { | ||
| 397 | close(); | ||
| 398 | } | ||
| 399 | }); | ||
| 400 | 197 | ||
| 401 | // show the frame | 198 | frame.setSize(UiConfig.getWindowSize("Main Window", ScaleUtil.getDimension(1024, 576))); |
| 402 | pane.doLayout(); | 199 | frame.setMinimumSize(ScaleUtil.getDimension(640, 480)); |
| 403 | this.frame.setSize(UiConfig.getWindowSize("Main Window", ScaleUtil.getDimension(1024, 576))); | 200 | frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); |
| 404 | this.frame.setMinimumSize(ScaleUtil.getDimension(640, 480)); | ||
| 405 | this.frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); | ||
| 406 | 201 | ||
| 407 | Point windowPos = UiConfig.getWindowPos("Main Window", null); | 202 | Point windowPos = UiConfig.getWindowPos("Main Window", null); |
| 408 | if (windowPos != null) { | 203 | if (windowPos != null) { |
| 409 | this.frame.setLocation(windowPos); | 204 | frame.setLocation(windowPos); |
| 410 | } else { | 205 | } else { |
| 411 | this.frame.setLocationRelativeTo(null); | 206 | frame.setLocationRelativeTo(null); |
| 412 | } | 207 | } |
| 413 | 208 | ||
| 414 | this.frame.setVisible(true); | 209 | this.retranslateUi(); |
| 210 | } | ||
| 415 | 211 | ||
| 416 | LanguageUtil.addListener(this); | 212 | public MainWindow getMainWindow() { |
| 213 | return this.mainWindow; | ||
| 417 | } | 214 | } |
| 418 | 215 | ||
| 419 | public JFrame getFrame() { | 216 | public JFrame getFrame() { |
| 420 | return this.frame; | 217 | return this.mainWindow.frame(); |
| 421 | } | 218 | } |
| 422 | 219 | ||
| 423 | public GuiController getController() { | 220 | public GuiController getController() { |
| 424 | return this.controller; | 221 | return this.controller; |
| 425 | } | 222 | } |
| 426 | 223 | ||
| 427 | public void setSingleClassTree(boolean singleClassTree) { | 224 | public void setSingleClassTree(boolean singleClassTree) { |
| 428 | this.singleClassTree = singleClassTree; | 225 | this.singleClassTree = singleClassTree; |
| 429 | this.classesPanel.removeAll(); | 226 | this.classesPanel.removeAll(); |
| @@ -431,11 +228,11 @@ public class Gui implements LanguageChangeListener { | |||
| 431 | getController().refreshClasses(); | 228 | getController().refreshClasses(); |
| 432 | retranslateUi(); | 229 | retranslateUi(); |
| 433 | } | 230 | } |
| 434 | 231 | ||
| 435 | public boolean isSingleClassTree() { | 232 | public boolean isSingleClassTree() { |
| 436 | return singleClassTree; | 233 | return singleClassTree; |
| 437 | } | 234 | } |
| 438 | 235 | ||
| 439 | public void onStartOpenJar() { | 236 | public void onStartOpenJar() { |
| 440 | this.classesPanel.removeAll(); | 237 | this.classesPanel.removeAll(); |
| 441 | redraw(); | 238 | redraw(); |
| @@ -443,10 +240,10 @@ public class Gui implements LanguageChangeListener { | |||
| 443 | 240 | ||
| 444 | public void onFinishOpenJar(String jarName) { | 241 | public void onFinishOpenJar(String jarName) { |
| 445 | // update gui | 242 | // update gui |
| 446 | this.frame.setTitle(Enigma.NAME + " - " + jarName); | 243 | this.mainWindow.setTitle(Enigma.NAME + " - " + jarName); |
| 447 | this.classesPanel.removeAll(); | 244 | this.classesPanel.removeAll(); |
| 448 | this.classesPanel.add(isSingleClassTree() ? deobfPanel : splitClasses); | 245 | this.classesPanel.add(isSingleClassTree() ? deobfPanel : splitClasses); |
| 449 | closeAllEditorTabs(); | 246 | this.editorTabbedPane.closeAllEditorTabs(); |
| 450 | 247 | ||
| 451 | // update menu | 248 | // update menu |
| 452 | isJarOpen = true; | 249 | isJarOpen = true; |
| @@ -457,10 +254,10 @@ public class Gui implements LanguageChangeListener { | |||
| 457 | 254 | ||
| 458 | public void onCloseJar() { | 255 | public void onCloseJar() { |
| 459 | // update gui | 256 | // update gui |
| 460 | this.frame.setTitle(Enigma.NAME); | 257 | this.mainWindow.setTitle(Enigma.NAME); |
| 461 | setObfClasses(null); | 258 | setObfClasses(null); |
| 462 | setDeobfClasses(null); | 259 | setDeobfClasses(null); |
| 463 | closeAllEditorTabs(); | 260 | this.editorTabbedPane.closeAllEditorTabs(); |
| 464 | this.classesPanel.removeAll(); | 261 | this.classesPanel.removeAll(); |
| 465 | 262 | ||
| 466 | // update menu | 263 | // update menu |
| @@ -472,53 +269,25 @@ public class Gui implements LanguageChangeListener { | |||
| 472 | } | 269 | } |
| 473 | 270 | ||
| 474 | public EditorPanel openClass(ClassEntry entry) { | 271 | public EditorPanel openClass(ClassEntry entry) { |
| 475 | EditorPanel editorPanel = editors.computeIfAbsent(entry, e -> { | 272 | return this.editorTabbedPane.openClass(entry); |
| 476 | ClassHandle ch = controller.getClassHandleProvider().openClass(entry); | 273 | } |
| 477 | if (ch == null) return null; | ||
| 478 | EditorPanel ed = new EditorPanel(this); | ||
| 479 | ed.setup(); | ||
| 480 | ed.setClassHandle(ch); | ||
| 481 | openFiles.addTab(ed.getFileName(), ed.getUi()); | ||
| 482 | |||
| 483 | ClosableTabTitlePane titlePane = new ClosableTabTitlePane(ed.getFileName(), () -> closeEditor(ed)); | ||
| 484 | openFiles.setTabComponentAt(openFiles.indexOfComponent(ed.getUi()), titlePane.getUi()); | ||
| 485 | titlePane.setTabbedPane(openFiles); | ||
| 486 | |||
| 487 | ed.addListener(new EditorActionListener() { | ||
| 488 | @Override | ||
| 489 | public void onCursorReferenceChanged(EditorPanel editor, EntryReference<Entry<?>, Entry<?>> ref) { | ||
| 490 | updateSelectedReference(editor, ref); | ||
| 491 | } | ||
| 492 | |||
| 493 | @Override | ||
| 494 | public void onClassHandleChanged(EditorPanel editor, ClassEntry old, ClassHandle ch) { | ||
| 495 | editors.remove(old); | ||
| 496 | editors.put(ch.getRef(), editor); | ||
| 497 | } | ||
| 498 | 274 | ||
| 499 | @Override | 275 | @Nullable |
| 500 | public void onTitleChanged(EditorPanel editor, String title) { | 276 | public EditorPanel getActiveEditor() { |
| 501 | titlePane.setText(editor.getFileName()); | 277 | return this.editorTabbedPane.getActiveEditor(); |
| 502 | } | 278 | } |
| 503 | }); | ||
| 504 | |||
| 505 | ed.getEditor().addKeyListener(new KeyAdapter() { | ||
| 506 | @Override | ||
| 507 | public void keyPressed(KeyEvent e) { | ||
| 508 | if (e.getKeyCode() == KeyEvent.VK_4 && (e.getModifiersEx() & KeyEvent.CTRL_DOWN_MASK) != 0) { | ||
| 509 | closeEditor(ed); | ||
| 510 | } | ||
| 511 | } | ||
| 512 | }); | ||
| 513 | 279 | ||
| 514 | return ed; | 280 | public void closeEditor(EditorPanel editor) { |
| 515 | }); | 281 | this.editorTabbedPane.closeEditor(editor); |
| 516 | if (editorPanel != null) { | 282 | } |
| 517 | openFiles.setSelectedComponent(editors.get(entry).getUi()); | ||
| 518 | showStructure(editorPanel); | ||
| 519 | } | ||
| 520 | 283 | ||
| 521 | return editorPanel; | 284 | /** |
| 285 | * Navigates to the reference without modifying history. If the class is not currently loaded, it will be loaded. | ||
| 286 | * | ||
| 287 | * @param reference the reference | ||
| 288 | */ | ||
| 289 | public void showReference(EntryReference<Entry<?>, Entry<?>> reference) { | ||
| 290 | this.editorTabbedPane.openClass(reference.getLocationClassEntry().getOutermostClass()).showReference(reference); | ||
| 522 | } | 291 | } |
| 523 | 292 | ||
| 524 | public void setObfClasses(Collection<ClassEntry> obfClasses) { | 293 | public void setObfClasses(Collection<ClassEntry> obfClasses) { |
| @@ -534,202 +303,72 @@ public class Gui implements LanguageChangeListener { | |||
| 534 | updateUiState(); | 303 | updateUiState(); |
| 535 | } | 304 | } |
| 536 | 305 | ||
| 537 | public void closeEditor(EditorPanel ed) { | 306 | public void showTokens(EditorPanel editor, List<Token> tokens) { |
| 538 | openFiles.remove(ed.getUi()); | 307 | if (tokens.size() > 1) { |
| 539 | editors.inverse().remove(ed); | ||
| 540 | showStructure(getActiveEditor()); | ||
| 541 | ed.destroy(); | ||
| 542 | } | ||
| 543 | |||
| 544 | public void closeAllEditorTabs() { | ||
| 545 | for (Iterator<EditorPanel> iter = editors.values().iterator(); iter.hasNext(); ) { | ||
| 546 | EditorPanel e = iter.next(); | ||
| 547 | openFiles.remove(e.getUi()); | ||
| 548 | e.destroy(); | ||
| 549 | iter.remove(); | ||
| 550 | } | ||
| 551 | } | ||
| 552 | |||
| 553 | public void closeTabsLeftOf(EditorPanel ed) { | ||
| 554 | int index = openFiles.indexOfComponent(ed.getUi()); | ||
| 555 | for (int i = index - 1; i >= 0; i--) { | ||
| 556 | closeEditor(EditorPanel.byUi(openFiles.getComponentAt(i))); | ||
| 557 | } | ||
| 558 | } | ||
| 559 | |||
| 560 | public void closeTabsRightOf(EditorPanel ed) { | ||
| 561 | int index = openFiles.indexOfComponent(ed.getUi()); | ||
| 562 | for (int i = openFiles.getTabCount() - 1; i > index; i--) { | ||
| 563 | closeEditor(EditorPanel.byUi(openFiles.getComponentAt(i))); | ||
| 564 | } | ||
| 565 | } | ||
| 566 | |||
| 567 | public void closeTabsExcept(EditorPanel ed) { | ||
| 568 | int index = openFiles.indexOfComponent(ed.getUi()); | ||
| 569 | for (int i = openFiles.getTabCount() - 1; i >= 0; i--) { | ||
| 570 | if (i == index) continue; | ||
| 571 | closeEditor(EditorPanel.byUi(openFiles.getComponentAt(i))); | ||
| 572 | } | ||
| 573 | } | ||
| 574 | |||
| 575 | public void showTokens(EditorPanel editor, Collection<Token> tokens) { | ||
| 576 | Vector<Token> sortedTokens = new Vector<>(tokens); | ||
| 577 | Collections.sort(sortedTokens); | ||
| 578 | if (sortedTokens.size() > 1) { | ||
| 579 | // sort the tokens and update the tokens panel | ||
| 580 | this.controller.setTokenHandle(editor.getClassHandle().copy()); | 308 | this.controller.setTokenHandle(editor.getClassHandle().copy()); |
| 581 | this.tokens.setListData(sortedTokens); | 309 | this.callsTree.showTokens(tokens); |
| 582 | this.tokens.setSelectedIndex(0); | ||
| 583 | } else { | 310 | } else { |
| 584 | this.tokens.setListData(new Vector<>()); | 311 | this.callsTree.clearTokens(); |
| 585 | } | 312 | } |
| 586 | 313 | ||
| 587 | // show the first token | 314 | // show the first token |
| 588 | editor.navigateToToken(sortedTokens.get(0)); | 315 | editor.navigateToToken(tokens.get(0)); |
| 589 | } | 316 | } |
| 590 | 317 | ||
| 591 | private void updateSelectedReference(EditorPanel editor, EntryReference<Entry<?>, Entry<?>> ref) { | 318 | public void showCursorReference(EntryReference<Entry<?>, Entry<?>> reference) { |
| 592 | if (editor != getActiveEditor()) return; | ||
| 593 | |||
| 594 | showCursorReference(ref); | ||
| 595 | } | ||
| 596 | |||
| 597 | private void showCursorReference(EntryReference<Entry<?>, Entry<?>> reference) { | ||
| 598 | infoPanel.setReference(reference == null ? null : reference.entry); | 319 | infoPanel.setReference(reference == null ? null : reference.entry); |
| 599 | } | 320 | } |
| 600 | 321 | ||
| 601 | @Nullable | 322 | @Nullable |
| 602 | public EditorPanel getActiveEditor() { | ||
| 603 | return EditorPanel.byUi(openFiles.getSelectedComponent()); | ||
| 604 | } | ||
| 605 | |||
| 606 | @Nullable | ||
| 607 | public EntryReference<Entry<?>, Entry<?>> getCursorReference() { | 323 | public EntryReference<Entry<?>, Entry<?>> getCursorReference() { |
| 608 | EditorPanel activeEditor = getActiveEditor(); | 324 | EditorPanel activeEditor = this.editorTabbedPane.getActiveEditor(); |
| 609 | return activeEditor == null ? null : activeEditor.getCursorReference(); | 325 | return activeEditor == null ? null : activeEditor.getCursorReference(); |
| 610 | } | 326 | } |
| 611 | 327 | ||
| 612 | public void startDocChange(EditorPanel editor) { | 328 | public void startDocChange(EditorPanel editor) { |
| 613 | EntryReference<Entry<?>, Entry<?>> cursorReference = editor.getCursorReference(); | 329 | EntryReference<Entry<?>, Entry<?>> cursorReference = editor.getCursorReference(); |
| 614 | if (cursorReference == null || !this.isEditable(EditableType.JAVADOC)) return; | 330 | if (cursorReference == null || !this.isEditable(EditableType.JAVADOC)) return; |
| 615 | JavadocDialog.show(frame, getController(), cursorReference); | 331 | JavadocDialog.show(mainWindow.frame(), getController(), cursorReference); |
| 616 | } | 332 | } |
| 617 | 333 | ||
| 618 | public void startRename(EditorPanel editor, String text) { | 334 | public void startRename(EditorPanel editor, String text) { |
| 619 | if (editor != getActiveEditor()) return; | 335 | if (editor != this.editorTabbedPane.getActiveEditor()) return; |
| 620 | 336 | ||
| 621 | infoPanel.startRenaming(text); | 337 | infoPanel.startRenaming(text); |
| 622 | } | 338 | } |
| 623 | 339 | ||
| 624 | public void startRename(EditorPanel editor) { | 340 | public void startRename(EditorPanel editor) { |
| 625 | if (editor != getActiveEditor()) return; | 341 | if (editor != this.editorTabbedPane.getActiveEditor()) return; |
| 626 | 342 | ||
| 627 | infoPanel.startRenaming(); | 343 | infoPanel.startRenaming(); |
| 628 | } | 344 | } |
| 629 | 345 | ||
| 630 | public void showStructure(EditorPanel editor) { | 346 | public void showStructure(EditorPanel editor) { |
| 631 | JTree structureTree = this.structurePanel.getStructureTree(); | 347 | this.structurePanel.showStructure(editor); |
| 632 | structureTree.setModel(null); | ||
| 633 | |||
| 634 | if (editor == null) { | ||
| 635 | this.structurePanel.getSortingPanel().setVisible(false); | ||
| 636 | return; | ||
| 637 | } | ||
| 638 | |||
| 639 | ClassEntry classEntry = editor.getClassHandle().getRef(); | ||
| 640 | if (classEntry == null) return; | ||
| 641 | |||
| 642 | this.structurePanel.getSortingPanel().setVisible(true); | ||
| 643 | |||
| 644 | // get the class structure | ||
| 645 | StructureTreeNode node = this.controller.getClassStructure(classEntry, this.structurePanel.getOptions()); | ||
| 646 | |||
| 647 | // show the tree at the root | ||
| 648 | TreePath path = getPathToRoot(node); | ||
| 649 | structureTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); | ||
| 650 | structureTree.expandPath(path); | ||
| 651 | structureTree.setSelectionRow(structureTree.getRowForPath(path)); | ||
| 652 | |||
| 653 | redraw(); | ||
| 654 | } | 348 | } |
| 655 | 349 | ||
| 656 | public void showInheritance(EditorPanel editor) { | 350 | public void showInheritance(EditorPanel editor) { |
| 657 | EntryReference<Entry<?>, Entry<?>> cursorReference = editor.getCursorReference(); | 351 | EntryReference<Entry<?>, Entry<?>> cursorReference = editor.getCursorReference(); |
| 658 | if (cursorReference == null) return; | 352 | if (cursorReference == null) return; |
| 659 | 353 | ||
| 660 | inheritanceTree.setModel(null); | 354 | this.inheritanceTree.display(cursorReference.entry); |
| 661 | |||
| 662 | if (cursorReference.entry instanceof ClassEntry) { | ||
| 663 | // get the class inheritance | ||
| 664 | ClassInheritanceTreeNode classNode = this.controller.getClassInheritance((ClassEntry) cursorReference.entry); | ||
| 665 | |||
| 666 | // show the tree at the root | ||
| 667 | TreePath path = getPathToRoot(classNode); | ||
| 668 | inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); | ||
| 669 | inheritanceTree.expandPath(path); | ||
| 670 | inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path)); | ||
| 671 | } else if (cursorReference.entry instanceof MethodEntry) { | ||
| 672 | // get the method inheritance | ||
| 673 | MethodInheritanceTreeNode classNode = this.controller.getMethodInheritance((MethodEntry) cursorReference.entry); | ||
| 674 | |||
| 675 | // show the tree at the root | ||
| 676 | TreePath path = getPathToRoot(classNode); | ||
| 677 | inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); | ||
| 678 | inheritanceTree.expandPath(path); | ||
| 679 | inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path)); | ||
| 680 | } | ||
| 681 | |||
| 682 | tabs.setSelectedIndex(1); | 355 | tabs.setSelectedIndex(1); |
| 683 | |||
| 684 | redraw(); | ||
| 685 | } | 356 | } |
| 686 | 357 | ||
| 687 | public void showImplementations(EditorPanel editor) { | 358 | public void showImplementations(EditorPanel editor) { |
| 688 | EntryReference<Entry<?>, Entry<?>> cursorReference = editor.getCursorReference(); | 359 | EntryReference<Entry<?>, Entry<?>> cursorReference = editor.getCursorReference(); |
| 689 | if (cursorReference == null) return; | 360 | if (cursorReference == null) return; |
| 690 | 361 | ||
| 691 | implementationsTree.setModel(null); | 362 | this.implementationsTree.display(cursorReference.entry); |
| 692 | |||
| 693 | DefaultMutableTreeNode node = null; | ||
| 694 | |||
| 695 | // get the class implementations | ||
| 696 | if (cursorReference.entry instanceof ClassEntry) | ||
| 697 | node = this.controller.getClassImplementations((ClassEntry) cursorReference.entry); | ||
| 698 | else // get the method implementations | ||
| 699 | if (cursorReference.entry instanceof MethodEntry) | ||
| 700 | node = this.controller.getMethodImplementations((MethodEntry) cursorReference.entry); | ||
| 701 | |||
| 702 | if (node != null) { | ||
| 703 | // show the tree at the root | ||
| 704 | TreePath path = getPathToRoot(node); | ||
| 705 | implementationsTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); | ||
| 706 | implementationsTree.expandPath(path); | ||
| 707 | implementationsTree.setSelectionRow(implementationsTree.getRowForPath(path)); | ||
| 708 | } | ||
| 709 | |||
| 710 | tabs.setSelectedIndex(2); | 363 | tabs.setSelectedIndex(2); |
| 711 | |||
| 712 | redraw(); | ||
| 713 | } | 364 | } |
| 714 | 365 | ||
| 715 | public void showCalls(EditorPanel editor, boolean recurse) { | 366 | public void showCalls(EditorPanel editor, boolean recurse) { |
| 716 | EntryReference<Entry<?>, Entry<?>> cursorReference = editor.getCursorReference(); | 367 | EntryReference<Entry<?>, Entry<?>> cursorReference = editor.getCursorReference(); |
| 717 | if (cursorReference == null) return; | 368 | if (cursorReference == null) return; |
| 718 | 369 | ||
| 719 | if (cursorReference.entry instanceof ClassEntry) { | 370 | this.callsTree.showCalls(cursorReference.entry, recurse); |
| 720 | ClassReferenceTreeNode node = this.controller.getClassReferences((ClassEntry) cursorReference.entry); | ||
| 721 | callsTree.setModel(new DefaultTreeModel(node)); | ||
| 722 | } else if (cursorReference.entry instanceof FieldEntry) { | ||
| 723 | FieldReferenceTreeNode node = this.controller.getFieldReferences((FieldEntry) cursorReference.entry); | ||
| 724 | callsTree.setModel(new DefaultTreeModel(node)); | ||
| 725 | } else if (cursorReference.entry instanceof MethodEntry) { | ||
| 726 | MethodReferenceTreeNode node = this.controller.getMethodReferences((MethodEntry) cursorReference.entry, recurse); | ||
| 727 | callsTree.setModel(new DefaultTreeModel(node)); | ||
| 728 | } | ||
| 729 | |||
| 730 | tabs.setSelectedIndex(3); | 371 | tabs.setSelectedIndex(3); |
| 731 | |||
| 732 | redraw(); | ||
| 733 | } | 372 | } |
| 734 | 373 | ||
| 735 | public void toggleMapping(EditorPanel editor) { | 374 | public void toggleMapping(EditorPanel editor) { |
| @@ -757,13 +396,13 @@ public class Gui implements LanguageChangeListener { | |||
| 757 | } | 396 | } |
| 758 | 397 | ||
| 759 | public void showDiscardDiag(Function<Integer, Void> callback, String... options) { | 398 | public void showDiscardDiag(Function<Integer, Void> callback, String... options) { |
| 760 | int response = JOptionPane.showOptionDialog(this.frame, I18n.translate("prompt.close.summary"), I18n.translate("prompt.close.title"), JOptionPane.YES_NO_CANCEL_OPTION, | 399 | int response = JOptionPane.showOptionDialog(this.mainWindow.frame(), I18n.translate("prompt.close.summary"), I18n.translate("prompt.close.title"), JOptionPane.YES_NO_CANCEL_OPTION, |
| 761 | JOptionPane.QUESTION_MESSAGE, null, options, options[2]); | 400 | JOptionPane.QUESTION_MESSAGE, null, options, options[2]); |
| 762 | callback.apply(response); | 401 | callback.apply(response); |
| 763 | } | 402 | } |
| 764 | 403 | ||
| 765 | public CompletableFuture<Void> saveMapping() { | 404 | public CompletableFuture<Void> saveMapping() { |
| 766 | if (this.enigmaMappingsFileChooser.getSelectedFile() != null || this.enigmaMappingsFileChooser.showSaveDialog(this.frame) == JFileChooser.APPROVE_OPTION) | 405 | if (this.enigmaMappingsFileChooser.getSelectedFile() != null || this.enigmaMappingsFileChooser.showSaveDialog(this.mainWindow.frame()) == JFileChooser.APPROVE_OPTION) |
| 767 | return this.controller.saveMappings(this.enigmaMappingsFileChooser.getSelectedFile().toPath()); | 406 | return this.controller.saveMappings(this.enigmaMappingsFileChooser.getSelectedFile().toPath()); |
| 768 | return CompletableFuture.completedFuture(null); | 407 | return CompletableFuture.completedFuture(null); |
| 769 | } | 408 | } |
| @@ -788,8 +427,8 @@ public class Gui implements LanguageChangeListener { | |||
| 788 | } | 427 | } |
| 789 | 428 | ||
| 790 | private void exit() { | 429 | private void exit() { |
| 791 | UiConfig.setWindowPos("Main Window", this.frame.getLocationOnScreen()); | 430 | UiConfig.setWindowPos("Main Window", this.mainWindow.frame().getLocationOnScreen()); |
| 792 | UiConfig.setWindowSize("Main Window", this.frame.getSize()); | 431 | UiConfig.setWindowSize("Main Window", this.mainWindow.frame().getSize()); |
| 793 | UiConfig.setLayout( | 432 | UiConfig.setLayout( |
| 794 | this.splitClasses.getDividerLocation(), | 433 | this.splitClasses.getDividerLocation(), |
| 795 | this.splitCenter.getDividerLocation(), | 434 | this.splitCenter.getDividerLocation(), |
| @@ -800,13 +439,15 @@ public class Gui implements LanguageChangeListener { | |||
| 800 | if (searchDialog != null) { | 439 | if (searchDialog != null) { |
| 801 | searchDialog.dispose(); | 440 | searchDialog.dispose(); |
| 802 | } | 441 | } |
| 803 | this.frame.dispose(); | 442 | this.mainWindow.frame().dispose(); |
| 804 | System.exit(0); | 443 | System.exit(0); |
| 805 | } | 444 | } |
| 806 | 445 | ||
| 807 | public void redraw() { | 446 | public void redraw() { |
| 808 | this.frame.validate(); | 447 | JFrame frame = this.mainWindow.frame(); |
| 809 | this.frame.repaint(); | 448 | |
| 449 | frame.validate(); | ||
| 450 | frame.repaint(); | ||
| 810 | } | 451 | } |
| 811 | 452 | ||
| 812 | public void onRenameFromClassTree(ValidationContext vc, Object prevData, Object data, DefaultMutableTreeNode node) { | 453 | public void onRenameFromClassTree(ValidationContext vc, Object prevData, Object data, DefaultMutableTreeNode node) { |
| @@ -896,19 +537,16 @@ public class Gui implements LanguageChangeListener { | |||
| 896 | return searchDialog; | 537 | return searchDialog; |
| 897 | } | 538 | } |
| 898 | 539 | ||
| 899 | |||
| 900 | public MenuBar getMenuBar() { | ||
| 901 | return menuBar; | ||
| 902 | } | ||
| 903 | |||
| 904 | public void addMessage(Message message) { | 540 | public void addMessage(Message message) { |
| 905 | JScrollBar verticalScrollBar = messageScrollPane.getVerticalScrollBar(); | 541 | JScrollBar verticalScrollBar = messageScrollPane.getVerticalScrollBar(); |
| 906 | boolean isAtBottom = verticalScrollBar.getValue() >= verticalScrollBar.getMaximum() - verticalScrollBar.getModel().getExtent(); | 542 | boolean isAtBottom = verticalScrollBar.getValue() >= verticalScrollBar.getMaximum() - verticalScrollBar.getModel().getExtent(); |
| 907 | messageModel.addElement(message); | 543 | messageModel.addElement(message); |
| 544 | |||
| 908 | if (isAtBottom) { | 545 | if (isAtBottom) { |
| 909 | SwingUtilities.invokeLater(() -> verticalScrollBar.setValue(verticalScrollBar.getMaximum() - verticalScrollBar.getModel().getExtent())); | 546 | SwingUtilities.invokeLater(() -> verticalScrollBar.setValue(verticalScrollBar.getMaximum() - verticalScrollBar.getModel().getExtent())); |
| 910 | } | 547 | } |
| 911 | statusLabel.setText(message.translate()); | 548 | |
| 549 | this.mainWindow.statusBar().showMessage(message.translate(), 5000); | ||
| 912 | } | 550 | } |
| 913 | 551 | ||
| 914 | public void setUserList(List<String> users) { | 552 | public void setUserList(List<String> users) { |
| @@ -946,7 +584,6 @@ public class Gui implements LanguageChangeListener { | |||
| 946 | splitRight.setDividerLocation(splitRight.getDividerLocation()); | 584 | splitRight.setDividerLocation(splitRight.getDividerLocation()); |
| 947 | } | 585 | } |
| 948 | 586 | ||
| 949 | @Override | ||
| 950 | public void retranslateUi() { | 587 | public void retranslateUi() { |
| 951 | this.jarFileChooser.setDialogTitle(I18n.translate("menu.file.jar.open")); | 588 | this.jarFileChooser.setDialogTitle(I18n.translate("menu.file.jar.open")); |
| 952 | this.exportJarFileChooser.setDialogTitle(I18n.translate("menu.file.export.jar")); | 589 | this.exportJarFileChooser.setDialogTitle(I18n.translate("menu.file.export.jar")); |
| @@ -963,16 +600,17 @@ public class Gui implements LanguageChangeListener { | |||
| 963 | this.menuBar.retranslateUi(); | 600 | this.menuBar.retranslateUi(); |
| 964 | this.obfPanel.retranslateUi(); | 601 | this.obfPanel.retranslateUi(); |
| 965 | this.deobfPanel.retranslateUi(); | 602 | this.deobfPanel.retranslateUi(); |
| 966 | this.deobfPanelPopupMenu.retranslateUi(); | ||
| 967 | this.infoPanel.retranslateUi(); | 603 | this.infoPanel.retranslateUi(); |
| 968 | this.structurePanel.retranslateUi(); | 604 | this.structurePanel.retranslateUi(); |
| 969 | this.editorTabPopupMenu.retranslateUi(); | 605 | this.editorTabbedPane.retranslateUi(); |
| 970 | this.editors.values().forEach(EditorPanel::retranslateUi); | 606 | this.inheritanceTree.retranslateUi(); |
| 607 | this.implementationsTree.retranslateUi(); | ||
| 608 | this.structurePanel.retranslateUi(); | ||
| 609 | this.callsTree.retranslateUi(); | ||
| 971 | } | 610 | } |
| 972 | 611 | ||
| 973 | public void setConnectionState(ConnectionState state) { | 612 | public void setConnectionState(ConnectionState state) { |
| 974 | connectionState = state; | 613 | connectionState = state; |
| 975 | statusLabel.setText(I18n.translate("status.ready")); | ||
| 976 | updateUiState(); | 614 | updateUiState(); |
| 977 | } | 615 | } |
| 978 | 616 | ||
| @@ -984,10 +622,6 @@ public class Gui implements LanguageChangeListener { | |||
| 984 | return this.connectionState; | 622 | return this.connectionState; |
| 985 | } | 623 | } |
| 986 | 624 | ||
| 987 | public IdentifierPanel getInfoPanel() { | ||
| 988 | return infoPanel; | ||
| 989 | } | ||
| 990 | |||
| 991 | public boolean validateImmediateAction(Consumer<ValidationContext> op) { | 625 | public boolean validateImmediateAction(Consumer<ValidationContext> op) { |
| 992 | ValidationContext vc = new ValidationContext(); | 626 | ValidationContext vc = new ValidationContext(); |
| 993 | op.accept(vc); | 627 | op.accept(vc); |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java index 4a15b418..67ac6625 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java | |||
| @@ -85,6 +85,8 @@ public class GuiController implements ClientPacketHandler { | |||
| 85 | private EnigmaClient client; | 85 | private EnigmaClient client; |
| 86 | private EnigmaServer server; | 86 | private EnigmaServer server; |
| 87 | 87 | ||
| 88 | private History<EntryReference<Entry<?>, Entry<?>>> referenceHistory; | ||
| 89 | |||
| 88 | public GuiController(Gui gui, EnigmaProfile profile) { | 90 | public GuiController(Gui gui, EnigmaProfile profile) { |
| 89 | this.gui = gui; | 91 | this.gui = gui; |
| 90 | this.enigma = Enigma.builder() | 92 | this.enigma = Enigma.builder() |
| @@ -299,53 +301,46 @@ public class GuiController implements ClientPacketHandler { | |||
| 299 | if (reference == null) { | 301 | if (reference == null) { |
| 300 | throw new IllegalArgumentException("Reference cannot be null!"); | 302 | throw new IllegalArgumentException("Reference cannot be null!"); |
| 301 | } | 303 | } |
| 302 | if (this.gui.referenceHistory == null) { | 304 | if (this.referenceHistory == null) { |
| 303 | this.gui.referenceHistory = new History<>(reference); | 305 | this.referenceHistory = new History<>(reference); |
| 304 | } else { | 306 | } else { |
| 305 | if (!reference.equals(this.gui.referenceHistory.getCurrent())) { | 307 | if (!reference.equals(this.referenceHistory.getCurrent())) { |
| 306 | this.gui.referenceHistory.push(reference); | 308 | this.referenceHistory.push(reference); |
| 307 | } | 309 | } |
| 308 | } | 310 | } |
| 309 | setReference(reference); | ||
| 310 | } | ||
| 311 | 311 | ||
| 312 | /** | 312 | this.gui.showReference(reference); |
| 313 | * Navigates to the reference without modifying history. If the class is not currently loaded, it will be loaded. | ||
| 314 | * | ||
| 315 | * @param reference the reference | ||
| 316 | */ | ||
| 317 | private void setReference(EntryReference<Entry<?>, Entry<?>> reference) { | ||
| 318 | gui.openClass(reference.getLocationClassEntry().getOutermostClass()).showReference(reference); | ||
| 319 | } | 313 | } |
| 320 | 314 | ||
| 321 | public Collection<Token> getTokensForReference(DecompiledClassSource source, EntryReference<Entry<?>, Entry<?>> reference) { | 315 | public List<Token> getTokensForReference(DecompiledClassSource source, EntryReference<Entry<?>, Entry<?>> reference) { |
| 322 | EntryRemapper mapper = this.project.getMapper(); | 316 | EntryRemapper mapper = this.project.getMapper(); |
| 323 | 317 | ||
| 324 | SourceIndex index = source.getIndex(); | 318 | SourceIndex index = source.getIndex(); |
| 325 | return mapper.getObfResolver().resolveReference(reference, ResolutionStrategy.RESOLVE_CLOSEST) | 319 | return mapper.getObfResolver().resolveReference(reference, ResolutionStrategy.RESOLVE_CLOSEST) |
| 326 | .stream() | 320 | .stream() |
| 327 | .flatMap(r -> index.getReferenceTokens(r).stream()) | 321 | .flatMap(r -> index.getReferenceTokens(r).stream()) |
| 322 | .sorted() | ||
| 328 | .toList(); | 323 | .toList(); |
| 329 | } | 324 | } |
| 330 | 325 | ||
| 331 | public void openPreviousReference() { | 326 | public void openPreviousReference() { |
| 332 | if (hasPreviousReference()) { | 327 | if (hasPreviousReference()) { |
| 333 | setReference(gui.referenceHistory.goBack()); | 328 | this.gui.showReference(referenceHistory.goBack()); |
| 334 | } | 329 | } |
| 335 | } | 330 | } |
| 336 | 331 | ||
| 337 | public boolean hasPreviousReference() { | 332 | public boolean hasPreviousReference() { |
| 338 | return gui.referenceHistory != null && gui.referenceHistory.canGoBack(); | 333 | return referenceHistory != null && referenceHistory.canGoBack(); |
| 339 | } | 334 | } |
| 340 | 335 | ||
| 341 | public void openNextReference() { | 336 | public void openNextReference() { |
| 342 | if (hasNextReference()) { | 337 | if (hasNextReference()) { |
| 343 | setReference(gui.referenceHistory.goForward()); | 338 | this.gui.showReference(referenceHistory.goForward()); |
| 344 | } | 339 | } |
| 345 | } | 340 | } |
| 346 | 341 | ||
| 347 | public boolean hasNextReference() { | 342 | public boolean hasNextReference() { |
| 348 | return gui.referenceHistory != null && gui.referenceHistory.canGoForward(); | 343 | return referenceHistory != null && referenceHistory.canGoForward(); |
| 349 | } | 344 | } |
| 350 | 345 | ||
| 351 | public void navigateTo(Entry<?> entry) { | 346 | public void navigateTo(Entry<?> entry) { |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/Main.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/Main.java index f6a03f54..1172a393 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/Main.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/Main.java | |||
| @@ -25,6 +25,7 @@ import joptsimple.*; | |||
| 25 | import cuchaz.enigma.EnigmaProfile; | 25 | import cuchaz.enigma.EnigmaProfile; |
| 26 | import cuchaz.enigma.gui.config.Themes; | 26 | import cuchaz.enigma.gui.config.Themes; |
| 27 | import cuchaz.enigma.gui.config.UiConfig; | 27 | import cuchaz.enigma.gui.config.UiConfig; |
| 28 | import cuchaz.enigma.gui.dialog.CrashDialog; | ||
| 28 | import cuchaz.enigma.translation.mapping.serde.MappingFormat; | 29 | import cuchaz.enigma.translation.mapping.serde.MappingFormat; |
| 29 | import cuchaz.enigma.utils.I18n; | 30 | import cuchaz.enigma.utils.I18n; |
| 30 | 31 | ||
| @@ -114,6 +115,17 @@ public class Main { | |||
| 114 | gui.setSingleClassTree(true); | 115 | gui.setSingleClassTree(true); |
| 115 | } | 116 | } |
| 116 | 117 | ||
| 118 | if (Boolean.parseBoolean(System.getProperty("enigma.catchExceptions", "true"))) { | ||
| 119 | // install a global exception handler to the event thread | ||
| 120 | CrashDialog.init(gui.getFrame()); | ||
| 121 | Thread.setDefaultUncaughtExceptionHandler((thread, t) -> { | ||
| 122 | t.printStackTrace(System.err); | ||
| 123 | if (!ExceptionIgnorer.shouldIgnore(t)) { | ||
| 124 | CrashDialog.show(t); | ||
| 125 | } | ||
| 126 | }); | ||
| 127 | } | ||
| 128 | |||
| 117 | if (options.has(jar)) { | 129 | if (options.has(jar)) { |
| 118 | Path jarPath = options.valueOf(jar); | 130 | Path jarPath = options.valueOf(jar); |
| 119 | controller.openJar(jarPath) | 131 | controller.openJar(jarPath) |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/AbstractInheritanceTree.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/AbstractInheritanceTree.java new file mode 100644 index 00000000..39aa212f --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/AbstractInheritanceTree.java | |||
| @@ -0,0 +1,86 @@ | |||
| 1 | package cuchaz.enigma.gui.elements; | ||
| 2 | |||
| 3 | import java.awt.BorderLayout; | ||
| 4 | import java.awt.event.MouseEvent; | ||
| 5 | |||
| 6 | import javax.annotation.Nullable; | ||
| 7 | import javax.swing.JPanel; | ||
| 8 | import javax.swing.JScrollPane; | ||
| 9 | import javax.swing.JTree; | ||
| 10 | import javax.swing.tree.*; | ||
| 11 | |||
| 12 | import cuchaz.enigma.analysis.ClassInheritanceTreeNode; | ||
| 13 | import cuchaz.enigma.analysis.MethodInheritanceTreeNode; | ||
| 14 | import cuchaz.enigma.gui.Gui; | ||
| 15 | import cuchaz.enigma.gui.util.GuiUtil; | ||
| 16 | import cuchaz.enigma.gui.util.SingleTreeSelectionModel; | ||
| 17 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 18 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 19 | |||
| 20 | public abstract class AbstractInheritanceTree { | ||
| 21 | private final JPanel panel = new JPanel(new BorderLayout()); | ||
| 22 | |||
| 23 | private final JTree tree = new JTree(); | ||
| 24 | |||
| 25 | protected final Gui gui; | ||
| 26 | |||
| 27 | public AbstractInheritanceTree(Gui gui, TreeCellRenderer cellRenderer) { | ||
| 28 | this.gui = gui; | ||
| 29 | |||
| 30 | this.tree.setModel(null); | ||
| 31 | this.tree.setCellRenderer(cellRenderer); | ||
| 32 | this.tree.setSelectionModel(new SingleTreeSelectionModel()); | ||
| 33 | this.tree.setShowsRootHandles(true); | ||
| 34 | this.tree.addMouseListener(GuiUtil.onMouseClick(this::onClick)); | ||
| 35 | |||
| 36 | this.panel.add(new JScrollPane(this.tree)); | ||
| 37 | } | ||
| 38 | |||
| 39 | private void onClick(MouseEvent event) { | ||
| 40 | if (event.getClickCount() >= 2 && event.getButton() == MouseEvent.BUTTON1) { | ||
| 41 | // get the selected node | ||
| 42 | TreePath path = tree.getSelectionPath(); | ||
| 43 | if (path == null) { | ||
| 44 | return; | ||
| 45 | } | ||
| 46 | |||
| 47 | Object node = path.getLastPathComponent(); | ||
| 48 | if (node instanceof ClassInheritanceTreeNode classNode) { | ||
| 49 | gui.getController().navigateTo(new ClassEntry(classNode.getObfClassName())); | ||
| 50 | } else if (node instanceof MethodInheritanceTreeNode methodNode) { | ||
| 51 | if (methodNode.isImplemented()) { | ||
| 52 | gui.getController().navigateTo(methodNode.getMethodEntry()); | ||
| 53 | } | ||
| 54 | } | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | public void display(Entry<?> entry) { | ||
| 59 | this.tree.setModel(null); | ||
| 60 | |||
| 61 | DefaultMutableTreeNode node = this.getNodeFor(entry); | ||
| 62 | |||
| 63 | if (node != null) { | ||
| 64 | // show the tree at the root | ||
| 65 | TreePath path = GuiUtil.getPathToRoot(node); | ||
| 66 | this.tree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); | ||
| 67 | this.tree.expandPath(path); | ||
| 68 | this.tree.setSelectionRow(this.tree.getRowForPath(path)); | ||
| 69 | } | ||
| 70 | |||
| 71 | this.panel.show(); | ||
| 72 | } | ||
| 73 | |||
| 74 | public void retranslateUi() { | ||
| 75 | |||
| 76 | } | ||
| 77 | |||
| 78 | @Nullable | ||
| 79 | protected abstract DefaultMutableTreeNode getNodeFor(Entry<?> entry); | ||
| 80 | |||
| 81 | protected abstract String getPanelName(); | ||
| 82 | |||
| 83 | public JPanel getPanel() { | ||
| 84 | return this.panel; | ||
| 85 | } | ||
| 86 | } | ||
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/CallsTree.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/CallsTree.java new file mode 100644 index 00000000..c92534f0 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/CallsTree.java | |||
| @@ -0,0 +1,125 @@ | |||
| 1 | package cuchaz.enigma.gui.elements; | ||
| 2 | |||
| 3 | import java.awt.BorderLayout; | ||
| 4 | import java.awt.event.MouseEvent; | ||
| 5 | import java.util.Collection; | ||
| 6 | import java.util.Vector; | ||
| 7 | |||
| 8 | import javax.swing.*; | ||
| 9 | import javax.swing.tree.DefaultTreeModel; | ||
| 10 | import javax.swing.tree.TreeNode; | ||
| 11 | import javax.swing.tree.TreePath; | ||
| 12 | |||
| 13 | import cuchaz.enigma.analysis.ReferenceTreeNode; | ||
| 14 | import cuchaz.enigma.gui.Gui; | ||
| 15 | import cuchaz.enigma.gui.TokenListCellRenderer; | ||
| 16 | import cuchaz.enigma.gui.renderer.CallsTreeCellRenderer; | ||
| 17 | import cuchaz.enigma.gui.util.GuiUtil; | ||
| 18 | import cuchaz.enigma.gui.util.ScaleUtil; | ||
| 19 | import cuchaz.enigma.gui.util.SingleTreeSelectionModel; | ||
| 20 | import cuchaz.enigma.source.Token; | ||
| 21 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 22 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 23 | import cuchaz.enigma.translation.representation.entry.FieldEntry; | ||
| 24 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 25 | |||
| 26 | public class CallsTree { | ||
| 27 | private final JPanel panel = new JPanel(new BorderLayout()); | ||
| 28 | |||
| 29 | private final JTree callsTree = new JTree(); | ||
| 30 | private final JList<Token> tokens = new JList<>(); | ||
| 31 | |||
| 32 | private final Gui gui; | ||
| 33 | |||
| 34 | public CallsTree(Gui gui) { | ||
| 35 | this.gui = gui; | ||
| 36 | |||
| 37 | this.callsTree.setModel(null); | ||
| 38 | this.callsTree.setCellRenderer(new CallsTreeCellRenderer(gui)); | ||
| 39 | this.callsTree.setSelectionModel(new SingleTreeSelectionModel()); | ||
| 40 | this.callsTree.setShowsRootHandles(true); | ||
| 41 | this.callsTree.addMouseListener(GuiUtil.onMouseClick(this::onTreeClicked)); | ||
| 42 | |||
| 43 | this.tokens.setCellRenderer(new TokenListCellRenderer(gui.getController())); | ||
| 44 | this.tokens.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); | ||
| 45 | this.tokens.setLayoutOrientation(JList.VERTICAL); | ||
| 46 | this.tokens.addMouseListener(GuiUtil.onMouseClick(this::onTokenClicked)); | ||
| 47 | this.tokens.setPreferredSize(ScaleUtil.getDimension(0, 200)); | ||
| 48 | this.tokens.setMinimumSize(ScaleUtil.getDimension(0, 200)); | ||
| 49 | |||
| 50 | JSplitPane contentPane = new JSplitPane( | ||
| 51 | JSplitPane.VERTICAL_SPLIT, | ||
| 52 | true, | ||
| 53 | new JScrollPane(this.callsTree), | ||
| 54 | new JScrollPane(this.tokens) | ||
| 55 | ); | ||
| 56 | |||
| 57 | contentPane.setResizeWeight(1); // let the top side take all the slack | ||
| 58 | contentPane.resetToPreferredSizes(); | ||
| 59 | this.panel.add(contentPane, BorderLayout.CENTER); | ||
| 60 | } | ||
| 61 | |||
| 62 | public void showCalls(Entry<?> entry, boolean recurse) { | ||
| 63 | TreeNode node = null; | ||
| 64 | |||
| 65 | if (entry instanceof ClassEntry classEntry) { | ||
| 66 | node = this.gui.getController().getClassReferences(classEntry); | ||
| 67 | } else if (entry instanceof FieldEntry fieldEntry) { | ||
| 68 | node = this.gui.getController().getFieldReferences(fieldEntry); | ||
| 69 | } else if (entry instanceof MethodEntry methodEntry) { | ||
| 70 | node = this.gui.getController().getMethodReferences(methodEntry, recurse); | ||
| 71 | } | ||
| 72 | |||
| 73 | this.callsTree.setModel(new DefaultTreeModel(node)); | ||
| 74 | |||
| 75 | this.panel.show(); | ||
| 76 | } | ||
| 77 | |||
| 78 | public void showTokens(Collection<Token> tokens) { | ||
| 79 | this.tokens.setListData(new Vector<>(tokens)); | ||
| 80 | this.tokens.setSelectedIndex(0); | ||
| 81 | } | ||
| 82 | |||
| 83 | public void clearTokens() { | ||
| 84 | this.tokens.setListData(new Vector<>()); | ||
| 85 | } | ||
| 86 | |||
| 87 | @SuppressWarnings("unchecked") | ||
| 88 | private void onTreeClicked(MouseEvent event) { | ||
| 89 | if (event.getClickCount() >= 2 && event.getButton() == MouseEvent.BUTTON1) { | ||
| 90 | // get the selected node | ||
| 91 | TreePath path = this.callsTree.getSelectionPath(); | ||
| 92 | |||
| 93 | if (path == null) { | ||
| 94 | return; | ||
| 95 | } | ||
| 96 | |||
| 97 | Object node = path.getLastPathComponent(); | ||
| 98 | |||
| 99 | if (node instanceof ReferenceTreeNode referenceNode) { | ||
| 100 | if (referenceNode.getReference() != null) { | ||
| 101 | this.gui.getController().navigateTo(referenceNode.getReference()); | ||
| 102 | } else { | ||
| 103 | this.gui.getController().navigateTo(referenceNode.getEntry()); | ||
| 104 | } | ||
| 105 | } | ||
| 106 | } | ||
| 107 | } | ||
| 108 | |||
| 109 | private void onTokenClicked(MouseEvent event) { | ||
| 110 | if (event.getClickCount() == 2) { | ||
| 111 | Token selected = this.tokens.getSelectedValue(); | ||
| 112 | if (selected != null) { | ||
| 113 | this.gui.openClass(this.gui.getController().getTokenHandle().getRef()).navigateToToken(selected); | ||
| 114 | } | ||
| 115 | } | ||
| 116 | } | ||
| 117 | |||
| 118 | public void retranslateUi() { | ||
| 119 | |||
| 120 | } | ||
| 121 | |||
| 122 | public JPanel getPanel() { | ||
| 123 | return this.panel; | ||
| 124 | } | ||
| 125 | } | ||
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/DeobfPanelPopupMenu.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/DeobfPanelPopupMenu.java index 9481412e..0b44881f 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/DeobfPanelPopupMenu.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/DeobfPanelPopupMenu.java | |||
| @@ -1,13 +1,13 @@ | |||
| 1 | package cuchaz.enigma.gui.elements; | 1 | package cuchaz.enigma.gui.elements; |
| 2 | 2 | ||
| 3 | import javax.swing.JMenuItem; | ||
| 4 | import javax.swing.JPopupMenu; | ||
| 5 | import javax.swing.tree.TreePath; | ||
| 6 | |||
| 3 | import cuchaz.enigma.gui.ClassSelector; | 7 | import cuchaz.enigma.gui.ClassSelector; |
| 4 | import cuchaz.enigma.gui.Gui; | 8 | import cuchaz.enigma.gui.panels.DeobfPanel; |
| 5 | import cuchaz.enigma.utils.I18n; | 9 | import cuchaz.enigma.utils.I18n; |
| 6 | 10 | ||
| 7 | import javax.swing.*; | ||
| 8 | import javax.swing.tree.TreePath; | ||
| 9 | import java.awt.*; | ||
| 10 | |||
| 11 | public class DeobfPanelPopupMenu { | 11 | public class DeobfPanelPopupMenu { |
| 12 | 12 | ||
| 13 | private final JPopupMenu ui; | 13 | private final JPopupMenu ui; |
| @@ -16,7 +16,7 @@ public class DeobfPanelPopupMenu { | |||
| 16 | private final JMenuItem expandAll = new JMenuItem(); | 16 | private final JMenuItem expandAll = new JMenuItem(); |
| 17 | private final JMenuItem collapseAll = new JMenuItem(); | 17 | private final JMenuItem collapseAll = new JMenuItem(); |
| 18 | 18 | ||
| 19 | public DeobfPanelPopupMenu(Gui gui) { | 19 | public DeobfPanelPopupMenu(DeobfPanel panel) { |
| 20 | this.ui = new JPopupMenu(); | 20 | this.ui = new JPopupMenu(); |
| 21 | 21 | ||
| 22 | this.ui.add(this.renamePackage); | 22 | this.ui.add(this.renamePackage); |
| @@ -25,7 +25,7 @@ public class DeobfPanelPopupMenu { | |||
| 25 | this.ui.add(this.expandAll); | 25 | this.ui.add(this.expandAll); |
| 26 | this.ui.add(this.collapseAll); | 26 | this.ui.add(this.collapseAll); |
| 27 | 27 | ||
| 28 | ClassSelector deobfClasses = gui.getDeobfPanel().deobfClasses; | 28 | ClassSelector deobfClasses = panel.deobfClasses; |
| 29 | 29 | ||
| 30 | this.renamePackage.addActionListener(a -> { | 30 | this.renamePackage.addActionListener(a -> { |
| 31 | TreePath path; | 31 | TreePath path; |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/EditorTabPopupMenu.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/EditorTabPopupMenu.java index c2982cd0..0b4926eb 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/EditorTabPopupMenu.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/EditorTabPopupMenu.java | |||
| @@ -7,7 +7,6 @@ import javax.swing.JMenuItem; | |||
| 7 | import javax.swing.JPopupMenu; | 7 | import javax.swing.JPopupMenu; |
| 8 | import javax.swing.KeyStroke; | 8 | import javax.swing.KeyStroke; |
| 9 | 9 | ||
| 10 | import cuchaz.enigma.gui.Gui; | ||
| 11 | import cuchaz.enigma.gui.panels.EditorPanel; | 10 | import cuchaz.enigma.gui.panels.EditorPanel; |
| 12 | import cuchaz.enigma.utils.I18n; | 11 | import cuchaz.enigma.utils.I18n; |
| 13 | 12 | ||
| @@ -20,33 +19,30 @@ public class EditorTabPopupMenu { | |||
| 20 | private final JMenuItem closeLeft; | 19 | private final JMenuItem closeLeft; |
| 21 | private final JMenuItem closeRight; | 20 | private final JMenuItem closeRight; |
| 22 | 21 | ||
| 23 | private final Gui gui; | ||
| 24 | private EditorPanel editor; | 22 | private EditorPanel editor; |
| 25 | 23 | ||
| 26 | public EditorTabPopupMenu(Gui gui) { | 24 | public EditorTabPopupMenu(EditorTabbedPane pane) { |
| 27 | this.gui = gui; | ||
| 28 | |||
| 29 | this.ui = new JPopupMenu(); | 25 | this.ui = new JPopupMenu(); |
| 30 | 26 | ||
| 31 | this.close = new JMenuItem(); | 27 | this.close = new JMenuItem(); |
| 32 | this.close.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_4, KeyEvent.CTRL_DOWN_MASK)); | 28 | this.close.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_4, KeyEvent.CTRL_DOWN_MASK)); |
| 33 | this.close.addActionListener(a -> gui.closeEditor(editor)); | 29 | this.close.addActionListener(a -> pane.closeEditor(editor)); |
| 34 | this.ui.add(this.close); | 30 | this.ui.add(this.close); |
| 35 | 31 | ||
| 36 | this.closeAll = new JMenuItem(); | 32 | this.closeAll = new JMenuItem(); |
| 37 | this.closeAll.addActionListener(a -> gui.closeAllEditorTabs()); | 33 | this.closeAll.addActionListener(a -> pane.closeAllEditorTabs()); |
| 38 | this.ui.add(this.closeAll); | 34 | this.ui.add(this.closeAll); |
| 39 | 35 | ||
| 40 | this.closeOthers = new JMenuItem(); | 36 | this.closeOthers = new JMenuItem(); |
| 41 | this.closeOthers.addActionListener(a -> gui.closeTabsExcept(editor)); | 37 | this.closeOthers.addActionListener(a -> pane.closeTabsExcept(editor)); |
| 42 | this.ui.add(this.closeOthers); | 38 | this.ui.add(this.closeOthers); |
| 43 | 39 | ||
| 44 | this.closeLeft = new JMenuItem(); | 40 | this.closeLeft = new JMenuItem(); |
| 45 | this.closeLeft.addActionListener(a -> gui.closeTabsLeftOf(editor)); | 41 | this.closeLeft.addActionListener(a -> pane.closeTabsLeftOf(editor)); |
| 46 | this.ui.add(this.closeLeft); | 42 | this.ui.add(this.closeLeft); |
| 47 | 43 | ||
| 48 | this.closeRight = new JMenuItem(); | 44 | this.closeRight = new JMenuItem(); |
| 49 | this.closeRight.addActionListener(a -> gui.closeTabsRightOf(editor)); | 45 | this.closeRight.addActionListener(a -> pane.closeTabsRightOf(editor)); |
| 50 | this.ui.add(this.closeRight); | 46 | this.ui.add(this.closeRight); |
| 51 | 47 | ||
| 52 | this.retranslateUi(); | 48 | this.retranslateUi(); |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/EditorTabbedPane.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/EditorTabbedPane.java new file mode 100644 index 00000000..ff0bba3f --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/EditorTabbedPane.java | |||
| @@ -0,0 +1,158 @@ | |||
| 1 | package cuchaz.enigma.gui.elements; | ||
| 2 | |||
| 3 | import java.awt.Component; | ||
| 4 | import java.awt.event.KeyAdapter; | ||
| 5 | import java.awt.event.KeyEvent; | ||
| 6 | import java.awt.event.MouseEvent; | ||
| 7 | import java.util.Iterator; | ||
| 8 | |||
| 9 | import javax.annotation.Nullable; | ||
| 10 | import javax.swing.JTabbedPane; | ||
| 11 | import javax.swing.SwingUtilities; | ||
| 12 | |||
| 13 | import com.google.common.collect.HashBiMap; | ||
| 14 | |||
| 15 | import cuchaz.enigma.analysis.EntryReference; | ||
| 16 | import cuchaz.enigma.classhandle.ClassHandle; | ||
| 17 | import cuchaz.enigma.gui.Gui; | ||
| 18 | import cuchaz.enigma.gui.events.EditorActionListener; | ||
| 19 | import cuchaz.enigma.gui.panels.ClosableTabTitlePane; | ||
| 20 | import cuchaz.enigma.gui.panels.EditorPanel; | ||
| 21 | import cuchaz.enigma.gui.util.GuiUtil; | ||
| 22 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 23 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 24 | |||
| 25 | public class EditorTabbedPane { | ||
| 26 | private final JTabbedPane openFiles = new JTabbedPane(JTabbedPane.TOP, JTabbedPane.SCROLL_TAB_LAYOUT); | ||
| 27 | private final HashBiMap<ClassEntry, EditorPanel> editors = HashBiMap.create(); | ||
| 28 | |||
| 29 | private final EditorTabPopupMenu editorTabPopupMenu; | ||
| 30 | private final Gui gui; | ||
| 31 | |||
| 32 | public EditorTabbedPane(Gui gui) { | ||
| 33 | this.gui = gui; | ||
| 34 | this.editorTabPopupMenu = new EditorTabPopupMenu(this); | ||
| 35 | |||
| 36 | this.openFiles.addMouseListener(GuiUtil.onMousePress(this::onTabPressed)); | ||
| 37 | } | ||
| 38 | |||
| 39 | public EditorPanel openClass(ClassEntry entry) { | ||
| 40 | EditorPanel editorPanel = this.editors.computeIfAbsent(entry, e -> { | ||
| 41 | ClassHandle ch = this.gui.getController().getClassHandleProvider().openClass(entry); | ||
| 42 | if (ch == null) return null; | ||
| 43 | EditorPanel ed = new EditorPanel(this.gui); | ||
| 44 | ed.setup(); | ||
| 45 | ed.setClassHandle(ch); | ||
| 46 | this.openFiles.addTab(ed.getFileName(), ed.getUi()); | ||
| 47 | |||
| 48 | ClosableTabTitlePane titlePane = new ClosableTabTitlePane(ed.getFileName(), () -> this.closeEditor(ed)); | ||
| 49 | this.openFiles.setTabComponentAt(this.openFiles.indexOfComponent(ed.getUi()), titlePane.getUi()); | ||
| 50 | titlePane.setTabbedPane(this.openFiles); | ||
| 51 | |||
| 52 | ed.addListener(new EditorActionListener() { | ||
| 53 | @Override | ||
| 54 | public void onCursorReferenceChanged(EditorPanel editor, EntryReference<Entry<?>, Entry<?>> ref) { | ||
| 55 | if (editor == getActiveEditor()) { | ||
| 56 | gui.showCursorReference(ref); | ||
| 57 | } | ||
| 58 | } | ||
| 59 | |||
| 60 | @Override | ||
| 61 | public void onClassHandleChanged(EditorPanel editor, ClassEntry old, ClassHandle ch) { | ||
| 62 | EditorTabbedPane.this.editors.remove(old); | ||
| 63 | EditorTabbedPane.this.editors.put(ch.getRef(), editor); | ||
| 64 | } | ||
| 65 | |||
| 66 | @Override | ||
| 67 | public void onTitleChanged(EditorPanel editor, String title) { | ||
| 68 | titlePane.setText(editor.getFileName()); | ||
| 69 | } | ||
| 70 | }); | ||
| 71 | |||
| 72 | ed.getEditor().addKeyListener(new KeyAdapter() { | ||
| 73 | @Override | ||
| 74 | public void keyPressed(KeyEvent e) { | ||
| 75 | if (e.getKeyCode() == KeyEvent.VK_4 && (e.getModifiersEx() & KeyEvent.CTRL_DOWN_MASK) != 0) { | ||
| 76 | closeEditor(ed); | ||
| 77 | } | ||
| 78 | } | ||
| 79 | }); | ||
| 80 | |||
| 81 | return ed; | ||
| 82 | }); | ||
| 83 | |||
| 84 | if (editorPanel != null) { | ||
| 85 | this.openFiles.setSelectedComponent(this.editors.get(entry).getUi()); | ||
| 86 | this.gui.showStructure(editorPanel); | ||
| 87 | } | ||
| 88 | |||
| 89 | return editorPanel; | ||
| 90 | } | ||
| 91 | |||
| 92 | public void closeEditor(EditorPanel ed) { | ||
| 93 | this.openFiles.remove(ed.getUi()); | ||
| 94 | this.editors.inverse().remove(ed); | ||
| 95 | this.gui.showStructure(this.getActiveEditor()); | ||
| 96 | ed.destroy(); | ||
| 97 | } | ||
| 98 | |||
| 99 | public void closeAllEditorTabs() { | ||
| 100 | for (Iterator<EditorPanel> iter = this.editors.values().iterator(); iter.hasNext(); ) { | ||
| 101 | EditorPanel e = iter.next(); | ||
| 102 | this.openFiles.remove(e.getUi()); | ||
| 103 | e.destroy(); | ||
| 104 | iter.remove(); | ||
| 105 | } | ||
| 106 | } | ||
| 107 | |||
| 108 | public void closeTabsLeftOf(EditorPanel ed) { | ||
| 109 | int index = this.openFiles.indexOfComponent(ed.getUi()); | ||
| 110 | |||
| 111 | for (int i = index - 1; i >= 0; i--) { | ||
| 112 | closeEditor(EditorPanel.byUi(this.openFiles.getComponentAt(i))); | ||
| 113 | } | ||
| 114 | } | ||
| 115 | |||
| 116 | public void closeTabsRightOf(EditorPanel ed) { | ||
| 117 | int index = this.openFiles.indexOfComponent(ed.getUi()); | ||
| 118 | |||
| 119 | for (int i = this.openFiles.getTabCount() - 1; i > index; i--) { | ||
| 120 | closeEditor(EditorPanel.byUi(this.openFiles.getComponentAt(i))); | ||
| 121 | } | ||
| 122 | } | ||
| 123 | |||
| 124 | public void closeTabsExcept(EditorPanel ed) { | ||
| 125 | int index = this.openFiles.indexOfComponent(ed.getUi()); | ||
| 126 | |||
| 127 | for (int i = this.openFiles.getTabCount() - 1; i >= 0; i--) { | ||
| 128 | if (i == index) continue; | ||
| 129 | closeEditor(EditorPanel.byUi(this.openFiles.getComponentAt(i))); | ||
| 130 | } | ||
| 131 | } | ||
| 132 | |||
| 133 | @Nullable | ||
| 134 | public EditorPanel getActiveEditor() { | ||
| 135 | return EditorPanel.byUi(this.openFiles.getSelectedComponent()); | ||
| 136 | } | ||
| 137 | |||
| 138 | private void onTabPressed(MouseEvent e) { | ||
| 139 | if (SwingUtilities.isRightMouseButton(e)) { | ||
| 140 | int i = this.openFiles.getUI().tabForCoordinate(this.openFiles, e.getX(), e.getY()); | ||
| 141 | |||
| 142 | if (i != -1) { | ||
| 143 | this.editorTabPopupMenu.show(this.openFiles, e.getX(), e.getY(), EditorPanel.byUi(this.openFiles.getComponentAt(i))); | ||
| 144 | } | ||
| 145 | } | ||
| 146 | |||
| 147 | this.gui.showStructure(this.getActiveEditor()); | ||
| 148 | } | ||
| 149 | |||
| 150 | public void retranslateUi() { | ||
| 151 | this.editorTabPopupMenu.retranslateUi(); | ||
| 152 | this.editors.values().forEach(EditorPanel::retranslateUi); | ||
| 153 | } | ||
| 154 | |||
| 155 | public Component getUi() { | ||
| 156 | return this.openFiles; | ||
| 157 | } | ||
| 158 | } | ||
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/ImplementationsTree.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/ImplementationsTree.java new file mode 100644 index 00000000..962cf273 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/ImplementationsTree.java | |||
| @@ -0,0 +1,34 @@ | |||
| 1 | package cuchaz.enigma.gui.elements; | ||
| 2 | |||
| 3 | import javax.annotation.Nullable; | ||
| 4 | import javax.swing.tree.DefaultMutableTreeNode; | ||
| 5 | |||
| 6 | import cuchaz.enigma.gui.Gui; | ||
| 7 | import cuchaz.enigma.gui.renderer.ImplementationsTreeCellRenderer; | ||
| 8 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 9 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 10 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 11 | import cuchaz.enigma.utils.I18n; | ||
| 12 | |||
| 13 | public class ImplementationsTree extends AbstractInheritanceTree { | ||
| 14 | public ImplementationsTree(Gui gui) { | ||
| 15 | super(gui, new ImplementationsTreeCellRenderer(gui)); | ||
| 16 | } | ||
| 17 | |||
| 18 | @Nullable | ||
| 19 | @Override | ||
| 20 | protected DefaultMutableTreeNode getNodeFor(Entry<?> entry) { | ||
| 21 | if (entry instanceof ClassEntry classEntry) { | ||
| 22 | return this.gui.getController().getClassImplementations(classEntry); | ||
| 23 | } else if (entry instanceof MethodEntry methodEntry) { | ||
| 24 | return this.gui.getController().getMethodImplementations(methodEntry); | ||
| 25 | } | ||
| 26 | |||
| 27 | return null; | ||
| 28 | } | ||
| 29 | |||
| 30 | @Override | ||
| 31 | protected String getPanelName() { | ||
| 32 | return I18n.translate("info_panel.tree.implementations"); | ||
| 33 | } | ||
| 34 | } | ||
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/InheritanceTree.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/InheritanceTree.java new file mode 100644 index 00000000..aeb173ba --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/InheritanceTree.java | |||
| @@ -0,0 +1,34 @@ | |||
| 1 | package cuchaz.enigma.gui.elements; | ||
| 2 | |||
| 3 | import javax.annotation.Nullable; | ||
| 4 | import javax.swing.tree.DefaultMutableTreeNode; | ||
| 5 | |||
| 6 | import cuchaz.enigma.gui.Gui; | ||
| 7 | import cuchaz.enigma.gui.renderer.InheritanceTreeCellRenderer; | ||
| 8 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 9 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 10 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 11 | import cuchaz.enigma.utils.I18n; | ||
| 12 | |||
| 13 | public class InheritanceTree extends AbstractInheritanceTree { | ||
| 14 | public InheritanceTree(Gui gui) { | ||
| 15 | super(gui, new InheritanceTreeCellRenderer(gui)); | ||
| 16 | } | ||
| 17 | |||
| 18 | @Nullable | ||
| 19 | @Override | ||
| 20 | protected DefaultMutableTreeNode getNodeFor(Entry<?> entry) { | ||
| 21 | if (entry instanceof ClassEntry classEntry) { | ||
| 22 | return this.gui.getController().getClassInheritance(classEntry); | ||
| 23 | } else if (entry instanceof MethodEntry methodEntry) { | ||
| 24 | return this.gui.getController().getMethodInheritance(methodEntry); | ||
| 25 | } | ||
| 26 | |||
| 27 | return null; | ||
| 28 | } | ||
| 29 | |||
| 30 | @Override | ||
| 31 | protected String getPanelName() { | ||
| 32 | return I18n.translate("info_panel.tree.inheritance"); | ||
| 33 | } | ||
| 34 | } | ||
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MainWindow.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MainWindow.java new file mode 100644 index 00000000..3330948a --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MainWindow.java | |||
| @@ -0,0 +1,50 @@ | |||
| 1 | package cuchaz.enigma.gui.elements; | ||
| 2 | |||
| 3 | import java.awt.BorderLayout; | ||
| 4 | import java.awt.Container; | ||
| 5 | |||
| 6 | import javax.swing.JFrame; | ||
| 7 | import javax.swing.JMenuBar; | ||
| 8 | import javax.swing.JPanel; | ||
| 9 | |||
| 10 | public class MainWindow { | ||
| 11 | private final JFrame frame; | ||
| 12 | private final JPanel workArea = new JPanel(); | ||
| 13 | |||
| 14 | private final JMenuBar menuBar = new JMenuBar(); | ||
| 15 | private final StatusBar statusBar = new StatusBar(); | ||
| 16 | |||
| 17 | public MainWindow(String title) { | ||
| 18 | this.frame = new JFrame(title); | ||
| 19 | this.frame.setJMenuBar(this.menuBar); | ||
| 20 | |||
| 21 | Container contentPane = this.frame.getContentPane(); | ||
| 22 | contentPane.setLayout(new BorderLayout()); | ||
| 23 | contentPane.add(this.workArea, BorderLayout.CENTER); | ||
| 24 | contentPane.add(this.statusBar.getUi(), BorderLayout.SOUTH); | ||
| 25 | } | ||
| 26 | |||
| 27 | public void setVisible(boolean visible) { | ||
| 28 | this.frame.setVisible(visible); | ||
| 29 | } | ||
| 30 | |||
| 31 | public JMenuBar menuBar() { | ||
| 32 | return this.menuBar; | ||
| 33 | } | ||
| 34 | |||
| 35 | public StatusBar statusBar() { | ||
| 36 | return this.statusBar; | ||
| 37 | } | ||
| 38 | |||
| 39 | public Container workArea() { | ||
| 40 | return this.workArea; | ||
| 41 | } | ||
| 42 | |||
| 43 | public JFrame frame() { | ||
| 44 | return this.frame; | ||
| 45 | } | ||
| 46 | |||
| 47 | public void setTitle(String title) { | ||
| 48 | this.frame.setTitle(title); | ||
| 49 | } | ||
| 50 | } | ||
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java index 61f97803..eeb52ccf 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java | |||
| @@ -30,8 +30,6 @@ import cuchaz.enigma.utils.Pair; | |||
| 30 | 30 | ||
| 31 | public class MenuBar { | 31 | public class MenuBar { |
| 32 | 32 | ||
| 33 | private final JMenuBar ui = new JMenuBar(); | ||
| 34 | |||
| 35 | private final JMenu fileMenu = new JMenu(); | 33 | private final JMenu fileMenu = new JMenu(); |
| 36 | private final JMenuItem jarOpenItem = new JMenuItem(); | 34 | private final JMenuItem jarOpenItem = new JMenuItem(); |
| 37 | private final JMenuItem jarCloseItem = new JMenuItem(); | 35 | private final JMenuItem jarCloseItem = new JMenuItem(); |
| @@ -74,6 +72,8 @@ public class MenuBar { | |||
| 74 | public MenuBar(Gui gui) { | 72 | public MenuBar(Gui gui) { |
| 75 | this.gui = gui; | 73 | this.gui = gui; |
| 76 | 74 | ||
| 75 | JMenuBar ui = gui.getMainWindow().menuBar(); | ||
| 76 | |||
| 77 | this.retranslateUi(); | 77 | this.retranslateUi(); |
| 78 | 78 | ||
| 79 | prepareOpenMenu(this.openMenu, gui); | 79 | prepareOpenMenu(this.openMenu, gui); |
| @@ -101,29 +101,29 @@ public class MenuBar { | |||
| 101 | this.fileMenu.add(this.statsItem); | 101 | this.fileMenu.add(this.statsItem); |
| 102 | this.fileMenu.addSeparator(); | 102 | this.fileMenu.addSeparator(); |
| 103 | this.fileMenu.add(this.exitItem); | 103 | this.fileMenu.add(this.exitItem); |
| 104 | this.ui.add(this.fileMenu); | 104 | ui.add(this.fileMenu); |
| 105 | 105 | ||
| 106 | this.ui.add(this.decompilerMenu); | 106 | ui.add(this.decompilerMenu); |
| 107 | 107 | ||
| 108 | this.viewMenu.add(this.themesMenu); | 108 | this.viewMenu.add(this.themesMenu); |
| 109 | this.viewMenu.add(this.languagesMenu); | 109 | this.viewMenu.add(this.languagesMenu); |
| 110 | this.scaleMenu.add(this.customScaleItem); | 110 | this.scaleMenu.add(this.customScaleItem); |
| 111 | this.viewMenu.add(this.scaleMenu); | 111 | this.viewMenu.add(this.scaleMenu); |
| 112 | this.viewMenu.add(this.fontItem); | 112 | this.viewMenu.add(this.fontItem); |
| 113 | this.ui.add(this.viewMenu); | 113 | ui.add(this.viewMenu); |
| 114 | 114 | ||
| 115 | this.searchMenu.add(this.searchClassItem); | 115 | this.searchMenu.add(this.searchClassItem); |
| 116 | this.searchMenu.add(this.searchMethodItem); | 116 | this.searchMenu.add(this.searchMethodItem); |
| 117 | this.searchMenu.add(this.searchFieldItem); | 117 | this.searchMenu.add(this.searchFieldItem); |
| 118 | this.ui.add(this.searchMenu); | 118 | ui.add(this.searchMenu); |
| 119 | 119 | ||
| 120 | this.collabMenu.add(this.connectItem); | 120 | this.collabMenu.add(this.connectItem); |
| 121 | this.collabMenu.add(this.startServerItem); | 121 | this.collabMenu.add(this.startServerItem); |
| 122 | this.ui.add(this.collabMenu); | 122 | ui.add(this.collabMenu); |
| 123 | 123 | ||
| 124 | this.helpMenu.add(this.aboutItem); | 124 | this.helpMenu.add(this.aboutItem); |
| 125 | this.helpMenu.add(this.githubItem); | 125 | this.helpMenu.add(this.githubItem); |
| 126 | this.ui.add(this.helpMenu); | 126 | ui.add(this.helpMenu); |
| 127 | 127 | ||
| 128 | this.saveMappingsItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK)); | 128 | this.saveMappingsItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK)); |
| 129 | this.searchClassItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, InputEvent.SHIFT_DOWN_MASK)); | 129 | this.searchClassItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, InputEvent.SHIFT_DOWN_MASK)); |
| @@ -210,10 +210,6 @@ public class MenuBar { | |||
| 210 | this.githubItem.setText(I18n.translate("menu.help.github")); | 210 | this.githubItem.setText(I18n.translate("menu.help.github")); |
| 211 | } | 211 | } |
| 212 | 212 | ||
| 213 | public JMenuBar getUi() { | ||
| 214 | return this.ui; | ||
| 215 | } | ||
| 216 | |||
| 217 | private void onOpenJarClicked() { | 213 | private void onOpenJarClicked() { |
| 218 | JFileChooser d = this.gui.jarFileChooser; | 214 | JFileChooser d = this.gui.jarFileChooser; |
| 219 | d.setCurrentDirectory(new File(UiConfig.getLastSelectedDir())); | 215 | d.setCurrentDirectory(new File(UiConfig.getLastSelectedDir())); |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/StatusBar.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/StatusBar.java new file mode 100644 index 00000000..0c667c00 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/StatusBar.java | |||
| @@ -0,0 +1,137 @@ | |||
| 1 | package cuchaz.enigma.gui.elements; | ||
| 2 | |||
| 3 | import java.awt.BorderLayout; | ||
| 4 | import java.awt.Component; | ||
| 5 | import java.awt.ComponentOrientation; | ||
| 6 | import java.awt.GridLayout; | ||
| 7 | |||
| 8 | import javax.swing.BoxLayout; | ||
| 9 | import javax.swing.JLabel; | ||
| 10 | import javax.swing.JPanel; | ||
| 11 | import javax.swing.Timer; | ||
| 12 | |||
| 13 | /** | ||
| 14 | * Implements a generic status bar for use in windows. The API is loosely based | ||
| 15 | * on Qt's QStatusBar. | ||
| 16 | */ | ||
| 17 | public class StatusBar { | ||
| 18 | private final JPanel ui = new JPanel(new BorderLayout()); | ||
| 19 | private final JPanel leftPanel = new JPanel(new GridLayout(1, 1, 0, 0)); | ||
| 20 | private final JPanel components = new JPanel(); | ||
| 21 | private final JPanel permanentComponents = new JPanel(); | ||
| 22 | |||
| 23 | private final JLabel temporaryMessage = new JLabel(); | ||
| 24 | private final Timer timer = new Timer(0, e -> this.clearMessage()); | ||
| 25 | |||
| 26 | public StatusBar() { | ||
| 27 | this.timer.setRepeats(false); | ||
| 28 | |||
| 29 | this.components.setLayout(new BoxLayout(this.components, BoxLayout.LINE_AXIS)); | ||
| 30 | this.permanentComponents.setLayout(new BoxLayout(this.permanentComponents, BoxLayout.LINE_AXIS)); | ||
| 31 | this.permanentComponents.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT); | ||
| 32 | |||
| 33 | this.leftPanel.add(this.components); | ||
| 34 | this.temporaryMessage.setHorizontalTextPosition(JLabel.LEFT); | ||
| 35 | this.ui.add(this.leftPanel, BorderLayout.CENTER); | ||
| 36 | this.ui.add(this.permanentComponents, BorderLayout.EAST); | ||
| 37 | } | ||
| 38 | |||
| 39 | /** | ||
| 40 | * Displays a temporary message in the status bar. The message is displayed | ||
| 41 | * until it is explicitly cleared through {@link #clearMessage()} or a new | ||
| 42 | * message is displayed. | ||
| 43 | * | ||
| 44 | * @param message the message to display | ||
| 45 | */ | ||
| 46 | public void showMessage(String message) { | ||
| 47 | this.showMessage(message, 0); | ||
| 48 | } | ||
| 49 | |||
| 50 | /** | ||
| 51 | * Displays a temporary message in the status bar. The message is displayed | ||
| 52 | * until it is explicitly cleared through {@link #clearMessage()}, a new | ||
| 53 | * message is displayed, or the timeout, if any, is reached. | ||
| 54 | * | ||
| 55 | * @param message the message to display | ||
| 56 | * @param timeout the timeout in milliseconds to wait until clearing the | ||
| 57 | * message; if 0, the message is not automatically cleared | ||
| 58 | */ | ||
| 59 | public void showMessage(String message, int timeout) { | ||
| 60 | this.timer.stop(); | ||
| 61 | |||
| 62 | this.temporaryMessage.setText(message); | ||
| 63 | this.leftPanel.removeAll(); | ||
| 64 | this.leftPanel.add(this.temporaryMessage); | ||
| 65 | this.leftPanel.revalidate(); | ||
| 66 | |||
| 67 | if (timeout > 0) { | ||
| 68 | this.timer.setInitialDelay(timeout); | ||
| 69 | this.timer.start(); | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | /** | ||
| 74 | * Clears any currently displayed temporary message. | ||
| 75 | */ | ||
| 76 | public void clearMessage() { | ||
| 77 | this.timer.stop(); | ||
| 78 | |||
| 79 | this.leftPanel.removeAll(); | ||
| 80 | this.leftPanel.add(this.components); | ||
| 81 | this.leftPanel.revalidate(); | ||
| 82 | this.temporaryMessage.setText(""); | ||
| 83 | } | ||
| 84 | |||
| 85 | /** | ||
| 86 | * Returns the currently displayed message, or the empty string otherwise. | ||
| 87 | * | ||
| 88 | * @return the currently displayed message | ||
| 89 | */ | ||
| 90 | public String currentMessage() { | ||
| 91 | return this.temporaryMessage.getText(); | ||
| 92 | } | ||
| 93 | |||
| 94 | /** | ||
| 95 | * Adds a component to the status bar. These components are positioned on | ||
| 96 | * the left side of the status bar. When a temporary message is displayed, | ||
| 97 | * the component will be hidden until the message is cleared. | ||
| 98 | * | ||
| 99 | * @param comp the component to add | ||
| 100 | */ | ||
| 101 | public void addComponent(Component comp) { | ||
| 102 | this.components.add(comp); | ||
| 103 | } | ||
| 104 | |||
| 105 | /** | ||
| 106 | * Removes a component from the status bar. | ||
| 107 | * | ||
| 108 | * @param comp the component to remove | ||
| 109 | */ | ||
| 110 | public void removeComponent(Component comp) { | ||
| 111 | this.components.remove(comp); | ||
| 112 | } | ||
| 113 | |||
| 114 | /** | ||
| 115 | * Adds a permanent component to the status bar. These components will not | ||
| 116 | * be hidden by temporary messages and will be displayed on the right side | ||
| 117 | * of the status bar. | ||
| 118 | * | ||
| 119 | * @param comp the component to add | ||
| 120 | */ | ||
| 121 | public void addPermanentComponent(Component comp) { | ||
| 122 | this.permanentComponents.add(comp); | ||
| 123 | } | ||
| 124 | |||
| 125 | /** | ||
| 126 | * Removes a permanent component from the status bar. | ||
| 127 | * | ||
| 128 | * @param comp the component to remove | ||
| 129 | */ | ||
| 130 | public void removePermanentComponent(Component comp) { | ||
| 131 | this.permanentComponents.remove(comp); | ||
| 132 | } | ||
| 133 | |||
| 134 | public JPanel getUi() { | ||
| 135 | return this.ui; | ||
| 136 | } | ||
| 137 | } | ||
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/DeobfPanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/DeobfPanel.java index cd09c1a1..10fc5e1a 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/DeobfPanel.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/DeobfPanel.java | |||
| @@ -1,13 +1,17 @@ | |||
| 1 | package cuchaz.enigma.gui.panels; | 1 | package cuchaz.enigma.gui.panels; |
| 2 | 2 | ||
| 3 | import java.awt.BorderLayout; | 3 | import java.awt.BorderLayout; |
| 4 | import java.awt.event.MouseEvent; | ||
| 4 | 5 | ||
| 5 | import javax.swing.JLabel; | 6 | import javax.swing.JLabel; |
| 6 | import javax.swing.JPanel; | 7 | import javax.swing.JPanel; |
| 7 | import javax.swing.JScrollPane; | 8 | import javax.swing.JScrollPane; |
| 9 | import javax.swing.SwingUtilities; | ||
| 8 | 10 | ||
| 9 | import cuchaz.enigma.gui.ClassSelector; | 11 | import cuchaz.enigma.gui.ClassSelector; |
| 10 | import cuchaz.enigma.gui.Gui; | 12 | import cuchaz.enigma.gui.Gui; |
| 13 | import cuchaz.enigma.gui.elements.DeobfPanelPopupMenu; | ||
| 14 | import cuchaz.enigma.gui.util.GuiUtil; | ||
| 11 | import cuchaz.enigma.utils.I18n; | 15 | import cuchaz.enigma.utils.I18n; |
| 12 | 16 | ||
| 13 | public class DeobfPanel extends JPanel { | 17 | public class DeobfPanel extends JPanel { |
| @@ -15,6 +19,8 @@ public class DeobfPanel extends JPanel { | |||
| 15 | public final ClassSelector deobfClasses; | 19 | public final ClassSelector deobfClasses; |
| 16 | private final JLabel title = new JLabel(); | 20 | private final JLabel title = new JLabel(); |
| 17 | 21 | ||
| 22 | public final DeobfPanelPopupMenu deobfPanelPopupMenu; | ||
| 23 | |||
| 18 | private final Gui gui; | 24 | private final Gui gui; |
| 19 | 25 | ||
| 20 | public DeobfPanel(Gui gui) { | 26 | public DeobfPanel(Gui gui) { |
| @@ -23,16 +29,30 @@ public class DeobfPanel extends JPanel { | |||
| 23 | this.deobfClasses = new ClassSelector(gui, ClassSelector.DEOBF_CLASS_COMPARATOR, true); | 29 | this.deobfClasses = new ClassSelector(gui, ClassSelector.DEOBF_CLASS_COMPARATOR, true); |
| 24 | this.deobfClasses.setSelectionListener(gui.getController()::navigateTo); | 30 | this.deobfClasses.setSelectionListener(gui.getController()::navigateTo); |
| 25 | this.deobfClasses.setRenameSelectionListener(gui::onRenameFromClassTree); | 31 | this.deobfClasses.setRenameSelectionListener(gui::onRenameFromClassTree); |
| 32 | this.deobfPanelPopupMenu = new DeobfPanelPopupMenu(this); | ||
| 26 | 33 | ||
| 27 | this.setLayout(new BorderLayout()); | 34 | this.setLayout(new BorderLayout()); |
| 28 | this.add(this.title, BorderLayout.NORTH); | 35 | this.add(this.title, BorderLayout.NORTH); |
| 29 | this.add(new JScrollPane(this.deobfClasses), BorderLayout.CENTER); | 36 | this.add(new JScrollPane(this.deobfClasses), BorderLayout.CENTER); |
| 30 | 37 | ||
| 38 | this.deobfClasses.addMouseListener(GuiUtil.onMousePress(this::onPress)); | ||
| 39 | |||
| 31 | this.retranslateUi(); | 40 | this.retranslateUi(); |
| 32 | } | 41 | } |
| 33 | 42 | ||
| 43 | private void onPress(MouseEvent e) { | ||
| 44 | if (SwingUtilities.isRightMouseButton(e)) { | ||
| 45 | deobfClasses.setSelectionRow(deobfClasses.getClosestRowForLocation(e.getX(), e.getY())); | ||
| 46 | int i = deobfClasses.getRowForPath(deobfClasses.getSelectionPath()); | ||
| 47 | if (i != -1) { | ||
| 48 | deobfPanelPopupMenu.show(deobfClasses, e.getX(), e.getY()); | ||
| 49 | } | ||
| 50 | } | ||
| 51 | } | ||
| 52 | |||
| 34 | public void retranslateUi() { | 53 | public void retranslateUi() { |
| 35 | this.title.setText(I18n.translate(gui.isSingleClassTree() ? "info_panel.classes" : "info_panel.classes.deobfuscated")); | 54 | this.title.setText(I18n.translate(gui.isSingleClassTree() ? "info_panel.classes" : "info_panel.classes.deobfuscated")); |
| 55 | this.deobfPanelPopupMenu.retranslateUi(); | ||
| 36 | } | 56 | } |
| 37 | 57 | ||
| 38 | } | 58 | } |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/EditorPanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/EditorPanel.java index 2055309c..f4b190bc 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/EditorPanel.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/EditorPanel.java | |||
| @@ -518,7 +518,7 @@ public class EditorPanel { | |||
| 518 | if (this.source == null) return; | 518 | if (this.source == null) return; |
| 519 | if (reference == null) return; | 519 | if (reference == null) return; |
| 520 | 520 | ||
| 521 | Collection<Token> tokens = this.controller.getTokensForReference(this.source, reference); | 521 | List<Token> tokens = this.controller.getTokensForReference(this.source, reference); |
| 522 | if (tokens.isEmpty()) { | 522 | if (tokens.isEmpty()) { |
| 523 | // DEBUG | 523 | // DEBUG |
| 524 | System.err.println(String.format("WARNING: no tokens found for %s in %s", reference, this.classHandle.getRef())); | 524 | System.err.println(String.format("WARNING: no tokens found for %s in %s", reference, this.classHandle.getRef())); |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/IdentifierPanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/IdentifierPanel.java index 4ae0b7be..e71894db 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/IdentifierPanel.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/IdentifierPanel.java | |||
| @@ -33,7 +33,7 @@ public class IdentifierPanel { | |||
| 33 | 33 | ||
| 34 | private final Gui gui; | 34 | private final Gui gui; |
| 35 | 35 | ||
| 36 | private final JPanel ui; | 36 | private final JPanel ui = new JPanel(); |
| 37 | 37 | ||
| 38 | private Entry<?> entry; | 38 | private Entry<?> entry; |
| 39 | private Entry<?> deobfEntry; | 39 | private Entry<?> deobfEntry; |
| @@ -45,7 +45,6 @@ public class IdentifierPanel { | |||
| 45 | public IdentifierPanel(Gui gui) { | 45 | public IdentifierPanel(Gui gui) { |
| 46 | this.gui = gui; | 46 | this.gui = gui; |
| 47 | 47 | ||
| 48 | this.ui = new JPanel(); | ||
| 49 | this.ui.setLayout(new GridBagLayout()); | 48 | this.ui.setLayout(new GridBagLayout()); |
| 50 | this.ui.setPreferredSize(ScaleUtil.getDimension(0, 120)); | 49 | this.ui.setPreferredSize(ScaleUtil.getDimension(0, 120)); |
| 51 | this.ui.setBorder(BorderFactory.createTitledBorder(I18n.translate("info_panel.identifier"))); | 50 | this.ui.setBorder(BorderFactory.createTitledBorder(I18n.translate("info_panel.identifier"))); |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/StructurePanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/StructurePanel.java index 1bff9a98..ccded45c 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/StructurePanel.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/StructurePanel.java | |||
| @@ -1,7 +1,16 @@ | |||
| 1 | package cuchaz.enigma.gui.panels; | 1 | package cuchaz.enigma.gui.panels; |
| 2 | 2 | ||
| 3 | import cuchaz.enigma.analysis.StructureTreeOptions; | 3 | import java.awt.*; |
| 4 | import java.awt.event.MouseEvent; | ||
| 5 | |||
| 6 | import javax.swing.*; | ||
| 7 | import javax.swing.tree.DefaultTreeCellRenderer; | ||
| 8 | import javax.swing.tree.DefaultTreeModel; | ||
| 9 | import javax.swing.tree.TreeNode; | ||
| 10 | import javax.swing.tree.TreePath; | ||
| 11 | |||
| 4 | import cuchaz.enigma.analysis.StructureTreeNode; | 12 | import cuchaz.enigma.analysis.StructureTreeNode; |
| 13 | import cuchaz.enigma.analysis.StructureTreeOptions; | ||
| 5 | import cuchaz.enigma.gui.Gui; | 14 | import cuchaz.enigma.gui.Gui; |
| 6 | import cuchaz.enigma.gui.renderer.StructureOptionListCellRenderer; | 15 | import cuchaz.enigma.gui.renderer.StructureOptionListCellRenderer; |
| 7 | import cuchaz.enigma.gui.util.GridBagConstraintsBuilder; | 16 | import cuchaz.enigma.gui.util.GridBagConstraintsBuilder; |
| @@ -13,14 +22,11 @@ import cuchaz.enigma.translation.representation.entry.MethodEntry; | |||
| 13 | import cuchaz.enigma.translation.representation.entry.ParentedEntry; | 22 | import cuchaz.enigma.translation.representation.entry.ParentedEntry; |
| 14 | import cuchaz.enigma.utils.I18n; | 23 | import cuchaz.enigma.utils.I18n; |
| 15 | 24 | ||
| 16 | import javax.swing.*; | 25 | public class StructurePanel { |
| 17 | import javax.swing.tree.DefaultTreeCellRenderer; | 26 | private final Gui gui; |
| 18 | import javax.swing.tree.TreePath; | 27 | |
| 19 | import java.awt.*; | 28 | private final JPanel panel = new JPanel(new BorderLayout()); |
| 20 | import java.awt.event.MouseAdapter; | ||
| 21 | import java.awt.event.MouseEvent; | ||
| 22 | 29 | ||
| 23 | public class StructurePanel extends JPanel { | ||
| 24 | private final JPanel optionsPanel; | 30 | private final JPanel optionsPanel; |
| 25 | 31 | ||
| 26 | private final JLabel obfuscationVisibilityLabel = new JLabel(); | 32 | private final JLabel obfuscationVisibilityLabel = new JLabel(); |
| @@ -34,6 +40,8 @@ public class StructurePanel extends JPanel { | |||
| 34 | private final JTree structureTree; | 40 | private final JTree structureTree; |
| 35 | 41 | ||
| 36 | public StructurePanel(Gui gui) { | 42 | public StructurePanel(Gui gui) { |
| 43 | this.gui = gui; | ||
| 44 | |||
| 37 | this.optionsPanel = new JPanel(new GridBagLayout()); | 45 | this.optionsPanel = new JPanel(new GridBagLayout()); |
| 38 | this.optionsPanel.setVisible(false); | 46 | this.optionsPanel.setVisible(false); |
| 39 | 47 | ||
| @@ -42,19 +50,19 @@ public class StructurePanel extends JPanel { | |||
| 42 | this.optionsPanel.add(this.obfuscationVisibilityLabel, cb.pos(0, 0).build()); | 50 | this.optionsPanel.add(this.obfuscationVisibilityLabel, cb.pos(0, 0).build()); |
| 43 | this.obfuscationVisibility = new JComboBox<>(StructureTreeOptions.ObfuscationVisibility.values()); | 51 | this.obfuscationVisibility = new JComboBox<>(StructureTreeOptions.ObfuscationVisibility.values()); |
| 44 | this.obfuscationVisibility.setRenderer(new StructureOptionListCellRenderer()); | 52 | this.obfuscationVisibility.setRenderer(new StructureOptionListCellRenderer()); |
| 45 | this.obfuscationVisibility.addActionListener(event -> gui.showStructure(gui.getActiveEditor())); | 53 | this.obfuscationVisibility.addActionListener(event -> this.showStructure(gui.getActiveEditor())); |
| 46 | this.optionsPanel.add(this.obfuscationVisibility, cb.pos(1, 0).build()); | 54 | this.optionsPanel.add(this.obfuscationVisibility, cb.pos(1, 0).build()); |
| 47 | 55 | ||
| 48 | this.optionsPanel.add(this.documentationVisibilityLabel, cb.pos(0, 1).build()); | 56 | this.optionsPanel.add(this.documentationVisibilityLabel, cb.pos(0, 1).build()); |
| 49 | this.documentationVisibility = new JComboBox<>(StructureTreeOptions.DocumentationVisibility.values()); | 57 | this.documentationVisibility = new JComboBox<>(StructureTreeOptions.DocumentationVisibility.values()); |
| 50 | this.documentationVisibility.setRenderer(new StructureOptionListCellRenderer()); | 58 | this.documentationVisibility.setRenderer(new StructureOptionListCellRenderer()); |
| 51 | this.documentationVisibility.addActionListener(event -> gui.showStructure(gui.getActiveEditor())); | 59 | this.documentationVisibility.addActionListener(event -> this.showStructure(gui.getActiveEditor())); |
| 52 | this.optionsPanel.add(this.documentationVisibility, cb.pos(1, 1).build()); | 60 | this.optionsPanel.add(this.documentationVisibility, cb.pos(1, 1).build()); |
| 53 | 61 | ||
| 54 | this.optionsPanel.add(this.sortingOrderLabel, cb.pos(0, 2).build()); | 62 | this.optionsPanel.add(this.sortingOrderLabel, cb.pos(0, 2).build()); |
| 55 | this.sortingOrder = new JComboBox<>(StructureTreeOptions.SortingOrder.values()); | 63 | this.sortingOrder = new JComboBox<>(StructureTreeOptions.SortingOrder.values()); |
| 56 | this.sortingOrder.setRenderer(new StructureOptionListCellRenderer()); | 64 | this.sortingOrder.setRenderer(new StructureOptionListCellRenderer()); |
| 57 | this.sortingOrder.addActionListener(event -> gui.showStructure(gui.getActiveEditor())); | 65 | this.sortingOrder.addActionListener(event -> this.showStructure(gui.getActiveEditor())); |
| 58 | this.optionsPanel.add(this.sortingOrder, cb.pos(1, 2).build()); | 66 | this.optionsPanel.add(this.sortingOrder, cb.pos(1, 2).build()); |
| 59 | 67 | ||
| 60 | this.structureTree = new JTree(); | 68 | this.structureTree = new JTree(); |
| @@ -62,39 +70,57 @@ public class StructurePanel extends JPanel { | |||
| 62 | this.structureTree.setCellRenderer(new StructureTreeCellRenderer(gui)); | 70 | this.structureTree.setCellRenderer(new StructureTreeCellRenderer(gui)); |
| 63 | this.structureTree.setSelectionModel(new SingleTreeSelectionModel()); | 71 | this.structureTree.setSelectionModel(new SingleTreeSelectionModel()); |
| 64 | this.structureTree.setShowsRootHandles(true); | 72 | this.structureTree.setShowsRootHandles(true); |
| 65 | this.structureTree.addMouseListener(new MouseAdapter() { | 73 | this.structureTree.addMouseListener(GuiUtil.onMouseClick(this::onClick)); |
| 66 | @Override | ||
| 67 | public void mouseClicked(MouseEvent event) { | ||
| 68 | if (event.getClickCount() >= 2 && event.getButton() == MouseEvent.BUTTON1) { | ||
| 69 | // get the selected node | ||
| 70 | TreePath path = structureTree.getSelectionPath(); | ||
| 71 | if (path == null) { | ||
| 72 | return; | ||
| 73 | } | ||
| 74 | |||
| 75 | Object node = path.getLastPathComponent(); | ||
| 76 | if (node instanceof StructureTreeNode) { | ||
| 77 | gui.getController().navigateTo(((StructureTreeNode) node).getEntry()); | ||
| 78 | } | ||
| 79 | } | ||
| 80 | } | ||
| 81 | }); | ||
| 82 | 74 | ||
| 83 | this.retranslateUi(); | 75 | this.retranslateUi(); |
| 84 | 76 | ||
| 85 | this.setLayout(new BorderLayout()); | 77 | this.panel.add(this.optionsPanel, BorderLayout.NORTH); |
| 86 | this.add(this.optionsPanel, BorderLayout.NORTH); | 78 | this.panel.add(new JScrollPane(this.structureTree)); |
| 87 | this.add(new JScrollPane(this.structureTree)); | ||
| 88 | } | 79 | } |
| 89 | 80 | ||
| 90 | public JPanel getSortingPanel() { | 81 | public void showStructure(EditorPanel editor) { |
| 91 | return this.optionsPanel; | 82 | structureTree.setModel(null); |
| 83 | |||
| 84 | if (editor == null) { | ||
| 85 | this.optionsPanel.setVisible(false); | ||
| 86 | return; | ||
| 87 | } | ||
| 88 | |||
| 89 | ClassEntry classEntry = editor.getClassHandle().getRef(); | ||
| 90 | if (classEntry == null) return; | ||
| 91 | |||
| 92 | this.optionsPanel.setVisible(true); | ||
| 93 | |||
| 94 | // get the class structure | ||
| 95 | StructureTreeNode node = this.gui.getController().getClassStructure(classEntry, this.getOptions()); | ||
| 96 | |||
| 97 | // show the tree at the root | ||
| 98 | TreePath path = GuiUtil.getPathToRoot(node); | ||
| 99 | structureTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); | ||
| 100 | structureTree.expandPath(path); | ||
| 101 | structureTree.setSelectionRow(structureTree.getRowForPath(path)); | ||
| 102 | } | ||
| 103 | |||
| 104 | private void onClick(MouseEvent event) { | ||
| 105 | if (event.getClickCount() >= 2 && event.getButton() == MouseEvent.BUTTON1) { | ||
| 106 | // get the selected node | ||
| 107 | TreePath path = structureTree.getSelectionPath(); | ||
| 108 | if (path == null) { | ||
| 109 | return; | ||
| 110 | } | ||
| 111 | |||
| 112 | Object node = path.getLastPathComponent(); | ||
| 113 | |||
| 114 | if (node instanceof StructureTreeNode) { | ||
| 115 | this.gui.getController().navigateTo(((StructureTreeNode) node).getEntry()); | ||
| 116 | } | ||
| 117 | } | ||
| 92 | } | 118 | } |
| 93 | 119 | ||
| 94 | /** | 120 | /** |
| 95 | * Creates and returns the options of this structure panel. | 121 | * Creates and returns the options of this structure panel. |
| 96 | */ | 122 | */ |
| 97 | public StructureTreeOptions getOptions() { | 123 | private StructureTreeOptions getOptions() { |
| 98 | return new StructureTreeOptions( | 124 | return new StructureTreeOptions( |
| 99 | (StructureTreeOptions.ObfuscationVisibility) this.obfuscationVisibility.getSelectedItem(), | 125 | (StructureTreeOptions.ObfuscationVisibility) this.obfuscationVisibility.getSelectedItem(), |
| 100 | (StructureTreeOptions.DocumentationVisibility) this.documentationVisibility.getSelectedItem(), | 126 | (StructureTreeOptions.DocumentationVisibility) this.documentationVisibility.getSelectedItem(), |
| @@ -102,17 +128,17 @@ public class StructurePanel extends JPanel { | |||
| 102 | ); | 128 | ); |
| 103 | } | 129 | } |
| 104 | 130 | ||
| 105 | public JTree getStructureTree() { | ||
| 106 | return this.structureTree; | ||
| 107 | } | ||
| 108 | |||
| 109 | public void retranslateUi() { | 131 | public void retranslateUi() { |
| 110 | this.obfuscationVisibilityLabel.setText(I18n.translate("structure.options.obfuscation")); | 132 | this.obfuscationVisibilityLabel.setText(I18n.translate("structure.options.obfuscation")); |
| 111 | this.documentationVisibilityLabel.setText(I18n.translate("structure.options.documentation")); | 133 | this.documentationVisibilityLabel.setText(I18n.translate("structure.options.documentation")); |
| 112 | this.sortingOrderLabel.setText(I18n.translate("structure.options.sorting")); | 134 | this.sortingOrderLabel.setText(I18n.translate("structure.options.sorting")); |
| 113 | } | 135 | } |
| 114 | 136 | ||
| 115 | class StructureTreeCellRenderer extends DefaultTreeCellRenderer { | 137 | public JPanel getPanel() { |
| 138 | return this.panel; | ||
| 139 | } | ||
| 140 | |||
| 141 | private static class StructureTreeCellRenderer extends DefaultTreeCellRenderer { | ||
| 116 | private final Gui gui; | 142 | private final Gui gui; |
| 117 | 143 | ||
| 118 | StructureTreeCellRenderer(Gui gui) { | 144 | StructureTreeCellRenderer(Gui gui) { |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/util/GuiUtil.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/GuiUtil.java index e26c29b3..28b4043e 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/util/GuiUtil.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/GuiUtil.java | |||
| @@ -1,23 +1,30 @@ | |||
| 1 | package cuchaz.enigma.gui.util; | 1 | package cuchaz.enigma.gui.util; |
| 2 | 2 | ||
| 3 | import com.formdev.flatlaf.extras.FlatSVGIcon; | ||
| 4 | import cuchaz.enigma.gui.Gui; | ||
| 5 | import cuchaz.enigma.translation.representation.AccessFlags; | ||
| 6 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 7 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 8 | import cuchaz.enigma.utils.Os; | ||
| 9 | |||
| 10 | import javax.swing.*; | ||
| 11 | import java.awt.*; | 3 | import java.awt.*; |
| 12 | import java.awt.datatransfer.StringSelection; | 4 | import java.awt.datatransfer.StringSelection; |
| 13 | import java.awt.event.MouseAdapter; | 5 | import java.awt.event.*; |
| 14 | import java.awt.event.MouseEvent; | ||
| 15 | import java.awt.font.TextAttribute; | 6 | import java.awt.font.TextAttribute; |
| 16 | import java.io.IOException; | 7 | import java.io.IOException; |
| 17 | import java.net.URI; | 8 | import java.net.URI; |
| 18 | import java.net.URISyntaxException; | 9 | import java.net.URISyntaxException; |
| 10 | import java.util.Collections; | ||
| 11 | import java.util.List; | ||
| 19 | import java.util.Map; | 12 | import java.util.Map; |
| 20 | import java.util.NoSuchElementException; | 13 | import java.util.NoSuchElementException; |
| 14 | import java.util.function.Consumer; | ||
| 15 | |||
| 16 | import javax.swing.*; | ||
| 17 | import javax.swing.tree.TreeNode; | ||
| 18 | import javax.swing.tree.TreePath; | ||
| 19 | |||
| 20 | import com.formdev.flatlaf.extras.FlatSVGIcon; | ||
| 21 | import com.google.common.collect.Lists; | ||
| 22 | |||
| 23 | import cuchaz.enigma.gui.Gui; | ||
| 24 | import cuchaz.enigma.translation.representation.AccessFlags; | ||
| 25 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 26 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 27 | import cuchaz.enigma.utils.Os; | ||
| 21 | 28 | ||
| 22 | public class GuiUtil { | 29 | public class GuiUtil { |
| 23 | public static final Icon CLASS_ICON = loadIcon("class"); | 30 | public static final Icon CLASS_ICON = loadIcon("class"); |
| @@ -135,4 +142,44 @@ public class GuiUtil { | |||
| 135 | } | 142 | } |
| 136 | return METHOD_ICON; | 143 | return METHOD_ICON; |
| 137 | } | 144 | } |
| 145 | |||
| 146 | public static TreePath getPathToRoot(TreeNode node) { | ||
| 147 | List<TreeNode> nodes = Lists.newArrayList(); | ||
| 148 | TreeNode n = node; | ||
| 149 | |||
| 150 | do { | ||
| 151 | nodes.add(n); | ||
| 152 | n = n.getParent(); | ||
| 153 | } while (n != null); | ||
| 154 | |||
| 155 | Collections.reverse(nodes); | ||
| 156 | return new TreePath(nodes.toArray()); | ||
| 157 | } | ||
| 158 | |||
| 159 | public static MouseListener onMouseClick(Consumer<MouseEvent> op) { | ||
| 160 | return new MouseAdapter() { | ||
| 161 | @Override | ||
| 162 | public void mouseClicked(MouseEvent e) { | ||
| 163 | op.accept(e); | ||
| 164 | } | ||
| 165 | }; | ||
| 166 | } | ||
| 167 | |||
| 168 | public static MouseListener onMousePress(Consumer<MouseEvent> op) { | ||
| 169 | return new MouseAdapter() { | ||
| 170 | @Override | ||
| 171 | public void mousePressed(MouseEvent e) { | ||
| 172 | op.accept(e); | ||
| 173 | } | ||
| 174 | }; | ||
| 175 | } | ||
| 176 | |||
| 177 | public static WindowListener onWindowClose(Consumer<WindowEvent> op) { | ||
| 178 | return new WindowAdapter() { | ||
| 179 | @Override | ||
| 180 | public void windowClosing(WindowEvent e) { | ||
| 181 | op.accept(e); | ||
| 182 | } | ||
| 183 | }; | ||
| 184 | } | ||
| 138 | } | 185 | } |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/util/LanguageChangeListener.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/LanguageChangeListener.java index 69612288..9f53a44f 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/util/LanguageChangeListener.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/LanguageChangeListener.java | |||
| @@ -4,4 +4,8 @@ public interface LanguageChangeListener { | |||
| 4 | 4 | ||
| 5 | void retranslateUi(); | 5 | void retranslateUi(); |
| 6 | 6 | ||
| 7 | default boolean isValid() { | ||
| 8 | return true; | ||
| 9 | } | ||
| 10 | |||
| 7 | } | 11 | } |