diff --git a/.editorconfig b/.editorconfig index 83c7049d8..38a985847 100644 --- a/.editorconfig +++ b/.editorconfig @@ -16,6 +16,9 @@ indent_style = tab [*.tiny] indent_style = tab +[*.json] +indent_style = tab + [*.{mapping,mappings}] indent_style = tab diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/MessageC2SPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/MessageC2SPacket.java index 3bc09e79d..c360687b4 100644 --- a/enigma-server/src/main/java/cuchaz/enigma/network/packet/MessageC2SPacket.java +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/MessageC2SPacket.java @@ -8,10 +8,10 @@ import cuchaz.enigma.network.Message; public class MessageC2SPacket implements Packet { - private String message; MessageC2SPacket() { + } public MessageC2SPacket(String message) { @@ -30,10 +30,9 @@ public void write(DataOutput output) throws IOException { @Override public void handle(ServerPacketHandler handler) { - String message = this.message.trim(); - if (!message.isEmpty()) { - handler.getServer().sendMessage(Message.chat(handler.getServer().getUsername(handler.getClient()), message)); + String trimmedMessage = this.message.trim(); + if (!trimmedMessage.isEmpty()) { + handler.getServer().sendMessage(Message.chat(handler.getServer().getUsername(handler.getClient()), trimmedMessage)); } } - } diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java index a1186b702..10af9edcf 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java @@ -11,27 +11,6 @@ package cuchaz.enigma.gui; -import java.awt.BorderLayout; -import java.awt.Container; -import java.awt.Point; -import java.awt.event.ActionEvent; -import java.nio.file.Path; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; -import java.util.function.Function; - -import javax.annotation.Nullable; -import javax.swing.*; -import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.TreeNode; -import javax.swing.tree.TreePath; - -import com.google.common.collect.Lists; - import cuchaz.enigma.Enigma; import cuchaz.enigma.EnigmaProfile; import cuchaz.enigma.analysis.EntryReference; @@ -39,14 +18,25 @@ import cuchaz.enigma.gui.config.UiConfig; import cuchaz.enigma.gui.dialog.JavadocDialog; import cuchaz.enigma.gui.dialog.SearchDialog; -import cuchaz.enigma.gui.elements.*; -import cuchaz.enigma.gui.panels.*; +import cuchaz.enigma.gui.elements.EditorTabbedPane; +import cuchaz.enigma.gui.elements.MainWindow; +import cuchaz.enigma.gui.elements.MenuBar; +import cuchaz.enigma.gui.elements.ValidatableUi; +import cuchaz.enigma.gui.panels.DeobfPanel; +import cuchaz.enigma.gui.panels.EditorPanel; +import cuchaz.enigma.gui.panels.IdentifierPanel; +import cuchaz.enigma.gui.panels.ObfPanel; +import cuchaz.enigma.gui.panels.right.CollabPanel; +import cuchaz.enigma.gui.panels.right.RightPanel; +import cuchaz.enigma.gui.panels.right.StructurePanel; +import cuchaz.enigma.gui.panels.right.CallsTree; +import cuchaz.enigma.gui.panels.right.ImplementationsTree; +import cuchaz.enigma.gui.panels.right.InheritanceTree; import cuchaz.enigma.gui.renderer.MessageListCellRenderer; import cuchaz.enigma.gui.util.GuiUtil; import cuchaz.enigma.gui.util.LanguageUtil; import cuchaz.enigma.gui.util.ScaleUtil; import cuchaz.enigma.network.Message; -import cuchaz.enigma.network.packet.MessageC2SPacket; import cuchaz.enigma.source.Token; import cuchaz.enigma.translation.mapping.EntryChange; import cuchaz.enigma.translation.mapping.EntryRemapper; @@ -56,9 +46,33 @@ import cuchaz.enigma.utils.validation.ParameterizedMessage; import cuchaz.enigma.utils.validation.ValidationContext; -public class Gui { +import javax.annotation.Nullable; +import javax.swing.DefaultListModel; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollBar; +import javax.swing.JSplitPane; +import javax.swing.SwingUtilities; +import javax.swing.WindowConstants; +import javax.swing.tree.DefaultMutableTreeNode; +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Point; +import java.nio.file.Path; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import java.util.function.IntFunction; - private final MainWindow mainWindow = new MainWindow(Enigma.NAME); +public class Gui { + private final MainWindow mainWindow; private final GuiController controller; private ConnectionState connectionState; @@ -70,29 +84,20 @@ public class Gui { private final ObfPanel obfPanel; private final DeobfPanel deobfPanel; private final IdentifierPanel infoPanel; - private final StructurePanel structurePanel; - private final InheritanceTree inheritanceTree; - private final ImplementationsTree implementationsTree; - private final CallsTree callsTree; private final EditorTabbedPane editorTabbedPane; private final JPanel classesPanel = new JPanel(new BorderLayout()); private final JSplitPane splitClasses; - private final JTabbedPane tabs = new JTabbedPane(); - private final CollapsibleTabbedPane logTabs = new CollapsibleTabbedPane(JTabbedPane.BOTTOM); - private final JSplitPane logSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, tabs, logTabs); private final JPanel centerPanel = new JPanel(new BorderLayout()); - private final JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, this.logSplit); - private final JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, this.classesPanel, splitRight); + private RightPanel rightPanel; + private final JSplitPane splitRight; + private final JSplitPane splitCenter; private final DefaultListModel userModel = new DefaultListModel<>(); private final DefaultListModel messageModel = new DefaultListModel<>(); - private final JList users = new JList<>(userModel); - private final JList messages = new JList<>(messageModel); - private final JPanel messagePanel = new JPanel(new BorderLayout()); - private final JScrollPane messageScrollPane = new JScrollPane(this.messages); - private final JTextField chatBox = new JTextField(); + private final JList users = new JList<>(this.userModel); + private final JList messages = new JList<>(this.messageModel); private final JLabel connectionStatusLabel = new JLabel(); @@ -104,18 +109,19 @@ public class Gui { public SearchDialog searchDialog; public Gui(EnigmaProfile profile, Set editableTypes) { + this.mainWindow = new MainWindow(Enigma.NAME); this.editableTypes = editableTypes; this.controller = new GuiController(this, profile); - this.structurePanel = new StructurePanel(this); this.deobfPanel = new DeobfPanel(this); this.infoPanel = new IdentifierPanel(this); this.obfPanel = new ObfPanel(this); this.menuBar = new MenuBar(this); - this.inheritanceTree = new InheritanceTree(this); - this.implementationsTree = new ImplementationsTree(this); - this.callsTree = new CallsTree(this); + this.setupRightPanels(); this.editorTabbedPane = new EditorTabbedPane(this); this.splitClasses = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, this.obfPanel, this.deobfPanel); + this.rightPanel = RightPanel.getPanel(UiConfig.getSelectedRightPanel()); + this.splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, rightPanel); + this.splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, this.classesPanel, splitRight); this.setupUi(); @@ -125,6 +131,32 @@ public Gui(EnigmaProfile profile, Set editableTypes) { this.mainWindow.setVisible(true); } + private void setupRightPanels() { + // right panels + // top panels + RightPanel.addPanel(new StructurePanel(this)); + RightPanel.addPanel(new InheritanceTree(this)); + RightPanel.addPanel(new ImplementationsTree(this)); + RightPanel.addPanel(new CallsTree(this)); + + // bottom panels + RightPanel.addPanel(new CollabPanel(this)); + + // set default sizes for right panels + for (RightPanel panel : RightPanel.getRightPanels().values()) { + panel.setPreferredSize(new Dimension(300, 100)); + } + + this.mainWindow.updateRightPanelSelector(); + + // verify the user has a valid panel id saved in their config + if (!RightPanel.getPanelClasses().containsKey(UiConfig.getSelectedRightPanel())) { + UiConfig.setSelectedRightPanel(RightPanel.DEFAULT); + // todo change with introduction of better logging! + System.out.println("invalid right panel id in config, resetting to default (" + RightPanel.DEFAULT + ")!"); + } + } + private void setupUi() { this.jarFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); this.tinyMappingsFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); @@ -141,36 +173,14 @@ private void setupUi() { this.classesPanel.setPreferredSize(ScaleUtil.getDimension(250, 0)); // layout controls - Container workArea = this.mainWindow.workArea(); + Container workArea = this.mainWindow.getWorkArea(); workArea.setLayout(new BorderLayout()); centerPanel.add(infoPanel.getUi(), BorderLayout.NORTH); centerPanel.add(this.editorTabbedPane.getUi(), BorderLayout.CENTER); - tabs.setPreferredSize(ScaleUtil.getDimension(250, 0)); - tabs.addTab(I18n.translate("info_panel.tree.structure"), structurePanel.getPanel()); - tabs.addTab(I18n.translate("info_panel.tree.inheritance"), inheritanceTree.getPanel()); - tabs.addTab(I18n.translate("info_panel.tree.implementations"), implementationsTree.getPanel()); - tabs.addTab(I18n.translate("info_panel.tree.calls"), callsTree.getPanel()); - messages.setCellRenderer(new MessageListCellRenderer()); - JPanel chatPanel = new JPanel(new BorderLayout()); - AbstractAction sendListener = new AbstractAction("Send") { - @Override - public void actionPerformed(ActionEvent e) { - sendMessage(); - } - }; - chatBox.addActionListener(sendListener); - JButton chatSendButton = new JButton(sendListener); - chatPanel.add(chatBox, BorderLayout.CENTER); - chatPanel.add(chatSendButton, BorderLayout.EAST); - messagePanel.add(messageScrollPane, BorderLayout.CENTER); - messagePanel.add(chatPanel, BorderLayout.SOUTH); - logTabs.addTab(I18n.translate("log_panel.users"), new JScrollPane(this.users)); - logTabs.addTab(I18n.translate("log_panel.messages"), messagePanel); - logSplit.setResizeWeight(0.5); - logSplit.resetToPreferredSizes(); + splitRight.setResizeWeight(1); // let the left side take all the slack splitRight.resetToPreferredSizes(); splitCenter.setResizeWeight(0); // let the right side take all the slack @@ -179,27 +189,31 @@ public void actionPerformed(ActionEvent e) { // restore state int[] layout = UiConfig.getLayout(); - if (layout.length >= 4) { + if (layout.length >= 3) { this.splitClasses.setDividerLocation(layout[0]); this.splitCenter.setDividerLocation(layout[1]); this.splitRight.setDividerLocation(layout[2]); - this.logSplit.setDividerLocation(layout[3]); } - this.mainWindow.statusBar().addPermanentComponent(this.connectionStatusLabel); + this.mainWindow.getStatusBar().addPermanentComponent(this.connectionStatusLabel); // init state setConnectionState(ConnectionState.NOT_CONNECTED); onCloseJar(); - JFrame frame = this.mainWindow.frame(); + // select correct right panel button + this.rightPanel.getButton().setSelected(true); + // configure selected right panel + this.splitRight.setDividerLocation(UiConfig.getRightPanelDividerLocation(this.getRightPanel().getId(), this.splitRight.getDividerLocation())); + + JFrame frame = this.mainWindow.getFrame(); frame.addWindowListener(GuiUtil.onWindowClose(e -> this.close())); - frame.setSize(UiConfig.getWindowSize("Main Window", ScaleUtil.getDimension(1024, 576))); + frame.setSize(UiConfig.getWindowSize(UiConfig.MAIN_WINDOW, ScaleUtil.getDimension(1024, 576))); frame.setMinimumSize(ScaleUtil.getDimension(640, 480)); frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); - Point windowPos = UiConfig.getWindowPos("Main Window", null); + Point windowPos = UiConfig.getWindowPos(UiConfig.MAIN_WINDOW, null); if (windowPos != null) { frame.setLocation(windowPos); } else { @@ -209,12 +223,66 @@ public void actionPerformed(ActionEvent e) { this.retranslateUi(); } + public RightPanel getRightPanel() { + return this.rightPanel; + } + + /** + * Sets the right panel to the given panel. + * @param clazz the new panel's class + * @param updateStateIfCurrent if the provided id is equal to the current id, this parameter determines whether to update the visibility of the panel + */ + public void setRightPanel(Class clazz, boolean updateStateIfCurrent) { + RightPanel newPanel = RightPanel.getPanel(clazz); + + if (newPanel.getId().equals(this.rightPanel.getId())) { + if (updateStateIfCurrent) { + this.saveRightPanelDividerLocation(); + + // swap visibility + this.rightPanel.setVisible(!this.rightPanel.isVisible()); + } + } else { + // save divider location and hide + this.saveRightPanelDividerLocation(); + this.rightPanel.setVisible(false); + + // set panel + this.rightPanel = newPanel; + this.rightPanel.setVisible(true); + + // show and save new data + this.splitRight.setRightComponent(this.rightPanel); + UiConfig.setSelectedRightPanel(newPanel.getId()); + } + + // we call getHeight on the right panel selector here since it's rotated, meaning its height is actually its width + this.splitRight.setDividerLocation(UiConfig.getRightPanelDividerLocation(newPanel.getId(), this.splitRight.getDividerLocation())); + + // repaint in case the panel was changing without clicking a button + this.mainWindow.getFrame().repaint(); + } + + private void saveRightPanelDividerLocation() { + if (this.rightPanel.isVisible()) { + UiConfig.setRightPanelDividerLocation(this.rightPanel.getId(), this.splitRight.getDividerLocation()); + } + } + public MainWindow getMainWindow() { return this.mainWindow; } + public JList getMessages() { + return this.messages; + } + + public JList getUsers() { + return this.users; + } + public JFrame getFrame() { - return this.mainWindow.frame(); + return this.mainWindow.getFrame(); } public GuiController getController() { @@ -305,10 +373,11 @@ public void setMappingsFile(Path path) { public void showTokens(EditorPanel editor, List tokens) { if (tokens.size() > 1) { + this.setRightPanel(CallsTree.class, false); this.controller.setTokenHandle(editor.getClassHandle().copy()); - this.callsTree.showTokens(tokens); + RightPanel.getPanel(CallsTree.class).showTokens(tokens); } else { - this.callsTree.clearTokens(); + RightPanel.getPanel(CallsTree.class).clearTokens(); } // show the first token @@ -328,7 +397,7 @@ public EntryReference, Entry> getCursorReference() { public void startDocChange(EditorPanel editor) { EntryReference, Entry> cursorReference = editor.getCursorReference(); if (cursorReference == null || !this.isEditable(EditableType.JAVADOC)) return; - JavadocDialog.show(mainWindow.frame(), getController(), cursorReference); + JavadocDialog.show(mainWindow.getFrame(), getController(), cursorReference); } public void startRename(EditorPanel editor, String text) { @@ -343,32 +412,57 @@ public void startRename(EditorPanel editor) { infoPanel.startRenaming(); } + /** + * Updates the structure right panel without opening it + * @param editor the editor to extract the new structure from + */ + public void updateStructure(EditorPanel editor) { + RightPanel.getPanel(StructurePanel.class).updateStructure(editor); + } + + /** + * Opens the Structure right panel and displays information for the provided editor + * @param editor the editor to extract structure from + */ public void showStructure(EditorPanel editor) { - this.structurePanel.showStructure(editor); + this.setRightPanel(StructurePanel.class, false); + this.updateStructure(editor); } + /** + * Opens the Inheritance right panel and displays information for the provided editor's cursor reference. + * @param editor the editor to extract the reference from + */ public void showInheritance(EditorPanel editor) { EntryReference, Entry> cursorReference = editor.getCursorReference(); if (cursorReference == null) return; - this.inheritanceTree.display(cursorReference.entry); - tabs.setSelectedIndex(1); + this.setRightPanel(InheritanceTree.class, false); + RightPanel.getPanel(InheritanceTree.class).display(cursorReference.entry); } + /** + * Opens the Implementations right panel and displays information for the provided editor's cursor reference. + * @param editor the editor to extract the reference from + */ public void showImplementations(EditorPanel editor) { EntryReference, Entry> cursorReference = editor.getCursorReference(); if (cursorReference == null) return; - this.implementationsTree.display(cursorReference.entry); - tabs.setSelectedIndex(2); + this.setRightPanel(ImplementationsTree.class, false); + RightPanel.getPanel(ImplementationsTree.class).display(cursorReference.entry); } + /** + * Opens the Calls right panel and displays information for the provided editor's cursor reference. + * @param editor the editor to extract the reference from + */ public void showCalls(EditorPanel editor, boolean recurse) { EntryReference, Entry> cursorReference = editor.getCursorReference(); if (cursorReference == null) return; - this.callsTree.showCalls(cursorReference.entry, recurse); - tabs.setSelectedIndex(3); + this.setRightPanel(CallsTree.class, false); + RightPanel.getPanel(CallsTree.class).showCalls(cursorReference.entry, recurse); } public void toggleMapping(EditorPanel editor) { @@ -387,14 +481,14 @@ public void toggleMappingFromEntry(Entry obfEntry) { } } - public void showDiscardDiag(Function callback, String... options) { - int response = JOptionPane.showOptionDialog(this.mainWindow.frame(), I18n.translate("prompt.close.summary"), I18n.translate("prompt.close.title"), JOptionPane.YES_NO_CANCEL_OPTION, + public void showDiscardDiag(IntFunction callback, String... options) { + int response = JOptionPane.showOptionDialog(this.mainWindow.getFrame(), I18n.translate("prompt.close.summary"), I18n.translate("prompt.close.title"), JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[2]); callback.apply(response); } public CompletableFuture saveMapping() { - if (this.enigmaMappingsFileChooser.getSelectedFile() != null || this.enigmaMappingsFileChooser.showSaveDialog(this.mainWindow.frame()) == JFileChooser.APPROVE_OPTION) + if (this.enigmaMappingsFileChooser.getSelectedFile() != null || this.enigmaMappingsFileChooser.showSaveDialog(this.mainWindow.getFrame()) == JFileChooser.APPROVE_OPTION) return this.controller.saveMappings(this.enigmaMappingsFileChooser.getSelectedFile().toPath()); return CompletableFuture.completedFuture(null); } @@ -405,7 +499,7 @@ public void close() { exit(); } else { // ask to save before closing - showDiscardDiag((response) -> { + showDiscardDiag(response -> { if (response == JOptionPane.YES_OPTION) { this.saveMapping().thenRun(this::exit); // do not join, as join waits on swing to clear events @@ -419,24 +513,24 @@ public void close() { } private void exit() { - UiConfig.setWindowPos("Main Window", this.mainWindow.frame().getLocationOnScreen()); - UiConfig.setWindowSize("Main Window", this.mainWindow.frame().getSize()); + UiConfig.setWindowPos(UiConfig.MAIN_WINDOW, this.mainWindow.getFrame().getLocationOnScreen()); + UiConfig.setWindowSize(UiConfig.MAIN_WINDOW, this.mainWindow.getFrame().getSize()); UiConfig.setLayout( this.splitClasses.getDividerLocation(), this.splitCenter.getDividerLocation(), - this.splitRight.getDividerLocation(), - this.logSplit.getDividerLocation()); + this.splitRight.getDividerLocation() + ); UiConfig.save(); if (searchDialog != null) { searchDialog.dispose(); } - this.mainWindow.frame().dispose(); + this.mainWindow.getFrame().dispose(); System.exit(0); } public void redraw() { - JFrame frame = this.mainWindow.frame(); + JFrame frame = this.mainWindow.getFrame(); frame.validate(); frame.repaint(); @@ -455,18 +549,17 @@ public void onRenameFromClassTree(ValidationContext vc, Object prevData, Object node.setUserObject(data); // Ob package will never be modified, just reload deob view this.deobfPanel.deobfClasses.reload(); - } else if (data instanceof ClassEntry) { + } else if (data instanceof ClassEntry entry) { // class rename // TODO optimize reverse class lookup, although it looks like it's - // fast enough for now + // fast enough for now EntryRemapper mapper = this.controller.project.getMapper(); - ClassEntry deobf = (ClassEntry) prevData; ClassEntry obf = mapper.getObfToDeobf().getAllEntries() - .filter(e -> e instanceof ClassEntry) - .map(e -> (ClassEntry) e) - .filter(e -> mapper.deobfuscate(e).equals(deobf)) - .findAny().orElse(deobf); + .filter(ClassEntry.class::isInstance) + .map(ClassEntry.class::cast) + .filter(e -> mapper.deobfuscate(e).equals(entry)) + .findAny().orElse(entry); this.controller.applyChange(vc, EntryChange.modify(obf).withDeobfName(((ClassEntry) data).getFullName())); } else { @@ -530,7 +623,7 @@ public SearchDialog getSearchDialog() { } public void addMessage(Message message) { - JScrollBar verticalScrollBar = messageScrollPane.getVerticalScrollBar(); + JScrollBar verticalScrollBar = RightPanel.getPanel(CollabPanel.class).getMessageScrollPane().getVerticalScrollBar(); boolean isAtBottom = verticalScrollBar.getValue() >= verticalScrollBar.getMaximum() - verticalScrollBar.getModel().getExtent(); messageModel.addElement(message); @@ -538,21 +631,24 @@ public void addMessage(Message message) { SwingUtilities.invokeLater(() -> verticalScrollBar.setValue(verticalScrollBar.getMaximum() - verticalScrollBar.getModel().getExtent())); } - this.mainWindow.statusBar().showMessage(message.translate(), 5000); + this.mainWindow.getStatusBar().showMessage(message.translate(), 5000); } public void setUserList(List users) { + boolean wasOffline = this.isOffline(); + userModel.clear(); users.forEach(userModel::addElement); connectionStatusLabel.setText(String.format(I18n.translate("status.connected_user_count"), users.size())); - } - private void sendMessage() { - String text = chatBox.getText().trim(); - if (!text.isEmpty()) { - getController().sendPacket(new MessageC2SPacket(text)); + // if we were previously offline, we need to reload multiplayer-restricted right panels (ex. messages) so they can be used + if (wasOffline && this.getRightPanel() instanceof CollabPanel collabPanel) { + collabPanel.setUp(); } - chatBox.setText(""); + } + + public boolean isOffline() { + return this.getUsers().getModel().getSize() <= 0; } /** @@ -562,30 +658,12 @@ private void sendMessage() { */ public void updateUiState() { menuBar.updateUiState(); - - connectionStatusLabel.setText(I18n.translate(connectionState == ConnectionState.NOT_CONNECTED ? "status.disconnected" : "status.connected")); - - if (connectionState == ConnectionState.NOT_CONNECTED) { - logSplit.setLeftComponent(null); - splitRight.setRightComponent(tabs); - } else { - splitRight.setRightComponent(logSplit); - logSplit.setLeftComponent(tabs); - } - - splitRight.setDividerLocation(splitRight.getDividerLocation()); + this.connectionStatusLabel.setText(I18n.translate(connectionState == ConnectionState.NOT_CONNECTED ? "status.disconnected" : "status.connected")); } public void retranslateUi() { this.jarFileChooser.setDialogTitle(I18n.translate("menu.file.jar.open")); this.exportJarFileChooser.setDialogTitle(I18n.translate("menu.file.export.jar")); - this.tabs.setTitleAt(0, I18n.translate("info_panel.tree.structure")); - this.tabs.setTitleAt(1, I18n.translate("info_panel.tree.inheritance")); - this.tabs.setTitleAt(2, I18n.translate("info_panel.tree.implementations")); - this.tabs.setTitleAt(3, I18n.translate("info_panel.tree.calls")); - this.logTabs.setTitleAt(0, I18n.translate("log_panel.users")); - this.logTabs.setTitleAt(1, I18n.translate("log_panel.messages")); - this.connectionStatusLabel.setText(I18n.translate(connectionState == ConnectionState.NOT_CONNECTED ? "status.disconnected" : "status.connected")); this.updateUiState(); @@ -593,12 +671,10 @@ public void retranslateUi() { this.obfPanel.retranslateUi(); this.deobfPanel.retranslateUi(); this.infoPanel.retranslateUi(); - this.structurePanel.retranslateUi(); this.editorTabbedPane.retranslateUi(); - this.inheritanceTree.retranslateUi(); - this.implementationsTree.retranslateUi(); - this.structurePanel.retranslateUi(); - this.callsTree.retranslateUi(); + for (RightPanel panel : RightPanel.getRightPanels().values()) { + panel.retranslateUi(); + } } public void setConnectionState(ConnectionState state) { @@ -618,9 +694,9 @@ public boolean validateImmediateAction(Consumer op) { ValidationContext vc = new ValidationContext(); op.accept(vc); if (!vc.canProceed()) { - List messages = vc.getMessages(); - String text = ValidatableUi.formatMessages(messages); - JOptionPane.showMessageDialog(this.getFrame(), text, String.format("%d message(s)", messages.size()), JOptionPane.ERROR_MESSAGE); + List parameterizedMessages = vc.getMessages(); + String text = ValidatableUi.formatMessages(parameterizedMessages); + JOptionPane.showMessageDialog(this.getFrame(), text, String.format("%d message(s)", parameterizedMessages.size()), JOptionPane.ERROR_MESSAGE); } return vc.canProceed(); } diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java index 684ae9551..0bcc4881a 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java @@ -388,10 +388,10 @@ public void addSeparatedClasses(List obfClasses, List de List obfService = enigma.getServices().get(ObfuscationTestService.TYPE); boolean obfuscated = result.isObfuscated() && deobfEntry.equals(entry); - if (obfuscated && !obfService.isEmpty()) { - if (obfService.stream().anyMatch(service -> service.testDeobfuscated(entry))) { - obfuscated = false; - } + if (obfuscated + && !obfService.isEmpty() + && obfService.stream().anyMatch(service -> service.testDeobfuscated(entry))) { + obfuscated = false; } if (obfuscated) { @@ -463,7 +463,7 @@ public boolean applyChangeFromServer(EntryChange change) { ValidationContext vc = new ValidationContext(); vc.setActiveElement(PrintValidatable.INSTANCE); this.applyChange0(vc, change); - gui.showStructure(gui.getActiveEditor()); + gui.updateStructure(gui.getActiveEditor()); return vc.canProceed(); } @@ -480,7 +480,7 @@ public void validateChange(ValidationContext vc, EntryChange change) { public void applyChange(ValidationContext vc, EntryChange change) { this.applyChange0(vc, change); - gui.showStructure(gui.getActiveEditor()); + gui.updateStructure(gui.getActiveEditor()); if (!vc.canProceed()) return; this.sendPacket(new EntryChangeC2SPacket(change)); } @@ -495,7 +495,7 @@ private void applyChange0(ValidationContext vc, EntryChange change) { boolean renamed = !change.getDeobfName().isUnchanged(); - if (renamed && target instanceof ClassEntry && !((ClassEntry) target).isInnerClass()) { + if (renamed && target instanceof ClassEntry classEntry && !classEntry.isInnerClass()) { this.gui.moveClassTree(target, prev.targetName() == null, mapping.targetName() == null); } @@ -506,7 +506,7 @@ private void applyChange0(ValidationContext vc, EntryChange change) { if (!Objects.equals(prev.javadoc(), mapping.javadoc())) { this.chp.invalidateJavadoc(target.getTopLevelClass()); } - gui.showStructure(gui.getActiveEditor()); + gui.updateStructure(gui.getActiveEditor()); } public void openStats(Set includedMembers, String topLevelPackage, boolean includeSynthetic) { diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/Main.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/Main.java index 87a3f48ac..62a523dd2 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/Main.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/Main.java @@ -20,7 +20,6 @@ import java.util.Set; import com.google.common.io.MoreFiles; -import cuchaz.enigma.gui.config.KeyBindsConfig; import cuchaz.enigma.gui.config.keybind.KeyBinds; import joptsimple.*; @@ -32,7 +31,6 @@ import cuchaz.enigma.utils.I18n; public class Main { - public static void main(String[] args) throws IOException { OptionParser parser = new OptionParser(); diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java index 4ef04428d..d0a82fcf1 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java @@ -17,9 +17,8 @@ import java.awt.*; public class TokenListCellRenderer implements ListCellRenderer { - - private GuiController controller; - private DefaultListCellRenderer defaultRenderer; + private final GuiController controller; + private final DefaultListCellRenderer defaultRenderer; public TokenListCellRenderer(GuiController controller) { this.controller = controller; @@ -32,5 +31,4 @@ public Component getListCellRendererComponent(JList list, Token label.setText(this.controller.getReadableToken(token).toString()); return label; } - } diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/config/UiConfig.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/UiConfig.java index 3265547fc..b9c628595 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/config/UiConfig.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/UiConfig.java @@ -6,10 +6,66 @@ import cuchaz.enigma.config.ConfigContainer; import cuchaz.enigma.config.ConfigSection; +import cuchaz.enigma.gui.panels.right.RightPanel; import cuchaz.enigma.gui.util.ScaleUtil; import cuchaz.enigma.utils.I18n; public final class UiConfig { + // sections + public static final String MAIN_WINDOW = "Main Window"; + public static final String GENERAL = "General"; + public static final String LANGUAGE = "Language"; + public static final String SCALE_FACTOR = "Scale Factor"; + public static final String RIGHT_PANEL = "Right Panel"; + public static final String RIGHT_PANEL_DIVIDER_LOCATIONS = "Right Panel Divider Locations"; + public static final String LAYOUT = "Layout"; + public static final String THEMES = "Themes"; + public static final String COLORS = "Colors"; + public static final String DECOMPILER = "Decompiler"; + public static final String FONTS = "Fonts"; + public static final String FILE_DIALOG = "File Dialog"; + public static final String MAPPING_STATS = "Mapping Stats"; + + // fields + public static final String CURRENT = "Current"; + public static final String SELECTED = "Selected"; + public static final String USE_CUSTOM = "Use Custom"; + public static final String DEFAULT = "Default"; + public static final String DEFAULT_2 = "Default 2"; + public static final String SMALL = "Small"; + public static final String EDITOR = "Editor"; + public static final String TOP_LEVEL_PACKAGE = "Top Level Package"; + public static final String SYNTHETIC_PARAMETERS = "Synthetic Parameters"; + public static final String LINE_NUMBERS_FOREGROUND = "Line Numbers Foreground"; + public static final String LINE_NUMBERS_BACKGROUND = "Line Numbers Background"; + public static final String LINE_NUMBERS_SELECTED = "Line Numbers Selected"; + public static final String OBFUSCATED = "Obfuscated"; + public static final String OBFUSCATED_ALPHA = "Obfuscated Alpha"; + public static final String OBFUSCATED_OUTLINE = "Obfuscated Outline"; + public static final String OBFUSCATED_OUTLINE_ALPHA = "Obfuscated Outline Alpha"; + public static final String PROPOSED = "Proposed"; + public static final String PROPOSED_ALPHA = "Proposed Alpha"; + public static final String PROPOSED_OUTLINE = "Proposed Outline"; + public static final String PROPOSED_OUTLINE_ALPHA = "Proposed Outline Alpha"; + public static final String DEOBFUSCATED = "Deobfuscated"; + public static final String DEOBFUSCATED_ALPHA = "Deobfuscated Alpha"; + public static final String DEOBFUSCATED_OUTLINE = "Deobfuscated Outline"; + public static final String DEOBFUSCATED_OUTLINE_ALPHA = "Deobfuscated Outline Alpha"; + public static final String EDITOR_BACKGROUND = "Editor Background"; + public static final String HIGHLIGHT = "Highlight"; + public static final String CARET = "Caret"; + public static final String SELECTION_HIGHLIGHT = "Selection Highlight"; + public static final String STRING = "String"; + public static final String NUMBER = "Number"; + public static final String OPERATOR = "Operator"; + public static final String DELIMITER = "Delimiter"; + public static final String TYPE = "Type"; + public static final String IDENTIFIER = "Identifier"; + public static final String TEXT = "Text"; + public static final String DEBUG_TOKEN = "Debug Token"; + public static final String DEBUG_TOKEN_ALPHA = "Debug Token Alpha"; + public static final String DEBUG_TOKEN_OUTLINE = "Debug Token Outline"; + public static final String DEBUG_TOKEN_OUTLINE_ALPHA = "Debug Token Outline Alpha"; private UiConfig() { } @@ -45,23 +101,39 @@ public static void save() { } public static String getLanguage() { - return ui.data().section("General").setIfAbsentString("Language", I18n.DEFAULT_LANGUAGE); + return ui.data().section(GENERAL).setIfAbsentString(LANGUAGE, I18n.DEFAULT_LANGUAGE); } public static void setLanguage(String language) { - ui.data().section("General").setString("Language", language); + ui.data().section(GENERAL).setString(LANGUAGE, language); } public static float getScaleFactor() { - return (float) swing.data().section("General").setIfAbsentDouble("Scale Factor", 1.0); + return (float) swing.data().section(GENERAL).setIfAbsentDouble(SCALE_FACTOR, 1.0); } public static float getActiveScaleFactor() { - return (float) runningSwing.section("General").setIfAbsentDouble("Scale Factor", 1.0); + return (float) runningSwing.section(GENERAL).setIfAbsentDouble(SCALE_FACTOR, 1.0); } public static void setScaleFactor(float scale) { - swing.data().section("General").setDouble("Scale Factor", scale); + swing.data().section(GENERAL).setDouble(SCALE_FACTOR, scale); + } + + public static void setSelectedRightPanel(String id) { + swing.data().section(GENERAL).setString(RIGHT_PANEL, id); + } + + public static String getSelectedRightPanel() { + return swing.data().section(GENERAL).setIfAbsentString(RIGHT_PANEL, RightPanel.DEFAULT); + } + + public static void setRightPanelDividerLocation(String id, int width) { + swing.data().section(RIGHT_PANEL_DIVIDER_LOCATIONS).setInt(id, width); + } + + public static int getRightPanelDividerLocation(String id, int defaultLocation) { + return swing.data().section(RIGHT_PANEL_DIVIDER_LOCATIONS).setIfAbsentInt(id, defaultLocation); } /** @@ -72,37 +144,36 @@ public static void setScaleFactor(float scale) { *
  • [0] - The height of the obfuscated classes panel
  • *
  • [1] - The width of the classes panel
  • *
  • [2] - The width of the center panel
  • - *
  • [3] - The height of the tabs panel. Only used if the logs panel should appear
  • * * - * @return an integer array composed of these 4 dimensions + * @return an integer array composed of these 3 dimensions */ public static int[] getLayout() { - return swing.data().section("Main Window").getIntArray("Layout").orElseGet(() -> new int[] { -1, -1, -1, -1 }); + return swing.data().section(MAIN_WINDOW).getIntArray(LAYOUT).orElseGet(() -> new int[] { -1, -1, -1 }); } - public static void setLayout(int leftV, int left, int right, int rightH) { - swing.data().section("Main Window").setIntArray("Layout", new int[] { leftV, left, right, rightH }); + public static void setLayout(int leftV, int left, int right) { + swing.data().section(MAIN_WINDOW).setIntArray(LAYOUT, new int[] { leftV, left, right }); } public static LookAndFeel getLookAndFeel() { - return swing.data().section("Themes").setIfAbsentEnum(LookAndFeel::valueOf, "Current", LookAndFeel.NONE); + return swing.data().section(THEMES).setIfAbsentEnum(LookAndFeel::valueOf, CURRENT, LookAndFeel.NONE); } public static LookAndFeel getActiveLookAndFeel() { - return runningSwing.section("Themes").setIfAbsentEnum(LookAndFeel::valueOf, "Current", LookAndFeel.NONE); + return runningSwing.section(THEMES).setIfAbsentEnum(LookAndFeel::valueOf, CURRENT, LookAndFeel.NONE); } public static void setLookAndFeel(LookAndFeel laf) { - swing.data().section("Themes").setEnum("Current", laf); + swing.data().section(THEMES).setEnum(CURRENT, laf); } public static Decompiler getDecompiler() { - return ui.data().section("Decompiler").setIfAbsentEnum(Decompiler::valueOf, "Current", Decompiler.CFR); + return ui.data().section(DECOMPILER).setIfAbsentEnum(Decompiler::valueOf, CURRENT, Decompiler.CFR); } public static void setDecompiler(Decompiler d) { - ui.data().section("Decompiler").setEnum("Current", d); + ui.data().section(DECOMPILER).setEnum(CURRENT, d); } private static Color fromComponents(int rgb, double alpha) { @@ -111,12 +182,12 @@ private static Color fromComponents(int rgb, double alpha) { } private static Color getThemeColorRgba(String colorName) { - ConfigSection s = runningSwing.section("Themes").section(getActiveLookAndFeel().name()).section("Colors"); + ConfigSection s = runningSwing.section(THEMES).section(getActiveLookAndFeel().name()).section(COLORS); return fromComponents(s.getRgbColor(colorName).orElse(0), s.getDouble(String.format("%s Alpha", colorName)).orElse(0)); } private static Color getThemeColorRgb(String colorName) { - ConfigSection s = runningSwing.section("Themes").section(getActiveLookAndFeel().name()).section("Colors"); + ConfigSection s = runningSwing.section(THEMES).section(getActiveLookAndFeel().name()).section(COLORS); return new Color(s.getRgbColor(colorName).orElse(0)); } @@ -209,66 +280,66 @@ public static Color getLineNumbersSelectedColor() { } public static boolean useCustomFonts() { - return swing.data().section("Themes").section(getActiveLookAndFeel().name()).section("Fonts").setIfAbsentBool("Use Custom", false); + return swing.data().section(THEMES).section(getActiveLookAndFeel().name()).section(FONTS).setIfAbsentBool(USE_CUSTOM, false); } public static boolean activeUseCustomFonts() { - return runningSwing.section("Themes").section(getActiveLookAndFeel().name()).section("Fonts").setIfAbsentBool("Use Custom", false); + return runningSwing.section(THEMES).section(getActiveLookAndFeel().name()).section(FONTS).setIfAbsentBool(USE_CUSTOM, false); } public static void setUseCustomFonts(boolean b) { - swing.data().section("Themes").section(getActiveLookAndFeel().name()).section("Fonts").setBool("Use Custom", b); + swing.data().section(THEMES).section(getActiveLookAndFeel().name()).section(FONTS).setBool(USE_CUSTOM, b); } public static Optional getFont(String name) { - Optional spec = swing.data().section("Themes").section(getActiveLookAndFeel().name()).section("Fonts").getString(name); + Optional spec = swing.data().section(THEMES).section(getActiveLookAndFeel().name()).section(FONTS).getString(name); return spec.map(Font::decode); } public static Optional getActiveFont(String name) { - Optional spec = runningSwing.section("Themes").section(getActiveLookAndFeel().name()).section("Fonts").getString(name); + Optional spec = runningSwing.section(THEMES).section(getActiveLookAndFeel().name()).section(FONTS).getString(name); return spec.map(Font::decode); } public static void setFont(String name, Font font) { - swing.data().section("Themes").section(getLookAndFeel().name()).section("Fonts").setString(name, encodeFont(font)); + swing.data().section(THEMES).section(getLookAndFeel().name()).section(FONTS).setString(name, encodeFont(font)); } public static Font getDefaultFont() { - return getActiveFont("Default").orElseGet(() -> ScaleUtil.scaleFont(Font.decode(Font.DIALOG).deriveFont(Font.BOLD))); + return getActiveFont(DEFAULT).orElseGet(() -> ScaleUtil.scaleFont(Font.decode(Font.DIALOG).deriveFont(Font.BOLD))); } public static void setDefaultFont(Font font) { - setFont("Default", font); + setFont(DEFAULT, font); } public static Font getDefault2Font() { - return getActiveFont("Default 2").orElseGet(() -> ScaleUtil.scaleFont(Font.decode(Font.DIALOG))); + return getActiveFont(DEFAULT_2).orElseGet(() -> ScaleUtil.scaleFont(Font.decode(Font.DIALOG))); } public static void setDefault2Font(Font font) { - setFont("Default 2", font); + setFont(DEFAULT_2, font); } public static Font getSmallFont() { - return getActiveFont("Small").orElseGet(() -> ScaleUtil.scaleFont(Font.decode(Font.DIALOG))); + return getActiveFont(SMALL).orElseGet(() -> ScaleUtil.scaleFont(Font.decode(Font.DIALOG))); } public static void setSmallFont(Font font) { - setFont("Small", font); + setFont(SMALL, font); } public static Font getEditorFont() { - return getActiveFont("Editor").orElseGet(UiConfig::getFallbackEditorFont); + return getActiveFont(EDITOR).orElseGet(UiConfig::getFallbackEditorFont); } public static void setEditorFont(Font font) { - setFont("Editor", font); + setFont(EDITOR, font); } /** * Gets the fallback editor font. - * It is used + * It is used: *
      *
    • when there is no custom editor font chosen
    • *
    • when custom fonts are disabled
    • @@ -282,7 +353,13 @@ public static Font getFallbackEditorFont() { public static String encodeFont(Font font) { int style = font.getStyle(); - String s = style == (Font.BOLD | Font.ITALIC) ? "bolditalic" : style == Font.ITALIC ? "italic" : style == Font.BOLD ? "bold" : "plain"; + String s = switch (style) { + case Font.BOLD | Font.ITALIC -> "bolditalic"; + case Font.BOLD -> "bold"; + case Font.ITALIC -> "italic"; + default -> "plain"; + }; + return String.format("%s-%s-%s", font.getName(), s, font.getSize()); } @@ -333,98 +410,105 @@ public static void setWindowPos(String window, Point rect) { } public static String getLastSelectedDir() { - return swing.data().section("File Dialog").getString("Selected").orElse(""); + return swing.data().section(FILE_DIALOG).getString(SELECTED).orElse(""); } public static void setLastSelectedDir(String directory) { - swing.data().section("File Dialog").setString("Selected", directory); + swing.data().section(FILE_DIALOG).setString(SELECTED, directory); } public static String getLastTopLevelPackage() { - return swing.data().section("Mapping Stats").getString("Top-Level Package").orElse(""); + return swing.data().section(MAPPING_STATS).getString(TOP_LEVEL_PACKAGE).orElse(""); } public static void setLastTopLevelPackage(String topLevelPackage) { - swing.data().section("Mapping Stats").setString("Top-Level Package", topLevelPackage); + swing.data().section(MAPPING_STATS).setString(TOP_LEVEL_PACKAGE, topLevelPackage); } public static boolean shouldIncludeSyntheticParameters() { - return swing.data().section("Mapping Stats").setIfAbsentBool("Synthetic Parameters", false); + return swing.data().section(MAPPING_STATS).setIfAbsentBool(SYNTHETIC_PARAMETERS, false); } public static void setIncludeSyntheticParameters(boolean b) { - swing.data().section("Mapping Stats").setBool("Synthetic Parameters", b); + swing.data().section(MAPPING_STATS).setBool(SYNTHETIC_PARAMETERS, b); } public static void setLookAndFeelDefaults(LookAndFeel laf, boolean isDark) { - ConfigSection s = swing.data().section("Themes").section(laf.name()).section("Colors"); + ConfigSection s = swing.data().section(THEMES).section(laf.name()).section(COLORS); if (!isDark) { // Defaults found here: https://github.com/Sciss/SyntaxPane/blob/122da367ff7a5d31627a70c62a48a9f0f4f85a0a/src/main/resources/de/sciss/syntaxpane/defaultsyntaxkit/config.properties#L139 - s.setIfAbsentRgbColor("Line Numbers Foreground", 0x333300); - s.setIfAbsentRgbColor("Line Numbers Background", 0xEEEEFF); - s.setIfAbsentRgbColor("Line Numbers Selected", 0xCCCCEE); - s.setIfAbsentRgbColor("Obfuscated", 0xFFDCDC); - s.setIfAbsentDouble("Obfuscated Alpha", 1.0); - s.setIfAbsentRgbColor("Obfuscated Outline", 0xA05050); - s.setIfAbsentDouble("Obfuscated Outline Alpha", 1.0); - s.setIfAbsentRgbColor("Proposed", 0x000000); - s.setIfAbsentDouble("Proposed Alpha", 0.15); - s.setIfAbsentRgbColor("Proposed Outline", 0x000000); - s.setIfAbsentDouble("Proposed Outline Alpha", 0.75); - s.setIfAbsentRgbColor("Deobfuscated", 0xDCFFDC); - s.setIfAbsentDouble("Deobfuscated Alpha", 1.0); - s.setIfAbsentRgbColor("Deobfuscated Outline", 0x50A050); - s.setIfAbsentDouble("Deobfuscated Outline Alpha", 1.0); - s.setIfAbsentRgbColor("Editor Background", 0xFFFFFF); - s.setIfAbsentRgbColor("Highlight", 0x3333EE); - s.setIfAbsentRgbColor("Caret", 0x000000); - s.setIfAbsentRgbColor("Selection Highlight", 0x000000); - s.setIfAbsentRgbColor("String", 0xCC6600); - s.setIfAbsentRgbColor("Number", 0x999933); - s.setIfAbsentRgbColor("Operator", 0x000000); - s.setIfAbsentRgbColor("Delimiter", 0x000000); - s.setIfAbsentRgbColor("Type", 0x000000); - s.setIfAbsentRgbColor("Identifier", 0x000000); - s.setIfAbsentRgbColor("Text", 0x000000); - - s.setIfAbsentRgbColor("Debug Token", 0xD9BEF9); - s.setIfAbsentDouble("Debug Token Alpha", 1.0); - s.setIfAbsentRgbColor("Debug Token Outline", 0xBD93F9); - s.setIfAbsentDouble("Debug Token Outline Alpha", 1.0); + s.setIfAbsentRgbColor(LINE_NUMBERS_FOREGROUND, 0x333300); + s.setIfAbsentRgbColor(LINE_NUMBERS_BACKGROUND, 0xEEEEFF); + s.setIfAbsentRgbColor(LINE_NUMBERS_SELECTED, 0xCCCCEE); + + s.setIfAbsentRgbColor(OBFUSCATED, 0xFFDCDC); + s.setIfAbsentDouble(OBFUSCATED_ALPHA, 1.0); + s.setIfAbsentRgbColor(OBFUSCATED_OUTLINE, 0xA05050); + s.setIfAbsentDouble(OBFUSCATED_OUTLINE_ALPHA, 1.0); + + s.setIfAbsentRgbColor(PROPOSED, 0x000000); + s.setIfAbsentDouble(PROPOSED_ALPHA, 0.15); + s.setIfAbsentRgbColor(PROPOSED_OUTLINE, 0x000000); + s.setIfAbsentDouble(PROPOSED_OUTLINE_ALPHA, 0.75); + + s.setIfAbsentRgbColor(DEOBFUSCATED, 0xDCFFDC); + s.setIfAbsentDouble(DEOBFUSCATED_ALPHA, 1.0); + s.setIfAbsentRgbColor(DEOBFUSCATED_OUTLINE, 0x50A050); + s.setIfAbsentDouble(DEOBFUSCATED_OUTLINE_ALPHA, 1.0); + + s.setIfAbsentRgbColor(EDITOR_BACKGROUND, 0xFFFFFF); + s.setIfAbsentRgbColor(HIGHLIGHT, 0x3333EE); + s.setIfAbsentRgbColor(CARET, 0x000000); + s.setIfAbsentRgbColor(SELECTION_HIGHLIGHT, 0x000000); + s.setIfAbsentRgbColor(STRING, 0xCC6600); + s.setIfAbsentRgbColor(NUMBER, 0x999933); + s.setIfAbsentRgbColor(OPERATOR, 0x000000); + s.setIfAbsentRgbColor(DELIMITER, 0x000000); + s.setIfAbsentRgbColor(TYPE, 0x000000); + s.setIfAbsentRgbColor(IDENTIFIER, 0x000000); + s.setIfAbsentRgbColor(TEXT, 0x000000); + + s.setIfAbsentRgbColor(DEBUG_TOKEN, 0xD9BEF9); + s.setIfAbsentDouble(DEBUG_TOKEN_ALPHA, 1.0); + s.setIfAbsentRgbColor(DEBUG_TOKEN_OUTLINE, 0xBD93F9); + s.setIfAbsentDouble(DEBUG_TOKEN_OUTLINE_ALPHA, 1.0); } else { // Based off colors found here: https://github.com/dracula/dracula-theme/ - s.setIfAbsentRgbColor("Line Numbers Foreground", 0xA4A4A3); - s.setIfAbsentRgbColor("Line Numbers Background", 0x313335); - s.setIfAbsentRgbColor("Line Numbers Selected", 0x606366); - s.setIfAbsentRgbColor("Obfuscated", 0xFF5555); - s.setIfAbsentDouble("Obfuscated Alpha", 0.3); - s.setIfAbsentRgbColor("Obfuscated Outline", 0xFF5555); - s.setIfAbsentDouble("Obfuscated Outline Alpha", 0.5); - s.setIfAbsentRgbColor("Proposed", 0x606366); - s.setIfAbsentDouble("Proposed Alpha", 0.3); - s.setIfAbsentRgbColor("Proposed Outline", 0x606366); - s.setIfAbsentDouble("Proposed Outline Alpha", 0.5); - s.setIfAbsentRgbColor("Deobfuscated", 0x50FA7B); - s.setIfAbsentDouble("Deobfuscated Alpha", 0.3); - s.setIfAbsentRgbColor("Deobfuscated Outline", 0x50FA7B); - s.setIfAbsentDouble("Deobfuscated Outline Alpha", 0.5); - s.setIfAbsentRgbColor("Editor Background", 0x282A36); - s.setIfAbsentRgbColor("Highlight", 0xFF79C6); - s.setIfAbsentRgbColor("Caret", 0xF8F8F2); - s.setIfAbsentRgbColor("Selection Highlight", 0xF8F8F2); - s.setIfAbsentRgbColor("String", 0xF1FA8C); - s.setIfAbsentRgbColor("Number", 0xBD93F9); - s.setIfAbsentRgbColor("Operator", 0xF8F8F2); - s.setIfAbsentRgbColor("Delimiter", 0xF8F8F2); - s.setIfAbsentRgbColor("Type", 0xF8F8F2); - s.setIfAbsentRgbColor("Identifier", 0xF8F8F2); - s.setIfAbsentRgbColor("Text", 0xF8F8F2); - - s.setIfAbsentRgbColor("Debug Token", 0x4B1370); - s.setIfAbsentDouble("Debug Token Alpha", 0.5); - s.setIfAbsentRgbColor("Debug Token Outline", 0x701367); - s.setIfAbsentDouble("Debug Token Outline Alpha", 0.5); + s.setIfAbsentRgbColor(LINE_NUMBERS_FOREGROUND, 0xA4A4A3); + s.setIfAbsentRgbColor(LINE_NUMBERS_BACKGROUND, 0x313335); + s.setIfAbsentRgbColor(LINE_NUMBERS_SELECTED, 0x606366); + + s.setIfAbsentRgbColor(OBFUSCATED, 0xFF5555); + s.setIfAbsentDouble(OBFUSCATED_ALPHA, 0.3); + s.setIfAbsentRgbColor(OBFUSCATED_OUTLINE, 0xFF5555); + s.setIfAbsentDouble(OBFUSCATED_OUTLINE_ALPHA, 0.5); + + s.setIfAbsentRgbColor(PROPOSED, 0x606366); + s.setIfAbsentDouble(PROPOSED_ALPHA, 0.3); + s.setIfAbsentRgbColor(PROPOSED_OUTLINE, 0x606366); + s.setIfAbsentDouble(PROPOSED_OUTLINE_ALPHA, 0.5); + + s.setIfAbsentRgbColor(DEOBFUSCATED, 0x50FA7B); + s.setIfAbsentDouble(DEOBFUSCATED_ALPHA, 0.3); + s.setIfAbsentRgbColor(DEOBFUSCATED_OUTLINE, 0x50FA7B); + s.setIfAbsentDouble(DEOBFUSCATED_OUTLINE_ALPHA, 0.5); + + s.setIfAbsentRgbColor(EDITOR_BACKGROUND, 0x282A36); + s.setIfAbsentRgbColor(HIGHLIGHT, 0xFF79C6); + s.setIfAbsentRgbColor(CARET, 0xF8F8F2); + s.setIfAbsentRgbColor(SELECTION_HIGHLIGHT, 0xF8F8F2); + s.setIfAbsentRgbColor(STRING, 0xF1FA8C); + s.setIfAbsentRgbColor(NUMBER, 0xBD93F9); + s.setIfAbsentRgbColor(OPERATOR, 0xF8F8F2); + s.setIfAbsentRgbColor(DELIMITER, 0xF8F8F2); + s.setIfAbsentRgbColor(TYPE, 0xF8F8F2); + s.setIfAbsentRgbColor(IDENTIFIER, 0xF8F8F2); + s.setIfAbsentRgbColor(TEXT, 0xF8F8F2); + + s.setIfAbsentRgbColor(DEBUG_TOKEN, 0x4B1370); + s.setIfAbsentDouble(DEBUG_TOKEN_ALPHA, 0.5); + s.setIfAbsentRgbColor(DEBUG_TOKEN_OUTLINE, 0x701367); + s.setIfAbsentDouble(DEBUG_TOKEN_OUTLINE_ALPHA, 0.5); } } - } diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/CollapsibleTabbedPane.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/CollapsibleTabbedPane.java deleted file mode 100644 index fb497b11b..000000000 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/CollapsibleTabbedPane.java +++ /dev/null @@ -1,40 +0,0 @@ -package cuchaz.enigma.gui.elements; - -import java.awt.event.MouseEvent; - -import javax.swing.JTabbedPane; - -public class CollapsibleTabbedPane extends JTabbedPane { - - public CollapsibleTabbedPane() { - } - - public CollapsibleTabbedPane(int tabPlacement) { - super(tabPlacement); - } - - public CollapsibleTabbedPane(int tabPlacement, int tabLayoutPolicy) { - super(tabPlacement, tabLayoutPolicy); - } - - @Override - protected void processMouseEvent(MouseEvent e) { - int id = e.getID(); - if (id == MouseEvent.MOUSE_PRESSED) { - if (!isEnabled()) return; - int tabIndex = getUI().tabForCoordinate(this, e.getX(), e.getY()); - if (tabIndex >= 0 && isEnabledAt(tabIndex)) { - if (tabIndex == getSelectedIndex()) { - if (isFocusOwner() && isRequestFocusEnabled()) { - requestFocus(); - } else { - setSelectedIndex(-1); - } - return; - } - } - } - super.processMouseEvent(e); - } - -} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/EditorTabbedPane.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/EditorTabbedPane.java index aa22abdb0..e73df7c05 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/EditorTabbedPane.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/EditorTabbedPane.java @@ -17,6 +17,7 @@ import cuchaz.enigma.gui.events.EditorActionListener; import cuchaz.enigma.gui.panels.ClosableTabTitlePane; import cuchaz.enigma.gui.panels.EditorPanel; +import cuchaz.enigma.gui.panels.right.RightPanel; import cuchaz.enigma.gui.util.GuiUtil; import cuchaz.enigma.translation.representation.entry.ClassEntry; import cuchaz.enigma.translation.representation.entry.Entry; @@ -80,7 +81,7 @@ public void onTitleChanged(EditorPanel editor, String title) { if (editorPanel != null && activeEditor != editorPanel) { this.openFiles.setSelectedComponent(this.editors.get(entry).getUi()); - this.gui.showStructure(editorPanel); + this.gui.updateStructure(editorPanel); } return editorPanel; @@ -89,7 +90,7 @@ public void onTitleChanged(EditorPanel editor, String title) { public void closeEditor(EditorPanel ed) { this.openFiles.remove(ed.getUi()); this.editors.inverse().remove(ed); - this.gui.showStructure(this.getActiveEditor()); + this.gui.updateStructure(this.getActiveEditor()); ed.destroy(); } diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/JMultiLineToolTip.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/JMultiLineToolTip.java index 533d1b30f..b18d445b6 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/JMultiLineToolTip.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/JMultiLineToolTip.java @@ -3,6 +3,7 @@ import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; +import java.io.Serial; import javax.swing.CellRendererPane; import javax.swing.JComponent; @@ -13,18 +14,20 @@ /** * Implements a multi line tooltip for GUI components - * Copied from http://www.codeguru.com/java/articles/122.shtml + * Copied from this CodeGuru article * * @author Zafir Anjum */ public class JMultiLineToolTip extends JToolTip { + @Serial private static final long serialVersionUID = 7813662474312183098L; public JMultiLineToolTip() { updateUI(); } + @Override public void updateUI() { setUI(MultiLineToolTipUI.createUI(this)); } @@ -129,4 +132,4 @@ public Dimension getMinimumSize(JComponent c) { public Dimension getMaximumSize(JComponent c) { return getPreferredSize(c); } -} \ No newline at end of file +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MainWindow.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MainWindow.java index 3330948a4..a3d553653 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MainWindow.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MainWindow.java @@ -1,11 +1,17 @@ package cuchaz.enigma.gui.elements; +import cuchaz.enigma.gui.panels.right.RightPanel; +import cuchaz.enigma.gui.panels.right.RightAngleLayerUI; + import java.awt.BorderLayout; import java.awt.Container; +import java.awt.FlowLayout; import javax.swing.JFrame; +import javax.swing.JLayer; import javax.swing.JMenuBar; import javax.swing.JPanel; +import javax.swing.JToggleButton; public class MainWindow { private final JFrame frame; @@ -14,7 +20,26 @@ public class MainWindow { private final JMenuBar menuBar = new JMenuBar(); private final StatusBar statusBar = new StatusBar(); + private final JPanel topRightPanelSelector; + private final JPanel bottomRightPanelSelector; + public MainWindow(String title) { + JPanel rightPanelSelector = new JPanel(); + rightPanelSelector.setLayout(new BorderLayout()); + + // create separate panels for top and bottom button groups + // this is necessary because flow layout doesn't support using multiple alignments + this.topRightPanelSelector = new JPanel(); + this.topRightPanelSelector.setLayout(new FlowLayout(FlowLayout.LEFT)); + this.bottomRightPanelSelector = new JPanel(); + this.bottomRightPanelSelector.setLayout(new FlowLayout(FlowLayout.RIGHT)); + + // set up button groups + rightPanelSelector.add(this.topRightPanelSelector, BorderLayout.WEST); + rightPanelSelector.add(this.bottomRightPanelSelector, BorderLayout.EAST); + JLayer layer = new JLayer<>(rightPanelSelector); + layer.setUI(new RightAngleLayerUI(RightAngleLayerUI.Rotation.CLOCKWISE)); + this.frame = new JFrame(title); this.frame.setJMenuBar(this.menuBar); @@ -22,25 +47,42 @@ public MainWindow(String title) { contentPane.setLayout(new BorderLayout()); contentPane.add(this.workArea, BorderLayout.CENTER); contentPane.add(this.statusBar.getUi(), BorderLayout.SOUTH); + contentPane.add(layer, BorderLayout.EAST); + } + + public void updateRightPanelSelector() { + this.topRightPanelSelector.removeAll(); + this.bottomRightPanelSelector.removeAll(); + + // create buttons from right panel options + for (RightPanel panel : RightPanel.getRightPanels().values()) { + JToggleButton button = panel.getButton(); + + if (panel.getButtonPosition().equals(RightPanel.ButtonPosition.TOP)) { + this.topRightPanelSelector.add(button); + } else { + this.bottomRightPanelSelector.add(button); + } + } } public void setVisible(boolean visible) { this.frame.setVisible(visible); } - public JMenuBar menuBar() { + public JMenuBar getMenuBar() { return this.menuBar; } - public StatusBar statusBar() { + public StatusBar getStatusBar() { return this.statusBar; } - public Container workArea() { + public Container getWorkArea() { return this.workArea; } - public JFrame frame() { + public JFrame getFrame() { return this.frame; } diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java index cf1a42a75..a35ee8c18 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java @@ -75,7 +75,7 @@ public class MenuBar { public MenuBar(Gui gui) { this.gui = gui; - JMenuBar ui = gui.getMainWindow().menuBar(); + JMenuBar ui = gui.getMainWindow().getMenuBar(); this.retranslateUi(); diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/EditorPanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/EditorPanel.java index 1793c56cd..6e494eeb2 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/EditorPanel.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/EditorPanel.java @@ -498,8 +498,6 @@ public void showReference(EntryReference, Entry> reference) { /** * Navigates to the reference without modifying history. Assumes the class is loaded. - * - * @param reference */ private void showReference0(EntryReference, Entry> reference) { if (this.source == null) return; diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/ObfPanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/ObfPanel.java index 7783843d7..1f76f8c73 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/ObfPanel.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/ObfPanel.java @@ -13,15 +13,10 @@ import cuchaz.enigma.utils.I18n; public class ObfPanel extends JPanel { - public final ClassSelector obfClasses; private final JLabel title = new JLabel(); - private final Gui gui; - public ObfPanel(Gui gui) { - this.gui = gui; - Comparator obfClassComparator = (a, b) -> { String aname = a.getFullName(); String bname = b.getFullName(); @@ -45,5 +40,4 @@ public ObfPanel(Gui gui) { public void retranslateUi() { this.title.setText(I18n.translate("info_panel.classes.obfuscated")); } - } diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/StructurePanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/StructurePanel.java deleted file mode 100644 index ffec5c281..000000000 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/StructurePanel.java +++ /dev/null @@ -1,179 +0,0 @@ -package cuchaz.enigma.gui.panels; - -import java.awt.*; -import java.awt.event.KeyEvent; -import java.awt.event.MouseEvent; - -import javax.swing.*; -import javax.swing.tree.DefaultTreeCellRenderer; -import javax.swing.tree.DefaultTreeModel; -import javax.swing.tree.TreeNode; -import javax.swing.tree.TreePath; - -import cuchaz.enigma.analysis.StructureTreeNode; -import cuchaz.enigma.analysis.StructureTreeOptions; -import cuchaz.enigma.gui.Gui; -import cuchaz.enigma.gui.config.keybind.KeyBinds; -import cuchaz.enigma.gui.renderer.StructureOptionListCellRenderer; -import cuchaz.enigma.gui.util.GridBagConstraintsBuilder; -import cuchaz.enigma.gui.util.GuiUtil; -import cuchaz.enigma.gui.util.SingleTreeSelectionModel; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import cuchaz.enigma.translation.representation.entry.FieldEntry; -import cuchaz.enigma.translation.representation.entry.MethodEntry; -import cuchaz.enigma.translation.representation.entry.ParentedEntry; -import cuchaz.enigma.utils.I18n; - -public class StructurePanel { - private final Gui gui; - - private final JPanel panel = new JPanel(new BorderLayout()); - - private final JPanel optionsPanel; - - private final JLabel obfuscationVisibilityLabel = new JLabel(); - private final JLabel documentationVisibilityLabel = new JLabel(); - private final JLabel sortingOrderLabel = new JLabel(); - - private final JComboBox obfuscationVisibility; - private final JComboBox documentationVisibility; - private final JComboBox sortingOrder; - - private final JTree structureTree; - - public StructurePanel(Gui gui) { - this.gui = gui; - - this.optionsPanel = new JPanel(new GridBagLayout()); - this.optionsPanel.setVisible(false); - - GridBagConstraintsBuilder cb = GridBagConstraintsBuilder.create().insets(5).fill(GridBagConstraints.HORIZONTAL); - - this.optionsPanel.add(this.obfuscationVisibilityLabel, cb.pos(0, 0).build()); - this.obfuscationVisibility = new JComboBox<>(StructureTreeOptions.ObfuscationVisibility.values()); - this.obfuscationVisibility.setRenderer(new StructureOptionListCellRenderer()); - this.obfuscationVisibility.addActionListener(event -> this.showStructure(gui.getActiveEditor())); - this.optionsPanel.add(this.obfuscationVisibility, cb.pos(1, 0).build()); - - this.optionsPanel.add(this.documentationVisibilityLabel, cb.pos(0, 1).build()); - this.documentationVisibility = new JComboBox<>(StructureTreeOptions.DocumentationVisibility.values()); - this.documentationVisibility.setRenderer(new StructureOptionListCellRenderer()); - this.documentationVisibility.addActionListener(event -> this.showStructure(gui.getActiveEditor())); - this.optionsPanel.add(this.documentationVisibility, cb.pos(1, 1).build()); - - this.optionsPanel.add(this.sortingOrderLabel, cb.pos(0, 2).build()); - this.sortingOrder = new JComboBox<>(StructureTreeOptions.SortingOrder.values()); - this.sortingOrder.setRenderer(new StructureOptionListCellRenderer()); - this.sortingOrder.addActionListener(event -> this.showStructure(gui.getActiveEditor())); - this.optionsPanel.add(this.sortingOrder, cb.pos(1, 2).build()); - - this.structureTree = new JTree(); - this.structureTree.setModel(null); - this.structureTree.setCellRenderer(new StructureTreeCellRenderer(gui)); - this.structureTree.setSelectionModel(new SingleTreeSelectionModel()); - this.structureTree.setShowsRootHandles(true); - this.structureTree.addKeyListener(GuiUtil.onKeyPress(this::onKeyPress)); - this.structureTree.addMouseListener(GuiUtil.onMouseClick(this::onClick)); - - this.retranslateUi(); - - this.panel.add(this.optionsPanel, BorderLayout.NORTH); - this.panel.add(new JScrollPane(this.structureTree)); - } - - public void showStructure(EditorPanel editor) { - structureTree.setModel(null); - - if (editor == null) { - this.optionsPanel.setVisible(false); - return; - } - - ClassEntry classEntry = editor.getClassHandle().getRef(); - if (classEntry == null) return; - - this.optionsPanel.setVisible(true); - - // get the class structure - StructureTreeNode node = this.gui.getController().getClassStructure(classEntry, this.getOptions()); - - // show the tree at the root - TreePath path = GuiUtil.getPathToRoot(node); - structureTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); - structureTree.expandPath(path); - structureTree.setSelectionRow(structureTree.getRowForPath(path)); - } - - private void onKeyPress(KeyEvent e) { - if (KeyBinds.SELECT.matches(e)) { - navigateToSelectedNode(); - } - } - - private void onClick(MouseEvent event) { - if (event.getClickCount() >= 2 && event.getButton() == MouseEvent.BUTTON1) { - navigateToSelectedNode(); - } - } - - private void navigateToSelectedNode() { - // get the selected node - TreePath path = structureTree.getSelectionPath(); - if (path == null) { - return; - } - - Object node = path.getLastPathComponent(); - - if (node instanceof StructureTreeNode) { - this.gui.getController().navigateTo(((StructureTreeNode) node).getEntry()); - } - } - - /** - * Creates and returns the options of this structure panel. - */ - private StructureTreeOptions getOptions() { - return new StructureTreeOptions( - (StructureTreeOptions.ObfuscationVisibility) this.obfuscationVisibility.getSelectedItem(), - (StructureTreeOptions.DocumentationVisibility) this.documentationVisibility.getSelectedItem(), - (StructureTreeOptions.SortingOrder) this.sortingOrder.getSelectedItem() - ); - } - - public void retranslateUi() { - this.obfuscationVisibilityLabel.setText(I18n.translate("structure.options.obfuscation")); - this.documentationVisibilityLabel.setText(I18n.translate("structure.options.documentation")); - this.sortingOrderLabel.setText(I18n.translate("structure.options.sorting")); - } - - public JPanel getPanel() { - return this.panel; - } - - private static class StructureTreeCellRenderer extends DefaultTreeCellRenderer { - private final Gui gui; - - StructureTreeCellRenderer(Gui gui) { - this.gui = gui; - } - - @Override - public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { - Component c = super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus); - ParentedEntry entry = ((StructureTreeNode) value).getEntry(); - - if (entry instanceof ClassEntry classEntry) { - this.setIcon(GuiUtil.getClassIcon(gui, classEntry)); - } else if (entry instanceof MethodEntry methodEntry) { - this.setIcon(GuiUtil.getMethodIcon(methodEntry)); - } else if (entry instanceof FieldEntry) { - this.setIcon(GuiUtil.FIELD_ICON); - } - - this.setText("" + ((StructureTreeNode) value).toHtml()); - - return c; - } - } -} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/AbstractInheritanceTree.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/AbstractInheritanceTree.java similarity index 63% rename from enigma-swing/src/main/java/cuchaz/enigma/gui/elements/AbstractInheritanceTree.java rename to enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/AbstractInheritanceTree.java index 67cb6cd0d..765532677 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/AbstractInheritanceTree.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/AbstractInheritanceTree.java @@ -1,32 +1,27 @@ -package cuchaz.enigma.gui.elements; - -import java.awt.BorderLayout; -import java.awt.event.MouseEvent; - -import javax.annotation.Nullable; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTree; -import javax.swing.tree.*; +package cuchaz.enigma.gui.panels.right; import cuchaz.enigma.analysis.AbstractClassTreeNode; import cuchaz.enigma.analysis.AbstractMethodTreeNode; -import cuchaz.enigma.analysis.MethodInheritanceTreeNode; import cuchaz.enigma.gui.Gui; import cuchaz.enigma.gui.util.GuiUtil; import cuchaz.enigma.gui.util.SingleTreeSelectionModel; -import cuchaz.enigma.translation.representation.entry.ClassEntry; import cuchaz.enigma.translation.representation.entry.Entry; -public abstract class AbstractInheritanceTree { - private final JPanel panel = new JPanel(new BorderLayout()); +import javax.annotation.Nullable; +import javax.swing.JScrollPane; +import javax.swing.JTree; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreeCellRenderer; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; +import java.awt.event.MouseEvent; +public abstract class AbstractInheritanceTree extends RightPanel { private final JTree tree = new JTree(); - protected final Gui gui; - - public AbstractInheritanceTree(Gui gui, TreeCellRenderer cellRenderer) { - this.gui = gui; + protected AbstractInheritanceTree(Gui gui, TreeCellRenderer cellRenderer) { + super(gui); this.tree.setModel(null); this.tree.setCellRenderer(cellRenderer); @@ -34,7 +29,7 @@ public AbstractInheritanceTree(Gui gui, TreeCellRenderer cellRenderer) { this.tree.setShowsRootHandles(true); this.tree.addMouseListener(GuiUtil.onMouseClick(this::onClick)); - this.panel.add(new JScrollPane(this.tree)); + this.add(new JScrollPane(this.tree)); } private void onClick(MouseEvent event) { @@ -47,11 +42,9 @@ private void onClick(MouseEvent event) { Object node = path.getLastPathComponent(); if (node instanceof AbstractClassTreeNode classNode) { - gui.getController().navigateTo(new ClassEntry(classNode.getClassName())); + gui.getController().navigateTo(classNode.getClassEntry()); } else if (node instanceof AbstractMethodTreeNode methodNode) { - if (!(methodNode instanceof MethodInheritanceTreeNode inheritanceNode) || inheritanceNode.isImplemented()) { - gui.getController().navigateTo(methodNode.getMethodEntry()); - } + gui.getController().navigateTo(methodNode.getMethodEntry()); } } } @@ -69,19 +62,14 @@ public void display(Entry entry) { this.tree.setSelectionRow(this.tree.getRowForPath(path)); } - this.panel.show(); - } - - public void retranslateUi() { - + this.setVisible(true); } @Nullable protected abstract DefaultMutableTreeNode getNodeFor(Entry entry); - protected abstract String getPanelName(); - - public JPanel getPanel() { - return this.panel; + @Override + public ButtonPosition getButtonPosition() { + return ButtonPosition.TOP; } } diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/CallsTree.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/CallsTree.java similarity index 77% rename from enigma-swing/src/main/java/cuchaz/enigma/gui/elements/CallsTree.java rename to enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/CallsTree.java index c92534f0a..f2ca263dc 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/CallsTree.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/CallsTree.java @@ -1,11 +1,15 @@ -package cuchaz.enigma.gui.elements; +package cuchaz.enigma.gui.panels.right; import java.awt.BorderLayout; import java.awt.event.MouseEvent; import java.util.Collection; import java.util.Vector; -import javax.swing.*; +import javax.swing.JList; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTree; +import javax.swing.ListSelectionModel; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; @@ -23,22 +27,17 @@ import cuchaz.enigma.translation.representation.entry.FieldEntry; import cuchaz.enigma.translation.representation.entry.MethodEntry; -public class CallsTree { - private final JPanel panel = new JPanel(new BorderLayout()); - - private final JTree callsTree = new JTree(); +public class CallsTree extends RightPanel { + private final JTree tree = new JTree(); private final JList tokens = new JList<>(); - private final Gui gui; - public CallsTree(Gui gui) { - this.gui = gui; - - this.callsTree.setModel(null); - this.callsTree.setCellRenderer(new CallsTreeCellRenderer(gui)); - this.callsTree.setSelectionModel(new SingleTreeSelectionModel()); - this.callsTree.setShowsRootHandles(true); - this.callsTree.addMouseListener(GuiUtil.onMouseClick(this::onTreeClicked)); + super(gui); + this.tree.setModel(null); + this.tree.setCellRenderer(new CallsTreeCellRenderer(gui)); + this.tree.setSelectionModel(new SingleTreeSelectionModel()); + this.tree.setShowsRootHandles(true); + this.tree.addMouseListener(GuiUtil.onMouseClick(this::onTreeClicked)); this.tokens.setCellRenderer(new TokenListCellRenderer(gui.getController())); this.tokens.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); @@ -50,13 +49,13 @@ public CallsTree(Gui gui) { JSplitPane contentPane = new JSplitPane( JSplitPane.VERTICAL_SPLIT, true, - new JScrollPane(this.callsTree), + new JScrollPane(this.tree), new JScrollPane(this.tokens) ); contentPane.setResizeWeight(1); // let the top side take all the slack contentPane.resetToPreferredSizes(); - this.panel.add(contentPane, BorderLayout.CENTER); + this.add(contentPane, BorderLayout.CENTER); } public void showCalls(Entry entry, boolean recurse) { @@ -70,9 +69,9 @@ public void showCalls(Entry entry, boolean recurse) { node = this.gui.getController().getMethodReferences(methodEntry, recurse); } - this.callsTree.setModel(new DefaultTreeModel(node)); + this.tree.setModel(new DefaultTreeModel(node)); - this.panel.show(); + this.setVisible(true); } public void showTokens(Collection tokens) { @@ -88,7 +87,7 @@ public void clearTokens() { private void onTreeClicked(MouseEvent event) { if (event.getClickCount() >= 2 && event.getButton() == MouseEvent.BUTTON1) { // get the selected node - TreePath path = this.callsTree.getSelectionPath(); + TreePath path = this.tree.getSelectionPath(); if (path == null) { return; @@ -115,11 +114,13 @@ private void onTokenClicked(MouseEvent event) { } } - public void retranslateUi() { - + @Override + public ButtonPosition getButtonPosition() { + return ButtonPosition.TOP; } - public JPanel getPanel() { - return this.panel; + @Override + public String getId() { + return Type.CALLS; } } diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/CollabPanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/CollabPanel.java new file mode 100644 index 000000000..29e9b47cb --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/CollabPanel.java @@ -0,0 +1,158 @@ +package cuchaz.enigma.gui.panels.right; + +import cuchaz.enigma.gui.Gui; +import cuchaz.enigma.network.packet.MessageC2SPacket; +import cuchaz.enigma.utils.I18n; + +import javax.swing.AbstractAction; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.util.function.Supplier; + +public class CollabPanel extends RightPanel { + private final JLabel offlineLabel; + private final JPanel whenOfflinePanel; + private final JPanel whenOnlinePanel; + private final JButton sendPendingMessageButton; + private final JScrollPane messageScrollPane; + private final JTextField pendingMessageBox; + private final JLabel usersTitle; + private final JLabel messagesTitle; + private final JLabel titleCopy; + + private final Supplier offlineTextProvider = () -> I18n.translate("right_panel.collab.offline_text"); + private final Supplier usersTitleProvider = () -> I18n.translate("right_panel.collab.users_title"); + private final Supplier messagesTitleProvider = () -> I18n.translate("right_panel.collab.messages_title"); + private final Supplier sendButtonTextProvider = () -> I18n.translate("right_panel.collab.send"); + + private JPanel panel; + private boolean offline; + + public CollabPanel(Gui gui) { + super(gui); + + // offline panel + this.whenOfflinePanel = new JPanel(new BorderLayout()); + this.offlineLabel = new JLabel(this.offlineTextProvider.get()); + JPanel offlineTopPanel = new JPanel(new BorderLayout()); + + // there are ghosts in my code + this.titleCopy = new JLabel(this.titleProvider.get()); + + offlineTopPanel.add(this.offlineLabel, BorderLayout.SOUTH); + offlineTopPanel.add(this.titleCopy, BorderLayout.NORTH); + this.whenOfflinePanel.add(offlineTopPanel, BorderLayout.NORTH); + + // online panel + this.whenOnlinePanel = new JPanel(new BorderLayout()); + + // top panel : user list + JPanel topPanel = new JPanel(new BorderLayout()); + JPanel userListPanel = new JPanel(new BorderLayout()); + JScrollPane userScrollPane = new JScrollPane(gui.getUsers()); + + this.usersTitle = new JLabel(this.usersTitleProvider.get()); + userListPanel.add(this.usersTitle, BorderLayout.NORTH); + userListPanel.add(userScrollPane, BorderLayout.CENTER); + + topPanel.add(this.title, BorderLayout.NORTH); + topPanel.add(userListPanel, BorderLayout.SOUTH); + + // bottom panel : messages + JPanel bottomPanel = new JPanel(new BorderLayout()); + this.messageScrollPane = new JScrollPane(gui.getMessages()); + this.pendingMessageBox = new JTextField(); + AbstractAction sendListener = new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + sendPendingMessage(); + } + }; + this.pendingMessageBox.addActionListener(sendListener); + this.sendPendingMessageButton = new JButton(this.sendButtonTextProvider.get()); + this.sendPendingMessageButton.setAction(sendListener); + this.messagesTitle = new JLabel(this.messagesTitleProvider.get()); + JPanel chatPanel = new JPanel(new BorderLayout()); + chatPanel.add(this.pendingMessageBox, BorderLayout.CENTER); + chatPanel.add(this.sendPendingMessageButton, BorderLayout.EAST); + + bottomPanel.add(this.messagesTitle, BorderLayout.NORTH); + bottomPanel.add(this.messageScrollPane, BorderLayout.CENTER); + bottomPanel.add(chatPanel, BorderLayout.SOUTH); + + this.whenOnlinePanel.add(topPanel, BorderLayout.NORTH); + this.whenOnlinePanel.add(bottomPanel, BorderLayout.CENTER); + + this.setUp(); + } + + @Override + public ButtonPosition getButtonPosition() { + return ButtonPosition.BOTTOM; + } + + @Override + public String getId() { + return Type.COLLAB; + } + + private void sendPendingMessage() { + // get message + String text = this.pendingMessageBox.getText().trim(); + + // send message, filtering out empty messages + if (!text.isEmpty()) { + this.gui.getController().sendPacket(new MessageC2SPacket(text)); + } + + // clear chat box + this.pendingMessageBox.setText(""); + } + + public JScrollPane getMessageScrollPane() { + return this.messageScrollPane; + } + + @Override + public void retranslateUi() { + super.retranslateUi(); + this.offlineLabel.setText(this.offlineTextProvider.get()); + this.sendPendingMessageButton.setText(this.sendButtonTextProvider.get()); + this.usersTitle.setText(this.usersTitleProvider.get()); + this.messagesTitle.setText(this.messagesTitleProvider.get()); + this.titleCopy.setText(this.titleProvider.get()); + } + + @Override + public void setVisible(boolean visible) { + super.setVisible(visible); + if (this.gui.isOffline() != this.offline) { + this.setUp(); + } + } + + /** + * sets up the panel for its offline or online state + */ + public void setUp() { + if (gui.isOffline() != this.offline) { + this.offline = gui.isOffline(); + if (this.panel != null) { + this.remove(this.panel); + } + + if (this.offline) { + this.panel = this.whenOfflinePanel; + } else { + this.panel = this.whenOnlinePanel; + } + + this.add(this.panel); + } + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/ImplementationsTree.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/ImplementationsTree.java similarity index 84% rename from enigma-swing/src/main/java/cuchaz/enigma/gui/elements/ImplementationsTree.java rename to enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/ImplementationsTree.java index 962cf2730..a07c4233a 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/ImplementationsTree.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/ImplementationsTree.java @@ -1,4 +1,4 @@ -package cuchaz.enigma.gui.elements; +package cuchaz.enigma.gui.panels.right; import javax.annotation.Nullable; import javax.swing.tree.DefaultMutableTreeNode; @@ -8,7 +8,6 @@ import cuchaz.enigma.translation.representation.entry.ClassEntry; import cuchaz.enigma.translation.representation.entry.Entry; import cuchaz.enigma.translation.representation.entry.MethodEntry; -import cuchaz.enigma.utils.I18n; public class ImplementationsTree extends AbstractInheritanceTree { public ImplementationsTree(Gui gui) { @@ -28,7 +27,7 @@ protected DefaultMutableTreeNode getNodeFor(Entry entry) { } @Override - protected String getPanelName() { - return I18n.translate("info_panel.tree.implementations"); + public String getId() { + return Type.IMPLEMENTATIONS; } } diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/InheritanceTree.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/InheritanceTree.java similarity index 84% rename from enigma-swing/src/main/java/cuchaz/enigma/gui/elements/InheritanceTree.java rename to enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/InheritanceTree.java index aeb173bab..db4e8062d 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/InheritanceTree.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/InheritanceTree.java @@ -1,4 +1,4 @@ -package cuchaz.enigma.gui.elements; +package cuchaz.enigma.gui.panels.right; import javax.annotation.Nullable; import javax.swing.tree.DefaultMutableTreeNode; @@ -8,7 +8,6 @@ import cuchaz.enigma.translation.representation.entry.ClassEntry; import cuchaz.enigma.translation.representation.entry.Entry; import cuchaz.enigma.translation.representation.entry.MethodEntry; -import cuchaz.enigma.utils.I18n; public class InheritanceTree extends AbstractInheritanceTree { public InheritanceTree(Gui gui) { @@ -28,7 +27,7 @@ protected DefaultMutableTreeNode getNodeFor(Entry entry) { } @Override - protected String getPanelName() { - return I18n.translate("info_panel.tree.inheritance"); + public String getId() { + return Type.INHERITANCE; } } diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/RightAngleLayerUI.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/RightAngleLayerUI.java new file mode 100644 index 000000000..d92a50d2f --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/RightAngleLayerUI.java @@ -0,0 +1,356 @@ +package cuchaz.enigma.gui.panels.right; + +import javax.swing.JComponent; +import javax.swing.JLayer; +import javax.swing.SwingUtilities; +import javax.swing.plaf.LayerUI; +import java.awt.AWTEvent; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.awt.event.MouseWheelEvent; +import java.awt.event.MouseWheelListener; + +/** + * Originally by 2xsaiko. Modified to only allow right-angle rotations. + */ +public class RightAngleLayerUI extends LayerUI { + private final Rotation rotation; + + private Component lastEnteredTarget; + private Component lastPressedTarget; + private boolean dispatchingMode = false; + + public RightAngleLayerUI(Rotation rotation) { + this.rotation = rotation; + } + + @Override + public void paint(Graphics g, JComponent c) { + Graphics2D g2d = (Graphics2D) g; + if (rotation == Rotation.COUNTERCLOCKWISE) { + g2d.translate(0, c.getHeight()); + } else if (rotation == Rotation.CLOCKWISE) { + g2d.translate(c.getWidth(), 0); + } + g2d.rotate(-rotation.getAsInteger() * (Math.PI * 0.5)); + super.paint(g2d, c); + } + + @Override + public void doLayout(JLayer l) { + Component view = l.getView(); + Dimension d = rotate(new Dimension(l.getWidth(), l.getHeight())); + if (view != null) { + view.setBounds(0, 0, d.width, d.height); + } + Component glassPane = l.getGlassPane(); + if (glassPane != null) { + glassPane.setBounds(0, 0, d.width, d.height); + } + } + + /** + * Find the deepest component in the AWT hierarchy + * + * @param layer the layer to which this UI is installed + * @param targetPoint the point in layer's coordinates + * @return the component in the specified point + */ + private Component getTarget(JLayer layer, Point targetPoint) { + Component view = layer.getView(); + if (view == null) { + return null; + } else { + Point viewPoint = SwingUtilities.convertPoint(layer, targetPoint, view); + return SwingUtilities.getDeepestComponentAt(view, viewPoint.x, viewPoint.y); + } + } + + @Override + public Dimension getPreferredSize(JComponent c) { + return rotate(super.getPreferredSize(c)); + } + + @Override + public Dimension getMinimumSize(JComponent c) { + return rotate(super.getMinimumSize(c)); + } + + @Override + public Dimension getMaximumSize(JComponent c) { + return rotate(super.getMaximumSize(c)); + } + + @SuppressWarnings("unchecked") + @Override + public void installUI(JComponent c) { + super.installUI(c); + JLayer l = (JLayer) c; + l.setLayerEventMask(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.MOUSE_WHEEL_EVENT_MASK | AWTEvent.KEY_EVENT_MASK | AWTEvent.FOCUS_EVENT_MASK); + } + + @SuppressWarnings("unchecked") + @Override + public void uninstallUI(JComponent c) { + JLayer l = (JLayer) c; + l.setLayerEventMask(0); + super.uninstallUI(c); + } + + /** + * Process the mouse events and map the mouse coordinates inverting the internal affine transformation. + * + * @param event the event to be dispatched + * @param layer the layer this LayerUI is set to + */ + @Override + public void eventDispatched(AWTEvent event, JLayer layer) { + if (event instanceof MouseEvent mouseEvent) { + // The if discriminates between the generated and original event. + // Removing it will cause a stack overflow caused by the event being redispatched to this class. + + if (!dispatchingMode) { + // Process an original mouse event + dispatchingMode = true; + try { + redispatchMouseEvent(mouseEvent, layer); + } finally { + dispatchingMode = false; + } + } else { + // Process generated mouse events + // Added a check, because on mouse entered or exited, the cursor + // may be set to specific dragging cursors. + + if (MouseEvent.MOUSE_ENTERED == mouseEvent.getID() || MouseEvent.MOUSE_EXITED == mouseEvent.getID()) { + layer.getGlassPane().setCursor(null); + } else { + Component component = mouseEvent.getComponent(); + layer.getGlassPane().setCursor(component.getCursor()); + } + } + } else { + super.eventDispatched(event, layer); + } + layer.repaint(); + } + + private void redispatchMouseEvent(MouseEvent originalEvent, JLayer layer) { + if (layer.getView() != null) { + if (originalEvent.getComponent() != layer.getGlassPane()) { + originalEvent.consume(); + } + MouseEvent newEvent = null; + + Point realPoint = transform(originalEvent.getX(), originalEvent.getY(), layer.getWidth(), layer.getHeight(), rotation); + Component realTarget = getTarget(layer, realPoint); + + if (realTarget != null) { + realTarget = getListeningComponent(originalEvent, realTarget); + } + + switch (originalEvent.getID()) { + case MouseEvent.MOUSE_PRESSED -> { + newEvent = transformMouseEvent(layer, originalEvent, realTarget, realPoint); + if (newEvent != null) { + lastPressedTarget = newEvent.getComponent(); + } + } + case MouseEvent.MOUSE_RELEASED -> { + newEvent = transformMouseEvent(layer, originalEvent, lastPressedTarget, realPoint); + lastPressedTarget = null; + } + case MouseEvent.MOUSE_CLICKED -> { + newEvent = transformMouseEvent(layer, originalEvent, realTarget, realPoint); + lastPressedTarget = null; + } + case MouseEvent.MOUSE_MOVED -> { + newEvent = transformMouseEvent(layer, originalEvent, realTarget, realPoint); + generateEnterExitEvents(layer, originalEvent, realTarget, realPoint); + } + case MouseEvent.MOUSE_ENTERED, MouseEvent.MOUSE_EXITED -> + generateEnterExitEvents(layer, originalEvent, realTarget, realPoint); + case MouseEvent.MOUSE_DRAGGED -> { + newEvent = transformMouseEvent(layer, originalEvent, lastPressedTarget, realPoint); + generateEnterExitEvents(layer, originalEvent, realTarget, realPoint); + } + case MouseEvent.MOUSE_WHEEL -> + newEvent = transformMouseWheelEvent(layer, (MouseWheelEvent) originalEvent, realTarget, realPoint); + } + dispatchMouseEvent(newEvent); + } + } + + private MouseEvent transformMouseEvent(JLayer layer, MouseEvent mouseEvent, Component target, Point realPoint) { + return transformMouseEvent(layer, mouseEvent, target, realPoint, mouseEvent.getID()); + } + + /** + * Create the new event to be dispatched + */ + private MouseEvent transformMouseEvent(JLayer layer, MouseEvent mouseEvent, Component target, Point targetPoint, int id) { + if (target == null) { + return null; + } else { + Point newPoint = SwingUtilities.convertPoint(layer, targetPoint, target); + return new MouseEvent(target, + id, + mouseEvent.getWhen(), + mouseEvent.getModifiers(), + newPoint.x, + newPoint.y, + mouseEvent.getClickCount(), + mouseEvent.isPopupTrigger(), + mouseEvent.getButton()); + } + } + + /** + * Create the new mouse wheel event to be dispatched + */ + private MouseWheelEvent transformMouseWheelEvent(JLayer layer, MouseWheelEvent mouseWheelEvent, Component target, Point targetPoint) { + if (target == null) { + return null; + } else { + Point newPoint = SwingUtilities.convertPoint(layer, targetPoint, target); + return new MouseWheelEvent(target, + mouseWheelEvent.getID(), + mouseWheelEvent.getWhen(), + mouseWheelEvent.getModifiers(), + newPoint.x, + newPoint.y, + mouseWheelEvent.getClickCount(), + mouseWheelEvent.isPopupTrigger(), + mouseWheelEvent.getScrollType(), + mouseWheelEvent.getScrollAmount(), + mouseWheelEvent.getWheelRotation() + ); + } + } + + /** + * dispatch the {@code mouseEvent} + * + * @param mouseEvent the event to be dispatched + */ + private void dispatchMouseEvent(MouseEvent mouseEvent) { + if (mouseEvent != null) { + Component target = mouseEvent.getComponent(); + target.dispatchEvent(mouseEvent); + } + } + + /** + * Get the listening component associated to the {@code component}'s {@code event} + */ + private Component getListeningComponent(MouseEvent event, Component component) { + return switch (event.getID()) { + case MouseEvent.MOUSE_CLICKED, MouseEvent.MOUSE_ENTERED, MouseEvent.MOUSE_EXITED, MouseEvent.MOUSE_PRESSED, MouseEvent.MOUSE_RELEASED -> + getMouseListeningComponent(component); + case MouseEvent.MOUSE_DRAGGED, MouseEvent.MOUSE_MOVED -> getMouseMotionListeningComponent(component); + case MouseEvent.MOUSE_WHEEL -> getMouseWheelListeningComponent(component); + default -> null; + }; + } + + /** + * Cycles through the {@code component}'s parents to find the {@link Component} with associated {@link MouseListener} + */ + private Component getMouseListeningComponent(Component component) { + if (component.getMouseListeners().length > 0) { + return component; + } else { + Container parent = component.getParent(); + if (parent != null) { + return getMouseListeningComponent(parent); + } else { + return null; + } + } + } + + /** + * Cycles through the {@code component}'s parents to find the {@link Component} with associated {@link MouseMotionListener} + */ + private Component getMouseMotionListeningComponent(Component component) { + // Mouse motion events may result in MOUSE_ENTERED and MOUSE_EXITED. + // Therefore, components with MouseListeners registered should be + // returned as well. + + if (component.getMouseMotionListeners().length > 0 || component.getMouseListeners().length > 0) { + return component; + } else { + Container parent = component.getParent(); + if (parent != null) { + return getMouseMotionListeningComponent(parent); + } else { + return null; + } + } + } + + /** + * Cycles through the {@code component}'s parents to find the {@link Component} with associated {@link MouseWheelListener} + */ + private Component getMouseWheelListeningComponent(Component component) { + if (component.getMouseWheelListeners().length > 0) { + return component; + } else { + Container parent = component.getParent(); + if (parent != null) { + return getMouseWheelListeningComponent(parent); + } else { + return null; + } + } + } + + /** + * Generate a {@code MOUSE_ENTERED} and {@code MOUSE_EXITED} event when the target component is changed + */ + private void generateEnterExitEvents(JLayer layer, MouseEvent originalEvent, Component newTarget, Point realPoint) { + if (lastEnteredTarget != newTarget) { + dispatchMouseEvent(transformMouseEvent(layer, originalEvent, lastEnteredTarget, realPoint, MouseEvent.MOUSE_EXITED)); + lastEnteredTarget = newTarget; + dispatchMouseEvent(transformMouseEvent(layer, originalEvent, lastEnteredTarget, realPoint, MouseEvent.MOUSE_ENTERED)); + } + } + + @SuppressWarnings("SuspiciousNameCombination") + private static Dimension rotate(Dimension self) { + return new Dimension(self.height, self.width); + } + + @SuppressWarnings("SuspiciousNameCombination") + private static Point transform(int x, int y, int width, int height, Rotation rotation) { + if (rotation == Rotation.COUNTERCLOCKWISE) { + return new Point(height - y, x); + } else if (rotation == Rotation.CLOCKWISE) { + return new Point(y, width - x); + } else { + throw new IllegalArgumentException("Unknown rotation: " + rotation); + } + } + + public enum Rotation { + CLOCKWISE(3), + COUNTERCLOCKWISE(1); + + private final int asInteger; + + Rotation(int rotation) { + this.asInteger = rotation; + } + + public int getAsInteger() { + return asInteger; + } + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/RightPanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/RightPanel.java new file mode 100644 index 000000000..2aa74701e --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/RightPanel.java @@ -0,0 +1,97 @@ +package cuchaz.enigma.gui.panels.right; + +import cuchaz.enigma.gui.Gui; +import cuchaz.enigma.utils.I18n; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JToggleButton; +import java.awt.BorderLayout; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Supplier; + +public abstract class RightPanel extends JPanel { + public static final String DEFAULT = Type.STRUCTURE; + private static final Map, RightPanel> panels = new LinkedHashMap<>(); + private static final Map> panelClasses = new HashMap<>(); + + protected final Gui gui; + protected final JToggleButton button; + protected final JLabel title; + protected final Supplier titleProvider = () -> I18n.translate("right_panel." + this.getId() + ".title"); + + protected RightPanel(Gui gui) { + super(new BorderLayout()); + this.gui = gui; + this.button = new JToggleButton(this.titleProvider.get()); + this.button.addActionListener(e -> gui.setRightPanel(this.getClass(), true)); + this.title = new JLabel(this.titleProvider.get()); + this.add(this.title, BorderLayout.NORTH); + } + + public abstract RightPanel.ButtonPosition getButtonPosition(); + + public abstract String getId(); + + @Override + public void setVisible(boolean visible) { + super.setVisible(visible); + this.getButton().setSelected(visible); + } + + public void retranslateUi() { + String translatedTitle = this.titleProvider.get(); + this.button.setText(translatedTitle); + this.title.setText(translatedTitle); + } + + public JToggleButton getButton() { + return this.button; + } + + public static void addPanel(RightPanel panel) { + panels.put(panel.getClass(), panel); + panelClasses.put(panel.getId(), panel.getClass()); + } + + @SuppressWarnings("unchecked") + public static T getPanel(Class clazz) { + RightPanel panel = panels.get(clazz); + if (panel != null) { + return (T) panels.get(clazz); + } else { + throw new IllegalArgumentException("no panel registered for class " + clazz); + } + } + + public static RightPanel getPanel(String id) { + if (!panelClasses.containsKey(id)) { + throw new IllegalArgumentException("no panel registered for id " + id); + } + + return getPanel(panelClasses.get(id)); + } + + public static Map, RightPanel> getRightPanels() { + return panels; + } + + public static Map> getPanelClasses() { + return panelClasses; + } + + public static final class Type { + public static final String STRUCTURE = "structure"; + public static final String INHERITANCE = "inheritance"; + public static final String CALLS = "calls"; + public static final String IMPLEMENTATIONS = "implementations"; + public static final String COLLAB = "collab"; + } + + public enum ButtonPosition { + TOP, + BOTTOM + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/StructurePanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/StructurePanel.java new file mode 100644 index 000000000..929fcde80 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/StructurePanel.java @@ -0,0 +1,191 @@ +package cuchaz.enigma.gui.panels.right; + +import java.awt.*; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; + +import javax.swing.JComboBox; +import javax.swing.JPanel; +import javax.swing.JLabel; +import javax.swing.JScrollPane; +import javax.swing.JTree; +import javax.swing.tree.DefaultTreeCellRenderer; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; + +import cuchaz.enigma.analysis.StructureTreeNode; +import cuchaz.enigma.analysis.StructureTreeOptions; +import cuchaz.enigma.gui.Gui; +import cuchaz.enigma.gui.panels.EditorPanel; +import cuchaz.enigma.gui.config.keybind.KeyBinds; +import cuchaz.enigma.gui.renderer.StructureOptionListCellRenderer; +import cuchaz.enigma.gui.util.GridBagConstraintsBuilder; +import cuchaz.enigma.gui.util.GuiUtil; +import cuchaz.enigma.gui.util.SingleTreeSelectionModel; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.FieldEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; +import cuchaz.enigma.translation.representation.entry.ParentedEntry; +import cuchaz.enigma.utils.I18n; + +public class StructurePanel extends RightPanel { + private final JPanel optionsPanel; + + private final JLabel obfuscationVisibilityLabel = new JLabel(); + private final JLabel documentationVisibilityLabel = new JLabel(); + private final JLabel sortingOrderLabel = new JLabel(); + + private final JComboBox obfuscationVisibility; + private final JComboBox documentationVisibility; + private final JComboBox sortingOrder; + + private final JTree structureTree; + + public StructurePanel(Gui gui) { + super(gui); + this.optionsPanel = new JPanel(new GridBagLayout()); + this.optionsPanel.setVisible(false); + + GridBagConstraintsBuilder cb = GridBagConstraintsBuilder.create().insets(5).fill(GridBagConstraints.HORIZONTAL); + + this.optionsPanel.add(this.obfuscationVisibilityLabel, cb.pos(0, 0).build()); + this.obfuscationVisibility = new JComboBox<>(StructureTreeOptions.ObfuscationVisibility.values()); + this.obfuscationVisibility.setRenderer(new StructureOptionListCellRenderer()); + this.obfuscationVisibility.addActionListener(event -> this.updateStructure(gui.getActiveEditor())); + this.optionsPanel.add(this.obfuscationVisibility, cb.pos(1, 0).build()); + + this.optionsPanel.add(this.documentationVisibilityLabel, cb.pos(0, 1).build()); + this.documentationVisibility = new JComboBox<>(StructureTreeOptions.DocumentationVisibility.values()); + this.documentationVisibility.setRenderer(new StructureOptionListCellRenderer()); + this.documentationVisibility.addActionListener(event -> this.updateStructure(gui.getActiveEditor())); + this.optionsPanel.add(this.documentationVisibility, cb.pos(1, 1).build()); + + this.optionsPanel.add(this.sortingOrderLabel, cb.pos(0, 2).build()); + this.sortingOrder = new JComboBox<>(StructureTreeOptions.SortingOrder.values()); + this.sortingOrder.setRenderer(new StructureOptionListCellRenderer()); + this.sortingOrder.addActionListener(event -> this.updateStructure(gui.getActiveEditor())); + this.optionsPanel.add(this.sortingOrder, cb.pos(1, 2).build()); + + this.structureTree = new JTree(); + this.structureTree.setModel(null); + this.structureTree.setCellRenderer(new StructureTreeCellRenderer(gui)); + this.structureTree.setSelectionModel(new SingleTreeSelectionModel()); + this.structureTree.setShowsRootHandles(true); + this.structureTree.addKeyListener(GuiUtil.onKeyPress(this::onKeyPress)); + this.structureTree.addMouseListener(GuiUtil.onMouseClick(this::onClick)); + + this.retranslateUi(); + + JPanel topPanel = new JPanel(new BorderLayout()); + topPanel.add(this.optionsPanel, BorderLayout.SOUTH); + topPanel.add(this.title, BorderLayout.NORTH); + + this.add(topPanel, BorderLayout.NORTH); + this.add(new JScrollPane(this.structureTree)); + } + + public void updateStructure(EditorPanel editor) { + structureTree.setModel(null); + + if (editor == null) { + this.optionsPanel.setVisible(false); + return; + } + + ClassEntry classEntry = editor.getClassHandle().getRef(); + if (classEntry == null) return; + + this.optionsPanel.setVisible(true); + + // get the class structure + StructureTreeNode node = this.gui.getController().getClassStructure(classEntry, this.getOptions()); + + // show the tree at the root + TreePath path = GuiUtil.getPathToRoot(node); + structureTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); + structureTree.expandPath(path); + structureTree.setSelectionRow(structureTree.getRowForPath(path)); + } + + private void onKeyPress(KeyEvent e) { + if (KeyBinds.SELECT.matches(e)) { + navigateToSelectedNode(); + } + } + + private void onClick(MouseEvent event) { + if (event.getClickCount() >= 2 && event.getButton() == MouseEvent.BUTTON1) { + navigateToSelectedNode(); + } + } + + private void navigateToSelectedNode() { + // get the selected node + TreePath path = structureTree.getSelectionPath(); + if (path == null) { + return; + } + + Object node = path.getLastPathComponent(); + + if (node instanceof StructureTreeNode structureTreeNode) { + this.gui.getController().navigateTo(structureTreeNode.getEntry()); + } + } + + /** + * Creates and returns the options of this structure panel. + */ + private StructureTreeOptions getOptions() { + return new StructureTreeOptions( + (StructureTreeOptions.ObfuscationVisibility) this.obfuscationVisibility.getSelectedItem(), + (StructureTreeOptions.DocumentationVisibility) this.documentationVisibility.getSelectedItem(), + (StructureTreeOptions.SortingOrder) this.sortingOrder.getSelectedItem() + ); + } + + @Override + public void retranslateUi() { + super.retranslateUi(); + this.obfuscationVisibilityLabel.setText(I18n.translate("structure.options.obfuscation")); + this.documentationVisibilityLabel.setText(I18n.translate("structure.options.documentation")); + this.sortingOrderLabel.setText(I18n.translate("structure.options.sorting")); + } + + @Override + public ButtonPosition getButtonPosition() { + return ButtonPosition.TOP; + } + + @Override + public String getId() { + return Type.STRUCTURE; + } + + private static class StructureTreeCellRenderer extends DefaultTreeCellRenderer { + private final Gui gui; + + StructureTreeCellRenderer(Gui gui) { + this.gui = gui; + } + + @Override + public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { + Component c = super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus); + ParentedEntry entry = ((StructureTreeNode) value).getEntry(); + + if (entry instanceof ClassEntry classEntry) { + this.setIcon(GuiUtil.getClassIcon(gui, classEntry)); + } else if (entry instanceof MethodEntry methodEntry) { + this.setIcon(GuiUtil.getMethodIcon(methodEntry)); + } else if (entry instanceof FieldEntry) { + this.setIcon(GuiUtil.FIELD_ICON); + } + + this.setText("" + ((StructureTreeNode) value).toHtml()); + + return c; + } + } +} diff --git a/enigma/src/main/resources/lang/de_de.json b/enigma/src/main/resources/lang/de_de.json index 2c69bcd61..b1ef4ec67 100644 --- a/enigma/src/main/resources/lang/de_de.json +++ b/enigma/src/main/resources/lang/de_de.json @@ -40,4 +40,4 @@ "validation.message.illegal_identifier.long": "Ungültiges Zeichen „%2$s“ an Position %3$d.", "validation.message.illegal_doc_comment_end": "Javadoc-Kommentar darf die Zeichenfolge „*/“ nicht enthalten.", "validation.message.reserved_identifier": "„%s“ ist ein reservierter Name." -} \ No newline at end of file +} diff --git a/enigma/src/main/resources/lang/en_us.json b/enigma/src/main/resources/lang/en_us.json index 3034e1cef..21a93a167 100644 --- a/enigma/src/main/resources/lang/en_us.json +++ b/enigma/src/main/resources/lang/en_us.json @@ -129,10 +129,6 @@ "info_panel.identifier.index": "Index", "info_panel.editor.class.decompiling": "(decompiling...)", "info_panel.editor.class.not_found": "Unable to find class:", - "info_panel.tree.structure": "Structure", - "info_panel.tree.inheritance": "Inheritance", - "info_panel.tree.implementations": "Implementations", - "info_panel.tree.calls": "Call Graph", "popup.copied": "Copied!", @@ -149,9 +145,6 @@ "structure.options.sorting.a_z": "A to Z", "structure.options.sorting.z_a": "Z to A", - "log_panel.messages": "Messages", - "log_panel.users": "Users", - "progress.operation": "%s - Operation in progress", "progress.jar.indexing": "Indexing jar", "progress.jar.indexing.entries": "Entries...", @@ -268,5 +261,15 @@ "keybind.menu.reload_mappings": "Reload mappings", "keybind.menu.reload_all": "Reload Jar and mappings", "keybind.menu.mapping_stats": "Mapping stats", - "keybind.menu.save": "Save mappings" + "keybind.menu.save": "Save mappings", + + "right_panel.collab.send": "Send", + "right_panel.collab.title": "Collab", + "right_panel.collab.messages_title": "Messages", + "right_panel.collab.users_title": "Users", + "right_panel.collab.offline_text": "Enigma is currently running offline.", + "right_panel.calls.title": "Calls", + "right_panel.structure.title": "Structure", + "right_panel.inheritance.title": "Inheritance", + "right_panel.implementations.title": "Implementations" } diff --git a/enigma/src/main/resources/lang/fr_fr.json b/enigma/src/main/resources/lang/fr_fr.json index d3d0c2976..e4996ab59 100644 --- a/enigma/src/main/resources/lang/fr_fr.json +++ b/enigma/src/main/resources/lang/fr_fr.json @@ -108,10 +108,6 @@ "info_panel.identifier.index": "Index", "info_panel.editor.class.decompiling": "(décompilation...)", "info_panel.editor.class.not_found": "Impossible de trouver la classe :", - "info_panel.tree.structure": "Structure", - "info_panel.tree.inheritance": "Héritage", - "info_panel.tree.implementations": "Implémentations", - "info_panel.tree.calls": "Graphique des appels", "popup.copied": "Copié !", @@ -128,9 +124,6 @@ "structure.options.sorting.a_z": "A à Z", "structure.options.sorting.z_a": "Z à A", - "log_panel.messages": "Messages", - "log_panel.users": "Utilisateurs", - "progress.operation": "%s - Opération en cours", "progress.jar.indexing": "Indexation du jar", "progress.jar.indexing.entries": "Entrées...", @@ -219,5 +212,14 @@ "crash.export": "Exporter", "crash.ignore": "Ignorer", "crash.exit": "Quitter", - "crash.exit.warning": "Si vous choisissez Quitter, vous perdrez tout travail non sauvegardé." + "crash.exit.warning": "Si vous choisissez Quitter, vous perdrez tout travail non sauvegardé.", + + "right_panel.structure": "Structure", + "right_panel.inheritance": "Héritage", + "right_panel.implementations": "Implémentations", + "right_panel.calls": "Graphique des appels", + "right_panel.collab.messages_title": "Messages", + "right_panel.collab.users_title": "Utilisateurs", + "right_panel.collab.send": "Envoie", + "right_panel.collab.offline_text": "Enigma n'est pas connecté à un serveur." } diff --git a/enigma/src/main/resources/lang/ja_jp.json b/enigma/src/main/resources/lang/ja_jp.json index 09e7ee08b..88b0926df 100644 --- a/enigma/src/main/resources/lang/ja_jp.json +++ b/enigma/src/main/resources/lang/ja_jp.json @@ -105,14 +105,10 @@ "info_panel.identifier.class": "クラス", "info_panel.identifier.type_descriptor": "型記述子", "info_panel.identifier.method_descriptor": "メソッド記述子", - "info_panel.identifier.modifier": "修飾子", + "info_panel.identifier.modifier": "修飾子", "info_panel.identifier.index": "Index", "info_panel.editor.class.decompiling": "(デコンパイル中...)", "info_panel.editor.class.not_found": "クラスを見つけられません:", - "info_panel.tree.structure": "構造", - "info_panel.tree.inheritance": "継承", - "info_panel.tree.implementations": "実装", - "info_panel.tree.calls": "呼び出し関係", "popup.copied": "コピーしました!", @@ -129,9 +125,6 @@ "structure.options.sorting.a_z": "A から Z", "structure.options.sorting.z_a": "Z から A", - "log_panel.messages": "メッセージ", - "log_panel.users": "ユーザー", - "progress.operation": "%s - 処理中", "progress.jar.indexing": "jarのインデックス中", "progress.jar.indexing.entries": "エントリー...", @@ -220,5 +213,12 @@ "crash.export": "エクスポート", "crash.ignore": "無視", "crash.exit": "終了", - "crash.exit.warning": "終了すると、未保存の変更は失われます" + "crash.exit.warning": "終了すると、未保存の変更は失われます", + + "right_panel.structure.title": "構造", + "right_panel.inheritance.title": "継承", + "right_panel.implementations.title": "実装", + "right_panel.calls.title": "呼び出し関係", + "right_panel.collab.messages_title": "メッセージ", + "right_panel.collab.users_title": "ユーザー" } diff --git a/enigma/src/main/resources/lang/zh_cn.json b/enigma/src/main/resources/lang/zh_cn.json index fe806fb37..5e012ac49 100644 --- a/enigma/src/main/resources/lang/zh_cn.json +++ b/enigma/src/main/resources/lang/zh_cn.json @@ -71,9 +71,6 @@ "info_panel.identifier.index": "索引", "info_panel.editor.class.decompiling": "(反编译中...)", "info_panel.editor.class.not_found": "找不到类:", - "info_panel.tree.inheritance": "继承", - "info_panel.tree.implementations": "实现", - "info_panel.tree.calls": "调用图", "progress.operation": "%s - 进行中", "progress.jar.indexing": "索引 Jar", @@ -115,5 +112,9 @@ "crash.export": "输出", "crash.ignore": "忽略", "crash.exit": "退出", - "crash.exit.warning": "如果选择退出,将丢失所有未保存的工作。" + "crash.exit.warning": "如果选择退出,将丢失所有未保存的工作。", + + "right_panel.inheritance.title": "继承", + "right_panel.implementations.title": "实现", + "right_panel.calls.title": "调用图" } diff --git a/enigma/src/main/resources/profile.json b/enigma/src/main/resources/profile.json index e1af4cdbd..4dda7c1fc 100644 --- a/enigma/src/main/resources/profile.json +++ b/enigma/src/main/resources/profile.json @@ -1,20 +1,20 @@ { - "services": { - "jar_indexer": [ - { - "id": "enigma:enum_initializer_indexer" - }, - { - "id": "enigma:specialized_bridge_method_indexer" - } - ], - "name_proposal": [ - { - "id": "enigma:enum_name_proposer" - }, - { - "id": "enigma:specialized_method_name_proposer" - } - ] - } -} \ No newline at end of file + "services": { + "jar_indexer": [ + { + "id": "enigma:enum_initializer_indexer" + }, + { + "id": "enigma:specialized_bridge_method_indexer" + } + ], + "name_proposal": [ + { + "id": "enigma:enum_name_proposer" + }, + { + "id": "enigma:specialized_method_name_proposer" + } + ] + } +}