diff options
| author | 2021-07-07 10:09:23 +0200 | |
|---|---|---|
| committer | 2021-09-05 13:25:38 +0200 | |
| commit | 03f39ef355bac7cf3d7233a9e60bd60440d4a2ae (patch) | |
| tree | b598f19481be18f4cb7a3b246fd81a40e5d93e1e /enigma-swing/src/main/java | |
| parent | Update to shadow 7.0.0, second try (diff) | |
| download | enigma-03f39ef355bac7cf3d7233a9e60bd60440d4a2ae.tar.gz enigma-03f39ef355bac7cf3d7233a9e60bd60440d4a2ae.tar.xz enigma-03f39ef355bac7cf3d7233a9e60bd60440d4a2ae.zip | |
Refactor and clean up Gui class
(cherry-picked from feature/customizable-ui branch)
Clean up Gui constructor
Separate out code from Gui class
Separate out inheritance tree
Separate out implementations tree
Move click listener to separate method
Make StructurePanel not extend JPanel
Handle DeobfPanelPopupMenu in DeobfPanel
Fix the deobf panel popup menu init failing
Common code for implementations & inheritance tree
Move call tree code to separate class
Move methods from MouseListenerUtil to GuiUtil
Move editor tab code to separate class
Replace WindowAdapter with GuiUtil.onWindowClose
Move showStructure to StructurePanel
Diffstat (limited to 'enigma-swing/src/main/java')
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 | } |