diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/ClassSelector.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/ClassSelector.java index 0c1ae82a2..0f72f41d3 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/ClassSelector.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/ClassSelector.java @@ -88,8 +88,8 @@ public ClassSelector(Gui gui, Comparator comparator, boolean isRenam public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus); - if (leaf && value instanceof ClassSelectorClassNode) { - setIcon(GuiUtil.getClassIcon(gui, ((ClassSelectorClassNode) value).getObfEntry())); + if (leaf && value instanceof ClassSelectorClassNode node) { + setIcon(GuiUtil.getClassIcon(gui, node.getObfEntry())); } return this; @@ -160,19 +160,23 @@ public void setRenameSelectionListener(RenameSelectionListener renameSelectionLi this.renameSelectionListener = renameSelectionListener; } + public NestedPackages getPackageManager() { + return this.packageManager; + } + public void setClasses(Collection classEntries) { - List state = getExpansionState(); + List state = this.getExpansionState(); if (classEntries == null) { - setModel(null); + this.setModel(null); return; } // update the tree control - packageManager = new NestedPackages(classEntries, comparator, controller.project.getMapper()); - setModel(new DefaultTreeModel(packageManager.getRoot())); + this.packageManager = new NestedPackages(classEntries, this.comparator, this.controller.project.getMapper()); + this.setModel(new DefaultTreeModel(this.packageManager.getRoot())); - restoreExpansionState(state); + this.restoreExpansionState(state); } public ClassEntry getSelectedClass() { @@ -211,12 +215,13 @@ public List getExpansionState() { } public void restoreExpansionState(List expansionState) { - clearSelection(); + this.clearSelection(); for (StateEntry entry : expansionState) { - switch (entry.state) { - case SELECTED -> addSelectionPath(entry.path); - case EXPANDED -> expandPath(entry.path); + if (entry.state() == State.EXPANDED) { + this.expandPath(entry.path()); + } else if (entry.state() == State.SELECTED) { + this.addSelectionPath(entry.path()); } } } 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 10af9edcf..eebb74bc8 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java @@ -22,16 +22,18 @@ 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.docker.ObfuscatedClassesDocker; +import cuchaz.enigma.gui.docker.CollabDocker; +import cuchaz.enigma.gui.docker.StructureDocker; +import cuchaz.enigma.gui.docker.CallsTreeDocker; +import cuchaz.enigma.gui.docker.ImplementationsTreeDocker; +import cuchaz.enigma.gui.docker.InheritanceTreeDocker; +import cuchaz.enigma.gui.docker.DeobfuscatedClassesDocker; +import cuchaz.enigma.gui.docker.AllClassesDocker; +import cuchaz.enigma.gui.docker.Dock; +import cuchaz.enigma.gui.docker.Docker; import cuchaz.enigma.gui.renderer.MessageListCellRenderer; import cuchaz.enigma.gui.util.GuiUtil; import cuchaz.enigma.gui.util.LanguageUtil; @@ -64,6 +66,7 @@ import java.awt.Dimension; import java.awt.Point; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; @@ -78,50 +81,54 @@ public class Gui { private ConnectionState connectionState; private boolean isJarOpen; private final Set editableTypes; - private boolean singleClassTree; private final MenuBar menuBar; - private final ObfPanel obfPanel; - private final DeobfPanel deobfPanel; private final IdentifierPanel infoPanel; private final EditorTabbedPane editorTabbedPane; - private final JPanel classesPanel = new JPanel(new BorderLayout()); - private final JSplitPane splitClasses; - private final JPanel centerPanel = new JPanel(new BorderLayout()); - private RightPanel rightPanel; + private final JPanel centerPanel; + private final Dock rightDock; + private final Dock leftDock; private final JSplitPane splitRight; - private final JSplitPane splitCenter; + private final JSplitPane splitLeft; - private final DefaultListModel userModel = new DefaultListModel<>(); - private final DefaultListModel messageModel = new DefaultListModel<>(); - private final JList users = new JList<>(this.userModel); - private final JList messages = new JList<>(this.messageModel); + private final DefaultListModel userModel; + private final DefaultListModel messageModel; + private final JList users; + private final JList messages; - private final JLabel connectionStatusLabel = new JLabel(); + private final JLabel connectionStatusLabel; - public final JFileChooser jarFileChooser = new JFileChooser(); - public final JFileChooser tinyMappingsFileChooser = new JFileChooser(); - public final JFileChooser enigmaMappingsFileChooser = new JFileChooser(); - public final JFileChooser exportSourceFileChooser = new JFileChooser(); - public final JFileChooser exportJarFileChooser = new JFileChooser(); + public final JFileChooser jarFileChooser; + public final JFileChooser tinyMappingsFileChooser; + public final JFileChooser enigmaMappingsFileChooser; + public final JFileChooser exportSourceFileChooser; + public final JFileChooser exportJarFileChooser; public SearchDialog searchDialog; public Gui(EnigmaProfile profile, Set editableTypes) { this.mainWindow = new MainWindow(Enigma.NAME); + this.centerPanel = new JPanel(new BorderLayout()); this.editableTypes = editableTypes; this.controller = new GuiController(this, profile); - this.deobfPanel = new DeobfPanel(this); this.infoPanel = new IdentifierPanel(this); - this.obfPanel = new ObfPanel(this); this.menuBar = new MenuBar(this); - this.setupRightPanels(); + this.userModel = new DefaultListModel<>(); + this.messageModel = new DefaultListModel<>(); + this.users = new JList<>(this.userModel); + this.messages = new JList<>(this.messageModel); 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.rightDock = new Dock(this, Docker.Side.RIGHT); + this.leftDock = new Dock(this, Docker.Side.LEFT); + this.splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, rightDock); + this.splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, leftDock, splitRight); + this.jarFileChooser = new JFileChooser(); + this.tinyMappingsFileChooser = new JFileChooser(); + this.enigmaMappingsFileChooser = new JFileChooser(); + this.exportSourceFileChooser = new JFileChooser(); + this.exportJarFileChooser = new JFileChooser(); + this.connectionStatusLabel = new JLabel(); this.setupUi(); @@ -131,33 +138,39 @@ 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)); + private void setupDockers() { + // right dockers + // top + Docker.addDocker(new StructureDocker(this)); + Docker.addDocker(new InheritanceTreeDocker(this)); + Docker.addDocker(new ImplementationsTreeDocker(this)); + Docker.addDocker(new CallsTreeDocker(this)); - // bottom panels - RightPanel.addPanel(new CollabPanel(this)); + // bottom + Docker.addDocker(new CollabDocker(this)); - // set default sizes for right panels - for (RightPanel panel : RightPanel.getRightPanels().values()) { + // left dockers + // top + Docker.addDocker(new ObfuscatedClassesDocker(this)); + Docker.addDocker(new AllClassesDocker(this)); + + // bottom + Docker.addDocker(new DeobfuscatedClassesDocker(this)); + + // set default docker sizes + for (Docker panel : Docker.getDockers().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 + ")!"); + // set up selectors + for (Docker.Side side : Docker.Side.values()) { + this.mainWindow.getDockerSelector(side).configure(); } } private void setupUi() { + this.setupDockers(); + this.jarFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); this.tinyMappingsFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); @@ -169,42 +182,40 @@ private void setupUi() { this.exportJarFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); - this.splitClasses.setResizeWeight(0.3); - this.classesPanel.setPreferredSize(ScaleUtil.getDimension(250, 0)); - // layout controls Container workArea = this.mainWindow.getWorkArea(); workArea.setLayout(new BorderLayout()); - centerPanel.add(infoPanel.getUi(), BorderLayout.NORTH); - centerPanel.add(this.editorTabbedPane.getUi(), BorderLayout.CENTER); + this.centerPanel.add(this.infoPanel.getUi(), BorderLayout.NORTH); + this.centerPanel.add(this.editorTabbedPane.getUi(), BorderLayout.CENTER); - messages.setCellRenderer(new MessageListCellRenderer()); + this.messages.setCellRenderer(new MessageListCellRenderer()); - splitRight.setResizeWeight(1); // let the left side take all the slack - splitRight.resetToPreferredSizes(); - splitCenter.setResizeWeight(0); // let the right side take all the slack + workArea.add(this.splitLeft, BorderLayout.CENTER); - workArea.add(splitCenter, BorderLayout.CENTER); + this.mainWindow.getStatusBar().addPermanentComponent(this.connectionStatusLabel); - // restore state - int[] layout = UiConfig.getLayout(); - if (layout.length >= 3) { - this.splitClasses.setDividerLocation(layout[0]); - this.splitCenter.setDividerLocation(layout[1]); - this.splitRight.setDividerLocation(layout[2]); - } + // ensure that the center panel gets all extra resize space + // this prevents the right and left panels from getting far too big occasionally + this.splitRight.setResizeWeight(1); + this.splitLeft.setResizeWeight(0); - this.mainWindow.getStatusBar().addPermanentComponent(this.connectionStatusLabel); + // apply docker config + if (UiConfig.getHostedDockers(Docker.Side.LEFT).isPresent() || UiConfig.getHostedDockers(Docker.Side.RIGHT).isPresent()) { + // restore + this.rightDock.restoreState(); + this.leftDock.restoreState(); + } else { + // use default config + this.leftDock.host(Docker.getDocker(ObfuscatedClassesDocker.class), Docker.VerticalLocation.TOP); + this.leftDock.host(Docker.getDocker(DeobfuscatedClassesDocker.class), Docker.VerticalLocation.BOTTOM); - // init state - setConnectionState(ConnectionState.NOT_CONNECTED); - onCloseJar(); + this.rightDock.host(Docker.getDocker(StructureDocker.class), Docker.VerticalLocation.FULL); + } - // 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())); + // init state + this.setConnectionState(ConnectionState.NOT_CONNECTED); + this.onCloseJar(); JFrame frame = this.mainWindow.getFrame(); frame.addWindowListener(GuiUtil.onWindowClose(e -> this.close())); @@ -223,50 +234,27 @@ private void setupUi() { this.retranslateUi(); } - public RightPanel getRightPanel() { - return this.rightPanel; - } - /** - * Sets the right panel to the given panel. + * Opens the given docker in its preferred location. * @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); + public void openDocker(Class clazz) { + Docker newDocker = Docker.getDocker(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()); - } + Dock dock = (newDocker.getPreferredLocation().side() == Docker.Side.LEFT ? this.leftDock : this.rightDock); + dock.host(newDocker, newDocker.getPreferredLocation().verticalLocation()); + } - // 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())); + public JSplitPane getSplitLeft() { + return this.splitLeft; + } - // repaint in case the panel was changing without clicking a button - this.mainWindow.getFrame().repaint(); + public JSplitPane getSplitRight() { + return this.splitRight; } - private void saveRightPanelDividerLocation() { - if (this.rightPanel.isVisible()) { - UiConfig.setRightPanelDividerLocation(this.rightPanel.getId(), this.splitRight.getDividerLocation()); - } + public MenuBar getMenuBar() { + return this.menuBar; } public MainWindow getMainWindow() { @@ -289,35 +277,23 @@ public GuiController getController() { return this.controller; } - public void setSingleClassTree(boolean singleClassTree) { - this.singleClassTree = singleClassTree; - this.classesPanel.removeAll(); - this.classesPanel.add(isSingleClassTree() ? deobfPanel : splitClasses); - getController().refreshClasses(); - retranslateUi(); - } - - public boolean isSingleClassTree() { - return singleClassTree; - } - public void onStartOpenJar() { - this.classesPanel.removeAll(); - redraw(); + this.redraw(); } public void onFinishOpenJar(String jarName) { // update gui this.mainWindow.setTitle(Enigma.NAME + " - " + jarName); - this.classesPanel.removeAll(); - this.classesPanel.add(isSingleClassTree() ? deobfPanel : splitClasses); this.editorTabbedPane.closeAllEditorTabs(); // update menu - isJarOpen = true; + this.isJarOpen = true; - updateUiState(); - redraw(); + // update classes in dockers + this.controller.refreshClasses(); + + this.updateUiState(); + this.redraw(); } public void onCloseJar() { @@ -326,7 +302,6 @@ public void onCloseJar() { setObfClasses(null); setDeobfClasses(null); this.editorTabbedPane.closeAllEditorTabs(); - this.classesPanel.removeAll(); // update menu isJarOpen = false; @@ -359,11 +334,31 @@ public void showReference(EntryReference, Entry> reference) { } public void setObfClasses(Collection obfClasses) { - this.obfPanel.obfClasses.setClasses(obfClasses); + Docker.getDocker(ObfuscatedClassesDocker.class).getClassSelector().setClasses(obfClasses); + this.updateAllClasses(); } public void setDeobfClasses(Collection deobfClasses) { - this.deobfPanel.deobfClasses.setClasses(deobfClasses); + Docker.getDocker(DeobfuscatedClassesDocker.class).getClassSelector().setClasses(deobfClasses); + this.updateAllClasses(); + } + + public void updateAllClasses() { + ClassSelector allClasses = Docker.getDocker(AllClassesDocker.class).getClassSelector(); + + List entries = new ArrayList<>(); + NestedPackages obfuscatedPackages = Docker.getDocker(DeobfuscatedClassesDocker.class).getClassSelector().getPackageManager(); + NestedPackages deobfuscatedPackages = Docker.getDocker(ObfuscatedClassesDocker.class).getClassSelector().getPackageManager(); + + if (obfuscatedPackages != null) { + entries.addAll(obfuscatedPackages.getClassEntries()); + } + + if (deobfuscatedPackages != null) { + entries.addAll(deobfuscatedPackages.getClassEntries()); + } + + allClasses.setClasses(entries.isEmpty() ? null : entries); } public void setMappingsFile(Path path) { @@ -373,11 +368,11 @@ public void setMappingsFile(Path path) { public void showTokens(EditorPanel editor, List tokens) { if (tokens.size() > 1) { - this.setRightPanel(CallsTree.class, false); + this.openDocker(CallsTreeDocker.class); this.controller.setTokenHandle(editor.getClassHandle().copy()); - RightPanel.getPanel(CallsTree.class).showTokens(tokens); + Docker.getDocker(CallsTreeDocker.class).showTokens(tokens); } else { - RightPanel.getPanel(CallsTree.class).clearTokens(); + Docker.getDocker(CallsTreeDocker.class).clearTokens(); } // show the first token @@ -388,12 +383,6 @@ public void showCursorReference(EntryReference, Entry> reference) { infoPanel.setReference(reference == null ? null : reference.entry); } - @Nullable - public EntryReference, Entry> getCursorReference() { - EditorPanel activeEditor = this.editorTabbedPane.getActiveEditor(); - return activeEditor == null ? null : activeEditor.getCursorReference(); - } - public void startDocChange(EditorPanel editor) { EntryReference, Entry> cursorReference = editor.getCursorReference(); if (cursorReference == null || !this.isEditable(EditableType.JAVADOC)) return; @@ -413,56 +402,56 @@ public void startRename(EditorPanel editor) { } /** - * Updates the structure right panel without opening it + * Updates the Structure docker without opening it * @param editor the editor to extract the new structure from */ public void updateStructure(EditorPanel editor) { - RightPanel.getPanel(StructurePanel.class).updateStructure(editor); + Docker.getDocker(StructureDocker.class).updateStructure(editor); } /** - * Opens the Structure right panel and displays information for the provided editor + * Opens the Structure docker and displays information for the provided editor * @param editor the editor to extract structure from */ public void showStructure(EditorPanel editor) { - this.setRightPanel(StructurePanel.class, false); + this.openDocker(StructureDocker.class); this.updateStructure(editor); } /** - * Opens the Inheritance right panel and displays information for the provided editor's cursor reference. + * Opens the Inheritance docker 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.setRightPanel(InheritanceTree.class, false); - RightPanel.getPanel(InheritanceTree.class).display(cursorReference.entry); + this.openDocker(InheritanceTreeDocker.class); + Docker.getDocker(InheritanceTreeDocker.class).display(cursorReference.entry); } /** - * Opens the Implementations right panel and displays information for the provided editor's cursor reference. + * Opens the Implementations docker 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.setRightPanel(ImplementationsTree.class, false); - RightPanel.getPanel(ImplementationsTree.class).display(cursorReference.entry); + this.openDocker(ImplementationsTreeDocker.class); + Docker.getDocker(ImplementationsTreeDocker.class).display(cursorReference.entry); } /** - * Opens the Calls right panel and displays information for the provided editor's cursor reference. + * Opens the Calls docker 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.setRightPanel(CallsTree.class, false); - RightPanel.getPanel(CallsTree.class).showCalls(cursorReference.entry, recurse); + this.openDocker(CallsTreeDocker.class); + Docker.getDocker(CallsTreeDocker.class).showCalls(cursorReference.entry, recurse); } public void toggleMapping(EditorPanel editor) { @@ -515,11 +504,11 @@ public void close() { private void exit() { 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() - ); + + // save state for docker panels + this.rightDock.saveState(); + this.leftDock.saveState(); + UiConfig.save(); if (searchDialog != null) { @@ -547,8 +536,10 @@ public void onRenameFromClassTree(ValidationContext vc, Object prevData, Object onRenameFromClassTree(vc, prevDataChild, dataChild, node); } node.setUserObject(data); + // Ob package will never be modified, just reload deob view - this.deobfPanel.deobfClasses.reload(); + DeobfuscatedClassesDocker deobfuscatedPanel = Docker.getDocker(DeobfuscatedClassesDocker.class); + deobfuscatedPanel.getClassSelector().reload(); } else if (data instanceof ClassEntry entry) { // class rename @@ -567,52 +558,40 @@ public void onRenameFromClassTree(ValidationContext vc, Object prevData, Object } } - public void moveClassTree(Entry obfEntry, String newName) { - String oldEntry = obfEntry.getContainingClass().getPackageName(); - String newEntry = new ClassEntry(newName).getPackageName(); - moveClassTree(obfEntry, oldEntry == null, newEntry == null); - } - // TODO: getExpansionState will *not* actually update itself based on name changes! public void moveClassTree(Entry obfEntry, boolean isOldOb, boolean isNewOb) { ClassEntry classEntry = obfEntry.getContainingClass(); + ObfuscatedClassesDocker obfuscatedClassesDocker = Docker.getDocker(ObfuscatedClassesDocker.class); + DeobfuscatedClassesDocker deobfuscatedClassesDocker = Docker.getDocker(DeobfuscatedClassesDocker.class); - List stateDeobf = this.deobfPanel.deobfClasses.getExpansionState(); - List stateObf = this.obfPanel.obfClasses.getExpansionState(); + ClassSelector deobfuscatedClassSelector = deobfuscatedClassesDocker.getClassSelector(); + ClassSelector obfuscatedClassSelector = obfuscatedClassesDocker.getClassSelector(); + + List deobfuscatedPanelExpansionState = deobfuscatedClassSelector.getExpansionState(); + List obfuscatedPanelExpansionState = obfuscatedClassSelector.getExpansionState(); - // Ob -> deob if (!isNewOb) { - this.deobfPanel.deobfClasses.moveClassIn(classEntry); - this.obfPanel.obfClasses.removeEntry(classEntry); - this.deobfPanel.deobfClasses.reload(); - this.obfPanel.obfClasses.reload(); - } - // Deob -> ob - else if (!isOldOb) { - this.obfPanel.obfClasses.moveClassIn(classEntry); - this.deobfPanel.deobfClasses.removeEntry(classEntry); - this.deobfPanel.deobfClasses.reload(); - this.obfPanel.obfClasses.reload(); - } - // Local move - else if (isOldOb) { - this.obfPanel.obfClasses.moveClassIn(classEntry); - this.obfPanel.obfClasses.reload(); + // obfuscated -> deobfuscated + deobfuscatedClassSelector.moveClassIn(classEntry); + obfuscatedClassSelector.removeEntry(classEntry); + deobfuscatedClassSelector.reload(); + obfuscatedClassSelector.reload(); + } else if (!isOldOb) { + // deobfuscated -> obfuscated + obfuscatedClassSelector.moveClassIn(classEntry); + deobfuscatedClassSelector.removeEntry(classEntry); + deobfuscatedClassSelector.reload(); + obfuscatedClassSelector.reload(); } else { - this.deobfPanel.deobfClasses.moveClassIn(classEntry); - this.deobfPanel.deobfClasses.reload(); + // local move: deobfuscated -> deobfuscated + deobfuscatedClassSelector.moveClassIn(classEntry); + deobfuscatedClassSelector.reload(); } - this.deobfPanel.deobfClasses.restoreExpansionState(stateDeobf); - this.obfPanel.obfClasses.restoreExpansionState(stateObf); - } - - public ObfPanel getObfPanel() { - return obfPanel; - } + deobfuscatedClassSelector.restoreExpansionState(deobfuscatedPanelExpansionState); + obfuscatedClassSelector.restoreExpansionState(obfuscatedPanelExpansionState); - public DeobfPanel getDeobfPanel() { - return deobfPanel; + this.updateAllClasses(); } public SearchDialog getSearchDialog() { @@ -623,7 +602,7 @@ public SearchDialog getSearchDialog() { } public void addMessage(Message message) { - JScrollBar verticalScrollBar = RightPanel.getPanel(CollabPanel.class).getMessageScrollPane().getVerticalScrollBar(); + JScrollBar verticalScrollBar = Docker.getDocker(CollabDocker.class).getMessageScrollPane().getVerticalScrollBar(); boolean isAtBottom = verticalScrollBar.getValue() >= verticalScrollBar.getMaximum() - verticalScrollBar.getModel().getExtent(); messageModel.addElement(message); @@ -641,9 +620,10 @@ public void setUserList(List users) { users.forEach(userModel::addElement); connectionStatusLabel.setText(String.format(I18n.translate("status.connected_user_count"), users.size())); - // 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(); + // if we were previously offline, we need to reload multiplayer-restricted dockers (only collab for now) so they can be used + CollabDocker collabDocker = Docker.getDocker(CollabDocker.class); + if (wasOffline && Dock.Util.isDocked(collabDocker)) { + collabDocker.setUp(); } } @@ -668,11 +648,9 @@ public void retranslateUi() { this.updateUiState(); this.menuBar.retranslateUi(); - this.obfPanel.retranslateUi(); - this.deobfPanel.retranslateUi(); this.infoPanel.retranslateUi(); this.editorTabbedPane.retranslateUi(); - for (RightPanel panel : RightPanel.getRightPanels().values()) { + for (Docker panel : Docker.getDockers().values()) { panel.retranslateUi(); } } 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 0bcc4881a..405e604e9 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java @@ -121,39 +121,39 @@ public void closeJar() { } public CompletableFuture openMappings(MappingFormat format, Path path) { - if (project == null) return CompletableFuture.completedFuture(null); + if (this.project == null) return CompletableFuture.completedFuture(null); - gui.setMappingsFile(path); + this.gui.setMappingsFile(path); - return ProgressDialog.runOffThread(gui.getFrame(), progress -> { + return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> { try { - MappingSaveParameters saveParameters = enigma.getProfile().getMappingSaveParameters(); + MappingSaveParameters saveParameters = this.enigma.getProfile().getMappingSaveParameters(); EntryTree mappings = format.read(path, progress, saveParameters); - project.setMappings(mappings); + this.project.setMappings(mappings); - loadedMappingFormat = format; - loadedMappingPath = path; + this.loadedMappingFormat = format; + this.loadedMappingPath = path; refreshClasses(); - chp.invalidateJavadoc(); + this.chp.invalidateJavadoc(); } catch (MappingParseException e) { - JOptionPane.showMessageDialog(gui.getFrame(), e.getMessage()); + JOptionPane.showMessageDialog(this.gui.getFrame(), e.getMessage()); } }); } @Override public void openMappings(EntryTree mappings) { - if (project == null) return; + if (this.project == null) return; - project.setMappings(mappings); + this.project.setMappings(mappings); refreshClasses(); - chp.invalidateJavadoc(); + this.chp.invalidateJavadoc(); } public CompletableFuture saveMappings(Path path) { - return saveMappings(path, loadedMappingFormat); + return saveMappings(path, this.loadedMappingFormat); } /** @@ -168,17 +168,17 @@ public CompletableFuture saveMappings(Path path) { * @return the future of saving */ public CompletableFuture saveMappings(Path path, MappingFormat format) { - if (project == null) return CompletableFuture.completedFuture(null); + if (this.project == null) return CompletableFuture.completedFuture(null); return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> { - EntryRemapper mapper = project.getMapper(); - MappingSaveParameters saveParameters = enigma.getProfile().getMappingSaveParameters(); + EntryRemapper mapper = this.project.getMapper(); + MappingSaveParameters saveParameters = this.enigma.getProfile().getMappingSaveParameters(); MappingDelta delta = mapper.takeMappingDelta(); - boolean saveAll = !path.equals(loadedMappingPath); + boolean saveAll = !path.equals(this.loadedMappingPath); - loadedMappingFormat = format; - loadedMappingPath = path; + this.loadedMappingFormat = format; + this.loadedMappingPath = path; if (saveAll) { format.write(mapper.getObfToDeobf(), path, progress, saveParameters); @@ -189,49 +189,45 @@ public CompletableFuture saveMappings(Path path, MappingFormat format) { } public void closeMappings() { - if (project == null) return; + if (this.project == null) return; - project.setMappings(null); + this.project.setMappings(null); this.gui.setMappingsFile(null); refreshClasses(); - chp.invalidateJavadoc(); + this.chp.invalidateJavadoc(); } public void reloadAll() { Path jarPath = this.project.getJarPath(); - MappingFormat loadedMappingFormat = this.loadedMappingFormat; - Path loadedMappingPath = this.loadedMappingPath; if (jarPath != null) { this.closeJar(); CompletableFuture f = this.openJar(jarPath); - if (loadedMappingFormat != null && loadedMappingPath != null) { - f.whenComplete((v, t) -> this.openMappings(loadedMappingFormat, loadedMappingPath)); + if (this.loadedMappingFormat != null && this.loadedMappingPath != null) { + f.whenComplete((v, t) -> this.openMappings(this.loadedMappingFormat, this.loadedMappingPath)); } } } public void reloadMappings() { - MappingFormat loadedMappingFormat = this.loadedMappingFormat; - Path loadedMappingPath = this.loadedMappingPath; - if (loadedMappingFormat != null && loadedMappingPath != null) { + if (this.loadedMappingFormat != null && this.loadedMappingPath != null) { this.closeMappings(); - this.openMappings(loadedMappingFormat, loadedMappingPath); + this.openMappings(this.loadedMappingFormat, this.loadedMappingPath); } } public CompletableFuture dropMappings() { - if (project == null) return CompletableFuture.completedFuture(null); + if (this.project == null) return CompletableFuture.completedFuture(null); - return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> project.dropMappings(progress)); + return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> this.project.dropMappings(progress)); } public CompletableFuture exportSource(final Path path) { - if (project == null) return CompletableFuture.completedFuture(null); + if (this.project == null) return CompletableFuture.completedFuture(null); return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> { - EnigmaProject.JarExport jar = project.exportRemappedJar(progress); - jar.decompileStream(progress, chp.getDecompilerService(), EnigmaProject.DecompileErrorStrategy.TRACE_AS_SOURCE) + EnigmaProject.JarExport jar = this.project.exportRemappedJar(progress); + jar.decompileStream(progress, this.chp.getDecompilerService(), EnigmaProject.DecompileErrorStrategy.TRACE_AS_SOURCE) .forEach(source -> { try { source.writeTo(source.resolvePath(path)); @@ -243,33 +239,33 @@ public CompletableFuture exportSource(final Path path) { } public CompletableFuture exportJar(final Path path) { - if (project == null) return CompletableFuture.completedFuture(null); + if (this.project == null) return CompletableFuture.completedFuture(null); return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> { - EnigmaProject.JarExport jar = project.exportRemappedJar(progress); + EnigmaProject.JarExport jar = this.project.exportRemappedJar(progress); jar.write(path, progress); }); } public void setTokenHandle(ClassHandle handle) { - if (tokenHandle != null) { - tokenHandle.close(); + if (this.tokenHandle != null) { + this.tokenHandle.close(); } - tokenHandle = handle; + this.tokenHandle = handle; } public ClassHandle getTokenHandle() { - return tokenHandle; + return this.tokenHandle; } public ReadableToken getReadableToken(Token token) { - if (tokenHandle == null) { + if (this.tokenHandle == null) { return null; } try { - return tokenHandle.getSource().get() + return this.tokenHandle.getSource().get() .map(DecompiledClassSource::getIndex) .map(index -> new ReadableToken( index.getLineNumber(token.start), @@ -326,26 +322,26 @@ public List getTokensForReference(DecompiledClassSource source, EntryRefe public void openPreviousReference() { if (hasPreviousReference()) { - this.gui.showReference(referenceHistory.goBack()); + this.gui.showReference(this.referenceHistory.goBack()); } } public boolean hasPreviousReference() { - return referenceHistory != null && referenceHistory.canGoBack(); + return this.referenceHistory != null && this.referenceHistory.canGoBack(); } public void openNextReference() { if (hasNextReference()) { - this.gui.showReference(referenceHistory.goForward()); + this.gui.showReference(this.referenceHistory.goForward()); } } public boolean hasNextReference() { - return referenceHistory != null && referenceHistory.canGoForward(); + return this.referenceHistory != null && this.referenceHistory.canGoForward(); } public void navigateTo(Entry entry) { - if (!project.isNavigable(entry)) { + if (!this.project.isNavigable(entry)) { // entry is not in the jar. Ignore it return; } @@ -353,14 +349,14 @@ public void navigateTo(Entry entry) { } public void navigateTo(EntryReference, Entry> reference) { - if (!project.isNavigable(reference.getLocationClassEntry())) { + if (!this.project.isNavigable(reference.getLocationClassEntry())) { return; } openReference(reference); } public void refreshClasses() { - if (project == null) return; + if (this.project == null) return; List obfClasses = Lists.newArrayList(); List deobfClasses = Lists.newArrayList(); @@ -370,22 +366,17 @@ public void refreshClasses() { } public void addSeparatedClasses(List obfClasses, List deobfClasses) { - EntryRemapper mapper = project.getMapper(); + EntryRemapper mapper = this.project.getMapper(); - Collection classes = project.getJarIndex().getEntryIndex().getClasses(); + Collection classes = this.project.getJarIndex().getEntryIndex().getClasses(); Stream visibleClasses = classes.stream() .filter(entry -> !entry.isInnerClass()); visibleClasses.forEach(entry -> { - if (gui.isSingleClassTree()) { - deobfClasses.add(entry); - return; - } - TranslateResult result = mapper.extendedDeobfuscate(entry); ClassEntry deobfEntry = result.getValue(); - List obfService = enigma.getServices().get(ObfuscationTestService.TYPE); + List obfService = this.enigma.getServices().get(ObfuscationTestService.TYPE); boolean obfuscated = result.isObfuscated() && deobfEntry.equals(entry); if (obfuscated @@ -409,25 +400,25 @@ public StructureTreeNode getClassStructure(ClassEntry entry, StructureTreeOption } public ClassInheritanceTreeNode getClassInheritance(ClassEntry entry) { - Translator translator = project.getMapper().getDeobfuscator(); - ClassInheritanceTreeNode rootNode = indexTreeBuilder.buildClassInheritance(translator, entry); + Translator translator = this.project.getMapper().getDeobfuscator(); + ClassInheritanceTreeNode rootNode = this.indexTreeBuilder.buildClassInheritance(translator, entry); return ClassInheritanceTreeNode.findNode(rootNode, entry); } public ClassImplementationsTreeNode getClassImplementations(ClassEntry entry) { - Translator translator = project.getMapper().getDeobfuscator(); + Translator translator = this.project.getMapper().getDeobfuscator(); return this.indexTreeBuilder.buildClassImplementations(translator, entry); } public MethodInheritanceTreeNode getMethodInheritance(MethodEntry entry) { - Translator translator = project.getMapper().getDeobfuscator(); + Translator translator = this.project.getMapper().getDeobfuscator(); MethodInheritanceTreeNode rootNode = indexTreeBuilder.buildMethodInheritance(translator, entry); return MethodInheritanceTreeNode.findNode(rootNode, entry); } public MethodImplementationsTreeNode getMethodImplementations(MethodEntry entry) { - Translator translator = project.getMapper().getDeobfuscator(); - List rootNodes = indexTreeBuilder.buildMethodImplementations(translator, entry); + Translator translator = this.project.getMapper().getDeobfuscator(); + List rootNodes = this.indexTreeBuilder.buildMethodImplementations(translator, entry); if (rootNodes.isEmpty()) { return null; } @@ -438,23 +429,23 @@ public MethodImplementationsTreeNode getMethodImplementations(MethodEntry entry) } public ClassReferenceTreeNode getClassReferences(ClassEntry entry) { - Translator deobfuscator = project.getMapper().getDeobfuscator(); + Translator deobfuscator = this.project.getMapper().getDeobfuscator(); ClassReferenceTreeNode rootNode = new ClassReferenceTreeNode(deobfuscator, entry); - rootNode.load(project.getJarIndex(), true); + rootNode.load(this.project.getJarIndex(), true); return rootNode; } public FieldReferenceTreeNode getFieldReferences(FieldEntry entry) { - Translator translator = project.getMapper().getDeobfuscator(); + Translator translator = this.project.getMapper().getDeobfuscator(); FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(translator, entry); - rootNode.load(project.getJarIndex(), true); + rootNode.load(this.project.getJarIndex(), true); return rootNode; } public MethodReferenceTreeNode getMethodReferences(MethodEntry entry, boolean recursive) { - Translator translator = project.getMapper().getDeobfuscator(); + Translator translator = this.project.getMapper().getDeobfuscator(); MethodReferenceTreeNode rootNode = new MethodReferenceTreeNode(translator, entry); - rootNode.load(project.getJarIndex(), true, recursive); + rootNode.load(this.project.getJarIndex(), true, recursive); return rootNode; } @@ -463,7 +454,7 @@ public boolean applyChangeFromServer(EntryChange change) { ValidationContext vc = new ValidationContext(); vc.setActiveElement(PrintValidatable.INSTANCE); this.applyChange0(vc, change); - gui.updateStructure(gui.getActiveEditor()); + this.gui.updateStructure(this.gui.getActiveEditor()); return vc.canProceed(); } @@ -480,7 +471,7 @@ public void validateChange(ValidationContext vc, EntryChange change) { public void applyChange(ValidationContext vc, EntryChange change) { this.applyChange0(vc, change); - gui.updateStructure(gui.getActiveEditor()); + this.gui.updateStructure(this.gui.getActiveEditor()); if (!vc.canProceed()) return; this.sendPacket(new EntryChangeC2SPacket(change)); } @@ -506,12 +497,13 @@ private void applyChange0(ValidationContext vc, EntryChange change) { if (!Objects.equals(prev.javadoc(), mapping.javadoc())) { this.chp.invalidateJavadoc(target.getTopLevelClass()); } - gui.updateStructure(gui.getActiveEditor()); + + this.gui.updateStructure(this.gui.getActiveEditor()); } public void openStats(Set includedMembers, String topLevelPackage, boolean includeSynthetic) { - ProgressDialog.runOffThread(gui.getFrame(), progress -> { - String data = new StatsGenerator(project).generate(progress, includedMembers, topLevelPackage, includeSynthetic).getTreeJson(); + ProgressDialog.runOffThread(this.gui.getFrame(), progress -> { + String data = new StatsGenerator(this.project).generate(progress, includedMembers, topLevelPackage, includeSynthetic).getTreeJson(); try { File statsFile = File.createTempFile("stats", ".html"); @@ -531,76 +523,75 @@ public void openStats(Set includedMembers, String topLevelPackage, } public void setDecompiler(DecompilerService service) { - if (chp != null) { - chp.setDecompilerService(service); + if (this.chp != null) { + this.chp.setDecompilerService(service); } } public ClassHandleProvider getClassHandleProvider() { - return chp; + return this.chp; } public EnigmaClient getClient() { - return client; + return this.client; } public EnigmaServer getServer() { - return server; + return this.server; } public void createClient(String username, String ip, int port, char[] password) throws IOException { client = new EnigmaClient(this, ip, port); client.connect(); - client.sendPacket(new LoginC2SPacket(project.getJarChecksum(), password, username)); + client.sendPacket(new LoginC2SPacket(this.project.getJarChecksum(), password, username)); gui.setConnectionState(ConnectionState.CONNECTED); } public void createServer(int port, char[] password) throws IOException { - server = new IntegratedEnigmaServer(project.getJarChecksum(), password, EntryRemapper.mapped(project.getJarIndex(), new HashEntryTree<>(project.getMapper().getObfToDeobf())), port); - server.start(); - client = new EnigmaClient(this, "127.0.0.1", port); - client.connect(); - client.sendPacket(new LoginC2SPacket(project.getJarChecksum(), password, NetConfig.getUsername())); - gui.setConnectionState(ConnectionState.HOSTING); + this.server = new IntegratedEnigmaServer(this.project.getJarChecksum(), password, EntryRemapper.mapped(this.project.getJarIndex(), new HashEntryTree<>(this.project.getMapper().getObfToDeobf())), port); + this.server.start(); + this.client = new EnigmaClient(this, "127.0.0.1", port); + this.client.connect(); + this.client.sendPacket(new LoginC2SPacket(this.project.getJarChecksum(), password, NetConfig.getUsername())); + this.gui.setConnectionState(ConnectionState.HOSTING); } @Override public synchronized void disconnectIfConnected(String reason) { - if (client == null && server == null) { + if (this.client == null && this.server == null) { return; } - if (client != null) { - client.disconnect(); + if (this.client != null) { + this.client.disconnect(); } - if (server != null) { - server.stop(); + if (this.server != null) { + this.server.stop(); } - client = null; - server = null; + this.client = null; + this.server = null; SwingUtilities.invokeLater(() -> { if (reason != null) { - JOptionPane.showMessageDialog(gui.getFrame(), I18n.translate(reason), I18n.translate("disconnect.disconnected"), JOptionPane.INFORMATION_MESSAGE); + JOptionPane.showMessageDialog(this.gui.getFrame(), I18n.translate(reason), I18n.translate("disconnect.disconnected"), JOptionPane.INFORMATION_MESSAGE); } - gui.setConnectionState(ConnectionState.NOT_CONNECTED); + this.gui.setConnectionState(ConnectionState.NOT_CONNECTED); }); } @Override public void sendPacket(Packet packet) { - if (client != null) { - client.sendPacket(packet); + if (this.client != null) { + this.client.sendPacket(packet); } } @Override public void addMessage(Message message) { - gui.addMessage(message); + this.gui.addMessage(message); } @Override public void updateUserList(List users) { - gui.setUserList(users); + this.gui.setUserList(users); } - } 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 62a523dd2..c657cc30b 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/Main.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/Main.java @@ -21,6 +21,7 @@ import com.google.common.io.MoreFiles; import cuchaz.enigma.gui.config.keybind.KeyBinds; +import cuchaz.enigma.gui.docker.AllClassesDocker; import joptsimple.*; import cuchaz.enigma.EnigmaProfile; @@ -115,10 +116,6 @@ public static void main(String[] args) throws IOException { Gui gui = new Gui(parsedProfile, editables); GuiController controller = gui.getController(); - - if (options.has("single-class-tree")) { - gui.setSingleClassTree(true); - } if (Boolean.parseBoolean(System.getProperty("enigma.catchExceptions", "true"))) { // install a global exception handler to the event thread @@ -131,6 +128,11 @@ public static void main(String[] args) throws IOException { }); } + if (options.has("single-class-tree")) { + System.out.println("warning: --single-class-tree is deprecated and will be removed in the next minor version! simply use the \"all classes\" docker instead."); + gui.openDocker(AllClassesDocker.class); + } + if (options.has(jar)) { Path jarPath = options.valueOf(jar); controller.openJar(jarPath) diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/NestedPackages.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/NestedPackages.java index 091aab7e3..4ad0145da 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/NestedPackages.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/NestedPackages.java @@ -104,6 +104,10 @@ public void removeClassNode(ClassEntry entry) { } } + public Collection getClassEntries() { + return classToNode.keySet(); + } + public Collection getPackageNodes() { return packageToNode.values(); } 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 b9c628595..91e26977c 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 @@ -1,12 +1,15 @@ package cuchaz.enigma.gui.config; import java.awt.*; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; import java.util.OptionalInt; import cuchaz.enigma.config.ConfigContainer; import cuchaz.enigma.config.ConfigSection; -import cuchaz.enigma.gui.panels.right.RightPanel; +import cuchaz.enigma.gui.docker.Dock; +import cuchaz.enigma.gui.docker.Docker; import cuchaz.enigma.gui.util.ScaleUtil; import cuchaz.enigma.utils.I18n; @@ -16,9 +19,9 @@ public final class UiConfig { 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 VERTICAL_DIVIDER_LOCATIONS = "Vertical Divider Locations"; + public static final String HORIZONTAL_DIVIDER_LOCATIONS = "Horizontal Divider Locations"; + public static final String HOSTED_DOCKERS = "Hosted Dockers"; public static final String THEMES = "Themes"; public static final String COLORS = "Colors"; public static final String DECOMPILER = "Decompiler"; @@ -34,6 +37,7 @@ public final class UiConfig { public static final String DEFAULT_2 = "Default 2"; public static final String SMALL = "Small"; public static final String EDITOR = "Editor"; + public static final String SAVED_WITH_LEFT_OPEN = "Saved With Left Open"; 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"; @@ -66,6 +70,7 @@ public final class UiConfig { 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"; + public static final String DOCK_HIGHLIGHT = "Dock Highlight"; private UiConfig() { } @@ -120,40 +125,70 @@ public static void setScaleFactor(float 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 void setHostedDockers(Docker.Side side, Docker[] dockers) { + String[] dockerData = new String[]{"", ""}; + for (int i = 0; i < dockers.length; i++) { + Docker docker = dockers[i]; + + if (docker != null) { + Docker.Location location = Dock.Util.findLocation(docker); + if (location != null) { + dockerData[i] = (docker.getId() + ":" + location.verticalLocation()); + } + } + } + + swing.data().section(HOSTED_DOCKERS).setArray(side.name(), dockerData); } - public static String getSelectedRightPanel() { - return swing.data().section(GENERAL).setIfAbsentString(RIGHT_PANEL, RightPanel.DEFAULT); + public static Optional> getHostedDockers(Docker.Side side) { + Optional hostedDockers = swing.data().section(HOSTED_DOCKERS).getArray(side.name()); + + if (hostedDockers.isEmpty()) { + return Optional.empty(); + } + + Map dockers = new HashMap<>(); + + for (String dockInfo : hostedDockers.get()) { + if (!dockInfo.isBlank()) { + String[] split = dockInfo.split(":"); + try { + Docker.VerticalLocation location = Docker.VerticalLocation.valueOf(split[1]); + Docker docker = Docker.getDocker(split[0]); + + dockers.put(docker, location); + } catch (Exception e) { + System.err.println("failed to read docker state for " + dockInfo + ", ignoring! (" + e.getMessage() + ")"); + } + } + } + + return Optional.of(dockers); } - public static void setRightPanelDividerLocation(String id, int width) { - swing.data().section(RIGHT_PANEL_DIVIDER_LOCATIONS).setInt(id, width); + public static void setVerticalDockDividerLocation(Docker.Side side, int location) { + swing.data().section(VERTICAL_DIVIDER_LOCATIONS).setInt(side.name(), location); } - public static int getRightPanelDividerLocation(String id, int defaultLocation) { - return swing.data().section(RIGHT_PANEL_DIVIDER_LOCATIONS).setIfAbsentInt(id, defaultLocation); + public static int getVerticalDockDividerLocation(Docker.Side side) { + return swing.data().section(VERTICAL_DIVIDER_LOCATIONS).setIfAbsentInt(side.name(), 300); } - /** - * Gets the dimensions of the different panels of the GUI. - *

These dimensions are used to determine the location of the separators between these panels.

- * - *
    - *
  • [0] - The height of the obfuscated classes panel
  • - *
  • [1] - The width of the classes panel
  • - *
  • [2] - The width of the center panel
  • - *
- * - * @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 }); + public static void setHorizontalDividerLocation(Docker.Side side, int location) { + swing.data().section(HORIZONTAL_DIVIDER_LOCATIONS).setInt(side.name(), location); + } + + public static int getHorizontalDividerLocation(Docker.Side side) { + return swing.data().section(HORIZONTAL_DIVIDER_LOCATIONS).setIfAbsentInt(side.name(), side == Docker.Side.LEFT ? 300 : 700); + } + + public static void setSavedWithLeftOpen(boolean open) { + swing.data().section(GENERAL).setBool(SAVED_WITH_LEFT_OPEN, open); } - public static void setLayout(int leftV, int left, int right) { - swing.data().section(MAIN_WINDOW).setIntArray(LAYOUT, new int[] { leftV, left, right }); + public static boolean getSavedWithLeftOpen() { + return swing.data().section(GENERAL).setIfAbsentBool(SAVED_WITH_LEFT_OPEN, false); } public static LookAndFeel getLookAndFeel() { @@ -192,91 +227,95 @@ private static Color getThemeColorRgb(String colorName) { } public static Color getObfuscatedColor() { - return getThemeColorRgba("Obfuscated"); + return getThemeColorRgba(OBFUSCATED); } public static Color getObfuscatedOutlineColor() { - return getThemeColorRgba("Obfuscated Outline"); + return getThemeColorRgba(OBFUSCATED_OUTLINE); } public static Color getProposedColor() { - return getThemeColorRgba("Proposed"); + return getThemeColorRgba(PROPOSED); } public static Color getProposedOutlineColor() { - return getThemeColorRgba("Proposed Outline"); + return getThemeColorRgba(PROPOSED_OUTLINE); } public static Color getDeobfuscatedColor() { - return getThemeColorRgba("Deobfuscated"); + return getThemeColorRgba(DEOBFUSCATED); } public static Color getDeobfuscatedOutlineColor() { - return getThemeColorRgba("Deobfuscated Outline"); + return getThemeColorRgba(DEOBFUSCATED_OUTLINE); } public static Color getDebugTokenColor() { - return getThemeColorRgba("Debug Token"); + return getThemeColorRgba(DEBUG_TOKEN); } public static Color getDebugTokenOutlineColor() { - return getThemeColorRgba("Debug Token Outline"); + return getThemeColorRgba(DEBUG_TOKEN_OUTLINE); } public static Color getEditorBackgroundColor() { - return getThemeColorRgb("Editor Background"); + return getThemeColorRgb(EDITOR_BACKGROUND); } public static Color getHighlightColor() { - return getThemeColorRgb("Highlight"); + return getThemeColorRgb(HIGHLIGHT); } public static Color getCaretColor() { - return getThemeColorRgb("Caret"); + return getThemeColorRgb(CARET); } public static Color getSelectionHighlightColor() { - return getThemeColorRgb("Selection Highlight"); + return getThemeColorRgb(SELECTION_HIGHLIGHT); } public static Color getStringColor() { - return getThemeColorRgb("String"); + return getThemeColorRgb(STRING); } public static Color getNumberColor() { - return getThemeColorRgb("Number"); + return getThemeColorRgb(NUMBER); } public static Color getOperatorColor() { - return getThemeColorRgb("Operator"); + return getThemeColorRgb(OPERATOR); } public static Color getDelimiterColor() { - return getThemeColorRgb("Delimiter"); + return getThemeColorRgb(DELIMITER); } public static Color getTypeColor() { - return getThemeColorRgb("Type"); + return getThemeColorRgb(TYPE); } public static Color getIdentifierColor() { - return getThemeColorRgb("Identifier"); + return getThemeColorRgb(IDENTIFIER); } public static Color getTextColor() { - return getThemeColorRgb("Text"); + return getThemeColorRgb(TEXT); } public static Color getLineNumbersForegroundColor() { - return getThemeColorRgb("Line Numbers Foreground"); + return getThemeColorRgb(LINE_NUMBERS_FOREGROUND); } public static Color getLineNumbersBackgroundColor() { - return getThemeColorRgb("Line Numbers Background"); + return getThemeColorRgb(LINE_NUMBERS_BACKGROUND); } public static Color getLineNumbersSelectedColor() { - return getThemeColorRgb("Line Numbers Selected"); + return getThemeColorRgb(LINE_NUMBERS_SELECTED); + } + + public static Color getDockHighlightColor() { + return getThemeColorRgb(DOCK_HIGHLIGHT); } public static boolean useCustomFonts() { @@ -309,34 +348,18 @@ public static Font getDefaultFont() { return getActiveFont(DEFAULT).orElseGet(() -> ScaleUtil.scaleFont(Font.decode(Font.DIALOG).deriveFont(Font.BOLD))); } - public static void setDefaultFont(Font font) { - setFont(DEFAULT, font); - } - public static Font getDefault2Font() { return getActiveFont(DEFAULT_2).orElseGet(() -> ScaleUtil.scaleFont(Font.decode(Font.DIALOG))); } - public static void setDefault2Font(Font font) { - setFont(DEFAULT_2, font); - } - public static Font getSmallFont() { return getActiveFont(SMALL).orElseGet(() -> ScaleUtil.scaleFont(Font.decode(Font.DIALOG))); } - public static void setSmallFont(Font font) { - setFont(SMALL, font); - } - public static Font getEditorFont() { return getActiveFont(EDITOR).orElseGet(UiConfig::getFallbackEditorFont); } - public static void setEditorFont(Font font) { - setFont(EDITOR, font); - } - /** * Gets the fallback editor font. * It is used: @@ -435,6 +458,8 @@ public static void setIncludeSyntheticParameters(boolean b) { public static void setLookAndFeelDefaults(LookAndFeel laf, boolean isDark) { ConfigSection s = swing.data().section(THEMES).section(laf.name()).section(COLORS); + + // theme-dependent 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); @@ -510,5 +535,8 @@ public static void setLookAndFeelDefaults(LookAndFeel laf, boolean isDark) { s.setIfAbsentRgbColor(DEBUG_TOKEN_OUTLINE, 0x701367); s.setIfAbsentDouble(DEBUG_TOKEN_OUTLINE_ALPHA, 0.5); } + + // theme-independent colors + s.setIfAbsentRgbColor(DOCK_HIGHLIGHT, 0x0000FF); } } diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java index 6968ea532..34d0c263e 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java @@ -27,6 +27,9 @@ import cuchaz.enigma.gui.Gui; import cuchaz.enigma.gui.GuiController; import cuchaz.enigma.gui.config.keybind.KeyBinds; +import cuchaz.enigma.gui.docker.Docker; +import cuchaz.enigma.gui.docker.DeobfuscatedClassesDocker; +import cuchaz.enigma.gui.docker.ObfuscatedClassesDocker; import cuchaz.enigma.gui.util.AbstractListCellRenderer; import cuchaz.enigma.gui.util.GuiUtil; import cuchaz.enigma.gui.util.ScaleUtil; @@ -94,7 +97,7 @@ public void changedUpdate(DocumentEvent e) { openEntry(entry); } })); - contentPane.add(new JScrollPane(classList, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER), BorderLayout.CENTER); + contentPane.add(new JScrollPane(classList, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER), BorderLayout.CENTER); JPanel buttonBar = new JPanel(); buttonBar.setLayout(new FlowLayout(FlowLayout.RIGHT)); @@ -160,21 +163,25 @@ private void openSelected() { } } - private void openEntry(SearchEntryImpl e) { + private void openEntry(SearchEntryImpl entryImpl) { close(); - su.hit(e); - parent.getController().navigateTo(e.obf); - if (e.obf instanceof ClassEntry) { - if (e.deobf != null) { - parent.getDeobfPanel().deobfClasses.setSelectionClass((ClassEntry) e.deobf); + su.hit(entryImpl); + parent.getController().navigateTo(entryImpl.obf); + if (entryImpl.obf instanceof ClassEntry entry) { + if (entryImpl.deobf != null) { + DeobfuscatedClassesDocker deobfuscatedPanel = Docker.getDocker(DeobfuscatedClassesDocker.class); + deobfuscatedPanel.getClassSelector().setSelectionClass((ClassEntry) entryImpl.deobf); } else { - parent.getObfPanel().obfClasses.setSelectionClass((ClassEntry) e.obf); + ObfuscatedClassesDocker obfuscatedPanel = Docker.getDocker(ObfuscatedClassesDocker.class); + obfuscatedPanel.getClassSelector().setSelectionClass(entry); } } else { - if (e.deobf != null) { - parent.getDeobfPanel().deobfClasses.setSelectionClass((ClassEntry) e.deobf.getParent()); - } else { - parent.getObfPanel().obfClasses.setSelectionClass((ClassEntry) e.obf.getParent()); + if (entryImpl.deobf != null && entryImpl.deobf.getParent() != null) { + DeobfuscatedClassesDocker deobfuscatedPanel = Docker.getDocker(DeobfuscatedClassesDocker.class); + deobfuscatedPanel.getClassSelector().setSelectionClass((ClassEntry) entryImpl.deobf.getParent()); + } else if (entryImpl.obf.getParent() != null) { + ObfuscatedClassesDocker obfuscatedPanel = Docker.getDocker(ObfuscatedClassesDocker.class); + obfuscatedPanel.getClassSelector().setSelectionClass((ClassEntry) entryImpl.obf.getParent()); } } } diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/AbstractInheritanceTree.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/AbstractInheritanceTreeDocker.java similarity index 87% rename from enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/AbstractInheritanceTree.java rename to enigma-swing/src/main/java/cuchaz/enigma/gui/docker/AbstractInheritanceTreeDocker.java index 765532677..9c59957fa 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/AbstractInheritanceTree.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/AbstractInheritanceTreeDocker.java @@ -1,4 +1,4 @@ -package cuchaz.enigma.gui.panels.right; +package cuchaz.enigma.gui.docker; import cuchaz.enigma.analysis.AbstractClassTreeNode; import cuchaz.enigma.analysis.AbstractMethodTreeNode; @@ -17,10 +17,10 @@ import javax.swing.tree.TreePath; import java.awt.event.MouseEvent; -public abstract class AbstractInheritanceTree extends RightPanel { +public abstract class AbstractInheritanceTreeDocker extends Docker { private final JTree tree = new JTree(); - protected AbstractInheritanceTree(Gui gui, TreeCellRenderer cellRenderer) { + protected AbstractInheritanceTreeDocker(Gui gui, TreeCellRenderer cellRenderer) { super(gui); this.tree.setModel(null); @@ -69,7 +69,7 @@ public void display(Entry entry) { protected abstract DefaultMutableTreeNode getNodeFor(Entry entry); @Override - public ButtonPosition getButtonPosition() { - return ButtonPosition.TOP; + public Location getButtonPosition() { + return new Location(Side.RIGHT, VerticalLocation.TOP); } } diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/AllClassesDocker.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/AllClassesDocker.java new file mode 100644 index 000000000..abf56c580 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/AllClassesDocker.java @@ -0,0 +1,25 @@ +package cuchaz.enigma.gui.docker; + +import cuchaz.enigma.gui.ClassSelector; +import cuchaz.enigma.gui.Gui; + +public class AllClassesDocker extends ClassesDocker { + public AllClassesDocker(Gui gui) { + super(gui, new ClassSelector(gui, ClassSelector.DEOBF_CLASS_COMPARATOR, true)); + } + + @Override + public String getId() { + return Type.ALL_CLASSES; + } + + @Override + public Location getButtonPosition() { + return new Location(Side.LEFT, VerticalLocation.TOP); + } + + @Override + public Location getPreferredLocation() { + return new Location(Side.LEFT, VerticalLocation.FULL); + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/CallsTree.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/CallsTreeDocker.java similarity index 92% rename from enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/CallsTree.java rename to enigma-swing/src/main/java/cuchaz/enigma/gui/docker/CallsTreeDocker.java index f2ca263dc..03977c229 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/CallsTree.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/CallsTreeDocker.java @@ -1,4 +1,4 @@ -package cuchaz.enigma.gui.panels.right; +package cuchaz.enigma.gui.docker; import java.awt.BorderLayout; import java.awt.event.MouseEvent; @@ -27,11 +27,11 @@ import cuchaz.enigma.translation.representation.entry.FieldEntry; import cuchaz.enigma.translation.representation.entry.MethodEntry; -public class CallsTree extends RightPanel { +public class CallsTreeDocker extends Docker { private final JTree tree = new JTree(); private final JList tokens = new JList<>(); - public CallsTree(Gui gui) { + public CallsTreeDocker(Gui gui) { super(gui); this.tree.setModel(null); this.tree.setCellRenderer(new CallsTreeCellRenderer(gui)); @@ -115,8 +115,13 @@ private void onTokenClicked(MouseEvent event) { } @Override - public ButtonPosition getButtonPosition() { - return ButtonPosition.TOP; + public Location getButtonPosition() { + return new Location(Side.RIGHT, VerticalLocation.TOP); + } + + @Override + public Location getPreferredLocation() { + return new Location(Side.RIGHT, VerticalLocation.FULL); } @Override diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/ClassesDocker.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/ClassesDocker.java new file mode 100644 index 000000000..dbbe6e859 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/ClassesDocker.java @@ -0,0 +1,24 @@ +package cuchaz.enigma.gui.docker; + +import cuchaz.enigma.gui.ClassSelector; +import cuchaz.enigma.gui.Gui; + +import javax.swing.JScrollPane; +import java.awt.BorderLayout; + +public abstract class ClassesDocker extends Docker { + protected final ClassSelector selector; + + protected ClassesDocker(Gui gui, ClassSelector selector) { + super(gui); + this.selector = selector; + this.selector.setSelectionListener(gui.getController()::navigateTo); + this.selector.setRenameSelectionListener(gui::onRenameFromClassTree); + + this.add(new JScrollPane(this.selector), BorderLayout.CENTER); + } + + public ClassSelector getClassSelector() { + return this.selector; + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/CollabPanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/CollabDocker.java similarity index 56% rename from enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/CollabPanel.java rename to enigma-swing/src/main/java/cuchaz/enigma/gui/docker/CollabDocker.java index 29e9b47cb..257ba9a9c 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/CollabPanel.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/CollabDocker.java @@ -1,6 +1,7 @@ -package cuchaz.enigma.gui.panels.right; +package cuchaz.enigma.gui.docker; import cuchaz.enigma.gui.Gui; +import cuchaz.enigma.gui.docker.component.DockerTitleBar; import cuchaz.enigma.network.packet.MessageC2SPacket; import cuchaz.enigma.utils.I18n; @@ -14,38 +15,54 @@ import java.awt.event.ActionEvent; import java.util.function.Supplier; -public class CollabPanel extends RightPanel { +public class CollabDocker extends Docker { + private static final Supplier OFFLINE_TEXT_PROVIDER = () -> I18n.translate("docker.collab.offline_text"); + private static final Supplier START_SERVER_TEXT_PROVIDER = () -> I18n.translate("menu.collab.server.start"); + private static final Supplier CONNECT_TO_SERVER_TEXT_PROVIDER = () -> I18n.translate("menu.collab.connect"); + + private static final Supplier USERS_TITLE_PROVIDER = () -> I18n.translate("docker.collab.users_title"); + private static final Supplier MESSAGES_TITLE_PROVIDER = () -> I18n.translate("docker.collab.messages_title"); + private static final Supplier SEND_BUTTON_TEXT_PROVIDER = () -> I18n.translate("docker.collab.send"); + private final JLabel offlineLabel; + private final DockerTitleBar titleCopy; + private final JButton startServerButton; + private final JButton connectToServerButton; 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) { + public CollabDocker(Gui gui) { super(gui); // offline panel this.whenOfflinePanel = new JPanel(new BorderLayout()); - this.offlineLabel = new JLabel(this.offlineTextProvider.get()); + this.offlineLabel = new JLabel(OFFLINE_TEXT_PROVIDER.get()); JPanel offlineTopPanel = new JPanel(new BorderLayout()); - // there are ghosts in my code - this.titleCopy = new JLabel(this.titleProvider.get()); + JPanel connectionButtonPanel = new JPanel(new BorderLayout()); + this.startServerButton = new JButton(START_SERVER_TEXT_PROVIDER.get()); + this.connectToServerButton = new JButton(CONNECT_TO_SERVER_TEXT_PROVIDER.get()); + connectionButtonPanel.add(this.startServerButton, BorderLayout.NORTH); + connectionButtonPanel.add(this.connectToServerButton, BorderLayout.SOUTH); + + this.startServerButton.addActionListener(e -> this.gui.getMenuBar().onStartServerClicked()); + this.connectToServerButton.addActionListener(e -> this.gui.getMenuBar().onConnectClicked()); + + // we make a copy of the title bar to avoid having to shuffle it around both panels + this.titleCopy = new DockerTitleBar(this, this.titleSupplier); - offlineTopPanel.add(this.offlineLabel, BorderLayout.SOUTH); offlineTopPanel.add(this.titleCopy, BorderLayout.NORTH); + offlineTopPanel.add(this.offlineLabel, BorderLayout.CENTER); + offlineTopPanel.add(connectionButtonPanel, BorderLayout.SOUTH); this.whenOfflinePanel.add(offlineTopPanel, BorderLayout.NORTH); // online panel @@ -56,7 +73,7 @@ public CollabPanel(Gui gui) { JPanel userListPanel = new JPanel(new BorderLayout()); JScrollPane userScrollPane = new JScrollPane(gui.getUsers()); - this.usersTitle = new JLabel(this.usersTitleProvider.get()); + this.usersTitle = new JLabel(USERS_TITLE_PROVIDER.get()); userListPanel.add(this.usersTitle, BorderLayout.NORTH); userListPanel.add(userScrollPane, BorderLayout.CENTER); @@ -74,9 +91,9 @@ public void actionPerformed(ActionEvent e) { } }; this.pendingMessageBox.addActionListener(sendListener); - this.sendPendingMessageButton = new JButton(this.sendButtonTextProvider.get()); + this.sendPendingMessageButton = new JButton(SEND_BUTTON_TEXT_PROVIDER.get()); this.sendPendingMessageButton.setAction(sendListener); - this.messagesTitle = new JLabel(this.messagesTitleProvider.get()); + this.messagesTitle = new JLabel(MESSAGES_TITLE_PROVIDER.get()); JPanel chatPanel = new JPanel(new BorderLayout()); chatPanel.add(this.pendingMessageBox, BorderLayout.CENTER); chatPanel.add(this.sendPendingMessageButton, BorderLayout.EAST); @@ -92,8 +109,13 @@ public void actionPerformed(ActionEvent e) { } @Override - public ButtonPosition getButtonPosition() { - return ButtonPosition.BOTTOM; + public Location getButtonPosition() { + return new Location(Side.RIGHT, VerticalLocation.BOTTOM); + } + + @Override + public Location getPreferredLocation() { + return new Location(Side.RIGHT, VerticalLocation.FULL); } @Override @@ -121,11 +143,13 @@ public JScrollPane getMessageScrollPane() { @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()); + this.offlineLabel.setText(OFFLINE_TEXT_PROVIDER.get()); + this.sendPendingMessageButton.setText(SEND_BUTTON_TEXT_PROVIDER.get()); + this.usersTitle.setText(USERS_TITLE_PROVIDER.get()); + this.messagesTitle.setText(MESSAGES_TITLE_PROVIDER.get()); + this.startServerButton.setText(START_SERVER_TEXT_PROVIDER.get()); + this.connectToServerButton.setText(CONNECT_TO_SERVER_TEXT_PROVIDER.get()); + this.titleCopy.retranslateUi(); } @Override diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/DeobfuscatedClassesDocker.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/DeobfuscatedClassesDocker.java new file mode 100644 index 000000000..8e3e68188 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/DeobfuscatedClassesDocker.java @@ -0,0 +1,53 @@ +package cuchaz.enigma.gui.docker; + +import cuchaz.enigma.gui.ClassSelector; +import cuchaz.enigma.gui.Gui; +import cuchaz.enigma.gui.elements.DeobfPanelPopupMenu; +import cuchaz.enigma.gui.util.GuiUtil; + +import javax.swing.SwingUtilities; +import java.awt.event.MouseEvent; + +public class DeobfuscatedClassesDocker extends ClassesDocker { + private final DeobfPanelPopupMenu popupMenu; + + public DeobfuscatedClassesDocker(Gui gui) { + super(gui, new ClassSelector(gui, ClassSelector.DEOBF_CLASS_COMPARATOR, true)); + + this.popupMenu = new DeobfPanelPopupMenu(this); + this.selector.addMouseListener(GuiUtil.onMousePress(this::onPress)); + + this.retranslateUi(); + } + + private void onPress(MouseEvent e) { + if (SwingUtilities.isRightMouseButton(e)) { + this.selector.setSelectionRow(this.selector.getClosestRowForLocation(e.getX(), e.getY())); + int i = this.selector.getRowForPath(this.selector.getSelectionPath()); + if (i != -1) { + popupMenu.show(this.selector, e.getX(), e.getY()); + } + } + } + + @Override + public void retranslateUi() { + super.retranslateUi(); + this.popupMenu.retranslateUi(); + } + + @Override + public String getId() { + return Type.DEOBFUSCATED_CLASSES; + } + + @Override + public Location getButtonPosition() { + return new Location(Side.LEFT, VerticalLocation.BOTTOM); + } + + @Override + public Location getPreferredLocation() { + return new Location(Side.LEFT, VerticalLocation.BOTTOM); + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/Dock.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/Dock.java new file mode 100644 index 000000000..1ffc54df8 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/Dock.java @@ -0,0 +1,452 @@ +package cuchaz.enigma.gui.docker; + +import cuchaz.enigma.gui.Gui; +import cuchaz.enigma.gui.config.UiConfig; + +import javax.swing.JPanel; +import javax.swing.JSplitPane; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * Handles the docking of {@link Docker}s. + */ +public class Dock extends JPanel { + private static final List INSTANCES = new ArrayList<>(); + + private final Gui gui; + private final JSplitPane splitPane; + private final DockerContainer topDock; + private final DockerContainer bottomDock; + private final Docker.Side side; + + /** + * Controls hover highlighting for this dock. A value of {@code null} represents no hover, otherwise it represents the currently hovered height. + */ + private Docker.VerticalLocation hovered; + private boolean isSplit; + private DockerContainer unifiedDock; + private Docker toSave; + + @SuppressWarnings("SuspiciousNameCombination") + public Dock(Gui gui, Docker.Side side) { + super(new BorderLayout()); + + this.topDock = new DockerContainer(); + this.bottomDock = new DockerContainer(); + this.unifiedDock = null; + this.splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, this.topDock, this.bottomDock); + this.side = side; + this.gui = gui; + + this.isSplit = true; + this.add(this.splitPane); + this.setVisible(false); + + INSTANCES.add(this); + } + + /** + * Restores the state of this dock to the version saved in the config file. + */ + public void restoreState() { + // restore docker state + Optional> hostedDockers = UiConfig.getHostedDockers(this.side); + hostedDockers.ifPresent(m -> m.forEach(this::host)); + + this.restoreDividerState(true); + + if (this.isEmpty()) { + this.setVisible(false); + } + } + + /** + * Saves the state of this dock to the config file. + */ + public void saveState() { + UiConfig.setHostedDockers(this.side, this.getDockers()); + this.saveDividerState(); + } + + public void restoreDividerState(boolean init) { + // restore vertical divider state + if (this.isSplit) { + this.splitPane.setDividerLocation(UiConfig.getVerticalDockDividerLocation(this.side)); + } + + // restore horizontal divider state + JSplitPane parentSplitPane = this.getParentSplitPane(); + int location = UiConfig.getHorizontalDividerLocation(this.side); + + // hack fix: if the right dock is closed while the left dock is open, the divider location is saved as if the left dock is open, + // thereby offsetting the divider location by the width of the left dock. which means, if the right dock is reopened while the left dock is closed, + // the divider location is too far to the left by the width of the left dock. so here we offset the location to avoid that. + if (!init && this.side == Docker.Side.RIGHT && !this.gui.getSplitLeft().getLeftComponent().isVisible() && UiConfig.getSavedWithLeftOpen()) { + location += UiConfig.getHorizontalDividerLocation(Docker.Side.LEFT); + } + + parentSplitPane.setDividerLocation(location); + } + + public void saveDividerState() { + if (this.isVisible()) { + // save vertical divider state + if (this.isSplit) { + UiConfig.setVerticalDockDividerLocation(this.side, this.splitPane.getDividerLocation()); + } + + // save horizontal divider state + JSplitPane parentSplitPane = this.getParentSplitPane(); + UiConfig.setHorizontalDividerLocation(this.side, parentSplitPane.getDividerLocation()); + + // hack + if (this.side == Docker.Side.RIGHT) { + UiConfig.setSavedWithLeftOpen(this.gui.getSplitLeft().getLeftComponent().isVisible()); + } + } + } + + public void receiveMouseEvent(MouseEvent e) { + if (this.isDisplayable()) { + if (e.getID() == MouseEvent.MOUSE_DRAGGED) { + for (Docker.VerticalLocation verticalLocation : Docker.VerticalLocation.values()) { + if (this.containsMouse(e, verticalLocation)) { + this.hovered = verticalLocation; + return; + } + } + + // we've checked every height and can confirm the dock is not being hovered + this.hovered = null; + } else if (e.getID() == MouseEvent.MOUSE_RELEASED) { + this.hovered = null; + this.repaint(); + } + } + } + + public void host(Docker docker, Docker.VerticalLocation verticalLocation) { + this.host(docker, verticalLocation, true); + } + + private void host(Docker docker, Docker.VerticalLocation verticalLocation, boolean avoidEmptySpace) { + Dock dock = Util.findDock(docker); + if (dock != null) { + dock.removeDocker(verticalLocation, avoidEmptySpace); + } + + switch (verticalLocation) { + case BOTTOM, TOP -> { + // if we'd be leaving empty space via opening, we want to host the docker as the full panel + // this is to avoid wasting space + if (avoidEmptySpace && (this.isSplit && this.getDock(verticalLocation.inverse()).getHostedDocker() == null) + || (!this.isSplit && this.unifiedDock.getHostedDocker() == null) + || (!this.isSplit && this.unifiedDock.getHostedDocker().getId().equals(docker.getId()))) { + this.host(docker, Docker.VerticalLocation.FULL); + return; + } + + if (!this.isSplit) { + this.split(); + } + + this.getDock(verticalLocation).setHostedDocker(docker); + if (this.toSave != null && !this.toSave.equals(docker)) { + this.host(this.toSave, verticalLocation.inverse()); + this.toSave = null; + } + } + case FULL -> { + // note: always uses top, since it doesn't matter + // since we're setting the hosted docker anyway + + if (this.isSplit) { + this.unify(Docker.VerticalLocation.TOP); + } + + // we cannot assume top here, since it could be called on a unified side + this.unifiedDock.setHostedDocker(docker); + } + } + + this.updateVisibility(); + this.revalidate(); + } + + public Docker[] getDockers() { + return new Docker[]{this.topDock.hostedDocker, this.bottomDock.hostedDocker}; + } + + public void updateVisibility() { + if (this.isVisible() && this.isEmpty()) { + this.saveDividerState(); + this.setVisible(false); + } else if (!this.isVisible()) { + this.restoreDividerState(false); + this.setVisible(true); + } + } + + public void removeDocker(Docker docker) { + Docker.Location location = Util.findLocation(docker); + if (location != null) { + this.removeDocker(location.verticalLocation()); + } else { + throw new IllegalArgumentException("attempted to remove docker from dock for side " + this.side + " that is not added to that dock!"); + } + } + + public void removeDocker(Docker.VerticalLocation location) { + this.removeDocker(location, true); + } + + private void removeDocker(Docker.VerticalLocation location, boolean avoidEmptySpace) { + // do not leave empty dockers + if (avoidEmptySpace && location != Docker.VerticalLocation.FULL && this.getDock(location.inverse()).getHostedDocker() != null) { + this.host(this.getDock(location.inverse()).getHostedDocker(), Docker.VerticalLocation.FULL, false); + return; + } + + DockerContainer container = this.getDock(location); + if (container != null) { + container.setHostedDocker(null); + } + + this.updateVisibility(); + this.revalidate(); + this.repaint(); + } + + public boolean containsMouse(MouseEvent e, Docker.VerticalLocation checkedLocation) { + if (this.isVisible()) { + Rectangle screenBounds = this.getBoundsFor(this.getLocationOnScreen(), checkedLocation); + return contains(screenBounds, e.getLocationOnScreen()); + } + + return false; + } + + private boolean isEmpty() { + return (!this.isSplit && this.unifiedDock.getHostedDocker() == null) || (this.topDock.getHostedDocker() == null && this.bottomDock.getHostedDocker() == null); + } + + public void split() { + this.saveDividerState(); + this.removeAll(); + + for (Docker docker : this.getDockers()) { + if (docker != null) { + this.toSave = docker; + } + } + + this.splitPane.setBottomComponent(this.bottomDock); + this.splitPane.setTopComponent(this.topDock); + this.add(this.splitPane); + + this.isSplit = true; + this.unifiedDock = null; + } + + public void unify(Docker.VerticalLocation keptLocation) { + this.saveDividerState(); + + this.removeAll(); + if (keptLocation == Docker.VerticalLocation.TOP) { + this.add(this.topDock); + this.unifiedDock = this.topDock; + } else if (keptLocation == Docker.VerticalLocation.BOTTOM) { + this.add(this.bottomDock); + this.unifiedDock = this.bottomDock; + } else { + throw new IllegalArgumentException("cannot keep nonexistent dock for location: " + keptLocation); + } + + this.removeDocker(keptLocation.inverse()); + this.isSplit = false; + } + + public void dropDockerFromMouse(Docker docker, MouseEvent event) { + for (Docker.VerticalLocation verticalLocation : Docker.VerticalLocation.values()) { + if (this.containsMouse(event, verticalLocation)) { + this.host(docker, verticalLocation); + return; + } + } + } + + public DockerContainer getDock(Docker.VerticalLocation verticalLocation) { + return switch (verticalLocation) { + case TOP -> this.topDock; + case BOTTOM -> this.bottomDock; + case FULL -> this.unifiedDock; + }; + } + + private Rectangle getHighlightBoundsFor(Point topLeft, Docker.VerticalLocation checkedLocation) { + Rectangle bounds = this.getBoundsFor(topLeft, checkedLocation); + int height = switch (checkedLocation) { + case FULL -> bounds.height; + case BOTTOM, TOP -> bounds.height * 2; + }; + return new Rectangle(bounds.x, checkedLocation == Docker.VerticalLocation.BOTTOM ? bounds.y - this.getHeight() / 4 : bounds.y, bounds.width, height); + } + + private Rectangle getBoundsFor(Point topLeft, Docker.VerticalLocation checkedLocation) { + if (checkedLocation == Docker.VerticalLocation.TOP) { + // top: 0 to 1/4 y + return new Rectangle(topLeft.x, topLeft.y, this.getWidth(), this.getHeight() / 4); + } else if (checkedLocation == Docker.VerticalLocation.BOTTOM) { + // bottom: 3/4 to 1 y + return new Rectangle(topLeft.x, topLeft.y + (this.getHeight() / 4) * 3, this.getWidth(), this.getHeight() / 4); + } else { + // full: 1/4 to 3/4 y + return new Rectangle(topLeft.x, topLeft.y + this.getHeight() / 4, this.getWidth(), this.getHeight() / 2); + } + } + + private JSplitPane getParentSplitPane() { + return this.side == Docker.Side.RIGHT ? this.gui.getSplitRight() : this.gui.getSplitLeft(); + } + + private static boolean contains(Rectangle rectangle, Point point) { + return (point.x >= rectangle.x && point.x <= rectangle.x + rectangle.width) + && (point.y >= rectangle.y && point.y <= rectangle.y + rectangle.height); + } + + @Override + public void paint(Graphics graphics) { + super.paint(graphics); + + // we can rely on paint to always be called when the label is being dragged over the docker + if (this.hovered != null) { + Rectangle paintedBounds = this.getHighlightBoundsFor(new Point(0, 0), this.hovered); + + Color color = UiConfig.getDockHighlightColor(); + graphics.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 100)); + graphics.fillRect(paintedBounds.x, paintedBounds.y, paintedBounds.width, paintedBounds.height); + this.repaint(); + } + } + + /** + * Helper class to store a docker. + */ + private static class DockerContainer extends JPanel { + private Docker hostedDocker; + + public DockerContainer() { + super(new BorderLayout()); + this.hostedDocker = null; + } + + public Docker getHostedDocker() { + return this.hostedDocker; + } + + public void setHostedDocker(Docker hostedDocker) { + if (this.hostedDocker != null) { + this.remove(this.hostedDocker); + this.hostedDocker.setVisible(false); + } + + this.hostedDocker = hostedDocker; + + if (this.hostedDocker != null) { + this.add(this.hostedDocker); + this.hostedDocker.setVisible(true); + } + } + } + + public static class Util { + /** + * Calls {@link Dock#receiveMouseEvent(MouseEvent)}} on all sides. + * @param event the mouse event to pass to the docks + */ + public static void receiveMouseEvent(MouseEvent event) { + for (Dock dock : INSTANCES) { + dock.receiveMouseEvent(event); + } + } + + /** + * Drops the docker after it has been dragged. + * Checks all docks to see if it's positioned over one, and if yes, snaps it into to that dock. + * @param docker the docker to open + * @param event an {@link MouseEvent} to use to check if the docker was held over a dock + */ + public static void dropDocker(Docker docker, MouseEvent event) { + for (Dock dock : INSTANCES) { + if (dock.isDisplayable()) { + dock.dropDockerFromMouse(docker, event); + } + } + } + + /** + * @return the location of the provided docker, or {@code null} if it is not currently present on the screen. + */ + public static Docker.Location findLocation(Docker docker) { + for (Dock dock : INSTANCES) { + for (Docker d : dock.getDockers()) { + if (d != null && d.getId().equals(docker.getId())) { + if (dock.unifiedDock != null && d.equals(dock.unifiedDock.getHostedDocker())) { + return new Docker.Location(dock.side, Docker.VerticalLocation.FULL); + } else if (d.equals(dock.topDock.getHostedDocker())) { + return new Docker.Location(dock.side, Docker.VerticalLocation.TOP); + } else if (d.equals(dock.bottomDock.getHostedDocker())) { + return new Docker.Location(dock.side, Docker.VerticalLocation.BOTTOM); + } + } + } + } + + return null; + } + + /** + * @return the docker's parent {@link Dock}, or {@code null} if it is not currently present on the screen. + */ + public static Dock findDock(Docker docker) { + for (Dock dock : INSTANCES) { + for (Docker d : dock.getDockers()) { + if (d != null && d.getId().equals(docker.getId())) { + return dock; + } + } + } + + return null; + } + + /** + * Removes the docker from the screen. + */ + public static void undock(Docker docker) { + for (Dock dock : INSTANCES) { + for (Docker d : dock.getDockers()) { + if (d != null && d.getId().equals(docker.getId())) { + dock.removeDocker(d); + } + } + } + } + + /** + * @return whether the docker is currently visible on the screen + */ + public static boolean isDocked(Docker docker) { + return findDock(docker) != null; + } + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/Docker.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/Docker.java new file mode 100644 index 000000000..29040c05e --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/Docker.java @@ -0,0 +1,177 @@ +package cuchaz.enigma.gui.docker; + +import cuchaz.enigma.gui.Gui; +import cuchaz.enigma.gui.docker.component.DockerTitleBar; +import cuchaz.enigma.utils.I18n; + +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.Objects; +import java.util.function.Supplier; + +/** + * Represents a window that can be docked on the sides of the editor panel. + *
A docker is an instance of {@link JPanel} that uses a {@link BorderLayout} by default. + */ +public abstract class Docker extends JPanel { + private static final Map, Docker> DOCKERS = new LinkedHashMap<>(); + private static final Map> DOCKER_CLASSES = new HashMap<>(); + + protected final Supplier titleSupplier = () -> I18n.translate("docker." + this.getId() + ".title"); + protected final DockerTitleBar title; + protected final JToggleButton button; + protected final Gui gui; + + protected Docker(Gui gui) { + super(new BorderLayout()); + this.gui = gui; + this.title = new DockerTitleBar(this, this.titleSupplier); + this.button = new JToggleButton(this.titleSupplier.get()); + // add action listener to open and close the docker when its button is pressed + this.button.addActionListener(e -> { + Docker docker = getDocker(this.getClass()); + + if (Dock.Util.isDocked(docker)) { + Dock.Util.undock(docker); + } else { + gui.openDocker(this.getClass()); + } + }); + + this.add(this.title, BorderLayout.NORTH); + + // validate to prevent difficult-to-trace errors + if (this.getButtonPosition().verticalLocation == VerticalLocation.FULL) { + throw new IllegalStateException("docker button vertical location cannot be full! allowed values are top and bottom."); + } + } + + public void retranslateUi() { + this.button.setText(this.titleSupplier.get()); + this.title.retranslateUi(); + } + + /** + * @return the side panel button that opens and closes this docker + */ + public JToggleButton getButton() { + return this.button; + } + + /** + * @return an ID used to check equality and save docker information to the config + */ + public abstract String getId(); + + /** + * @return the position of the docker's button in the selector panels. cannot use {@link Docker.VerticalLocation#FULL} + */ + public abstract Docker.Location getButtonPosition(); + + /** + * @return an {@link Location} representing this docker's preferred position: where the panel will open when the user clicks its button + */ + public abstract Location getPreferredLocation(); + + @Override + public void setVisible(boolean visible) { + super.setVisible(visible); + this.getButton().setSelected(visible); + // repaint to avoid corruption in the docker selectors + this.gui.getMainWindow().getFrame().getContentPane().repaint(); + } + + @Override + public boolean equals(Object obj) { + // there should only be one instance of each docker, so we only check ID here + if (obj instanceof Docker docker) { + return docker.getId().equals(this.getId()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(this.getId()); + } + + public static void addDocker(Docker panel) { + DOCKERS.put(panel.getClass(), panel); + DOCKER_CLASSES.put(panel.getId(), panel.getClass()); + } + + @SuppressWarnings("unchecked") + public static T getDocker(Class clazz) { + Docker panel = DOCKERS.get(clazz); + if (panel != null) { + return (T) DOCKERS.get(clazz); + } else { + throw new IllegalArgumentException("no docker registered for class " + clazz); + } + } + + public static Docker getDocker(String id) { + if (!DOCKER_CLASSES.containsKey(id)) { + throw new IllegalArgumentException("no docker registered for id " + id); + } + + return getDocker(DOCKER_CLASSES.get(id)); + } + + public static Map, Docker> getDockers() { + return DOCKERS; + } + + /** + * Contains the IDs for all existing dockers. + */ + 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 static final String ALL_CLASSES = "all_classes"; + public static final String DEOBFUSCATED_CLASSES = "deobfuscated_classes"; + public static final String OBFUSCATED_CLASSES = "obfuscated_classes"; + } + + /** + * Represents the location of a docker on the screen. + * @param side the side of the screen, either right or left + * @param verticalLocation the vertical location of the docker, being full, top or bottom + */ + public record Location(Side side, VerticalLocation verticalLocation) { + + } + + /** + * Represents the side of the screen a docker is located on. + */ + public enum Side { + LEFT, + RIGHT + } + + /** + * Represents the occupied vertical location of a docker. + */ + public enum VerticalLocation { + TOP, + BOTTOM, + FULL; + + public VerticalLocation inverse() { + if (this == FULL) { + throw new IllegalStateException("cannot invert vertical location \"" + this.name() + "\""); + } + + return this == TOP ? BOTTOM : TOP; + } + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/ImplementationsTree.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/ImplementationsTreeDocker.java similarity index 76% rename from enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/ImplementationsTree.java rename to enigma-swing/src/main/java/cuchaz/enigma/gui/docker/ImplementationsTreeDocker.java index a07c4233a..d56bdcd12 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/ImplementationsTree.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/ImplementationsTreeDocker.java @@ -1,4 +1,4 @@ -package cuchaz.enigma.gui.panels.right; +package cuchaz.enigma.gui.docker; import javax.annotation.Nullable; import javax.swing.tree.DefaultMutableTreeNode; @@ -9,8 +9,8 @@ import cuchaz.enigma.translation.representation.entry.Entry; import cuchaz.enigma.translation.representation.entry.MethodEntry; -public class ImplementationsTree extends AbstractInheritanceTree { - public ImplementationsTree(Gui gui) { +public class ImplementationsTreeDocker extends AbstractInheritanceTreeDocker { + public ImplementationsTreeDocker(Gui gui) { super(gui, new ImplementationsTreeCellRenderer(gui)); } @@ -30,4 +30,9 @@ protected DefaultMutableTreeNode getNodeFor(Entry entry) { public String getId() { return Type.IMPLEMENTATIONS; } + + @Override + public Location getPreferredLocation() { + return new Location(Side.RIGHT, VerticalLocation.FULL); + } } diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/InheritanceTree.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/InheritanceTreeDocker.java similarity index 76% rename from enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/InheritanceTree.java rename to enigma-swing/src/main/java/cuchaz/enigma/gui/docker/InheritanceTreeDocker.java index db4e8062d..8766ffaf6 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/InheritanceTree.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/InheritanceTreeDocker.java @@ -1,4 +1,4 @@ -package cuchaz.enigma.gui.panels.right; +package cuchaz.enigma.gui.docker; import javax.annotation.Nullable; import javax.swing.tree.DefaultMutableTreeNode; @@ -9,8 +9,8 @@ import cuchaz.enigma.translation.representation.entry.Entry; import cuchaz.enigma.translation.representation.entry.MethodEntry; -public class InheritanceTree extends AbstractInheritanceTree { - public InheritanceTree(Gui gui) { +public class InheritanceTreeDocker extends AbstractInheritanceTreeDocker { + public InheritanceTreeDocker(Gui gui) { super(gui, new InheritanceTreeCellRenderer(gui)); } @@ -30,4 +30,9 @@ protected DefaultMutableTreeNode getNodeFor(Entry entry) { public String getId() { return Type.INHERITANCE; } + + @Override + public Location getPreferredLocation() { + return new Location(Side.RIGHT, VerticalLocation.FULL); + } } diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/ObfuscatedClassesDocker.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/ObfuscatedClassesDocker.java new file mode 100644 index 000000000..b8e6ff254 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/ObfuscatedClassesDocker.java @@ -0,0 +1,37 @@ +package cuchaz.enigma.gui.docker; + +import cuchaz.enigma.gui.ClassSelector; +import cuchaz.enigma.gui.Gui; +import cuchaz.enigma.translation.representation.entry.ClassEntry; + +import java.util.Comparator; + +public class ObfuscatedClassesDocker extends ClassesDocker { + private static final Comparator obfuscatedClassComparator = (a, b) -> { + String aName = a.getFullName(); + String bName = b.getFullName(); + if (aName.length() != bName.length()) { + return aName.length() - bName.length(); + } + return aName.compareTo(bName); + }; + + public ObfuscatedClassesDocker(Gui gui) { + super(gui, new ClassSelector(gui, obfuscatedClassComparator, false)); + } + + @Override + public String getId() { + return Type.OBFUSCATED_CLASSES; + } + + @Override + public Location getButtonPosition() { + return new Location(Side.LEFT, VerticalLocation.TOP); + } + + @Override + public Location getPreferredLocation() { + return new Location(Side.LEFT, VerticalLocation.TOP); + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/StructurePanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/StructureDocker.java similarity index 95% rename from enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/StructurePanel.java rename to enigma-swing/src/main/java/cuchaz/enigma/gui/docker/StructureDocker.java index 929fcde80..570e823cc 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/StructurePanel.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/StructureDocker.java @@ -1,4 +1,4 @@ -package cuchaz.enigma.gui.panels.right; +package cuchaz.enigma.gui.docker; import java.awt.*; import java.awt.event.KeyEvent; @@ -29,7 +29,7 @@ import cuchaz.enigma.translation.representation.entry.ParentedEntry; import cuchaz.enigma.utils.I18n; -public class StructurePanel extends RightPanel { +public class StructureDocker extends Docker { private final JPanel optionsPanel; private final JLabel obfuscationVisibilityLabel = new JLabel(); @@ -42,7 +42,7 @@ public class StructurePanel extends RightPanel { private final JTree structureTree; - public StructurePanel(Gui gui) { + public StructureDocker(Gui gui) { super(gui); this.optionsPanel = new JPanel(new GridBagLayout()); this.optionsPanel.setVisible(false); @@ -154,8 +154,8 @@ public void retranslateUi() { } @Override - public ButtonPosition getButtonPosition() { - return ButtonPosition.TOP; + public Location getButtonPosition() { + return new Location(Side.RIGHT, VerticalLocation.TOP); } @Override @@ -163,6 +163,11 @@ public String getId() { return Type.STRUCTURE; } + @Override + public Location getPreferredLocation() { + return new Location(Side.RIGHT, VerticalLocation.FULL); + } + private static class StructureTreeCellRenderer extends DefaultTreeCellRenderer { private final Gui gui; diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/component/DockerLabel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/component/DockerLabel.java new file mode 100644 index 000000000..3d99d1be1 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/component/DockerLabel.java @@ -0,0 +1,172 @@ +package cuchaz.enigma.gui.docker.component; + +import cuchaz.enigma.gui.docker.Docker; +import cuchaz.enigma.gui.docker.Dock; + +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRootPane; +import javax.swing.SwingUtilities; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.GraphicsEnvironment; +import java.awt.Rectangle; +import java.awt.Toolkit; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; + +/** + * A user-draggable label that is used for docker titles. + */ +public class DockerLabel extends JLabel { + private JComponent initialParent; + private Object constraints; + private boolean beingDragged; + + private final Docker docker; + + public DockerLabel(Docker docker, String text) { + super(text); + this.setOpaque(false); + // note: docker and parent are not the same! + // the parent could be a sub-container of the docker + this.docker = docker; + this.initialParent = null; + this.constraints = null; + + this.addMouseListener(new MouseListener() { + @Override + public void mouseClicked(MouseEvent e) { + // no-op + } + + @Override + public void mousePressed(MouseEvent e) { + DockerLabel.this.beingDragged = true; + + // save parent for re-addition after dragging is finished + DockerLabel.this.initialParent = (JComponent) DockerLabel.this.getParent(); + + // validate + DockerLabel.this.ensureConfigured(); + + // configure object to be on the glass pane instead of its former pane + DockerLabel.this.setVisible(false); + JRootPane rootPane = DockerLabel.this.getRootPane(); + JPanel glassPane = (JPanel) rootPane.getGlassPane(); + DockerLabel.this.initialParent.remove(DockerLabel.this); + glassPane.add(DockerLabel.this); + + // repaint former panel to display removal of element + DockerLabel.this.initialParent.repaint(); + + // set up glass pane to actually display elements + glassPane.setOpaque(false); + glassPane.setVisible(true); + + DockerLabel.this.setMouse(Cursor.MOVE_CURSOR); + } + + @Override + public void mouseReleased(MouseEvent e) { + DockerLabel.this.ensureConfigured(); + + Dock.Util.receiveMouseEvent(e); + + // remove from glass pane and repaint to display removal + JPanel glassPane = (JPanel) DockerLabel.this.getRootPane().getGlassPane(); + glassPane.remove(DockerLabel.this); + glassPane.repaint(); + + // return label to old position + DockerLabel.this.initialParent.add(DockerLabel.this, DockerLabel.this.constraints); + DockerLabel.this.setVisible(true); + DockerLabel.this.initialParent.revalidate(); + DockerLabel.this.initialParent.repaint(); + + // if dropped over a docker, snap into place + Dock.Util.dropDocker(DockerLabel.this.docker, e); + + DockerLabel.this.initialParent = null; + // constraints are not reset, we assume that the label will stay with the same parent + + DockerLabel.this.setMouse(Cursor.DEFAULT_CURSOR); + + DockerLabel.this.beingDragged = false; + } + + @Override + public void mouseEntered(MouseEvent e) { + if (!DockerLabel.this.beingDragged) { + DockerLabel.this.setMouse(Cursor.HAND_CURSOR); + } + } + + @Override + public void mouseExited(MouseEvent e) { + if (!DockerLabel.this.beingDragged) { + DockerLabel.this.setMouse(Cursor.DEFAULT_CURSOR); + } + } + }); + + this.addMouseMotionListener(new MouseMotionListener() { + @Override + public void mouseDragged(MouseEvent e) { + // get task bar height + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + Rectangle windowSize = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds(); + int taskBarHeight = (int) (screenSize.getHeight() - windowSize.getHeight()); + + int mouseScreenX = e.getXOnScreen(); + int mouseScreenY = e.getYOnScreen(); + + // calculate, offsetting y for the task bar + // note: task bar offsetting will probably break if people have their taskbar at the top of the screen! + // good thing I don't care! + JFrame frame = (JFrame) SwingUtilities.getRoot(DockerLabel.this); + int mouseFrameX = mouseScreenX - frame.getX(); + int mouseFrameY = mouseScreenY - frame.getY() - taskBarHeight; + + // set location and ensure visibility + DockerLabel.this.setLocation(mouseFrameX, mouseFrameY); + DockerLabel.this.setVisible(true); + + // update dock highlighting + Dock.Util.receiveMouseEvent(e); + } + + @Override + public void mouseMoved(MouseEvent e) { + // no-op + } + }); + } + + /** + * saves constraints for re-adding component to old parent after dragging is finished, provided the panel is not dropped in a dock when released + *
must be called when adding a draggable label to a panel! + * @param constraints the constraints to use when re-adding the component to the old parent + */ + public void setConstraints(Object constraints) { + this.constraints = constraints; + } + + private void setMouse(int mouse) { + JRootPane rootPane = this.getRootPane(); + rootPane.setCursor(Cursor.getPredefinedCursor(mouse)); + } + + /** + * ensures that the label is properly configured and ready to be dragged. + *
should be called before dragging begins to prevent difficult-to-trace errors! + */ + private void ensureConfigured() { + if (DockerLabel.this.constraints == null || DockerLabel.this.initialParent == null) { + throw new IllegalStateException("draggable label \"" + this.getText() + "\" was not properly configured and therefore cannot properly be re-added to its parent if dropped outside a dock! (did you forget to call setConstraints()?)"); + } + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/component/DockerSelector.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/component/DockerSelector.java new file mode 100644 index 000000000..707a77e36 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/component/DockerSelector.java @@ -0,0 +1,55 @@ +package cuchaz.enigma.gui.docker.component; + +import cuchaz.enigma.gui.docker.Docker; + +import javax.swing.JLayer; +import javax.swing.JPanel; +import javax.swing.JToggleButton; +import java.awt.BorderLayout; +import java.awt.FlowLayout; + +public class DockerSelector { + private final JLayer panel; + private final JPanel bottomSelector; + private final JPanel topSelector; + private final Docker.Side side; + + public DockerSelector(Docker.Side side) { + JPanel mainPanel = new JPanel(new BorderLayout()); + this.bottomSelector = new JPanel(new FlowLayout()); + this.topSelector = new JPanel(new FlowLayout()); + this.side = side; + + mainPanel.add(this.topSelector, side == Docker.Side.RIGHT ? BorderLayout.WEST : BorderLayout.EAST); + mainPanel.add(this.bottomSelector, side == Docker.Side.RIGHT ? BorderLayout.EAST : BorderLayout.WEST); + + this.panel = new JLayer<>(mainPanel); + this.panel.setUI(new RightAngleLayerUI(side == Docker.Side.RIGHT ? RightAngleLayerUI.Rotation.CLOCKWISE : RightAngleLayerUI.Rotation.COUNTERCLOCKWISE)); + } + + /** + * Adds all buttons that match this selector's side to it. This method should be called after all dockers have been registered. + */ + public void configure() { + this.topSelector.removeAll(); + this.bottomSelector.removeAll(); + + // create buttons docker options + for (Docker docker : Docker.getDockers().values()) { + // only use buttons that match this selector's side + if (docker.getButtonPosition().side() == this.side) { + JToggleButton button = docker.getButton(); + + if (docker.getButtonPosition().verticalLocation() == Docker.VerticalLocation.TOP) { + this.topSelector.add(button); + } else { + this.bottomSelector.add(button); + } + } + } + } + + public JLayer getPanel() { + return this.panel; + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/component/DockerTitleBar.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/component/DockerTitleBar.java new file mode 100644 index 000000000..27170a7ec --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/component/DockerTitleBar.java @@ -0,0 +1,39 @@ +package cuchaz.enigma.gui.docker.component; + +import cuchaz.enigma.gui.docker.Docker; +import cuchaz.enigma.gui.docker.Dock; + +import javax.swing.JButton; +import javax.swing.JPanel; +import java.awt.BorderLayout; +import java.awt.Insets; +import java.util.function.Supplier; + +public class DockerTitleBar extends JPanel { + private final Supplier titleSupplier; + private final DockerLabel label; + + public DockerTitleBar(Docker parent, Supplier titleSupplier) { + super(new BorderLayout()); + this.label = new DockerLabel(parent, titleSupplier.get()); + this.titleSupplier = titleSupplier; + JButton minimiseButton = new JButton("-"); + + minimiseButton.addActionListener(e -> { + Docker docker = Docker.getDocker(parent.getClass()); + Dock.Util.undock(docker); + }); + + // if we set the left and right margins to 4, the button lines up *really* cutely with the scroll bar in any JScrollPane underneath it + minimiseButton.setMargin(new Insets(0, 4, 0, 4)); + + // set up + this.add(this.label, BorderLayout.WEST); + this.add(minimiseButton, BorderLayout.EAST); + this.label.setConstraints(BorderLayout.WEST); + } + + public void retranslateUi() { + this.label.setText(this.titleSupplier.get()); + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/RightAngleLayerUI.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/component/RightAngleLayerUI.java similarity index 98% rename from enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/RightAngleLayerUI.java rename to enigma-swing/src/main/java/cuchaz/enigma/gui/docker/component/RightAngleLayerUI.java index d92a50d2f..f1d1fc577 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/RightAngleLayerUI.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/docker/component/RightAngleLayerUI.java @@ -1,4 +1,4 @@ -package cuchaz.enigma.gui.panels.right; +package cuchaz.enigma.gui.docker.component; import javax.swing.JComponent; import javax.swing.JLayer; @@ -59,7 +59,7 @@ public void doLayout(JLayer l) { /** * Find the deepest component in the AWT hierarchy * - * @param layer the layer to which this UI is installed + * @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 */ @@ -193,7 +193,7 @@ private MouseEvent transformMouseEvent(JLayer layer, Mouse } /** - * Create the new event to be dispatched + * Create the new event to be dispatched. */ private MouseEvent transformMouseEvent(JLayer layer, MouseEvent mouseEvent, Component target, Point targetPoint, int id) { if (target == null) { @@ -213,7 +213,7 @@ private MouseEvent transformMouseEvent(JLayer layer, Mouse } /** - * Create the new mouse wheel event to be dispatched + * Create the new mouse wheel event to be dispatched. */ private MouseWheelEvent transformMouseWheelEvent(JLayer layer, MouseWheelEvent mouseWheelEvent, Component target, Point targetPoint) { if (target == null) { diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/DeobfPanelPopupMenu.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/DeobfPanelPopupMenu.java index 0b44881f1..a3bb8a954 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/DeobfPanelPopupMenu.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/DeobfPanelPopupMenu.java @@ -5,7 +5,7 @@ import javax.swing.tree.TreePath; import cuchaz.enigma.gui.ClassSelector; -import cuchaz.enigma.gui.panels.DeobfPanel; +import cuchaz.enigma.gui.docker.DeobfuscatedClassesDocker; import cuchaz.enigma.utils.I18n; public class DeobfPanelPopupMenu { @@ -16,7 +16,7 @@ public class DeobfPanelPopupMenu { private final JMenuItem expandAll = new JMenuItem(); private final JMenuItem collapseAll = new JMenuItem(); - public DeobfPanelPopupMenu(DeobfPanel panel) { + public DeobfPanelPopupMenu(DeobfuscatedClassesDocker panel) { this.ui = new JPopupMenu(); this.ui.add(this.renamePackage); @@ -25,12 +25,12 @@ public DeobfPanelPopupMenu(DeobfPanel panel) { this.ui.add(this.expandAll); this.ui.add(this.collapseAll); - ClassSelector deobfClasses = panel.deobfClasses; + ClassSelector deobfClasses = panel.getClassSelector(); this.renamePackage.addActionListener(a -> { TreePath path; - if (deobfClasses.getSelectedClass() != null) { + if (deobfClasses.getSelectedClass() != null && deobfClasses.getSelectionPath() != null) { // Rename parent package if selected path is a class path = deobfClasses.getSelectionPath().getParentPath(); } else { 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 e73df7c05..98fd950d8 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 @@ -6,6 +6,7 @@ import javax.annotation.Nullable; import javax.swing.JTabbedPane; +import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import com.google.common.collect.HashBiMap; @@ -17,13 +18,12 @@ 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; public class EditorTabbedPane { - private final JTabbedPane openFiles = new JTabbedPane(JTabbedPane.TOP, JTabbedPane.SCROLL_TAB_LAYOUT); + private final JTabbedPane openFiles = new JTabbedPane(SwingConstants.TOP, JTabbedPane.SCROLL_TAB_LAYOUT); private final HashBiMap editors = HashBiMap.create(); private final EditorTabPopupMenu editorTabPopupMenu; @@ -134,15 +134,15 @@ public EditorPanel getActiveEditor() { } private void onTabPressed(MouseEvent e) { - if (SwingUtilities.isRightMouseButton(e)) { - int i = this.openFiles.getUI().tabForCoordinate(this.openFiles, e.getX(), e.getY()); + int i = this.openFiles.getUI().tabForCoordinate(this.openFiles, e.getX(), e.getY()); - if (i != -1) { + if (i != -1) { + if (SwingUtilities.isRightMouseButton(e)) { this.editorTabPopupMenu.show(this.openFiles, e.getX(), e.getY(), EditorPanel.byUi(this.openFiles.getComponentAt(i))); } - } - this.gui.showStructure(this.getActiveEditor()); + this.gui.showStructure(this.getActiveEditor()); + } } public void retranslateUi() { 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 a3d553653..22a4b0bb0 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,17 +1,14 @@ package cuchaz.enigma.gui.elements; -import cuchaz.enigma.gui.panels.right.RightPanel; -import cuchaz.enigma.gui.panels.right.RightAngleLayerUI; +import cuchaz.enigma.gui.docker.Docker; +import cuchaz.enigma.gui.docker.component.DockerSelector; 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; @@ -19,26 +16,12 @@ public class MainWindow { private final JMenuBar menuBar = new JMenuBar(); private final StatusBar statusBar = new StatusBar(); - - private final JPanel topRightPanelSelector; - private final JPanel bottomRightPanelSelector; + private final DockerSelector rightDockerSelector; + private final DockerSelector leftDockerSelector; 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.rightDockerSelector = new DockerSelector(Docker.Side.RIGHT); + this.leftDockerSelector = new DockerSelector(Docker.Side.LEFT); this.frame = new JFrame(title); this.frame.setJMenuBar(this.menuBar); @@ -47,29 +30,18 @@ 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); - } - } + contentPane.add(this.rightDockerSelector.getPanel(), BorderLayout.EAST); + contentPane.add(this.leftDockerSelector.getPanel(), BorderLayout.WEST); } public void setVisible(boolean visible) { this.frame.setVisible(visible); } + public DockerSelector getDockerSelector(Docker.Side side) { + return side == Docker.Side.LEFT ? this.leftDockerSelector : this.rightDockerSelector; + } + public JMenuBar getMenuBar() { return this.menuBar; } 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 a35ee8c18..e96cf9374 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 @@ -132,28 +132,28 @@ public MenuBar(Gui gui) { setKeyBinds(); - this.jarOpenItem.addActionListener(_e -> this.onOpenJarClicked()); - this.jarCloseItem.addActionListener(_e -> this.gui.getController().closeJar()); - this.saveMappingsItem.addActionListener(_e -> this.onSaveMappingsClicked()); - this.closeMappingsItem.addActionListener(_e -> this.onCloseMappingsClicked()); - this.dropMappingsItem.addActionListener(_e -> this.gui.getController().dropMappings()); - this.reloadMappingsItem.addActionListener(_e -> this.onReloadMappingsClicked()); - this.reloadAllItem.addActionListener(_e -> this.onReloadAllClicked()); - this.exportSourceItem.addActionListener(_e -> this.onExportSourceClicked()); - this.exportJarItem.addActionListener(_e -> this.onExportJarClicked()); - this.statsItem.addActionListener(_e -> StatsDialog.show(this.gui)); - this.configureKeyBindsItem.addActionListener(_e -> ConfigureKeyBindsDialog.show(this.gui)); - this.exitItem.addActionListener(_e -> this.gui.close()); - this.decompilerSettingsItem.addActionListener(_e -> DecompilerSettingsDialog.show(this.gui)); - this.customScaleItem.addActionListener(_e -> this.onCustomScaleClicked()); - this.fontItem.addActionListener(_e -> this.onFontClicked(this.gui)); - this.searchClassItem.addActionListener(_e -> this.onSearchClicked(SearchDialog.Type.CLASS)); - this.searchMethodItem.addActionListener(_e -> this.onSearchClicked(SearchDialog.Type.METHOD)); - this.searchFieldItem.addActionListener(_e -> this.onSearchClicked(SearchDialog.Type.FIELD)); - this.connectItem.addActionListener(_e -> this.onConnectClicked()); - this.startServerItem.addActionListener(_e -> this.onStartServerClicked()); - this.aboutItem.addActionListener(_e -> AboutDialog.show(this.gui.getFrame())); - this.githubItem.addActionListener(_e -> this.onGithubClicked()); + this.jarOpenItem.addActionListener(e -> this.onOpenJarClicked()); + this.jarCloseItem.addActionListener(e -> this.gui.getController().closeJar()); + this.saveMappingsItem.addActionListener(e -> this.onSaveMappingsClicked()); + this.closeMappingsItem.addActionListener(e -> this.onCloseMappingsClicked()); + this.dropMappingsItem.addActionListener(e -> this.gui.getController().dropMappings()); + this.reloadMappingsItem.addActionListener(e -> this.onReloadMappingsClicked()); + this.reloadAllItem.addActionListener(e -> this.onReloadAllClicked()); + this.exportSourceItem.addActionListener(e -> this.onExportSourceClicked()); + this.exportJarItem.addActionListener(e -> this.onExportJarClicked()); + this.statsItem.addActionListener(e -> StatsDialog.show(this.gui)); + this.configureKeyBindsItem.addActionListener(e -> ConfigureKeyBindsDialog.show(this.gui)); + this.exitItem.addActionListener(e -> this.gui.close()); + this.decompilerSettingsItem.addActionListener(e -> DecompilerSettingsDialog.show(this.gui)); + this.customScaleItem.addActionListener(e -> this.onCustomScaleClicked()); + this.fontItem.addActionListener(e -> this.onFontClicked(this.gui)); + this.searchClassItem.addActionListener(e -> this.onSearchClicked(SearchDialog.Type.CLASS)); + this.searchMethodItem.addActionListener(e -> this.onSearchClicked(SearchDialog.Type.METHOD)); + this.searchFieldItem.addActionListener(e -> this.onSearchClicked(SearchDialog.Type.FIELD)); + this.connectItem.addActionListener(e -> this.onConnectClicked()); + this.startServerItem.addActionListener(e -> this.onStartServerClicked()); + this.aboutItem.addActionListener(e -> AboutDialog.show(this.gui.getFrame())); + this.githubItem.addActionListener(e -> this.onGithubClicked()); } public void setKeyBinds() { @@ -338,7 +338,7 @@ private void onSearchClicked(SearchDialog.Type type) { } } - private void onConnectClicked() { + public void onConnectClicked() { if (this.gui.getController().getClient() != null) { this.gui.getController().disconnectIfConnected(null); return; @@ -361,7 +361,7 @@ private void onConnectClicked() { Arrays.fill(result.getPassword(), (char) 0); } - private void onStartServerClicked() { + public void onStartServerClicked() { if (this.gui.getController().getServer() != null) { this.gui.getController().disconnectIfConnected(null); return; @@ -454,7 +454,7 @@ private static void prepareThemesMenu(JMenu themesMenu, Gui gui) { if (lookAndFeel.equals(UiConfig.getLookAndFeel())) { themeButton.setSelected(true); } - themeButton.addActionListener(_e -> { + themeButton.addActionListener(e -> { UiConfig.setLookAndFeel(lookAndFeel); UiConfig.save(); ChangeDialog.show(gui.getFrame()); diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/DeobfPanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/DeobfPanel.java deleted file mode 100644 index 10fc5e1af..000000000 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/DeobfPanel.java +++ /dev/null @@ -1,58 +0,0 @@ -package cuchaz.enigma.gui.panels; - -import java.awt.BorderLayout; -import java.awt.event.MouseEvent; - -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.SwingUtilities; - -import cuchaz.enigma.gui.ClassSelector; -import cuchaz.enigma.gui.Gui; -import cuchaz.enigma.gui.elements.DeobfPanelPopupMenu; -import cuchaz.enigma.gui.util.GuiUtil; -import cuchaz.enigma.utils.I18n; - -public class DeobfPanel extends JPanel { - - public final ClassSelector deobfClasses; - private final JLabel title = new JLabel(); - - public final DeobfPanelPopupMenu deobfPanelPopupMenu; - - private final Gui gui; - - public DeobfPanel(Gui gui) { - this.gui = gui; - - this.deobfClasses = new ClassSelector(gui, ClassSelector.DEOBF_CLASS_COMPARATOR, true); - this.deobfClasses.setSelectionListener(gui.getController()::navigateTo); - this.deobfClasses.setRenameSelectionListener(gui::onRenameFromClassTree); - this.deobfPanelPopupMenu = new DeobfPanelPopupMenu(this); - - this.setLayout(new BorderLayout()); - this.add(this.title, BorderLayout.NORTH); - this.add(new JScrollPane(this.deobfClasses), BorderLayout.CENTER); - - this.deobfClasses.addMouseListener(GuiUtil.onMousePress(this::onPress)); - - this.retranslateUi(); - } - - private void onPress(MouseEvent e) { - if (SwingUtilities.isRightMouseButton(e)) { - deobfClasses.setSelectionRow(deobfClasses.getClosestRowForLocation(e.getX(), e.getY())); - int i = deobfClasses.getRowForPath(deobfClasses.getSelectionPath()); - if (i != -1) { - deobfPanelPopupMenu.show(deobfClasses, e.getX(), e.getY()); - } - } - } - - public void retranslateUi() { - this.title.setText(I18n.translate(gui.isSingleClassTree() ? "info_panel.classes" : "info_panel.classes.deobfuscated")); - this.deobfPanelPopupMenu.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 6e494eeb2..a8c63a59e 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 @@ -47,14 +47,13 @@ import cuchaz.enigma.utils.Result; public class EditorPanel { - private final JPanel ui = new JPanel(); private final JEditorPane editor = new JEditorPane(); private final JScrollPane editorScrollPane = new JScrollPane(this.editor); private final EditorPopupMenu popupMenu; // progress UI - private final JLabel decompilingLabel = new JLabel(I18n.translate("editor.decompiling"), JLabel.CENTER); + private final JLabel decompilingLabel = new JLabel(I18n.translate("editor.decompiling"), SwingConstants.CENTER); private final JProgressBar decompilingProgressBar = new JProgressBar(0, 100); // error display UI @@ -327,7 +326,7 @@ public void setDisplayMode(DisplayMode mode) { this.ui.setLayout(new GridBagLayout()); GridBagConstraintsBuilder cb = GridBagConstraintsBuilder.create().insets(2).weight(1.0, 0.0).anchor(GridBagConstraints.WEST); this.ui.add(this.errorLabel, cb.pos(0, 0).build()); - this.ui.add(new JSeparator(JSeparator.HORIZONTAL), cb.pos(0, 1).fill(GridBagConstraints.HORIZONTAL).build()); + this.ui.add(new JSeparator(SwingConstants.HORIZONTAL), cb.pos(0, 1).fill(GridBagConstraints.HORIZONTAL).build()); this.ui.add(this.errorScrollPane, cb.pos(0, 2).weight(1.0, 1.0).fill(GridBagConstraints.BOTH).build()); this.ui.add(this.retryButton, cb.pos(0, 3).weight(0.0, 0.0).anchor(GridBagConstraints.EAST).build()); break; @@ -614,5 +613,4 @@ private enum DisplayMode { SUCCESS, ERRORED, } - } 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 deleted file mode 100644 index 1f76f8c73..000000000 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/ObfPanel.java +++ /dev/null @@ -1,43 +0,0 @@ -package cuchaz.enigma.gui.panels; - -import java.awt.BorderLayout; -import java.util.Comparator; - -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; - -import cuchaz.enigma.gui.ClassSelector; -import cuchaz.enigma.gui.Gui; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import cuchaz.enigma.utils.I18n; - -public class ObfPanel extends JPanel { - public final ClassSelector obfClasses; - private final JLabel title = new JLabel(); - - public ObfPanel(Gui gui) { - Comparator obfClassComparator = (a, b) -> { - String aname = a.getFullName(); - String bname = b.getFullName(); - if (aname.length() != bname.length()) { - return aname.length() - bname.length(); - } - return aname.compareTo(bname); - }; - - this.obfClasses = new ClassSelector(gui, obfClassComparator, false); - this.obfClasses.setSelectionListener(gui.getController()::navigateTo); - this.obfClasses.setRenameSelectionListener(gui::onRenameFromClassTree); - - this.setLayout(new BorderLayout()); - this.add(this.title, BorderLayout.NORTH); - this.add(new JScrollPane(this.obfClasses), BorderLayout.CENTER); - - this.retranslateUi(); - } - - public void retranslateUi() { - this.title.setText(I18n.translate("info_panel.classes.obfuscated")); - } -} 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 deleted file mode 100644 index 2aa74701e..000000000 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/right/RightPanel.java +++ /dev/null @@ -1,97 +0,0 @@ -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/src/main/resources/lang/en_us.json b/enigma/src/main/resources/lang/en_us.json index 21a93a167..b105d9a65 100644 --- a/enigma/src/main/resources/lang/en_us.json +++ b/enigma/src/main/resources/lang/en_us.json @@ -112,9 +112,6 @@ "editor.decompile_error": "An error was encountered while decompiling.", "editor.remap_error": "An error was encountered while remapping.", - "info_panel.classes": "Classes", - "info_panel.classes.obfuscated": "Obfuscated Classes", - "info_panel.classes.deobfuscated": "De-obfuscated Classes", "info_panel.identifier": "Identifier Info", "info_panel.identifier.none": "No identifier selected", "info_panel.identifier.variable": "Variable", @@ -263,13 +260,16 @@ "keybind.menu.mapping_stats": "Mapping stats", "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" + "docker.collab.send": "Send", + "docker.collab.title": "Collab", + "docker.collab.messages_title": "Messages", + "docker.collab.users_title": "Users", + "docker.collab.offline_text": "Enigma is currently running offline.", + "docker.calls.title": "Calls", + "docker.structure.title": "Structure", + "docker.inheritance.title": "Inheritance", + "docker.implementations.title": "Implementations", + "docker.all_classes.title": "Classes", + "docker.obfuscated_classes.title": "Obfuscated Classes", + "docker.deobfuscated_classes.title": "Deobfuscated Classes" } diff --git a/enigma/src/main/resources/lang/fr_fr.json b/enigma/src/main/resources/lang/fr_fr.json index e4996ab59..20fb1c7b9 100644 --- a/enigma/src/main/resources/lang/fr_fr.json +++ b/enigma/src/main/resources/lang/fr_fr.json @@ -93,8 +93,6 @@ "editor.decompile_error": "Une erreur est survenue lors de la décompilation.", "editor.remap_error": "Une erreur est survenue lors du remapping.", - "info_panel.classes.obfuscated": "Classes obfusquées", - "info_panel.classes.deobfuscated": "Classes déobfusquées", "info_panel.identifier": "Informations sur l'identifiant", "info_panel.identifier.none": "Aucun identifiant sélectionné", "info_panel.identifier.variable": "Variable", @@ -214,12 +212,15 @@ "crash.exit": "Quitter", "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." + "docker.structure": "Structure", + "docker.inheritance": "Héritage", + "docker.implementations": "Implémentations", + "docker.calls": "Graphique des appels", + "docker.collab.messages_title": "Messages", + "docker.collab.users_title": "Utilisateurs", + "docker.collab.send": "Envoie", + "docker.collab.offline_text": "Enigma n'est pas connecté à un serveur.", + "docker.obfuscated_classes.title": "Classes obfusquées", + "docker.deobfuscated_classes.title": "Classes déobfusquées", + "docker.all_classes.title": "Classes" } diff --git a/enigma/src/main/resources/lang/ja_jp.json b/enigma/src/main/resources/lang/ja_jp.json index 88b0926df..0ed48c57e 100644 --- a/enigma/src/main/resources/lang/ja_jp.json +++ b/enigma/src/main/resources/lang/ja_jp.json @@ -93,9 +93,6 @@ "editor.decompile_error": "デコンパイル中にエラーが発生しました", "editor.remap_error": "リマッピング中にエラーが発生しました", - "info_panel.classes": "クラス一覧", - "info_panel.classes.obfuscated": "難読化されたクラス", - "info_panel.classes.deobfuscated": "難読化解除されたクラス", "info_panel.identifier": "識別子情報", "info_panel.identifier.none": "識別子が選択されていません", "info_panel.identifier.variable": "変数", @@ -215,10 +212,13 @@ "crash.exit": "終了", "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": "ユーザー" + "docker.structure.title": "構造", + "docker.inheritance.title": "継承", + "docker.implementations.title": "実装", + "docker.calls.title": "呼び出し関係", + "docker.collab.messages_title": "メッセージ", + "docker.collab.users_title": "ユーザー", + "docker.all_classes.title": "クラス一覧", + "docker.obfuscated_classes.title": "難読化されたクラス", + "docker.deobfuscated_classes.title": "難読化解除されたクラス" } diff --git a/enigma/src/main/resources/lang/zh_cn.json b/enigma/src/main/resources/lang/zh_cn.json index 5e012ac49..506093e49 100644 --- a/enigma/src/main/resources/lang/zh_cn.json +++ b/enigma/src/main/resources/lang/zh_cn.json @@ -56,8 +56,6 @@ "popup_menu.mark_deobfuscated": "标记为已反混淆", "popup_menu.reset_obfuscated": "重置混淆", - "info_panel.classes.obfuscated": "混淆类", - "info_panel.classes.deobfuscated": "反混淆类", "info_panel.identifier": "标识符信息", "info_panel.identifier.none": "未选择标识符", "info_panel.identifier.variable": "变量", @@ -114,7 +112,9 @@ "crash.exit": "退出", "crash.exit.warning": "如果选择退出,将丢失所有未保存的工作。", - "right_panel.inheritance.title": "继承", - "right_panel.implementations.title": "实现", - "right_panel.calls.title": "调用图" + "docker.inheritance.title": "继承", + "docker.implementations.title": "实现", + "docker.calls.title": "调用图", + "docker.obfuscated_classes.title": "混淆类", + "docker.deobfuscated_classes.title": "反混淆类" }