diff options
Diffstat (limited to 'src/main/java/cuchaz/enigma/gui')
9 files changed, 613 insertions, 53 deletions
diff --git a/src/main/java/cuchaz/enigma/gui/ConnectionState.java b/src/main/java/cuchaz/enigma/gui/ConnectionState.java new file mode 100644 index 0000000..db6590d --- /dev/null +++ b/src/main/java/cuchaz/enigma/gui/ConnectionState.java | |||
| @@ -0,0 +1,7 @@ | |||
| 1 | package cuchaz.enigma.gui; | ||
| 2 | |||
| 3 | public enum ConnectionState { | ||
| 4 | NOT_CONNECTED, | ||
| 5 | HOSTING, | ||
| 6 | CONNECTED, | ||
| 7 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java b/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java index f7097f0..08df3e7 100644 --- a/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java +++ b/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java | |||
| @@ -126,6 +126,32 @@ public class DecompiledClassSource { | |||
| 126 | highlightedTokens.computeIfAbsent(highlightType, t -> new ArrayList<>()).add(token); | 126 | highlightedTokens.computeIfAbsent(highlightType, t -> new ArrayList<>()).add(token); |
| 127 | } | 127 | } |
| 128 | 128 | ||
| 129 | public int getObfuscatedOffset(int deobfOffset) { | ||
| 130 | return getOffset(remappedIndex, obfuscatedIndex, deobfOffset); | ||
| 131 | } | ||
| 132 | |||
| 133 | public int getDeobfuscatedOffset(int obfOffset) { | ||
| 134 | return getOffset(obfuscatedIndex, remappedIndex, obfOffset); | ||
| 135 | } | ||
| 136 | |||
| 137 | private static int getOffset(SourceIndex fromIndex, SourceIndex toIndex, int fromOffset) { | ||
| 138 | int relativeOffset = 0; | ||
| 139 | |||
| 140 | Iterator<Token> fromTokenItr = fromIndex.referenceTokens().iterator(); | ||
| 141 | Iterator<Token> toTokenItr = toIndex.referenceTokens().iterator(); | ||
| 142 | while (fromTokenItr.hasNext() && toTokenItr.hasNext()) { | ||
| 143 | Token fromToken = fromTokenItr.next(); | ||
| 144 | Token toToken = toTokenItr.next(); | ||
| 145 | if (fromToken.end > fromOffset) { | ||
| 146 | break; | ||
| 147 | } | ||
| 148 | |||
| 149 | relativeOffset = toToken.end - fromToken.end; | ||
| 150 | } | ||
| 151 | |||
| 152 | return fromOffset + relativeOffset; | ||
| 153 | } | ||
| 154 | |||
| 129 | @Override | 155 | @Override |
| 130 | public String toString() { | 156 | public String toString() { |
| 131 | return remappedIndex.getSource(); | 157 | return remappedIndex.getSource(); |
diff --git a/src/main/java/cuchaz/enigma/gui/Gui.java b/src/main/java/cuchaz/enigma/gui/Gui.java index 3412cd5..3adabae 100644 --- a/src/main/java/cuchaz/enigma/gui/Gui.java +++ b/src/main/java/cuchaz/enigma/gui/Gui.java | |||
| @@ -34,6 +34,7 @@ import cuchaz.enigma.config.Themes; | |||
| 34 | import cuchaz.enigma.gui.dialog.CrashDialog; | 34 | import cuchaz.enigma.gui.dialog.CrashDialog; |
| 35 | import cuchaz.enigma.gui.dialog.JavadocDialog; | 35 | import cuchaz.enigma.gui.dialog.JavadocDialog; |
| 36 | import cuchaz.enigma.gui.dialog.SearchDialog; | 36 | import cuchaz.enigma.gui.dialog.SearchDialog; |
| 37 | import cuchaz.enigma.gui.elements.CollapsibleTabbedPane; | ||
| 37 | import cuchaz.enigma.gui.elements.MenuBar; | 38 | import cuchaz.enigma.gui.elements.MenuBar; |
| 38 | import cuchaz.enigma.gui.elements.PopupMenuBar; | 39 | import cuchaz.enigma.gui.elements.PopupMenuBar; |
| 39 | import cuchaz.enigma.gui.filechooser.FileChooserAny; | 40 | import cuchaz.enigma.gui.filechooser.FileChooserAny; |
| @@ -46,10 +47,12 @@ import cuchaz.enigma.gui.panels.PanelEditor; | |||
| 46 | import cuchaz.enigma.gui.panels.PanelIdentifier; | 47 | import cuchaz.enigma.gui.panels.PanelIdentifier; |
| 47 | import cuchaz.enigma.gui.panels.PanelObf; | 48 | import cuchaz.enigma.gui.panels.PanelObf; |
| 48 | import cuchaz.enigma.gui.util.History; | 49 | import cuchaz.enigma.gui.util.History; |
| 50 | import cuchaz.enigma.network.packet.*; | ||
| 49 | import cuchaz.enigma.throwables.IllegalNameException; | 51 | import cuchaz.enigma.throwables.IllegalNameException; |
| 50 | import cuchaz.enigma.translation.mapping.*; | 52 | import cuchaz.enigma.translation.mapping.*; |
| 51 | import cuchaz.enigma.translation.representation.entry.*; | 53 | import cuchaz.enigma.translation.representation.entry.*; |
| 52 | import cuchaz.enigma.utils.I18n; | 54 | import cuchaz.enigma.utils.I18n; |
| 55 | import cuchaz.enigma.utils.Message; | ||
| 53 | import cuchaz.enigma.gui.util.ScaleUtil; | 56 | import cuchaz.enigma.gui.util.ScaleUtil; |
| 54 | import cuchaz.enigma.utils.Utils; | 57 | import cuchaz.enigma.utils.Utils; |
| 55 | import de.sciss.syntaxpane.DefaultSyntaxKit; | 58 | import de.sciss.syntaxpane.DefaultSyntaxKit; |
| @@ -63,8 +66,11 @@ public class Gui { | |||
| 63 | private final MenuBar menuBar; | 66 | private final MenuBar menuBar; |
| 64 | // state | 67 | // state |
| 65 | public History<EntryReference<Entry<?>, Entry<?>>> referenceHistory; | 68 | public History<EntryReference<Entry<?>, Entry<?>>> referenceHistory; |
| 69 | public EntryReference<Entry<?>, Entry<?>> renamingReference; | ||
| 66 | public EntryReference<Entry<?>, Entry<?>> cursorReference; | 70 | public EntryReference<Entry<?>, Entry<?>> cursorReference; |
| 67 | private boolean shouldNavigateOnClick; | 71 | private boolean shouldNavigateOnClick; |
| 72 | private ConnectionState connectionState; | ||
| 73 | private boolean isJarOpen; | ||
| 68 | 74 | ||
| 69 | public FileDialog jarFileChooser; | 75 | public FileDialog jarFileChooser; |
| 70 | public FileDialog tinyMappingsFileChooser; | 76 | public FileDialog tinyMappingsFileChooser; |
| @@ -76,6 +82,7 @@ public class Gui { | |||
| 76 | private JFrame frame; | 82 | private JFrame frame; |
| 77 | public Config.LookAndFeel editorFeel; | 83 | public Config.LookAndFeel editorFeel; |
| 78 | public PanelEditor editor; | 84 | public PanelEditor editor; |
| 85 | public JScrollPane sourceScroller; | ||
| 79 | private JPanel classesPanel; | 86 | private JPanel classesPanel; |
| 80 | private JSplitPane splitClasses; | 87 | private JSplitPane splitClasses; |
| 81 | private PanelIdentifier infoPanel; | 88 | private PanelIdentifier infoPanel; |
| @@ -87,6 +94,20 @@ public class Gui { | |||
| 87 | private JList<Token> tokens; | 94 | private JList<Token> tokens; |
| 88 | private JTabbedPane tabs; | 95 | private JTabbedPane tabs; |
| 89 | 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 | |||
| 90 | public JTextField renameTextField; | 111 | public JTextField renameTextField; |
| 91 | public JTextArea javadocTextArea; | 112 | public JTextArea javadocTextArea; |
| 92 | 113 | ||
| @@ -150,7 +171,7 @@ public class Gui { | |||
| 150 | // init editor | 171 | // init editor |
| 151 | selectionHighlightPainter = new SelectionHighlightPainter(); | 172 | selectionHighlightPainter = new SelectionHighlightPainter(); |
| 152 | this.editor = new PanelEditor(this); | 173 | this.editor = new PanelEditor(this); |
| 153 | JScrollPane sourceScroller = new JScrollPane(this.editor); | 174 | this.sourceScroller = new JScrollPane(this.editor); |
| 154 | this.editor.setContentType("text/enigma-sources"); | 175 | this.editor.setContentType("text/enigma-sources"); |
| 155 | this.editor.setBackground(new Color(Config.getInstance().editorBackground)); | 176 | this.editor.setBackground(new Color(Config.getInstance().editorBackground)); |
| 156 | DefaultSyntaxKit kit = (DefaultSyntaxKit) this.editor.getEditorKit(); | 177 | DefaultSyntaxKit kit = (DefaultSyntaxKit) this.editor.getEditorKit(); |
| @@ -283,7 +304,34 @@ public class Gui { | |||
| 283 | tabs.addTab(I18n.translate("info_panel.tree.inheritance"), inheritancePanel); | 304 | tabs.addTab(I18n.translate("info_panel.tree.inheritance"), inheritancePanel); |
| 284 | tabs.addTab(I18n.translate("info_panel.tree.implementations"), implementationsPanel); | 305 | tabs.addTab(I18n.translate("info_panel.tree.implementations"), implementationsPanel); |
| 285 | tabs.addTab(I18n.translate("info_panel.tree.calls"), callPanel); | 306 | tabs.addTab(I18n.translate("info_panel.tree.calls"), callPanel); |
| 286 | JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, tabs); | 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); | ||
| 287 | splitRight.setResizeWeight(1); // let the left side take all the slack | 335 | splitRight.setResizeWeight(1); // let the left side take all the slack |
| 288 | splitRight.resetToPreferredSizes(); | 336 | splitRight.resetToPreferredSizes(); |
| 289 | JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, this.classesPanel, splitRight); | 337 | JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, this.classesPanel, splitRight); |
| @@ -294,7 +342,17 @@ public class Gui { | |||
| 294 | this.menuBar = new MenuBar(this); | 342 | this.menuBar = new MenuBar(this); |
| 295 | this.frame.setJMenuBar(this.menuBar); | 343 | this.frame.setJMenuBar(this.menuBar); |
| 296 | 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 | |||
| 297 | // init state | 354 | // init state |
| 355 | setConnectionState(ConnectionState.NOT_CONNECTED); | ||
| 298 | onCloseJar(); | 356 | onCloseJar(); |
| 299 | 357 | ||
| 300 | this.frame.addWindowListener(new WindowAdapter() { | 358 | this.frame.addWindowListener(new WindowAdapter() { |
| @@ -334,18 +392,14 @@ public class Gui { | |||
| 334 | setEditorText(null); | 392 | setEditorText(null); |
| 335 | 393 | ||
| 336 | // update menu | 394 | // update menu |
| 337 | this.menuBar.closeJarMenu.setEnabled(true); | 395 | isJarOpen = true; |
| 338 | this.menuBar.openMappingsMenus.forEach(item -> item.setEnabled(true)); | ||
| 339 | this.menuBar.saveMappingsMenu.setEnabled(false); | ||
| 340 | this.menuBar.saveMappingsMenus.forEach(item -> item.setEnabled(true)); | ||
| 341 | this.menuBar.closeMappingsMenu.setEnabled(true); | ||
| 342 | this.menuBar.exportSourceMenu.setEnabled(true); | ||
| 343 | this.menuBar.exportJarMenu.setEnabled(true); | ||
| 344 | 396 | ||
| 397 | updateUiState(); | ||
| 345 | redraw(); | 398 | redraw(); |
| 346 | } | 399 | } |
| 347 | 400 | ||
| 348 | public void onCloseJar() { | 401 | public void onCloseJar() { |
| 402 | |||
| 349 | // update gui | 403 | // update gui |
| 350 | this.frame.setTitle(Constants.NAME); | 404 | this.frame.setTitle(Constants.NAME); |
| 351 | setObfClasses(null); | 405 | setObfClasses(null); |
| @@ -354,14 +408,10 @@ public class Gui { | |||
| 354 | this.classesPanel.removeAll(); | 408 | this.classesPanel.removeAll(); |
| 355 | 409 | ||
| 356 | // update menu | 410 | // update menu |
| 357 | this.menuBar.closeJarMenu.setEnabled(false); | 411 | isJarOpen = false; |
| 358 | this.menuBar.openMappingsMenus.forEach(item -> item.setEnabled(false)); | 412 | setMappingsFile(null); |
| 359 | this.menuBar.saveMappingsMenu.setEnabled(false); | ||
| 360 | this.menuBar.saveMappingsMenus.forEach(item -> item.setEnabled(false)); | ||
| 361 | this.menuBar.closeMappingsMenu.setEnabled(false); | ||
| 362 | this.menuBar.exportSourceMenu.setEnabled(false); | ||
| 363 | this.menuBar.exportJarMenu.setEnabled(false); | ||
| 364 | 413 | ||
| 414 | updateUiState(); | ||
| 365 | redraw(); | 415 | redraw(); |
| 366 | } | 416 | } |
| 367 | 417 | ||
| @@ -375,7 +425,7 @@ public class Gui { | |||
| 375 | 425 | ||
| 376 | public void setMappingsFile(Path path) { | 426 | public void setMappingsFile(Path path) { |
| 377 | this.enigmaMappingsFileChooser.setSelectedFile(path != null ? path.toFile() : null); | 427 | this.enigmaMappingsFileChooser.setSelectedFile(path != null ? path.toFile() : null); |
| 378 | this.menuBar.saveMappingsMenu.setEnabled(path != null); | 428 | updateUiState(); |
| 379 | } | 429 | } |
| 380 | 430 | ||
| 381 | public void setEditorText(String source) { | 431 | public void setEditorText(String source) { |
| @@ -561,10 +611,12 @@ public class Gui { | |||
| 561 | boolean isConstructorEntry = isToken && referenceEntry instanceof MethodEntry && ((MethodEntry) referenceEntry).isConstructor(); | 611 | boolean isConstructorEntry = isToken && referenceEntry instanceof MethodEntry && ((MethodEntry) referenceEntry).isConstructor(); |
| 562 | boolean isRenamable = isToken && this.controller.project.isRenamable(cursorReference); | 612 | boolean isRenamable = isToken && this.controller.project.isRenamable(cursorReference); |
| 563 | 613 | ||
| 564 | if (isToken) { | 614 | if (!isRenaming()) { |
| 565 | showCursorReference(cursorReference); | 615 | if (isToken) { |
| 566 | } else { | 616 | showCursorReference(cursorReference); |
| 567 | infoPanel.clearReference(); | 617 | } else { |
| 618 | infoPanel.clearReference(); | ||
| 619 | } | ||
| 568 | } | 620 | } |
| 569 | 621 | ||
| 570 | this.popupMenu.renameMenu.setEnabled(isRenamable); | 622 | this.popupMenu.renameMenu.setEnabled(isRenamable); |
| @@ -586,6 +638,11 @@ public class Gui { | |||
| 586 | } | 638 | } |
| 587 | 639 | ||
| 588 | public void startDocChange() { | 640 | public void startDocChange() { |
| 641 | EntryReference<Entry<?>, Entry<?>> curReference = cursorReference; | ||
| 642 | if (isRenaming()) { | ||
| 643 | finishRename(false); | ||
| 644 | } | ||
| 645 | renamingReference = curReference; | ||
| 589 | 646 | ||
| 590 | // init the text box | 647 | // init the text box |
| 591 | javadocTextArea = new JTextArea(10, 40); | 648 | javadocTextArea = new JTextArea(10, 40); |
| @@ -603,7 +660,8 @@ public class Gui { | |||
| 603 | String newName = javadocTextArea.getText(); | 660 | String newName = javadocTextArea.getText(); |
| 604 | if (saveName) { | 661 | if (saveName) { |
| 605 | try { | 662 | try { |
| 606 | this.controller.changeDocs(cursorReference, newName); | 663 | this.controller.changeDocs(renamingReference, newName); |
| 664 | this.controller.sendPacket(new ChangeDocsC2SPacket(renamingReference.getNameableEntry(), newName)); | ||
| 607 | } catch (IllegalNameException ex) { | 665 | } catch (IllegalNameException ex) { |
| 608 | javadocTextArea.setBorder(BorderFactory.createLineBorder(Color.red, 1)); | 666 | javadocTextArea.setBorder(BorderFactory.createLineBorder(Color.red, 1)); |
| 609 | javadocTextArea.setToolTipText(ex.getReason()); | 667 | javadocTextArea.setToolTipText(ex.getReason()); |
| @@ -665,14 +723,19 @@ public class Gui { | |||
| 665 | else | 723 | else |
| 666 | renameTextField.selectAll(); | 724 | renameTextField.selectAll(); |
| 667 | 725 | ||
| 726 | renamingReference = cursorReference; | ||
| 727 | |||
| 668 | redraw(); | 728 | redraw(); |
| 669 | } | 729 | } |
| 670 | 730 | ||
| 671 | private void finishRename(boolean saveName) { | 731 | private void finishRename(boolean saveName) { |
| 672 | String newName = renameTextField.getText(); | 732 | String newName = renameTextField.getText(); |
| 733 | |||
| 673 | if (saveName && newName != null && !newName.isEmpty()) { | 734 | if (saveName && newName != null && !newName.isEmpty()) { |
| 674 | try { | 735 | try { |
| 675 | this.controller.rename(cursorReference, newName, true); | 736 | this.controller.rename(renamingReference, newName, true); |
| 737 | this.controller.sendPacket(new RenameC2SPacket(renamingReference.getNameableEntry(), newName, true)); | ||
| 738 | renameTextField = null; | ||
| 676 | } catch (IllegalNameException ex) { | 739 | } catch (IllegalNameException ex) { |
| 677 | renameTextField.setBorder(BorderFactory.createLineBorder(Color.red, 1)); | 740 | renameTextField.setBorder(BorderFactory.createLineBorder(Color.red, 1)); |
| 678 | renameTextField.setToolTipText(ex.getReason()); | 741 | renameTextField.setToolTipText(ex.getReason()); |
| @@ -681,18 +744,20 @@ public class Gui { | |||
| 681 | return; | 744 | return; |
| 682 | } | 745 | } |
| 683 | 746 | ||
| 684 | // abort the rename | ||
| 685 | JPanel panel = (JPanel) infoPanel.getComponent(0); | ||
| 686 | panel.remove(panel.getComponentCount() - 1); | ||
| 687 | panel.add(Utils.unboldLabel(new JLabel(cursorReference.getNameableName(), JLabel.LEFT))); | ||
| 688 | |||
| 689 | renameTextField = null; | 747 | renameTextField = null; |
| 690 | 748 | ||
| 749 | // abort the rename | ||
| 750 | showCursorReference(cursorReference); | ||
| 751 | |||
| 691 | this.editor.grabFocus(); | 752 | this.editor.grabFocus(); |
| 692 | 753 | ||
| 693 | redraw(); | 754 | redraw(); |
| 694 | } | 755 | } |
| 695 | 756 | ||
| 757 | private boolean isRenaming() { | ||
| 758 | return renameTextField != null; | ||
| 759 | } | ||
| 760 | |||
| 696 | public void showInheritance() { | 761 | public void showInheritance() { |
| 697 | 762 | ||
| 698 | if (cursorReference == null) { | 763 | if (cursorReference == null) { |
| @@ -783,8 +848,10 @@ public class Gui { | |||
| 783 | 848 | ||
| 784 | if (!Objects.equals(obfEntry, deobfEntry)) { | 849 | if (!Objects.equals(obfEntry, deobfEntry)) { |
| 785 | this.controller.removeMapping(cursorReference); | 850 | this.controller.removeMapping(cursorReference); |
| 851 | this.controller.sendPacket(new RemoveMappingC2SPacket(cursorReference.getNameableEntry())); | ||
| 786 | } else { | 852 | } else { |
| 787 | this.controller.markAsDeobfuscated(cursorReference); | 853 | this.controller.markAsDeobfuscated(cursorReference); |
| 854 | this.controller.sendPacket(new MarkDeobfuscatedC2SPacket(cursorReference.getNameableEntry())); | ||
| 788 | } | 855 | } |
| 789 | } | 856 | } |
| 790 | 857 | ||
| @@ -850,6 +917,7 @@ public class Gui { | |||
| 850 | ClassEntry prevDataChild = (ClassEntry) childNode.getUserObject(); | 917 | ClassEntry prevDataChild = (ClassEntry) childNode.getUserObject(); |
| 851 | ClassEntry dataChild = new ClassEntry(data + "/" + prevDataChild.getSimpleName()); | 918 | ClassEntry dataChild = new ClassEntry(data + "/" + prevDataChild.getSimpleName()); |
| 852 | this.controller.rename(new EntryReference<>(prevDataChild, prevDataChild.getFullName()), dataChild.getFullName(), false); | 919 | this.controller.rename(new EntryReference<>(prevDataChild, prevDataChild.getFullName()), dataChild.getFullName(), false); |
| 920 | this.controller.sendPacket(new RenameC2SPacket(prevDataChild, dataChild.getFullName(), false)); | ||
| 853 | childNode.setUserObject(dataChild); | 921 | childNode.setUserObject(dataChild); |
| 854 | } | 922 | } |
| 855 | node.setUserObject(data); | 923 | node.setUserObject(data); |
| @@ -857,8 +925,10 @@ public class Gui { | |||
| 857 | this.deobfPanel.deobfClasses.reload(); | 925 | this.deobfPanel.deobfClasses.reload(); |
| 858 | } | 926 | } |
| 859 | // class rename | 927 | // class rename |
| 860 | else if (data instanceof ClassEntry) | 928 | else if (data instanceof ClassEntry) { |
| 861 | this.controller.rename(new EntryReference<>((ClassEntry) prevData, ((ClassEntry) prevData).getFullName()), ((ClassEntry) data).getFullName(), false); | 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 | } | ||
| 862 | } | 932 | } |
| 863 | 933 | ||
| 864 | public void moveClassTree(EntryReference<Entry<?>, Entry<?>> obfReference, String newName) { | 934 | public void moveClassTree(EntryReference<Entry<?>, Entry<?>> obfReference, String newName) { |
| @@ -920,4 +990,69 @@ public class Gui { | |||
| 920 | return searchDialog; | 990 | return searchDialog; |
| 921 | } | 991 | } |
| 922 | 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 | |||
| 923 | } | 1058 | } |
diff --git a/src/main/java/cuchaz/enigma/gui/GuiController.java b/src/main/java/cuchaz/enigma/gui/GuiController.java index 742d6b8..cccc9e8 100644 --- a/src/main/java/cuchaz/enigma/gui/GuiController.java +++ b/src/main/java/cuchaz/enigma/gui/GuiController.java | |||
| @@ -24,24 +24,33 @@ import cuchaz.enigma.gui.dialog.ProgressDialog; | |||
| 24 | import cuchaz.enigma.gui.stats.StatsGenerator; | 24 | import cuchaz.enigma.gui.stats.StatsGenerator; |
| 25 | import cuchaz.enigma.gui.stats.StatsMember; | 25 | import cuchaz.enigma.gui.stats.StatsMember; |
| 26 | import cuchaz.enigma.gui.util.History; | 26 | import cuchaz.enigma.gui.util.History; |
| 27 | import cuchaz.enigma.network.EnigmaClient; | ||
| 28 | import cuchaz.enigma.network.EnigmaServer; | ||
| 29 | import cuchaz.enigma.network.IntegratedEnigmaServer; | ||
| 30 | import cuchaz.enigma.network.ServerPacketHandler; | ||
| 31 | import cuchaz.enigma.network.packet.LoginC2SPacket; | ||
| 32 | import cuchaz.enigma.network.packet.Packet; | ||
| 27 | import cuchaz.enigma.source.*; | 33 | import cuchaz.enigma.source.*; |
| 28 | import cuchaz.enigma.throwables.MappingParseException; | 34 | import cuchaz.enigma.throwables.MappingParseException; |
| 29 | import cuchaz.enigma.translation.Translator; | 35 | import cuchaz.enigma.translation.Translator; |
| 30 | import cuchaz.enigma.translation.mapping.*; | 36 | import cuchaz.enigma.translation.mapping.*; |
| 31 | import cuchaz.enigma.translation.mapping.serde.MappingFormat; | 37 | import cuchaz.enigma.translation.mapping.serde.MappingFormat; |
| 32 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | 38 | import cuchaz.enigma.translation.mapping.tree.EntryTree; |
| 39 | import cuchaz.enigma.translation.mapping.tree.HashEntryTree; | ||
| 33 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | 40 | import cuchaz.enigma.translation.representation.entry.ClassEntry; |
| 34 | import cuchaz.enigma.translation.representation.entry.Entry; | 41 | import cuchaz.enigma.translation.representation.entry.Entry; |
| 35 | import cuchaz.enigma.translation.representation.entry.FieldEntry; | 42 | import cuchaz.enigma.translation.representation.entry.FieldEntry; |
| 36 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | 43 | import cuchaz.enigma.translation.representation.entry.MethodEntry; |
| 37 | import cuchaz.enigma.utils.I18n; | 44 | import cuchaz.enigma.utils.I18n; |
| 45 | import cuchaz.enigma.utils.Message; | ||
| 38 | import cuchaz.enigma.utils.ReadableToken; | 46 | import cuchaz.enigma.utils.ReadableToken; |
| 39 | import cuchaz.enigma.utils.Utils; | 47 | import cuchaz.enigma.utils.Utils; |
| 40 | import org.objectweb.asm.tree.ClassNode; | 48 | import org.objectweb.asm.tree.ClassNode; |
| 41 | 49 | ||
| 42 | import javax.annotation.Nullable; | 50 | import javax.annotation.Nullable; |
| 43 | import javax.swing.JOptionPane; | 51 | import javax.swing.JOptionPane; |
| 44 | import java.awt.Desktop; | 52 | import javax.swing.SwingUtilities; |
| 53 | import java.awt.*; | ||
| 45 | import java.awt.event.ItemEvent; | 54 | import java.awt.event.ItemEvent; |
| 46 | import java.io.*; | 55 | import java.io.*; |
| 47 | import java.nio.file.Path; | 56 | import java.nio.file.Path; |
| @@ -76,6 +85,9 @@ public class GuiController { | |||
| 76 | private DecompiledClassSource currentSource; | 85 | private DecompiledClassSource currentSource; |
| 77 | private Source uncommentedSource; | 86 | private Source uncommentedSource; |
| 78 | 87 | ||
| 88 | private EnigmaClient client; | ||
| 89 | private EnigmaServer server; | ||
| 90 | |||
| 79 | public GuiController(Gui gui, EnigmaProfile profile) { | 91 | public GuiController(Gui gui, EnigmaProfile profile) { |
| 80 | this.gui = gui; | 92 | this.gui = gui; |
| 81 | this.enigma = Enigma.builder() | 93 | this.enigma = Enigma.builder() |
| @@ -143,6 +155,14 @@ public class GuiController { | |||
| 143 | }); | 155 | }); |
| 144 | } | 156 | } |
| 145 | 157 | ||
| 158 | public void openMappings(EntryTree<EntryMapping> mappings) { | ||
| 159 | if (project == null) return; | ||
| 160 | |||
| 161 | project.setMappings(mappings); | ||
| 162 | refreshClasses(); | ||
| 163 | refreshCurrentClass(); | ||
| 164 | } | ||
| 165 | |||
| 146 | public CompletableFuture<Void> saveMappings(Path path) { | 166 | public CompletableFuture<Void> saveMappings(Path path) { |
| 147 | return saveMappings(path, loadedMappingFormat); | 167 | return saveMappings(path, loadedMappingFormat); |
| 148 | } | 168 | } |
| @@ -388,11 +408,39 @@ public class GuiController { | |||
| 388 | 408 | ||
| 389 | private void refreshCurrentClass(EntryReference<Entry<?>, Entry<?>> reference, RefreshMode mode) { | 409 | private void refreshCurrentClass(EntryReference<Entry<?>, Entry<?>> reference, RefreshMode mode) { |
| 390 | if (currentSource != null) { | 410 | if (currentSource != null) { |
| 391 | loadClass(currentSource.getEntry(), () -> { | 411 | if (reference == null) { |
| 392 | if (reference != null) { | 412 | int obfSelectionStart = currentSource.getObfuscatedOffset(gui.editor.getSelectionStart()); |
| 393 | showReference(reference); | 413 | int obfSelectionEnd = currentSource.getObfuscatedOffset(gui.editor.getSelectionEnd()); |
| 414 | |||
| 415 | Rectangle viewportBounds = gui.sourceScroller.getViewport().getViewRect(); | ||
| 416 | // Here we pick an "anchor position", which we want to stay in the same vertical location on the screen after the new text has been set | ||
| 417 | int anchorModelPos = gui.editor.getSelectionStart(); | ||
| 418 | Rectangle anchorViewPos = Utils.safeModelToView(gui.editor, anchorModelPos); | ||
| 419 | if (anchorViewPos.y < viewportBounds.y || anchorViewPos.y >= viewportBounds.y + viewportBounds.height) { | ||
| 420 | anchorModelPos = gui.editor.viewToModel(new Point(0, viewportBounds.y)); | ||
| 421 | anchorViewPos = Utils.safeModelToView(gui.editor, anchorModelPos); | ||
| 394 | } | 422 | } |
| 395 | }, mode); | 423 | int obfAnchorPos = currentSource.getObfuscatedOffset(anchorModelPos); |
| 424 | Rectangle anchorViewPos_f = anchorViewPos; | ||
| 425 | int scrollX = gui.sourceScroller.getHorizontalScrollBar().getValue(); | ||
| 426 | |||
| 427 | loadClass(currentSource.getEntry(), () -> SwingUtilities.invokeLater(() -> { | ||
| 428 | int newAnchorModelPos = currentSource.getDeobfuscatedOffset(obfAnchorPos); | ||
| 429 | Rectangle newAnchorViewPos = Utils.safeModelToView(gui.editor, newAnchorModelPos); | ||
| 430 | int newScrollY = newAnchorViewPos.y - (anchorViewPos_f.y - viewportBounds.y); | ||
| 431 | |||
| 432 | gui.editor.select(currentSource.getDeobfuscatedOffset(obfSelectionStart), currentSource.getDeobfuscatedOffset(obfSelectionEnd)); | ||
| 433 | // Changing the selection scrolls to the caret position inside a SwingUtilities.invokeLater call, so | ||
| 434 | // we need to wrap our change to the scroll position inside another invokeLater so it happens after | ||
| 435 | // the caret's own scrolling. | ||
| 436 | SwingUtilities.invokeLater(() -> { | ||
| 437 | gui.sourceScroller.getHorizontalScrollBar().setValue(Math.min(scrollX, gui.sourceScroller.getHorizontalScrollBar().getMaximum())); | ||
| 438 | gui.sourceScroller.getVerticalScrollBar().setValue(Math.min(newScrollY, gui.sourceScroller.getVerticalScrollBar().getMaximum())); | ||
| 439 | }); | ||
| 440 | }), mode); | ||
| 441 | } else { | ||
| 442 | loadClass(currentSource.getEntry(), () -> showReference(reference), mode); | ||
| 443 | } | ||
| 396 | } | 444 | } |
| 397 | } | 445 | } |
| 398 | 446 | ||
| @@ -528,43 +576,59 @@ public class GuiController { | |||
| 528 | } | 576 | } |
| 529 | 577 | ||
| 530 | public void rename(EntryReference<Entry<?>, Entry<?>> reference, String newName, boolean refreshClassTree) { | 578 | public void rename(EntryReference<Entry<?>, Entry<?>> reference, String newName, boolean refreshClassTree) { |
| 579 | rename(reference, newName, refreshClassTree, true); | ||
| 580 | } | ||
| 581 | |||
| 582 | public void rename(EntryReference<Entry<?>, Entry<?>> reference, String newName, boolean refreshClassTree, boolean jumpToReference) { | ||
| 531 | Entry<?> entry = reference.getNameableEntry(); | 583 | Entry<?> entry = reference.getNameableEntry(); |
| 532 | project.getMapper().mapFromObf(entry, new EntryMapping(newName)); | 584 | project.getMapper().mapFromObf(entry, new EntryMapping(newName)); |
| 533 | 585 | ||
| 534 | if (refreshClassTree && reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass()) | 586 | if (refreshClassTree && reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass()) |
| 535 | this.gui.moveClassTree(reference, newName); | 587 | this.gui.moveClassTree(reference, newName); |
| 536 | 588 | ||
| 537 | refreshCurrentClass(reference); | 589 | refreshCurrentClass(jumpToReference ? reference : null); |
| 538 | } | 590 | } |
| 539 | 591 | ||
| 540 | public void removeMapping(EntryReference<Entry<?>, Entry<?>> reference) { | 592 | public void removeMapping(EntryReference<Entry<?>, Entry<?>> reference) { |
| 593 | removeMapping(reference, true); | ||
| 594 | } | ||
| 595 | |||
| 596 | public void removeMapping(EntryReference<Entry<?>, Entry<?>> reference, boolean jumpToReference) { | ||
| 541 | project.getMapper().removeByObf(reference.getNameableEntry()); | 597 | project.getMapper().removeByObf(reference.getNameableEntry()); |
| 542 | 598 | ||
| 543 | if (reference.entry instanceof ClassEntry) | 599 | if (reference.entry instanceof ClassEntry) |
| 544 | this.gui.moveClassTree(reference, false, true); | 600 | this.gui.moveClassTree(reference, false, true); |
| 545 | refreshCurrentClass(reference); | 601 | refreshCurrentClass(jumpToReference ? reference : null); |
| 546 | } | 602 | } |
| 547 | 603 | ||
| 548 | public void changeDocs(EntryReference<Entry<?>, Entry<?>> reference, String updatedDocs) { | 604 | public void changeDocs(EntryReference<Entry<?>, Entry<?>> reference, String updatedDocs) { |
| 549 | changeDoc(reference.entry, updatedDocs); | 605 | changeDocs(reference, updatedDocs, true); |
| 606 | } | ||
| 550 | 607 | ||
| 551 | refreshCurrentClass(reference, RefreshMode.JAVADOCS); | 608 | public void changeDocs(EntryReference<Entry<?>, Entry<?>> reference, String updatedDocs, boolean jumpToReference) { |
| 609 | changeDoc(reference.entry, Utils.isBlank(updatedDocs) ? null : updatedDocs); | ||
| 610 | |||
| 611 | refreshCurrentClass(jumpToReference ? reference : null, RefreshMode.JAVADOCS); | ||
| 552 | } | 612 | } |
| 553 | 613 | ||
| 554 | public void changeDoc(Entry<?> obfEntry, String newDoc) { | 614 | private void changeDoc(Entry<?> obfEntry, String newDoc) { |
| 555 | EntryRemapper mapper = project.getMapper(); | 615 | EntryRemapper mapper = project.getMapper(); |
| 556 | if (mapper.getDeobfMapping(obfEntry) == null) { | 616 | if (mapper.getDeobfMapping(obfEntry) == null) { |
| 557 | markAsDeobfuscated(obfEntry,false); // NPE | 617 | markAsDeobfuscated(obfEntry, false); // NPE |
| 558 | } | 618 | } |
| 559 | mapper.mapFromObf(obfEntry, mapper.getDeobfMapping(obfEntry).withDocs(newDoc), false); | 619 | mapper.mapFromObf(obfEntry, mapper.getDeobfMapping(obfEntry).withDocs(newDoc), false); |
| 560 | } | 620 | } |
| 561 | 621 | ||
| 562 | public void markAsDeobfuscated(Entry<?> obfEntry, boolean renaming) { | 622 | private void markAsDeobfuscated(Entry<?> obfEntry, boolean renaming) { |
| 563 | EntryRemapper mapper = project.getMapper(); | 623 | EntryRemapper mapper = project.getMapper(); |
| 564 | mapper.mapFromObf(obfEntry, new EntryMapping(mapper.deobfuscate(obfEntry).getName()), renaming); | 624 | mapper.mapFromObf(obfEntry, new EntryMapping(mapper.deobfuscate(obfEntry).getName()), renaming); |
| 565 | } | 625 | } |
| 566 | 626 | ||
| 567 | public void markAsDeobfuscated(EntryReference<Entry<?>, Entry<?>> reference) { | 627 | public void markAsDeobfuscated(EntryReference<Entry<?>, Entry<?>> reference) { |
| 628 | markAsDeobfuscated(reference, true); | ||
| 629 | } | ||
| 630 | |||
| 631 | public void markAsDeobfuscated(EntryReference<Entry<?>, Entry<?>> reference, boolean jumpToReference) { | ||
| 568 | EntryRemapper mapper = project.getMapper(); | 632 | EntryRemapper mapper = project.getMapper(); |
| 569 | Entry<?> entry = reference.getNameableEntry(); | 633 | Entry<?> entry = reference.getNameableEntry(); |
| 570 | mapper.mapFromObf(entry, new EntryMapping(mapper.deobfuscate(entry).getName())); | 634 | mapper.mapFromObf(entry, new EntryMapping(mapper.deobfuscate(entry).getName())); |
| @@ -572,7 +636,7 @@ public class GuiController { | |||
| 572 | if (reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass()) | 636 | if (reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass()) |
| 573 | this.gui.moveClassTree(reference, true, false); | 637 | this.gui.moveClassTree(reference, true, false); |
| 574 | 638 | ||
| 575 | refreshCurrentClass(reference); | 639 | refreshCurrentClass(jumpToReference ? reference : null); |
| 576 | } | 640 | } |
| 577 | 641 | ||
| 578 | public void openStats(Set<StatsMember> includedMembers) { | 642 | public void openStats(Set<StatsMember> includedMembers) { |
| @@ -602,4 +666,64 @@ public class GuiController { | |||
| 602 | decompiler = createDecompiler(); | 666 | decompiler = createDecompiler(); |
| 603 | refreshCurrentClass(null, RefreshMode.FULL); | 667 | refreshCurrentClass(null, RefreshMode.FULL); |
| 604 | } | 668 | } |
| 669 | |||
| 670 | public EnigmaClient getClient() { | ||
| 671 | return client; | ||
| 672 | } | ||
| 673 | |||
| 674 | public EnigmaServer getServer() { | ||
| 675 | return server; | ||
| 676 | } | ||
| 677 | |||
| 678 | public void createClient(String username, String ip, int port, char[] password) throws IOException { | ||
| 679 | client = new EnigmaClient(this, ip, port); | ||
| 680 | client.connect(); | ||
| 681 | client.sendPacket(new LoginC2SPacket(project.getJarChecksum(), password, username)); | ||
| 682 | gui.setConnectionState(ConnectionState.CONNECTED); | ||
| 683 | } | ||
| 684 | |||
| 685 | public void createServer(int port, char[] password) throws IOException { | ||
| 686 | server = new IntegratedEnigmaServer(project.getJarChecksum(), password, EntryRemapper.mapped(project.getJarIndex(), new HashEntryTree<>(project.getMapper().getObfToDeobf())), port); | ||
| 687 | server.start(); | ||
| 688 | client = new EnigmaClient(this, "127.0.0.1", port); | ||
| 689 | client.connect(); | ||
| 690 | client.sendPacket(new LoginC2SPacket(project.getJarChecksum(), password, EnigmaServer.OWNER_USERNAME)); | ||
| 691 | gui.setConnectionState(ConnectionState.HOSTING); | ||
| 692 | } | ||
| 693 | |||
| 694 | public synchronized void disconnectIfConnected(String reason) { | ||
| 695 | if (client == null && server == null) { | ||
| 696 | return; | ||
| 697 | } | ||
| 698 | |||
| 699 | if (client != null) { | ||
| 700 | client.disconnect(); | ||
| 701 | } | ||
| 702 | if (server != null) { | ||
| 703 | server.stop(); | ||
| 704 | } | ||
| 705 | client = null; | ||
| 706 | server = null; | ||
| 707 | SwingUtilities.invokeLater(() -> { | ||
| 708 | if (reason != null) { | ||
| 709 | JOptionPane.showMessageDialog(gui.getFrame(), I18n.translate(reason), I18n.translate("disconnect.disconnected"), JOptionPane.INFORMATION_MESSAGE); | ||
| 710 | } | ||
| 711 | gui.setConnectionState(ConnectionState.NOT_CONNECTED); | ||
| 712 | }); | ||
| 713 | } | ||
| 714 | |||
| 715 | public void sendPacket(Packet<ServerPacketHandler> packet) { | ||
| 716 | if (client != null) { | ||
| 717 | client.sendPacket(packet); | ||
| 718 | } | ||
| 719 | } | ||
| 720 | |||
| 721 | public void addMessage(Message message) { | ||
| 722 | gui.addMessage(message); | ||
| 723 | } | ||
| 724 | |||
| 725 | public void updateUserList(List<String> users) { | ||
| 726 | gui.setUserList(users); | ||
| 727 | } | ||
| 728 | |||
| 605 | } | 729 | } |
diff --git a/src/main/java/cuchaz/enigma/gui/MessageListCellRenderer.java b/src/main/java/cuchaz/enigma/gui/MessageListCellRenderer.java new file mode 100644 index 0000000..c9e38cb --- /dev/null +++ b/src/main/java/cuchaz/enigma/gui/MessageListCellRenderer.java | |||
| @@ -0,0 +1,24 @@ | |||
| 1 | package cuchaz.enigma.gui; | ||
| 2 | |||
| 3 | import java.awt.Component; | ||
| 4 | |||
| 5 | import javax.swing.DefaultListCellRenderer; | ||
| 6 | import javax.swing.JList; | ||
| 7 | |||
| 8 | import cuchaz.enigma.utils.Message; | ||
| 9 | |||
| 10 | // For now, just render the translated text. | ||
| 11 | // TODO: Icons or something later? | ||
| 12 | public class MessageListCellRenderer extends DefaultListCellRenderer { | ||
| 13 | |||
| 14 | @Override | ||
| 15 | public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) { | ||
| 16 | super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); | ||
| 17 | Message message = (Message) value; | ||
| 18 | if (message != null) { | ||
| 19 | setText(message.translate()); | ||
| 20 | } | ||
| 21 | return this; | ||
| 22 | } | ||
| 23 | |||
| 24 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java new file mode 100644 index 0000000..c5f505c --- /dev/null +++ b/src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java | |||
| @@ -0,0 +1,82 @@ | |||
| 1 | package cuchaz.enigma.gui.dialog; | ||
| 2 | |||
| 3 | import cuchaz.enigma.network.EnigmaServer; | ||
| 4 | import cuchaz.enigma.utils.I18n; | ||
| 5 | |||
| 6 | import javax.swing.*; | ||
| 7 | import java.awt.Frame; | ||
| 8 | |||
| 9 | public class ConnectToServerDialog { | ||
| 10 | |||
| 11 | public static Result show(Frame parentComponent) { | ||
| 12 | JTextField usernameField = new JTextField(System.getProperty("user.name"), 20); | ||
| 13 | JPanel usernameRow = new JPanel(); | ||
| 14 | usernameRow.add(new JLabel(I18n.translate("prompt.connect.username"))); | ||
| 15 | usernameRow.add(usernameField); | ||
| 16 | JTextField ipField = new JTextField(20); | ||
| 17 | JPanel ipRow = new JPanel(); | ||
| 18 | ipRow.add(new JLabel(I18n.translate("prompt.connect.ip"))); | ||
| 19 | ipRow.add(ipField); | ||
| 20 | JTextField portField = new JTextField(String.valueOf(EnigmaServer.DEFAULT_PORT), 10); | ||
| 21 | JPanel portRow = new JPanel(); | ||
| 22 | portRow.add(new JLabel(I18n.translate("prompt.port"))); | ||
| 23 | portRow.add(portField); | ||
| 24 | JPasswordField passwordField = new JPasswordField(20); | ||
| 25 | JPanel passwordRow = new JPanel(); | ||
| 26 | passwordRow.add(new JLabel(I18n.translate("prompt.password"))); | ||
| 27 | passwordRow.add(passwordField); | ||
| 28 | |||
| 29 | int response = JOptionPane.showConfirmDialog(parentComponent, new Object[]{usernameRow, ipRow, portRow, passwordRow}, I18n.translate("prompt.connect.title"), JOptionPane.OK_CANCEL_OPTION); | ||
| 30 | if (response != JOptionPane.OK_OPTION) { | ||
| 31 | return null; | ||
| 32 | } | ||
| 33 | |||
| 34 | String username = usernameField.getText(); | ||
| 35 | String ip = ipField.getText(); | ||
| 36 | int port; | ||
| 37 | try { | ||
| 38 | port = Integer.parseInt(portField.getText()); | ||
| 39 | } catch (NumberFormatException e) { | ||
| 40 | JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.port.nan"), I18n.translate("prompt.connect.title"), JOptionPane.ERROR_MESSAGE); | ||
| 41 | return null; | ||
| 42 | } | ||
| 43 | if (port < 0 || port >= 65536) { | ||
| 44 | JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.port.invalid"), I18n.translate("prompt.connect.title"), JOptionPane.ERROR_MESSAGE); | ||
| 45 | return null; | ||
| 46 | } | ||
| 47 | char[] password = passwordField.getPassword(); | ||
| 48 | |||
| 49 | return new Result(username, ip, port, password); | ||
| 50 | } | ||
| 51 | |||
| 52 | public static class Result { | ||
| 53 | private final String username; | ||
| 54 | private final String ip; | ||
| 55 | private final int port; | ||
| 56 | private final char[] password; | ||
| 57 | |||
| 58 | public Result(String username, String ip, int port, char[] password) { | ||
| 59 | this.username = username; | ||
| 60 | this.ip = ip; | ||
| 61 | this.port = port; | ||
| 62 | this.password = password; | ||
| 63 | } | ||
| 64 | |||
| 65 | public String getUsername() { | ||
| 66 | return username; | ||
| 67 | } | ||
| 68 | |||
| 69 | public String getIp() { | ||
| 70 | return ip; | ||
| 71 | } | ||
| 72 | |||
| 73 | public int getPort() { | ||
| 74 | return port; | ||
| 75 | } | ||
| 76 | |||
| 77 | public char[] getPassword() { | ||
| 78 | return password; | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/CreateServerDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/CreateServerDialog.java new file mode 100644 index 0000000..eea1dff --- /dev/null +++ b/src/main/java/cuchaz/enigma/gui/dialog/CreateServerDialog.java | |||
| @@ -0,0 +1,65 @@ | |||
| 1 | package cuchaz.enigma.gui.dialog; | ||
| 2 | |||
| 3 | import cuchaz.enigma.network.EnigmaServer; | ||
| 4 | import cuchaz.enigma.utils.I18n; | ||
| 5 | |||
| 6 | import javax.swing.*; | ||
| 7 | import java.awt.*; | ||
| 8 | |||
| 9 | public class CreateServerDialog { | ||
| 10 | |||
| 11 | public static Result show(Frame parentComponent) { | ||
| 12 | JTextField portField = new JTextField(String.valueOf(EnigmaServer.DEFAULT_PORT), 10); | ||
| 13 | JPanel portRow = new JPanel(); | ||
| 14 | portRow.add(new JLabel(I18n.translate("prompt.port"))); | ||
| 15 | portRow.add(portField); | ||
| 16 | JPasswordField passwordField = new JPasswordField(20); | ||
| 17 | JPanel passwordRow = new JPanel(); | ||
| 18 | passwordRow.add(new JLabel(I18n.translate("prompt.password"))); | ||
| 19 | passwordRow.add(passwordField); | ||
| 20 | |||
| 21 | int response = JOptionPane.showConfirmDialog(parentComponent, new Object[]{portRow, passwordRow}, I18n.translate("prompt.create_server.title"), JOptionPane.OK_CANCEL_OPTION); | ||
| 22 | if (response != JOptionPane.OK_OPTION) { | ||
| 23 | return null; | ||
| 24 | } | ||
| 25 | |||
| 26 | int port; | ||
| 27 | try { | ||
| 28 | port = Integer.parseInt(portField.getText()); | ||
| 29 | } catch (NumberFormatException e) { | ||
| 30 | JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.port.nan"), I18n.translate("prompt.create_server.title"), JOptionPane.ERROR_MESSAGE); | ||
| 31 | return null; | ||
| 32 | } | ||
| 33 | if (port < 0 || port >= 65536) { | ||
| 34 | JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.port.invalid"), I18n.translate("prompt.create_server.title"), JOptionPane.ERROR_MESSAGE); | ||
| 35 | return null; | ||
| 36 | } | ||
| 37 | |||
| 38 | char[] password = passwordField.getPassword(); | ||
| 39 | if (password.length > EnigmaServer.MAX_PASSWORD_LENGTH) { | ||
| 40 | JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.password.too_long"), I18n.translate("prompt.create_server.title"), JOptionPane.ERROR_MESSAGE); | ||
| 41 | return null; | ||
| 42 | } | ||
| 43 | |||
| 44 | return new Result(port, password); | ||
| 45 | } | ||
| 46 | |||
| 47 | public static class Result { | ||
| 48 | private final int port; | ||
| 49 | private final char[] password; | ||
| 50 | |||
| 51 | public Result(int port, char[] password) { | ||
| 52 | this.port = port; | ||
| 53 | this.password = password; | ||
| 54 | } | ||
| 55 | |||
| 56 | public int getPort() { | ||
| 57 | return port; | ||
| 58 | } | ||
| 59 | |||
| 60 | public char[] getPassword() { | ||
| 61 | return password; | ||
| 62 | } | ||
| 63 | } | ||
| 64 | |||
| 65 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/elements/CollapsibleTabbedPane.java b/src/main/java/cuchaz/enigma/gui/elements/CollapsibleTabbedPane.java new file mode 100644 index 0000000..fb497b1 --- /dev/null +++ b/src/main/java/cuchaz/enigma/gui/elements/CollapsibleTabbedPane.java | |||
| @@ -0,0 +1,40 @@ | |||
| 1 | package cuchaz.enigma.gui.elements; | ||
| 2 | |||
| 3 | import java.awt.event.MouseEvent; | ||
| 4 | |||
| 5 | import javax.swing.JTabbedPane; | ||
| 6 | |||
| 7 | public class CollapsibleTabbedPane extends JTabbedPane { | ||
| 8 | |||
| 9 | public CollapsibleTabbedPane() { | ||
| 10 | } | ||
| 11 | |||
| 12 | public CollapsibleTabbedPane(int tabPlacement) { | ||
| 13 | super(tabPlacement); | ||
| 14 | } | ||
| 15 | |||
| 16 | public CollapsibleTabbedPane(int tabPlacement, int tabLayoutPolicy) { | ||
| 17 | super(tabPlacement, tabLayoutPolicy); | ||
| 18 | } | ||
| 19 | |||
| 20 | @Override | ||
| 21 | protected void processMouseEvent(MouseEvent e) { | ||
| 22 | int id = e.getID(); | ||
| 23 | if (id == MouseEvent.MOUSE_PRESSED) { | ||
| 24 | if (!isEnabled()) return; | ||
| 25 | int tabIndex = getUI().tabForCoordinate(this, e.getX(), e.getY()); | ||
| 26 | if (tabIndex >= 0 && isEnabledAt(tabIndex)) { | ||
| 27 | if (tabIndex == getSelectedIndex()) { | ||
| 28 | if (isFocusOwner() && isRequestFocusEnabled()) { | ||
| 29 | requestFocus(); | ||
| 30 | } else { | ||
| 31 | setSelectedIndex(-1); | ||
| 32 | } | ||
| 33 | return; | ||
| 34 | } | ||
| 35 | } | ||
| 36 | } | ||
| 37 | super.processMouseEvent(e); | ||
| 38 | } | ||
| 39 | |||
| 40 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java b/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java index 8098178..f8e4f7e 100644 --- a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java +++ b/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java | |||
| @@ -1,5 +1,18 @@ | |||
| 1 | package cuchaz.enigma.gui.elements; | 1 | package cuchaz.enigma.gui.elements; |
| 2 | 2 | ||
| 3 | import cuchaz.enigma.config.Config; | ||
| 4 | import cuchaz.enigma.config.Themes; | ||
| 5 | import cuchaz.enigma.gui.Gui; | ||
| 6 | import cuchaz.enigma.gui.dialog.AboutDialog; | ||
| 7 | import cuchaz.enigma.gui.dialog.ConnectToServerDialog; | ||
| 8 | import cuchaz.enigma.gui.dialog.CreateServerDialog; | ||
| 9 | import cuchaz.enigma.gui.dialog.SearchDialog; | ||
| 10 | import cuchaz.enigma.gui.stats.StatsMember; | ||
| 11 | import cuchaz.enigma.gui.util.ScaleUtil; | ||
| 12 | import cuchaz.enigma.translation.mapping.serde.MappingFormat; | ||
| 13 | import cuchaz.enigma.utils.I18n; | ||
| 14 | import cuchaz.enigma.utils.Pair; | ||
| 15 | |||
| 3 | import java.awt.Container; | 16 | import java.awt.Container; |
| 4 | import java.awt.Desktop; | 17 | import java.awt.Desktop; |
| 5 | import java.awt.FlowLayout; | 18 | import java.awt.FlowLayout; |
| @@ -13,21 +26,11 @@ import java.nio.file.Files; | |||
| 13 | import java.nio.file.Path; | 26 | import java.nio.file.Path; |
| 14 | import java.nio.file.Paths; | 27 | import java.nio.file.Paths; |
| 15 | import java.util.*; | 28 | import java.util.*; |
| 29 | import java.util.List; | ||
| 16 | import java.util.stream.Collectors; | 30 | import java.util.stream.Collectors; |
| 17 | import java.util.stream.IntStream; | 31 | import java.util.stream.IntStream; |
| 18 | |||
| 19 | import javax.swing.*; | 32 | import javax.swing.*; |
| 20 | 33 | ||
| 21 | import cuchaz.enigma.config.Config; | ||
| 22 | import cuchaz.enigma.config.Themes; | ||
| 23 | import cuchaz.enigma.gui.Gui; | ||
| 24 | import cuchaz.enigma.gui.dialog.AboutDialog; | ||
| 25 | import cuchaz.enigma.gui.dialog.SearchDialog; | ||
| 26 | import cuchaz.enigma.gui.stats.StatsMember; | ||
| 27 | import cuchaz.enigma.gui.util.ScaleUtil; | ||
| 28 | import cuchaz.enigma.translation.mapping.serde.MappingFormat; | ||
| 29 | import cuchaz.enigma.utils.I18n; | ||
| 30 | import cuchaz.enigma.utils.Pair; | ||
| 31 | 34 | ||
| 32 | import javax.swing.*; | 35 | import javax.swing.*; |
| 33 | 36 | ||
| @@ -49,6 +52,8 @@ public class MenuBar extends JMenuBar { | |||
| 49 | public final JMenuItem dropMappingsMenu; | 52 | public final JMenuItem dropMappingsMenu; |
| 50 | public final JMenuItem exportSourceMenu; | 53 | public final JMenuItem exportSourceMenu; |
| 51 | public final JMenuItem exportJarMenu; | 54 | public final JMenuItem exportJarMenu; |
| 55 | public final JMenuItem connectToServerMenu; | ||
| 56 | public final JMenuItem startServerMenu; | ||
| 52 | private final Gui gui; | 57 | private final Gui gui; |
| 53 | 58 | ||
| 54 | public MenuBar(Gui gui) { | 59 | public MenuBar(Gui gui) { |
| @@ -343,6 +348,58 @@ public class MenuBar extends JMenuBar { | |||
| 343 | } | 348 | } |
| 344 | 349 | ||
| 345 | /* | 350 | /* |
| 351 | * Collab menu | ||
| 352 | */ | ||
| 353 | { | ||
| 354 | JMenu menu = new JMenu(I18n.translate("menu.collab")); | ||
| 355 | this.add(menu); | ||
| 356 | { | ||
| 357 | JMenuItem item = new JMenuItem(I18n.translate("menu.collab.connect")); | ||
| 358 | menu.add(item); | ||
| 359 | item.addActionListener(event -> { | ||
| 360 | if (this.gui.getController().getClient() != null) { | ||
| 361 | this.gui.getController().disconnectIfConnected(null); | ||
| 362 | return; | ||
| 363 | } | ||
| 364 | ConnectToServerDialog.Result result = ConnectToServerDialog.show(this.gui.getFrame()); | ||
| 365 | if (result == null) { | ||
| 366 | return; | ||
| 367 | } | ||
| 368 | this.gui.getController().disconnectIfConnected(null); | ||
| 369 | try { | ||
| 370 | this.gui.getController().createClient(result.getUsername(), result.getIp(), result.getPort(), result.getPassword()); | ||
| 371 | } catch (IOException e) { | ||
| 372 | JOptionPane.showMessageDialog(this.gui.getFrame(), e.toString(), I18n.translate("menu.collab.connect.error"), JOptionPane.ERROR_MESSAGE); | ||
| 373 | this.gui.getController().disconnectIfConnected(null); | ||
| 374 | } | ||
| 375 | Arrays.fill(result.getPassword(), (char)0); | ||
| 376 | }); | ||
| 377 | this.connectToServerMenu = item; | ||
| 378 | } | ||
| 379 | { | ||
| 380 | JMenuItem item = new JMenuItem(I18n.translate("menu.collab.server.start")); | ||
| 381 | menu.add(item); | ||
| 382 | item.addActionListener(event -> { | ||
| 383 | if (this.gui.getController().getServer() != null) { | ||
| 384 | this.gui.getController().disconnectIfConnected(null); | ||
| 385 | return; | ||
| 386 | } | ||
| 387 | CreateServerDialog.Result result = CreateServerDialog.show(this.gui.getFrame()); | ||
| 388 | if (result == null) { | ||
| 389 | return; | ||
| 390 | } | ||
| 391 | this.gui.getController().disconnectIfConnected(null); | ||
| 392 | try { | ||
| 393 | this.gui.getController().createServer(result.getPort(), result.getPassword()); | ||
| 394 | } catch (IOException e) { | ||
| 395 | JOptionPane.showMessageDialog(this.gui.getFrame(), e.toString(), I18n.translate("menu.collab.server.start.error"), JOptionPane.ERROR_MESSAGE); | ||
| 396 | this.gui.getController().disconnectIfConnected(null); | ||
| 397 | } | ||
| 398 | }); | ||
| 399 | this.startServerMenu = item; | ||
| 400 | } | ||
| 401 | } | ||
| 402 | /* | ||
| 346 | * Help menu | 403 | * Help menu |
| 347 | */ | 404 | */ |
| 348 | { | 405 | { |