summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/gui
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/cuchaz/enigma/gui')
-rw-r--r--src/main/java/cuchaz/enigma/gui/ConnectionState.java7
-rw-r--r--src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java26
-rw-r--r--src/main/java/cuchaz/enigma/gui/Gui.java193
-rw-r--r--src/main/java/cuchaz/enigma/gui/GuiController.java150
-rw-r--r--src/main/java/cuchaz/enigma/gui/MessageListCellRenderer.java24
-rw-r--r--src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java82
-rw-r--r--src/main/java/cuchaz/enigma/gui/dialog/CreateServerDialog.java65
-rw-r--r--src/main/java/cuchaz/enigma/gui/elements/CollapsibleTabbedPane.java40
-rw-r--r--src/main/java/cuchaz/enigma/gui/elements/MenuBar.java79
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 @@
1package cuchaz.enigma.gui;
2
3public 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;
34import cuchaz.enigma.gui.dialog.CrashDialog; 34import cuchaz.enigma.gui.dialog.CrashDialog;
35import cuchaz.enigma.gui.dialog.JavadocDialog; 35import cuchaz.enigma.gui.dialog.JavadocDialog;
36import cuchaz.enigma.gui.dialog.SearchDialog; 36import cuchaz.enigma.gui.dialog.SearchDialog;
37import cuchaz.enigma.gui.elements.CollapsibleTabbedPane;
37import cuchaz.enigma.gui.elements.MenuBar; 38import cuchaz.enigma.gui.elements.MenuBar;
38import cuchaz.enigma.gui.elements.PopupMenuBar; 39import cuchaz.enigma.gui.elements.PopupMenuBar;
39import cuchaz.enigma.gui.filechooser.FileChooserAny; 40import cuchaz.enigma.gui.filechooser.FileChooserAny;
@@ -46,10 +47,12 @@ import cuchaz.enigma.gui.panels.PanelEditor;
46import cuchaz.enigma.gui.panels.PanelIdentifier; 47import cuchaz.enigma.gui.panels.PanelIdentifier;
47import cuchaz.enigma.gui.panels.PanelObf; 48import cuchaz.enigma.gui.panels.PanelObf;
48import cuchaz.enigma.gui.util.History; 49import cuchaz.enigma.gui.util.History;
50import cuchaz.enigma.network.packet.*;
49import cuchaz.enigma.throwables.IllegalNameException; 51import cuchaz.enigma.throwables.IllegalNameException;
50import cuchaz.enigma.translation.mapping.*; 52import cuchaz.enigma.translation.mapping.*;
51import cuchaz.enigma.translation.representation.entry.*; 53import cuchaz.enigma.translation.representation.entry.*;
52import cuchaz.enigma.utils.I18n; 54import cuchaz.enigma.utils.I18n;
55import cuchaz.enigma.utils.Message;
53import cuchaz.enigma.gui.util.ScaleUtil; 56import cuchaz.enigma.gui.util.ScaleUtil;
54import cuchaz.enigma.utils.Utils; 57import cuchaz.enigma.utils.Utils;
55import de.sciss.syntaxpane.DefaultSyntaxKit; 58import 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;
24import cuchaz.enigma.gui.stats.StatsGenerator; 24import cuchaz.enigma.gui.stats.StatsGenerator;
25import cuchaz.enigma.gui.stats.StatsMember; 25import cuchaz.enigma.gui.stats.StatsMember;
26import cuchaz.enigma.gui.util.History; 26import cuchaz.enigma.gui.util.History;
27import cuchaz.enigma.network.EnigmaClient;
28import cuchaz.enigma.network.EnigmaServer;
29import cuchaz.enigma.network.IntegratedEnigmaServer;
30import cuchaz.enigma.network.ServerPacketHandler;
31import cuchaz.enigma.network.packet.LoginC2SPacket;
32import cuchaz.enigma.network.packet.Packet;
27import cuchaz.enigma.source.*; 33import cuchaz.enigma.source.*;
28import cuchaz.enigma.throwables.MappingParseException; 34import cuchaz.enigma.throwables.MappingParseException;
29import cuchaz.enigma.translation.Translator; 35import cuchaz.enigma.translation.Translator;
30import cuchaz.enigma.translation.mapping.*; 36import cuchaz.enigma.translation.mapping.*;
31import cuchaz.enigma.translation.mapping.serde.MappingFormat; 37import cuchaz.enigma.translation.mapping.serde.MappingFormat;
32import cuchaz.enigma.translation.mapping.tree.EntryTree; 38import cuchaz.enigma.translation.mapping.tree.EntryTree;
39import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
33import cuchaz.enigma.translation.representation.entry.ClassEntry; 40import cuchaz.enigma.translation.representation.entry.ClassEntry;
34import cuchaz.enigma.translation.representation.entry.Entry; 41import cuchaz.enigma.translation.representation.entry.Entry;
35import cuchaz.enigma.translation.representation.entry.FieldEntry; 42import cuchaz.enigma.translation.representation.entry.FieldEntry;
36import cuchaz.enigma.translation.representation.entry.MethodEntry; 43import cuchaz.enigma.translation.representation.entry.MethodEntry;
37import cuchaz.enigma.utils.I18n; 44import cuchaz.enigma.utils.I18n;
45import cuchaz.enigma.utils.Message;
38import cuchaz.enigma.utils.ReadableToken; 46import cuchaz.enigma.utils.ReadableToken;
39import cuchaz.enigma.utils.Utils; 47import cuchaz.enigma.utils.Utils;
40import org.objectweb.asm.tree.ClassNode; 48import org.objectweb.asm.tree.ClassNode;
41 49
42import javax.annotation.Nullable; 50import javax.annotation.Nullable;
43import javax.swing.JOptionPane; 51import javax.swing.JOptionPane;
44import java.awt.Desktop; 52import javax.swing.SwingUtilities;
53import java.awt.*;
45import java.awt.event.ItemEvent; 54import java.awt.event.ItemEvent;
46import java.io.*; 55import java.io.*;
47import java.nio.file.Path; 56import 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 @@
1package cuchaz.enigma.gui;
2
3import java.awt.Component;
4
5import javax.swing.DefaultListCellRenderer;
6import javax.swing.JList;
7
8import cuchaz.enigma.utils.Message;
9
10// For now, just render the translated text.
11// TODO: Icons or something later?
12public 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 @@
1package cuchaz.enigma.gui.dialog;
2
3import cuchaz.enigma.network.EnigmaServer;
4import cuchaz.enigma.utils.I18n;
5
6import javax.swing.*;
7import java.awt.Frame;
8
9public 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 @@
1package cuchaz.enigma.gui.dialog;
2
3import cuchaz.enigma.network.EnigmaServer;
4import cuchaz.enigma.utils.I18n;
5
6import javax.swing.*;
7import java.awt.*;
8
9public 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 @@
1package cuchaz.enigma.gui.elements;
2
3import java.awt.event.MouseEvent;
4
5import javax.swing.JTabbedPane;
6
7public 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 @@
1package cuchaz.enigma.gui.elements; 1package cuchaz.enigma.gui.elements;
2 2
3import cuchaz.enigma.config.Config;
4import cuchaz.enigma.config.Themes;
5import cuchaz.enigma.gui.Gui;
6import cuchaz.enigma.gui.dialog.AboutDialog;
7import cuchaz.enigma.gui.dialog.ConnectToServerDialog;
8import cuchaz.enigma.gui.dialog.CreateServerDialog;
9import cuchaz.enigma.gui.dialog.SearchDialog;
10import cuchaz.enigma.gui.stats.StatsMember;
11import cuchaz.enigma.gui.util.ScaleUtil;
12import cuchaz.enigma.translation.mapping.serde.MappingFormat;
13import cuchaz.enigma.utils.I18n;
14import cuchaz.enigma.utils.Pair;
15
3import java.awt.Container; 16import java.awt.Container;
4import java.awt.Desktop; 17import java.awt.Desktop;
5import java.awt.FlowLayout; 18import java.awt.FlowLayout;
@@ -13,21 +26,11 @@ import java.nio.file.Files;
13import java.nio.file.Path; 26import java.nio.file.Path;
14import java.nio.file.Paths; 27import java.nio.file.Paths;
15import java.util.*; 28import java.util.*;
29import java.util.List;
16import java.util.stream.Collectors; 30import java.util.stream.Collectors;
17import java.util.stream.IntStream; 31import java.util.stream.IntStream;
18
19import javax.swing.*; 32import javax.swing.*;
20 33
21import cuchaz.enigma.config.Config;
22import cuchaz.enigma.config.Themes;
23import cuchaz.enigma.gui.Gui;
24import cuchaz.enigma.gui.dialog.AboutDialog;
25import cuchaz.enigma.gui.dialog.SearchDialog;
26import cuchaz.enigma.gui.stats.StatsMember;
27import cuchaz.enigma.gui.util.ScaleUtil;
28import cuchaz.enigma.translation.mapping.serde.MappingFormat;
29import cuchaz.enigma.utils.I18n;
30import cuchaz.enigma.utils.Pair;
31 34
32import javax.swing.*; 35import 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 {