diff options
Diffstat (limited to 'src/main/java/cuchaz/enigma/gui/Gui.java')
| -rw-r--r-- | src/main/java/cuchaz/enigma/gui/Gui.java | 1058 |
1 files changed, 0 insertions, 1058 deletions
diff --git a/src/main/java/cuchaz/enigma/gui/Gui.java b/src/main/java/cuchaz/enigma/gui/Gui.java deleted file mode 100644 index ed32469..0000000 --- a/src/main/java/cuchaz/enigma/gui/Gui.java +++ /dev/null | |||
| @@ -1,1058 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.gui; | ||
| 13 | |||
| 14 | import java.awt.*; | ||
| 15 | import java.awt.event.*; | ||
| 16 | import java.nio.file.Path; | ||
| 17 | import java.util.List; | ||
| 18 | import java.util.*; | ||
| 19 | import java.util.function.Function; | ||
| 20 | |||
| 21 | import javax.swing.*; | ||
| 22 | import javax.swing.text.BadLocationException; | ||
| 23 | import javax.swing.text.Highlighter; | ||
| 24 | import javax.swing.tree.*; | ||
| 25 | |||
| 26 | import com.google.common.base.Strings; | ||
| 27 | import com.google.common.collect.Lists; | ||
| 28 | import cuchaz.enigma.Constants; | ||
| 29 | import cuchaz.enigma.EnigmaProfile; | ||
| 30 | import cuchaz.enigma.ExceptionIgnorer; | ||
| 31 | import cuchaz.enigma.analysis.*; | ||
| 32 | import cuchaz.enigma.config.Config; | ||
| 33 | import cuchaz.enigma.config.Themes; | ||
| 34 | import cuchaz.enigma.gui.dialog.CrashDialog; | ||
| 35 | import cuchaz.enigma.gui.dialog.JavadocDialog; | ||
| 36 | import cuchaz.enigma.gui.dialog.SearchDialog; | ||
| 37 | import cuchaz.enigma.gui.elements.CollapsibleTabbedPane; | ||
| 38 | import cuchaz.enigma.gui.elements.MenuBar; | ||
| 39 | import cuchaz.enigma.gui.elements.PopupMenuBar; | ||
| 40 | import cuchaz.enigma.gui.filechooser.FileChooserAny; | ||
| 41 | import cuchaz.enigma.gui.filechooser.FileChooserFolder; | ||
| 42 | import cuchaz.enigma.gui.highlight.BoxHighlightPainter; | ||
| 43 | import cuchaz.enigma.gui.highlight.SelectionHighlightPainter; | ||
| 44 | import cuchaz.enigma.gui.highlight.TokenHighlightType; | ||
| 45 | import cuchaz.enigma.gui.panels.PanelDeobf; | ||
| 46 | import cuchaz.enigma.gui.panels.PanelEditor; | ||
| 47 | import cuchaz.enigma.gui.panels.PanelIdentifier; | ||
| 48 | import cuchaz.enigma.gui.panels.PanelObf; | ||
| 49 | import cuchaz.enigma.gui.util.History; | ||
| 50 | import cuchaz.enigma.network.packet.*; | ||
| 51 | import cuchaz.enigma.throwables.IllegalNameException; | ||
| 52 | import cuchaz.enigma.translation.mapping.*; | ||
| 53 | import cuchaz.enigma.translation.representation.entry.*; | ||
| 54 | import cuchaz.enigma.utils.I18n; | ||
| 55 | import cuchaz.enigma.utils.Message; | ||
| 56 | import cuchaz.enigma.gui.util.ScaleUtil; | ||
| 57 | import cuchaz.enigma.utils.Utils; | ||
| 58 | import de.sciss.syntaxpane.DefaultSyntaxKit; | ||
| 59 | |||
| 60 | public class Gui { | ||
| 61 | |||
| 62 | public final PopupMenuBar popupMenu; | ||
| 63 | private final PanelObf obfPanel; | ||
| 64 | private final PanelDeobf deobfPanel; | ||
| 65 | |||
| 66 | private final MenuBar menuBar; | ||
| 67 | // state | ||
| 68 | public History<EntryReference<Entry<?>, Entry<?>>> referenceHistory; | ||
| 69 | public EntryReference<Entry<?>, Entry<?>> renamingReference; | ||
| 70 | public EntryReference<Entry<?>, Entry<?>> cursorReference; | ||
| 71 | private boolean shouldNavigateOnClick; | ||
| 72 | private ConnectionState connectionState; | ||
| 73 | private boolean isJarOpen; | ||
| 74 | |||
| 75 | public FileDialog jarFileChooser; | ||
| 76 | public FileDialog tinyMappingsFileChooser; | ||
| 77 | public SearchDialog searchDialog; | ||
| 78 | public JFileChooser enigmaMappingsFileChooser; | ||
| 79 | public JFileChooser exportSourceFileChooser; | ||
| 80 | public FileDialog exportJarFileChooser; | ||
| 81 | private GuiController controller; | ||
| 82 | private JFrame frame; | ||
| 83 | public Config.LookAndFeel editorFeel; | ||
| 84 | public PanelEditor editor; | ||
| 85 | public JScrollPane sourceScroller; | ||
| 86 | private JPanel classesPanel; | ||
| 87 | private JSplitPane splitClasses; | ||
| 88 | private PanelIdentifier infoPanel; | ||
| 89 | public Map<TokenHighlightType, BoxHighlightPainter> boxHighlightPainters; | ||
| 90 | private SelectionHighlightPainter selectionHighlightPainter; | ||
| 91 | private JTree inheritanceTree; | ||
| 92 | private JTree implementationsTree; | ||
| 93 | private JTree callsTree; | ||
| 94 | private JList<Token> tokens; | ||
| 95 | private JTabbedPane tabs; | ||
| 96 | |||
| 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 | public JTextField renameTextField; | ||
| 112 | public JTextArea javadocTextArea; | ||
| 113 | |||
| 114 | public void setEditorTheme(Config.LookAndFeel feel) { | ||
| 115 | if (editor != null && (editorFeel == null || editorFeel != feel)) { | ||
| 116 | editor.updateUI(); | ||
| 117 | editor.setBackground(new Color(Config.getInstance().editorBackground)); | ||
| 118 | if (editorFeel != null) { | ||
| 119 | getController().refreshCurrentClass(); | ||
| 120 | } | ||
| 121 | |||
| 122 | editorFeel = feel; | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | public Gui(EnigmaProfile profile) { | ||
| 127 | Config.getInstance().lookAndFeel.setGlobalLAF(); | ||
| 128 | |||
| 129 | // init frame | ||
| 130 | this.frame = new JFrame(Constants.NAME); | ||
| 131 | final Container pane = this.frame.getContentPane(); | ||
| 132 | pane.setLayout(new BorderLayout()); | ||
| 133 | |||
| 134 | if (Boolean.parseBoolean(System.getProperty("enigma.catchExceptions", "true"))) { | ||
| 135 | // install a global exception handler to the event thread | ||
| 136 | CrashDialog.init(this.frame); | ||
| 137 | Thread.setDefaultUncaughtExceptionHandler((thread, t) -> { | ||
| 138 | t.printStackTrace(System.err); | ||
| 139 | if (!ExceptionIgnorer.shouldIgnore(t)) { | ||
| 140 | CrashDialog.show(t); | ||
| 141 | } | ||
| 142 | }); | ||
| 143 | } | ||
| 144 | |||
| 145 | this.controller = new GuiController(this, profile); | ||
| 146 | |||
| 147 | Themes.updateTheme(this); | ||
| 148 | |||
| 149 | // init file choosers | ||
| 150 | this.jarFileChooser = new FileDialog(getFrame(), I18n.translate("menu.file.jar.open"), FileDialog.LOAD); | ||
| 151 | |||
| 152 | this.tinyMappingsFileChooser = new FileDialog(getFrame(), "Open tiny Mappings", FileDialog.LOAD); | ||
| 153 | this.enigmaMappingsFileChooser = new FileChooserAny(); | ||
| 154 | this.exportSourceFileChooser = new FileChooserFolder(); | ||
| 155 | this.exportJarFileChooser = new FileDialog(getFrame(), I18n.translate("menu.file.export.jar"), FileDialog.SAVE); | ||
| 156 | |||
| 157 | this.obfPanel = new PanelObf(this); | ||
| 158 | this.deobfPanel = new PanelDeobf(this); | ||
| 159 | |||
| 160 | // set up classes panel (don't add the splitter yet) | ||
| 161 | splitClasses = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, this.obfPanel, this.deobfPanel); | ||
| 162 | splitClasses.setResizeWeight(0.3); | ||
| 163 | this.classesPanel = new JPanel(); | ||
| 164 | this.classesPanel.setLayout(new BorderLayout()); | ||
| 165 | this.classesPanel.setPreferredSize(ScaleUtil.getDimension(250, 0)); | ||
| 166 | |||
| 167 | // init info panel | ||
| 168 | infoPanel = new PanelIdentifier(this); | ||
| 169 | infoPanel.clearReference(); | ||
| 170 | |||
| 171 | // init editor | ||
| 172 | selectionHighlightPainter = new SelectionHighlightPainter(); | ||
| 173 | this.editor = new PanelEditor(this); | ||
| 174 | this.sourceScroller = new JScrollPane(this.editor); | ||
| 175 | this.editor.setContentType("text/enigma-sources"); | ||
| 176 | this.editor.setBackground(new Color(Config.getInstance().editorBackground)); | ||
| 177 | DefaultSyntaxKit kit = (DefaultSyntaxKit) this.editor.getEditorKit(); | ||
| 178 | kit.toggleComponent(this.editor, "de.sciss.syntaxpane.components.TokenMarker"); | ||
| 179 | |||
| 180 | // init editor popup menu | ||
| 181 | this.popupMenu = new PopupMenuBar(this); | ||
| 182 | this.editor.setComponentPopupMenu(this.popupMenu); | ||
| 183 | |||
| 184 | // init inheritance panel | ||
| 185 | inheritanceTree = new JTree(); | ||
| 186 | inheritanceTree.setModel(null); | ||
| 187 | inheritanceTree.addMouseListener(new MouseAdapter() { | ||
| 188 | @Override | ||
| 189 | public void mouseClicked(MouseEvent event) { | ||
| 190 | if (event.getClickCount() >= 2) { | ||
| 191 | // get the selected node | ||
| 192 | TreePath path = inheritanceTree.getSelectionPath(); | ||
| 193 | if (path == null) { | ||
| 194 | return; | ||
| 195 | } | ||
| 196 | |||
| 197 | Object node = path.getLastPathComponent(); | ||
| 198 | if (node instanceof ClassInheritanceTreeNode) { | ||
| 199 | ClassInheritanceTreeNode classNode = (ClassInheritanceTreeNode) node; | ||
| 200 | controller.navigateTo(new ClassEntry(classNode.getObfClassName())); | ||
| 201 | } else if (node instanceof MethodInheritanceTreeNode) { | ||
| 202 | MethodInheritanceTreeNode methodNode = (MethodInheritanceTreeNode) node; | ||
| 203 | if (methodNode.isImplemented()) { | ||
| 204 | controller.navigateTo(methodNode.getMethodEntry()); | ||
| 205 | } | ||
| 206 | } | ||
| 207 | } | ||
| 208 | } | ||
| 209 | }); | ||
| 210 | TreeCellRenderer cellRenderer = inheritanceTree.getCellRenderer(); | ||
| 211 | inheritanceTree.setCellRenderer(new MethodTreeCellRenderer(cellRenderer)); | ||
| 212 | |||
| 213 | JPanel inheritancePanel = new JPanel(); | ||
| 214 | inheritancePanel.setLayout(new BorderLayout()); | ||
| 215 | inheritancePanel.add(new JScrollPane(inheritanceTree)); | ||
| 216 | |||
| 217 | // init implementations panel | ||
| 218 | implementationsTree = new JTree(); | ||
| 219 | implementationsTree.setModel(null); | ||
| 220 | implementationsTree.addMouseListener(new MouseAdapter() { | ||
| 221 | @Override | ||
| 222 | public void mouseClicked(MouseEvent event) { | ||
| 223 | if (event.getClickCount() >= 2) { | ||
| 224 | // get the selected node | ||
| 225 | TreePath path = implementationsTree.getSelectionPath(); | ||
| 226 | if (path == null) { | ||
| 227 | return; | ||
| 228 | } | ||
| 229 | |||
| 230 | Object node = path.getLastPathComponent(); | ||
| 231 | if (node instanceof ClassImplementationsTreeNode) { | ||
| 232 | ClassImplementationsTreeNode classNode = (ClassImplementationsTreeNode) node; | ||
| 233 | controller.navigateTo(classNode.getClassEntry()); | ||
| 234 | } else if (node instanceof MethodImplementationsTreeNode) { | ||
| 235 | MethodImplementationsTreeNode methodNode = (MethodImplementationsTreeNode) node; | ||
| 236 | controller.navigateTo(methodNode.getMethodEntry()); | ||
| 237 | } | ||
| 238 | } | ||
| 239 | } | ||
| 240 | }); | ||
| 241 | JPanel implementationsPanel = new JPanel(); | ||
| 242 | implementationsPanel.setLayout(new BorderLayout()); | ||
| 243 | implementationsPanel.add(new JScrollPane(implementationsTree)); | ||
| 244 | |||
| 245 | // init call panel | ||
| 246 | callsTree = new JTree(); | ||
| 247 | callsTree.setModel(null); | ||
| 248 | callsTree.addMouseListener(new MouseAdapter() { | ||
| 249 | @SuppressWarnings("unchecked") | ||
| 250 | @Override | ||
| 251 | public void mouseClicked(MouseEvent event) { | ||
| 252 | if (event.getClickCount() >= 2) { | ||
| 253 | // get the selected node | ||
| 254 | TreePath path = callsTree.getSelectionPath(); | ||
| 255 | if (path == null) { | ||
| 256 | return; | ||
| 257 | } | ||
| 258 | |||
| 259 | Object node = path.getLastPathComponent(); | ||
| 260 | if (node instanceof ReferenceTreeNode) { | ||
| 261 | ReferenceTreeNode<Entry<?>, Entry<?>> referenceNode = ((ReferenceTreeNode<Entry<?>, Entry<?>>) node); | ||
| 262 | if (referenceNode.getReference() != null) { | ||
| 263 | controller.navigateTo(referenceNode.getReference()); | ||
| 264 | } else { | ||
| 265 | controller.navigateTo(referenceNode.getEntry()); | ||
| 266 | } | ||
| 267 | } | ||
| 268 | } | ||
| 269 | } | ||
| 270 | }); | ||
| 271 | tokens = new JList<>(); | ||
| 272 | tokens.setCellRenderer(new TokenListCellRenderer(this.controller)); | ||
| 273 | tokens.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); | ||
| 274 | tokens.setLayoutOrientation(JList.VERTICAL); | ||
| 275 | tokens.addMouseListener(new MouseAdapter() { | ||
| 276 | @Override | ||
| 277 | public void mouseClicked(MouseEvent event) { | ||
| 278 | if (event.getClickCount() == 2) { | ||
| 279 | Token selected = tokens.getSelectedValue(); | ||
| 280 | if (selected != null) { | ||
| 281 | showToken(selected); | ||
| 282 | } | ||
| 283 | } | ||
| 284 | } | ||
| 285 | }); | ||
| 286 | tokens.setPreferredSize(ScaleUtil.getDimension(0, 200)); | ||
| 287 | tokens.setMinimumSize(ScaleUtil.getDimension(0, 200)); | ||
| 288 | JSplitPane callPanel = new JSplitPane( | ||
| 289 | JSplitPane.VERTICAL_SPLIT, | ||
| 290 | true, | ||
| 291 | new JScrollPane(callsTree), | ||
| 292 | new JScrollPane(tokens) | ||
| 293 | ); | ||
| 294 | callPanel.setResizeWeight(1); // let the top side take all the slack | ||
| 295 | callPanel.resetToPreferredSizes(); | ||
| 296 | |||
| 297 | // layout controls | ||
| 298 | JPanel centerPanel = new JPanel(); | ||
| 299 | centerPanel.setLayout(new BorderLayout()); | ||
| 300 | centerPanel.add(infoPanel, BorderLayout.NORTH); | ||
| 301 | centerPanel.add(sourceScroller, BorderLayout.CENTER); | ||
| 302 | tabs = new JTabbedPane(); | ||
| 303 | tabs.setPreferredSize(ScaleUtil.getDimension(250, 0)); | ||
| 304 | tabs.addTab(I18n.translate("info_panel.tree.inheritance"), inheritancePanel); | ||
| 305 | tabs.addTab(I18n.translate("info_panel.tree.implementations"), implementationsPanel); | ||
| 306 | tabs.addTab(I18n.translate("info_panel.tree.calls"), callPanel); | ||
| 307 | logTabs = new CollapsibleTabbedPane(JTabbedPane.BOTTOM); | ||
| 308 | userModel = new DefaultListModel<>(); | ||
| 309 | users = new JList<>(userModel); | ||
| 310 | messageModel = new DefaultListModel<>(); | ||
| 311 | messages = new JList<>(messageModel); | ||
| 312 | messages.setCellRenderer(new MessageListCellRenderer()); | ||
| 313 | JPanel messagePanel = new JPanel(new BorderLayout()); | ||
| 314 | messageScrollPane = new JScrollPane(this.messages); | ||
| 315 | messagePanel.add(messageScrollPane, BorderLayout.CENTER); | ||
| 316 | JPanel chatPanel = new JPanel(new BorderLayout()); | ||
| 317 | chatBox = new JTextField(); | ||
| 318 | AbstractAction sendListener = new AbstractAction("Send") { | ||
| 319 | @Override | ||
| 320 | public void actionPerformed(ActionEvent e) { | ||
| 321 | sendMessage(); | ||
| 322 | } | ||
| 323 | }; | ||
| 324 | chatBox.addActionListener(sendListener); | ||
| 325 | JButton chatSendButton = new JButton(sendListener); | ||
| 326 | chatPanel.add(chatBox, BorderLayout.CENTER); | ||
| 327 | chatPanel.add(chatSendButton, BorderLayout.EAST); | ||
| 328 | messagePanel.add(chatPanel, BorderLayout.SOUTH); | ||
| 329 | logTabs.addTab(I18n.translate("log_panel.users"), new JScrollPane(this.users)); | ||
| 330 | logTabs.addTab(I18n.translate("log_panel.messages"), messagePanel); | ||
| 331 | logSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, tabs, logTabs); | ||
| 332 | logSplit.setResizeWeight(0.5); | ||
| 333 | logSplit.resetToPreferredSizes(); | ||
| 334 | splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, this.logSplit); | ||
| 335 | splitRight.setResizeWeight(1); // let the left side take all the slack | ||
| 336 | splitRight.resetToPreferredSizes(); | ||
| 337 | JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, this.classesPanel, splitRight); | ||
| 338 | splitCenter.setResizeWeight(0); // let the right side take all the slack | ||
| 339 | pane.add(splitCenter, BorderLayout.CENTER); | ||
| 340 | |||
| 341 | // init menus | ||
| 342 | this.menuBar = new MenuBar(this); | ||
| 343 | this.frame.setJMenuBar(this.menuBar); | ||
| 344 | |||
| 345 | // init status bar | ||
| 346 | statusBar = new JPanel(new BorderLayout()); | ||
| 347 | statusBar.setBorder(BorderFactory.createLoweredBevelBorder()); | ||
| 348 | connectionStatusLabel = new JLabel(); | ||
| 349 | statusLabel = new JLabel(); | ||
| 350 | statusBar.add(statusLabel, BorderLayout.CENTER); | ||
| 351 | statusBar.add(connectionStatusLabel, BorderLayout.EAST); | ||
| 352 | pane.add(statusBar, BorderLayout.SOUTH); | ||
| 353 | |||
| 354 | // init state | ||
| 355 | setConnectionState(ConnectionState.NOT_CONNECTED); | ||
| 356 | onCloseJar(); | ||
| 357 | |||
| 358 | this.frame.addWindowListener(new WindowAdapter() { | ||
| 359 | @Override | ||
| 360 | public void windowClosing(WindowEvent event) { | ||
| 361 | close(); | ||
| 362 | } | ||
| 363 | }); | ||
| 364 | |||
| 365 | // show the frame | ||
| 366 | pane.doLayout(); | ||
| 367 | this.frame.setSize(ScaleUtil.getDimension(1024, 576)); | ||
| 368 | this.frame.setMinimumSize(ScaleUtil.getDimension(640, 480)); | ||
| 369 | this.frame.setVisible(true); | ||
| 370 | this.frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); | ||
| 371 | this.frame.setLocationRelativeTo(null); | ||
| 372 | } | ||
| 373 | |||
| 374 | public JFrame getFrame() { | ||
| 375 | return this.frame; | ||
| 376 | } | ||
| 377 | |||
| 378 | public GuiController getController() { | ||
| 379 | return this.controller; | ||
| 380 | } | ||
| 381 | |||
| 382 | public void onStartOpenJar() { | ||
| 383 | this.classesPanel.removeAll(); | ||
| 384 | redraw(); | ||
| 385 | } | ||
| 386 | |||
| 387 | public void onFinishOpenJar(String jarName) { | ||
| 388 | // update gui | ||
| 389 | this.frame.setTitle(Constants.NAME + " - " + jarName); | ||
| 390 | this.classesPanel.removeAll(); | ||
| 391 | this.classesPanel.add(splitClasses); | ||
| 392 | setEditorText(null); | ||
| 393 | |||
| 394 | // update menu | ||
| 395 | isJarOpen = true; | ||
| 396 | |||
| 397 | updateUiState(); | ||
| 398 | redraw(); | ||
| 399 | } | ||
| 400 | |||
| 401 | public void onCloseJar() { | ||
| 402 | |||
| 403 | // update gui | ||
| 404 | this.frame.setTitle(Constants.NAME); | ||
| 405 | setObfClasses(null); | ||
| 406 | setDeobfClasses(null); | ||
| 407 | setEditorText(null); | ||
| 408 | this.classesPanel.removeAll(); | ||
| 409 | |||
| 410 | // update menu | ||
| 411 | isJarOpen = false; | ||
| 412 | setMappingsFile(null); | ||
| 413 | |||
| 414 | updateUiState(); | ||
| 415 | redraw(); | ||
| 416 | } | ||
| 417 | |||
| 418 | public void setObfClasses(Collection<ClassEntry> obfClasses) { | ||
| 419 | this.obfPanel.obfClasses.setClasses(obfClasses); | ||
| 420 | } | ||
| 421 | |||
| 422 | public void setDeobfClasses(Collection<ClassEntry> deobfClasses) { | ||
| 423 | this.deobfPanel.deobfClasses.setClasses(deobfClasses); | ||
| 424 | } | ||
| 425 | |||
| 426 | public void setMappingsFile(Path path) { | ||
| 427 | this.enigmaMappingsFileChooser.setSelectedFile(path != null ? path.toFile() : null); | ||
| 428 | updateUiState(); | ||
| 429 | } | ||
| 430 | |||
| 431 | public void setEditorText(String source) { | ||
| 432 | this.editor.getHighlighter().removeAllHighlights(); | ||
| 433 | this.editor.setText(source); | ||
| 434 | } | ||
| 435 | |||
| 436 | public void setSource(DecompiledClassSource source) { | ||
| 437 | editor.setText(source.toString()); | ||
| 438 | setHighlightedTokens(source.getHighlightedTokens()); | ||
| 439 | } | ||
| 440 | |||
| 441 | public void showToken(final Token token) { | ||
| 442 | if (token == null) { | ||
| 443 | throw new IllegalArgumentException("Token cannot be null!"); | ||
| 444 | } | ||
| 445 | CodeReader.navigateToToken(this.editor, token, selectionHighlightPainter); | ||
| 446 | redraw(); | ||
| 447 | } | ||
| 448 | |||
| 449 | public void showTokens(Collection<Token> tokens) { | ||
| 450 | Vector<Token> sortedTokens = new Vector<>(tokens); | ||
| 451 | Collections.sort(sortedTokens); | ||
| 452 | if (sortedTokens.size() > 1) { | ||
| 453 | // sort the tokens and update the tokens panel | ||
| 454 | this.tokens.setListData(sortedTokens); | ||
| 455 | this.tokens.setSelectedIndex(0); | ||
| 456 | } else { | ||
| 457 | this.tokens.setListData(new Vector<>()); | ||
| 458 | } | ||
| 459 | |||
| 460 | // show the first token | ||
| 461 | showToken(sortedTokens.get(0)); | ||
| 462 | } | ||
| 463 | |||
| 464 | public void setHighlightedTokens(Map<TokenHighlightType, Collection<Token>> tokens) { | ||
| 465 | // remove any old highlighters | ||
| 466 | this.editor.getHighlighter().removeAllHighlights(); | ||
| 467 | |||
| 468 | if (boxHighlightPainters != null) { | ||
| 469 | for (TokenHighlightType type : tokens.keySet()) { | ||
| 470 | BoxHighlightPainter painter = boxHighlightPainters.get(type); | ||
| 471 | if (painter != null) { | ||
| 472 | setHighlightedTokens(tokens.get(type), painter); | ||
| 473 | } | ||
| 474 | } | ||
| 475 | } | ||
| 476 | |||
| 477 | redraw(); | ||
| 478 | } | ||
| 479 | |||
| 480 | private void setHighlightedTokens(Iterable<Token> tokens, Highlighter.HighlightPainter painter) { | ||
| 481 | for (Token token : tokens) { | ||
| 482 | try { | ||
| 483 | this.editor.getHighlighter().addHighlight(token.start, token.end, painter); | ||
| 484 | } catch (BadLocationException ex) { | ||
| 485 | throw new IllegalArgumentException(ex); | ||
| 486 | } | ||
| 487 | } | ||
| 488 | } | ||
| 489 | |||
| 490 | private void showCursorReference(EntryReference<Entry<?>, Entry<?>> reference) { | ||
| 491 | if (reference == null) { | ||
| 492 | infoPanel.clearReference(); | ||
| 493 | return; | ||
| 494 | } | ||
| 495 | |||
| 496 | this.cursorReference = reference; | ||
| 497 | |||
| 498 | EntryReference<Entry<?>, Entry<?>> translatedReference = controller.project.getMapper().deobfuscate(reference); | ||
| 499 | |||
| 500 | infoPanel.removeAll(); | ||
| 501 | if (translatedReference.entry instanceof ClassEntry) { | ||
| 502 | showClassEntry((ClassEntry) translatedReference.entry); | ||
| 503 | } else if (translatedReference.entry instanceof FieldEntry) { | ||
| 504 | showFieldEntry((FieldEntry) translatedReference.entry); | ||
| 505 | } else if (translatedReference.entry instanceof MethodEntry) { | ||
| 506 | showMethodEntry((MethodEntry) translatedReference.entry); | ||
| 507 | } else if (translatedReference.entry instanceof LocalVariableEntry) { | ||
| 508 | showLocalVariableEntry((LocalVariableEntry) translatedReference.entry); | ||
| 509 | } else { | ||
| 510 | throw new Error("Unknown entry desc: " + translatedReference.entry.getClass().getName()); | ||
| 511 | } | ||
| 512 | |||
| 513 | redraw(); | ||
| 514 | } | ||
| 515 | |||
| 516 | private void showLocalVariableEntry(LocalVariableEntry entry) { | ||
| 517 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.variable"), entry.getName()); | ||
| 518 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.class"), entry.getContainingClass().getFullName()); | ||
| 519 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.method"), entry.getParent().getName()); | ||
| 520 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.index"), Integer.toString(entry.getIndex())); | ||
| 521 | } | ||
| 522 | |||
| 523 | private void showClassEntry(ClassEntry entry) { | ||
| 524 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.class"), entry.getFullName()); | ||
| 525 | addModifierComboBox(infoPanel, I18n.translate("info_panel.identifier.modifier"), entry); | ||
| 526 | } | ||
| 527 | |||
| 528 | private void showFieldEntry(FieldEntry entry) { | ||
| 529 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.field"), entry.getName()); | ||
| 530 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.class"), entry.getParent().getFullName()); | ||
| 531 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.type_descriptor"), entry.getDesc().toString()); | ||
| 532 | addModifierComboBox(infoPanel, I18n.translate("info_panel.identifier.modifier"), entry); | ||
| 533 | } | ||
| 534 | |||
| 535 | private void showMethodEntry(MethodEntry entry) { | ||
| 536 | if (entry.isConstructor()) { | ||
| 537 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.constructor"), entry.getParent().getFullName()); | ||
| 538 | } else { | ||
| 539 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.method"), entry.getName()); | ||
| 540 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.class"), entry.getParent().getFullName()); | ||
| 541 | } | ||
| 542 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.method_descriptor"), entry.getDesc().toString()); | ||
| 543 | addModifierComboBox(infoPanel, I18n.translate("info_panel.identifier.modifier"), entry); | ||
| 544 | } | ||
| 545 | |||
| 546 | private void addNameValue(JPanel container, String name, String value) { | ||
| 547 | JPanel panel = new JPanel(); | ||
| 548 | panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0)); | ||
| 549 | |||
| 550 | JLabel label = new JLabel(name + ":", JLabel.RIGHT); | ||
| 551 | label.setPreferredSize(ScaleUtil.getDimension(100, ScaleUtil.invert(label.getPreferredSize().height))); | ||
| 552 | panel.add(label); | ||
| 553 | |||
| 554 | panel.add(Utils.unboldLabel(new JLabel(value, JLabel.LEFT))); | ||
| 555 | |||
| 556 | container.add(panel); | ||
| 557 | } | ||
| 558 | |||
| 559 | private JComboBox<AccessModifier> addModifierComboBox(JPanel container, String name, Entry<?> entry) { | ||
| 560 | if (!getController().project.isRenamable(entry)) | ||
| 561 | return null; | ||
| 562 | JPanel panel = new JPanel(); | ||
| 563 | panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0)); | ||
| 564 | JLabel label = new JLabel(name + ":", JLabel.RIGHT); | ||
| 565 | label.setPreferredSize(ScaleUtil.getDimension(100, ScaleUtil.invert(label.getPreferredSize().height))); | ||
| 566 | panel.add(label); | ||
| 567 | JComboBox<AccessModifier> combo = new JComboBox<>(AccessModifier.values()); | ||
| 568 | ((JLabel) combo.getRenderer()).setHorizontalAlignment(JLabel.LEFT); | ||
| 569 | combo.setPreferredSize(ScaleUtil.getDimension(100, ScaleUtil.invert(label.getPreferredSize().height))); | ||
| 570 | |||
| 571 | EntryMapping mapping = controller.project.getMapper().getDeobfMapping(entry); | ||
| 572 | if (mapping != null) { | ||
| 573 | combo.setSelectedIndex(mapping.getAccessModifier().ordinal()); | ||
| 574 | } else { | ||
| 575 | combo.setSelectedIndex(AccessModifier.UNCHANGED.ordinal()); | ||
| 576 | } | ||
| 577 | |||
| 578 | combo.addItemListener(controller::modifierChange); | ||
| 579 | |||
| 580 | panel.add(combo); | ||
| 581 | |||
| 582 | container.add(panel); | ||
| 583 | |||
| 584 | return combo; | ||
| 585 | } | ||
| 586 | |||
| 587 | public void onCaretMove(int pos, boolean fromClick) { | ||
| 588 | if (controller.project == null) | ||
| 589 | return; | ||
| 590 | EntryRemapper mapper = controller.project.getMapper(); | ||
| 591 | Token token = this.controller.getToken(pos); | ||
| 592 | boolean isToken = token != null; | ||
| 593 | |||
| 594 | cursorReference = this.controller.getReference(token); | ||
| 595 | Entry<?> referenceEntry = cursorReference != null ? cursorReference.entry : null; | ||
| 596 | |||
| 597 | if (referenceEntry != null && shouldNavigateOnClick && fromClick) { | ||
| 598 | shouldNavigateOnClick = false; | ||
| 599 | Entry<?> navigationEntry = referenceEntry; | ||
| 600 | if (cursorReference.context == null) { | ||
| 601 | EntryResolver resolver = mapper.getObfResolver(); | ||
| 602 | navigationEntry = resolver.resolveFirstEntry(referenceEntry, ResolutionStrategy.RESOLVE_ROOT); | ||
| 603 | } | ||
| 604 | controller.navigateTo(navigationEntry); | ||
| 605 | return; | ||
| 606 | } | ||
| 607 | |||
| 608 | boolean isClassEntry = isToken && referenceEntry instanceof ClassEntry; | ||
| 609 | boolean isFieldEntry = isToken && referenceEntry instanceof FieldEntry; | ||
| 610 | boolean isMethodEntry = isToken && referenceEntry instanceof MethodEntry && !((MethodEntry) referenceEntry).isConstructor(); | ||
| 611 | boolean isConstructorEntry = isToken && referenceEntry instanceof MethodEntry && ((MethodEntry) referenceEntry).isConstructor(); | ||
| 612 | boolean isRenamable = isToken && this.controller.project.isRenamable(cursorReference); | ||
| 613 | |||
| 614 | if (!isRenaming()) { | ||
| 615 | if (isToken) { | ||
| 616 | showCursorReference(cursorReference); | ||
| 617 | } else { | ||
| 618 | infoPanel.clearReference(); | ||
| 619 | } | ||
| 620 | } | ||
| 621 | |||
| 622 | this.popupMenu.renameMenu.setEnabled(isRenamable); | ||
| 623 | this.popupMenu.editJavadocMenu.setEnabled(isRenamable); | ||
| 624 | this.popupMenu.showInheritanceMenu.setEnabled(isClassEntry || isMethodEntry || isConstructorEntry); | ||
| 625 | this.popupMenu.showImplementationsMenu.setEnabled(isClassEntry || isMethodEntry); | ||
| 626 | this.popupMenu.showCallsMenu.setEnabled(isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry); | ||
| 627 | this.popupMenu.showCallsSpecificMenu.setEnabled(isMethodEntry); | ||
| 628 | this.popupMenu.openEntryMenu.setEnabled(isRenamable && (isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry)); | ||
| 629 | this.popupMenu.openPreviousMenu.setEnabled(this.controller.hasPreviousReference()); | ||
| 630 | this.popupMenu.openNextMenu.setEnabled(this.controller.hasNextReference()); | ||
| 631 | this.popupMenu.toggleMappingMenu.setEnabled(isRenamable); | ||
| 632 | |||
| 633 | if (isToken && !Objects.equals(referenceEntry, mapper.deobfuscate(referenceEntry))) { | ||
| 634 | this.popupMenu.toggleMappingMenu.setText(I18n.translate("popup_menu.reset_obfuscated")); | ||
| 635 | } else { | ||
| 636 | this.popupMenu.toggleMappingMenu.setText(I18n.translate("popup_menu.mark_deobfuscated")); | ||
| 637 | } | ||
| 638 | } | ||
| 639 | |||
| 640 | public void startDocChange() { | ||
| 641 | EntryReference<Entry<?>, Entry<?>> curReference = cursorReference; | ||
| 642 | if (isRenaming()) { | ||
| 643 | finishRename(false); | ||
| 644 | } | ||
| 645 | renamingReference = curReference; | ||
| 646 | |||
| 647 | // init the text box | ||
| 648 | javadocTextArea = new JTextArea(10, 40); | ||
| 649 | |||
| 650 | EntryReference<Entry<?>, Entry<?>> translatedReference = controller.project.getMapper().deobfuscate(cursorReference); | ||
| 651 | javadocTextArea.setText(Strings.nullToEmpty(translatedReference.entry.getJavadocs())); | ||
| 652 | |||
| 653 | JavadocDialog.init(frame, javadocTextArea, this::finishDocChange); | ||
| 654 | javadocTextArea.grabFocus(); | ||
| 655 | |||
| 656 | redraw(); | ||
| 657 | } | ||
| 658 | |||
| 659 | private void finishDocChange(JFrame ui, boolean saveName) { | ||
| 660 | String newName = javadocTextArea.getText(); | ||
| 661 | if (saveName) { | ||
| 662 | try { | ||
| 663 | this.controller.changeDocs(renamingReference, newName); | ||
| 664 | this.controller.sendPacket(new ChangeDocsC2SPacket(renamingReference.getNameableEntry(), newName)); | ||
| 665 | } catch (IllegalNameException ex) { | ||
| 666 | javadocTextArea.setBorder(BorderFactory.createLineBorder(Color.red, 1)); | ||
| 667 | javadocTextArea.setToolTipText(ex.getReason()); | ||
| 668 | Utils.showToolTipNow(javadocTextArea); | ||
| 669 | return; | ||
| 670 | } | ||
| 671 | |||
| 672 | ui.setVisible(false); | ||
| 673 | showCursorReference(cursorReference); | ||
| 674 | return; | ||
| 675 | } | ||
| 676 | |||
| 677 | // abort the jd change | ||
| 678 | javadocTextArea = null; | ||
| 679 | ui.setVisible(false); | ||
| 680 | showCursorReference(cursorReference); | ||
| 681 | |||
| 682 | this.editor.grabFocus(); | ||
| 683 | |||
| 684 | redraw(); | ||
| 685 | } | ||
| 686 | |||
| 687 | public void startRename() { | ||
| 688 | |||
| 689 | // init the text box | ||
| 690 | renameTextField = new JTextField(); | ||
| 691 | |||
| 692 | EntryReference<Entry<?>, Entry<?>> translatedReference = controller.project.getMapper().deobfuscate(cursorReference); | ||
| 693 | renameTextField.setText(translatedReference.getNameableName()); | ||
| 694 | |||
| 695 | renameTextField.setPreferredSize(ScaleUtil.getDimension(360, ScaleUtil.invert(renameTextField.getPreferredSize().height))); | ||
| 696 | renameTextField.addKeyListener(new KeyAdapter() { | ||
| 697 | @Override | ||
| 698 | public void keyPressed(KeyEvent event) { | ||
| 699 | switch (event.getKeyCode()) { | ||
| 700 | case KeyEvent.VK_ENTER: | ||
| 701 | finishRename(true); | ||
| 702 | break; | ||
| 703 | |||
| 704 | case KeyEvent.VK_ESCAPE: | ||
| 705 | finishRename(false); | ||
| 706 | break; | ||
| 707 | default: | ||
| 708 | break; | ||
| 709 | } | ||
| 710 | } | ||
| 711 | }); | ||
| 712 | |||
| 713 | // find the label with the name and replace it with the text box | ||
| 714 | JPanel panel = (JPanel) infoPanel.getComponent(0); | ||
| 715 | panel.remove(panel.getComponentCount() - 1); | ||
| 716 | panel.add(renameTextField); | ||
| 717 | renameTextField.grabFocus(); | ||
| 718 | |||
| 719 | int offset = renameTextField.getText().lastIndexOf('/') + 1; | ||
| 720 | // If it's a class and isn't in the default package, assume that it's deobfuscated. | ||
| 721 | if (translatedReference.getNameableEntry() instanceof ClassEntry && renameTextField.getText().contains("/") && offset != 0) | ||
| 722 | renameTextField.select(offset, renameTextField.getText().length()); | ||
| 723 | else | ||
| 724 | renameTextField.selectAll(); | ||
| 725 | |||
| 726 | renamingReference = cursorReference; | ||
| 727 | |||
| 728 | redraw(); | ||
| 729 | } | ||
| 730 | |||
| 731 | private void finishRename(boolean saveName) { | ||
| 732 | String newName = renameTextField.getText(); | ||
| 733 | |||
| 734 | if (saveName && newName != null && !newName.isEmpty()) { | ||
| 735 | try { | ||
| 736 | this.controller.rename(renamingReference, newName, true); | ||
| 737 | this.controller.sendPacket(new RenameC2SPacket(renamingReference.getNameableEntry(), newName, true)); | ||
| 738 | renameTextField = null; | ||
| 739 | } catch (IllegalNameException ex) { | ||
| 740 | renameTextField.setBorder(BorderFactory.createLineBorder(Color.red, 1)); | ||
| 741 | renameTextField.setToolTipText(ex.getReason()); | ||
| 742 | Utils.showToolTipNow(renameTextField); | ||
| 743 | } | ||
| 744 | return; | ||
| 745 | } | ||
| 746 | |||
| 747 | renameTextField = null; | ||
| 748 | |||
| 749 | // abort the rename | ||
| 750 | showCursorReference(cursorReference); | ||
| 751 | |||
| 752 | this.editor.grabFocus(); | ||
| 753 | |||
| 754 | redraw(); | ||
| 755 | } | ||
| 756 | |||
| 757 | private boolean isRenaming() { | ||
| 758 | return renameTextField != null; | ||
| 759 | } | ||
| 760 | |||
| 761 | public void showInheritance() { | ||
| 762 | |||
| 763 | if (cursorReference == null) { | ||
| 764 | return; | ||
| 765 | } | ||
| 766 | |||
| 767 | inheritanceTree.setModel(null); | ||
| 768 | |||
| 769 | if (cursorReference.entry instanceof ClassEntry) { | ||
| 770 | // get the class inheritance | ||
| 771 | ClassInheritanceTreeNode classNode = this.controller.getClassInheritance((ClassEntry) cursorReference.entry); | ||
| 772 | |||
| 773 | // show the tree at the root | ||
| 774 | TreePath path = getPathToRoot(classNode); | ||
| 775 | inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); | ||
| 776 | inheritanceTree.expandPath(path); | ||
| 777 | inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path)); | ||
| 778 | } else if (cursorReference.entry instanceof MethodEntry) { | ||
| 779 | // get the method inheritance | ||
| 780 | MethodInheritanceTreeNode classNode = this.controller.getMethodInheritance((MethodEntry) cursorReference.entry); | ||
| 781 | |||
| 782 | // show the tree at the root | ||
| 783 | TreePath path = getPathToRoot(classNode); | ||
| 784 | inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); | ||
| 785 | inheritanceTree.expandPath(path); | ||
| 786 | inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path)); | ||
| 787 | } | ||
| 788 | |||
| 789 | tabs.setSelectedIndex(0); | ||
| 790 | |||
| 791 | redraw(); | ||
| 792 | } | ||
| 793 | |||
| 794 | public void showImplementations() { | ||
| 795 | |||
| 796 | if (cursorReference == null) { | ||
| 797 | return; | ||
| 798 | } | ||
| 799 | |||
| 800 | implementationsTree.setModel(null); | ||
| 801 | |||
| 802 | DefaultMutableTreeNode node = null; | ||
| 803 | |||
| 804 | // get the class implementations | ||
| 805 | if (cursorReference.entry instanceof ClassEntry) | ||
| 806 | node = this.controller.getClassImplementations((ClassEntry) cursorReference.entry); | ||
| 807 | else // get the method implementations | ||
| 808 | if (cursorReference.entry instanceof MethodEntry) | ||
| 809 | node = this.controller.getMethodImplementations((MethodEntry) cursorReference.entry); | ||
| 810 | |||
| 811 | if (node != null) { | ||
| 812 | // show the tree at the root | ||
| 813 | TreePath path = getPathToRoot(node); | ||
| 814 | implementationsTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); | ||
| 815 | implementationsTree.expandPath(path); | ||
| 816 | implementationsTree.setSelectionRow(implementationsTree.getRowForPath(path)); | ||
| 817 | } | ||
| 818 | |||
| 819 | tabs.setSelectedIndex(1); | ||
| 820 | |||
| 821 | redraw(); | ||
| 822 | } | ||
| 823 | |||
| 824 | public void showCalls(boolean recurse) { | ||
| 825 | if (cursorReference == null) { | ||
| 826 | return; | ||
| 827 | } | ||
| 828 | |||
| 829 | if (cursorReference.entry instanceof ClassEntry) { | ||
| 830 | ClassReferenceTreeNode node = this.controller.getClassReferences((ClassEntry) cursorReference.entry); | ||
| 831 | callsTree.setModel(new DefaultTreeModel(node)); | ||
| 832 | } else if (cursorReference.entry instanceof FieldEntry) { | ||
| 833 | FieldReferenceTreeNode node = this.controller.getFieldReferences((FieldEntry) cursorReference.entry); | ||
| 834 | callsTree.setModel(new DefaultTreeModel(node)); | ||
| 835 | } else if (cursorReference.entry instanceof MethodEntry) { | ||
| 836 | MethodReferenceTreeNode node = this.controller.getMethodReferences((MethodEntry) cursorReference.entry, recurse); | ||
| 837 | callsTree.setModel(new DefaultTreeModel(node)); | ||
| 838 | } | ||
| 839 | |||
| 840 | tabs.setSelectedIndex(2); | ||
| 841 | |||
| 842 | redraw(); | ||
| 843 | } | ||
| 844 | |||
| 845 | public void toggleMapping() { | ||
| 846 | Entry<?> obfEntry = cursorReference.entry; | ||
| 847 | Entry<?> deobfEntry = controller.project.getMapper().deobfuscate(obfEntry); | ||
| 848 | |||
| 849 | if (!Objects.equals(obfEntry, deobfEntry)) { | ||
| 850 | this.controller.removeMapping(cursorReference); | ||
| 851 | this.controller.sendPacket(new RemoveMappingC2SPacket(cursorReference.getNameableEntry())); | ||
| 852 | } else { | ||
| 853 | this.controller.markAsDeobfuscated(cursorReference); | ||
| 854 | this.controller.sendPacket(new MarkDeobfuscatedC2SPacket(cursorReference.getNameableEntry())); | ||
| 855 | } | ||
| 856 | } | ||
| 857 | |||
| 858 | private TreePath getPathToRoot(TreeNode node) { | ||
| 859 | List<TreeNode> nodes = Lists.newArrayList(); | ||
| 860 | TreeNode n = node; | ||
| 861 | do { | ||
| 862 | nodes.add(n); | ||
| 863 | n = n.getParent(); | ||
| 864 | } while (n != null); | ||
| 865 | Collections.reverse(nodes); | ||
| 866 | return new TreePath(nodes.toArray()); | ||
| 867 | } | ||
| 868 | |||
| 869 | public void showDiscardDiag(Function<Integer, Void> callback, String... options) { | ||
| 870 | int response = JOptionPane.showOptionDialog(this.frame, I18n.translate("prompt.close.summary"), I18n.translate("prompt.close.title"), JOptionPane.YES_NO_CANCEL_OPTION, | ||
| 871 | JOptionPane.QUESTION_MESSAGE, null, options, options[2]); | ||
| 872 | callback.apply(response); | ||
| 873 | } | ||
| 874 | |||
| 875 | public void saveMapping() { | ||
| 876 | if (this.enigmaMappingsFileChooser.getSelectedFile() != null || this.enigmaMappingsFileChooser.showSaveDialog(this.frame) == JFileChooser.APPROVE_OPTION) | ||
| 877 | this.controller.saveMappings(this.enigmaMappingsFileChooser.getSelectedFile().toPath()); | ||
| 878 | } | ||
| 879 | |||
| 880 | public void close() { | ||
| 881 | if (!this.controller.isDirty()) { | ||
| 882 | // everything is saved, we can exit safely | ||
| 883 | exit(); | ||
| 884 | } else { | ||
| 885 | // ask to save before closing | ||
| 886 | showDiscardDiag((response) -> { | ||
| 887 | if (response == JOptionPane.YES_OPTION) { | ||
| 888 | this.saveMapping(); | ||
| 889 | exit(); | ||
| 890 | } else if (response == JOptionPane.NO_OPTION) { | ||
| 891 | exit(); | ||
| 892 | } | ||
| 893 | |||
| 894 | return null; | ||
| 895 | }, I18n.translate("prompt.close.save"), I18n.translate("prompt.close.discard"), I18n.translate("prompt.close.cancel")); | ||
| 896 | } | ||
| 897 | } | ||
| 898 | |||
| 899 | private void exit() { | ||
| 900 | if (searchDialog != null) { | ||
| 901 | searchDialog.dispose(); | ||
| 902 | } | ||
| 903 | this.frame.dispose(); | ||
| 904 | System.exit(0); | ||
| 905 | } | ||
| 906 | |||
| 907 | public void redraw() { | ||
| 908 | this.frame.validate(); | ||
| 909 | this.frame.repaint(); | ||
| 910 | } | ||
| 911 | |||
| 912 | public void onPanelRename(Object prevData, Object data, DefaultMutableTreeNode node) throws IllegalNameException { | ||
| 913 | // package rename | ||
| 914 | if (data instanceof String) { | ||
| 915 | for (int i = 0; i < node.getChildCount(); i++) { | ||
| 916 | DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) node.getChildAt(i); | ||
| 917 | ClassEntry prevDataChild = (ClassEntry) childNode.getUserObject(); | ||
| 918 | ClassEntry dataChild = new ClassEntry(data + "/" + prevDataChild.getSimpleName()); | ||
| 919 | this.controller.rename(new EntryReference<>(prevDataChild, prevDataChild.getFullName()), dataChild.getFullName(), false); | ||
| 920 | this.controller.sendPacket(new RenameC2SPacket(prevDataChild, dataChild.getFullName(), false)); | ||
| 921 | childNode.setUserObject(dataChild); | ||
| 922 | } | ||
| 923 | node.setUserObject(data); | ||
| 924 | // Ob package will never be modified, just reload deob view | ||
| 925 | this.deobfPanel.deobfClasses.reload(); | ||
| 926 | } | ||
| 927 | // class rename | ||
| 928 | else if (data instanceof ClassEntry) { | ||
| 929 | this.controller.rename(new EntryReference<>((ClassEntry) prevData, ((ClassEntry) prevData).getFullName()), ((ClassEntry) data).getFullName(), false); | ||
| 930 | this.controller.sendPacket(new RenameC2SPacket((ClassEntry) prevData, ((ClassEntry) data).getFullName(), false)); | ||
| 931 | } | ||
| 932 | } | ||
| 933 | |||
| 934 | public void moveClassTree(EntryReference<Entry<?>, Entry<?>> obfReference, String newName) { | ||
| 935 | String oldEntry = obfReference.entry.getContainingClass().getPackageName(); | ||
| 936 | String newEntry = new ClassEntry(newName).getPackageName(); | ||
| 937 | moveClassTree(obfReference, oldEntry == null, newEntry == null); | ||
| 938 | } | ||
| 939 | |||
| 940 | // TODO: getExpansionState will *not* actually update itself based on name changes! | ||
| 941 | public void moveClassTree(EntryReference<Entry<?>, Entry<?>> obfReference, boolean isOldOb, boolean isNewOb) { | ||
| 942 | ClassEntry classEntry = obfReference.entry.getContainingClass(); | ||
| 943 | |||
| 944 | List<ClassSelector.StateEntry> stateDeobf = this.deobfPanel.deobfClasses.getExpansionState(this.deobfPanel.deobfClasses); | ||
| 945 | List<ClassSelector.StateEntry> stateObf = this.obfPanel.obfClasses.getExpansionState(this.obfPanel.obfClasses); | ||
| 946 | |||
| 947 | // Ob -> deob | ||
| 948 | if (!isNewOb) { | ||
| 949 | this.deobfPanel.deobfClasses.moveClassIn(classEntry); | ||
| 950 | this.obfPanel.obfClasses.moveClassOut(classEntry); | ||
| 951 | this.deobfPanel.deobfClasses.reload(); | ||
| 952 | this.obfPanel.obfClasses.reload(); | ||
| 953 | } | ||
| 954 | // Deob -> ob | ||
| 955 | else if (!isOldOb) { | ||
| 956 | this.obfPanel.obfClasses.moveClassIn(classEntry); | ||
| 957 | this.deobfPanel.deobfClasses.moveClassOut(classEntry); | ||
| 958 | this.deobfPanel.deobfClasses.reload(); | ||
| 959 | this.obfPanel.obfClasses.reload(); | ||
| 960 | } | ||
| 961 | // Local move | ||
| 962 | else if (isOldOb) { | ||
| 963 | this.obfPanel.obfClasses.moveClassIn(classEntry); | ||
| 964 | this.obfPanel.obfClasses.reload(); | ||
| 965 | } else { | ||
| 966 | this.deobfPanel.deobfClasses.moveClassIn(classEntry); | ||
| 967 | this.deobfPanel.deobfClasses.reload(); | ||
| 968 | } | ||
| 969 | |||
| 970 | this.deobfPanel.deobfClasses.restoreExpansionState(this.deobfPanel.deobfClasses, stateDeobf); | ||
| 971 | this.obfPanel.obfClasses.restoreExpansionState(this.obfPanel.obfClasses, stateObf); | ||
| 972 | } | ||
| 973 | |||
| 974 | public PanelObf getObfPanel() { | ||
| 975 | return obfPanel; | ||
| 976 | } | ||
| 977 | |||
| 978 | public PanelDeobf getDeobfPanel() { | ||
| 979 | return deobfPanel; | ||
| 980 | } | ||
| 981 | |||
| 982 | public void setShouldNavigateOnClick(boolean shouldNavigateOnClick) { | ||
| 983 | this.shouldNavigateOnClick = shouldNavigateOnClick; | ||
| 984 | } | ||
| 985 | |||
| 986 | public SearchDialog getSearchDialog() { | ||
| 987 | if (searchDialog == null) { | ||
| 988 | searchDialog = new SearchDialog(this); | ||
| 989 | } | ||
| 990 | return searchDialog; | ||
| 991 | } | ||
| 992 | |||
| 993 | |||
| 994 | public MenuBar getMenuBar() { | ||
| 995 | return menuBar; | ||
| 996 | } | ||
| 997 | |||
| 998 | public void addMessage(Message message) { | ||
| 999 | JScrollBar verticalScrollBar = messageScrollPane.getVerticalScrollBar(); | ||
| 1000 | boolean isAtBottom = verticalScrollBar.getValue() >= verticalScrollBar.getMaximum() - verticalScrollBar.getModel().getExtent(); | ||
| 1001 | messageModel.addElement(message); | ||
| 1002 | if (isAtBottom) { | ||
| 1003 | SwingUtilities.invokeLater(() -> verticalScrollBar.setValue(verticalScrollBar.getMaximum() - verticalScrollBar.getModel().getExtent())); | ||
| 1004 | } | ||
| 1005 | statusLabel.setText(message.translate()); | ||
| 1006 | } | ||
| 1007 | |||
| 1008 | public void setUserList(List<String> users) { | ||
| 1009 | userModel.clear(); | ||
| 1010 | users.forEach(userModel::addElement); | ||
| 1011 | connectionStatusLabel.setText(String.format(I18n.translate("status.connected_user_count"), users.size())); | ||
| 1012 | } | ||
| 1013 | |||
| 1014 | private void sendMessage() { | ||
| 1015 | String text = chatBox.getText().trim(); | ||
| 1016 | if (!text.isEmpty()) { | ||
| 1017 | getController().sendPacket(new MessageC2SPacket(text)); | ||
| 1018 | } | ||
| 1019 | chatBox.setText(""); | ||
| 1020 | } | ||
| 1021 | |||
| 1022 | /** | ||
| 1023 | * Updates the state of the UI elements (button text, enabled state, ...) to reflect the current program state. | ||
| 1024 | * This is a central place to update the UI state to prevent multiple code paths from changing the same state, | ||
| 1025 | * causing inconsistencies. | ||
| 1026 | */ | ||
| 1027 | public void updateUiState() { | ||
| 1028 | menuBar.connectToServerMenu.setEnabled(isJarOpen && connectionState != ConnectionState.HOSTING); | ||
| 1029 | menuBar.connectToServerMenu.setText(I18n.translate(connectionState != ConnectionState.CONNECTED ? "menu.collab.connect" : "menu.collab.disconnect")); | ||
| 1030 | menuBar.startServerMenu.setEnabled(isJarOpen && connectionState != ConnectionState.CONNECTED); | ||
| 1031 | menuBar.startServerMenu.setText(I18n.translate(connectionState != ConnectionState.HOSTING ? "menu.collab.server.start" : "menu.collab.server.stop")); | ||
| 1032 | |||
| 1033 | menuBar.closeJarMenu.setEnabled(isJarOpen); | ||
| 1034 | menuBar.openMappingsMenus.forEach(item -> item.setEnabled(isJarOpen)); | ||
| 1035 | menuBar.saveMappingsMenu.setEnabled(isJarOpen && enigmaMappingsFileChooser.getSelectedFile() != null && connectionState != ConnectionState.CONNECTED); | ||
| 1036 | menuBar.saveMappingsMenus.forEach(item -> item.setEnabled(isJarOpen)); | ||
| 1037 | menuBar.closeMappingsMenu.setEnabled(isJarOpen); | ||
| 1038 | menuBar.exportSourceMenu.setEnabled(isJarOpen); | ||
| 1039 | menuBar.exportJarMenu.setEnabled(isJarOpen); | ||
| 1040 | |||
| 1041 | connectionStatusLabel.setText(I18n.translate(connectionState == ConnectionState.NOT_CONNECTED ? "status.disconnected" : "status.connected")); | ||
| 1042 | |||
| 1043 | if (connectionState == ConnectionState.NOT_CONNECTED) { | ||
| 1044 | logSplit.setLeftComponent(null); | ||
| 1045 | splitRight.setRightComponent(tabs); | ||
| 1046 | } else { | ||
| 1047 | splitRight.setRightComponent(logSplit); | ||
| 1048 | logSplit.setLeftComponent(tabs); | ||
| 1049 | } | ||
| 1050 | } | ||
| 1051 | |||
| 1052 | public void setConnectionState(ConnectionState state) { | ||
| 1053 | connectionState = state; | ||
| 1054 | statusLabel.setText(I18n.translate("status.ready")); | ||
| 1055 | updateUiState(); | ||
| 1056 | } | ||
| 1057 | |||
| 1058 | } | ||