diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/ClassSelector.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/ClassSelector.java index d613ce749..cfc0370a9 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/ClassSelector.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/ClassSelector.java @@ -19,6 +19,8 @@ import java.util.Comparator; import java.util.List; +import static org.quiltmc.enigma.gui.util.GuiUtil.putKeyBindAction; + public class ClassSelector extends JTree { public static final Comparator DEOBF_CLASS_COMPARATOR = Comparator.comparing(ClassEntry::getFullName); @@ -49,27 +51,29 @@ public ClassSelector(Gui gui, Comparator comparator) { } })); - this.addKeyListener(GuiUtil.onKeyPress(e -> { - TreePath[] paths = this.getSelectionPaths(); - + putKeyBindAction(KeyBinds.EDITOR_TOGGLE_MAPPING, this, e -> { + final TreePath[] paths = this.getSelectionPaths(); if (paths != null) { - if (KeyBinds.EDITOR_TOGGLE_MAPPING.matches(e)) { - for (TreePath path : paths) { - if (path.getLastPathComponent() instanceof ClassSelectorClassNode node) { - gui.toggleMappingFromEntry(node.getObfEntry()); - } + for (final TreePath path : paths) { + if (path.getLastPathComponent() instanceof ClassSelectorClassNode node) { + gui.toggleMappingFromEntry(node.getObfEntry()); } } + } + }); - if (this.selectionListener != null && KeyBinds.SELECT.matches(e)) { - for (TreePath path : paths) { + putKeyBindAction(KeyBinds.SELECT, this, e -> { + if (this.selectionListener != null) { + final TreePath[] paths = this.getSelectionPaths(); + if (paths != null) { + for (final TreePath path : paths) { if (path.getLastPathComponent() instanceof ClassSelectorClassNode node) { this.selectionListener.onSelectClass(node.getObfEntry()); } } } } - })); + }); this.setCellRenderer(new ClassTreeCellRenderer(gui, this)); ToolTipManager.sharedInstance().registerComponent(this); diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/config/Config.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/config/Config.java index e00f7ba28..d0d0038e1 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/config/Config.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/config/Config.java @@ -23,7 +23,6 @@ import org.quiltmc.enigma.gui.config.theme.properties.NoneThemeProperties; import org.quiltmc.enigma.gui.config.theme.properties.SystemThemeProperties; import org.quiltmc.enigma.gui.config.theme.properties.composite.SyntaxPaneProperties; -import org.quiltmc.enigma.gui.dialog.EnigmaQuickFindDialog; import org.quiltmc.enigma.util.I18n; import org.quiltmc.syntaxpain.SyntaxpainConfiguration; @@ -78,6 +77,9 @@ public final class Config extends ReflectiveConfig { @Comment("You shouldn't enable options in this section unless you know what you're doing!") public final DevSection development = new DevSection(); + @Comment("Whether editors' quick find toolbars should remain visible when they lose focus.") + public final TrackedValue persistentEditorQuickFind = this.value(true); + /** * The look and feel stored in the config: do not use this unless setting! Use {@link #activeThemeChoice} instead, * since look and feel is final once loaded. @@ -252,7 +254,8 @@ public static void updateSyntaxpain() { SyntaxPaneProperties.Colors colors = getCurrentSyntaxPaneColors(); SyntaxpainConfiguration.setEditorFont(fonts.editor.value()); - SyntaxpainConfiguration.setQuickFindDialogFactory(EnigmaQuickFindDialog::new); + // disable dialog; EditorPanel uses a tool bar component instead + SyntaxpainConfiguration.setQuickFindDialogFactory(null); SyntaxpainConfiguration.setLineRulerPrimaryColor(colors.lineNumbersForeground.value()); SyntaxpainConfiguration.setLineRulerSecondaryColor(colors.lineNumbersBackground.value()); diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/config/keybind/KeyBind.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/config/keybind/KeyBind.java index 60cf316bb..cf66bafc0 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/config/keybind/KeyBind.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/config/keybind/KeyBind.java @@ -1,12 +1,18 @@ package org.quiltmc.enigma.gui.config.keybind; +import org.quiltmc.enigma.gui.util.GuiUtil; import org.quiltmc.enigma.util.I18n; import org.tinylog.Logger; +import java.awt.event.ActionListener; import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; import java.util.ArrayList; import java.util.List; import java.util.Locale; +import javax.swing.ActionMap; +import javax.swing.InputMap; +import javax.swing.JComponent; import javax.swing.KeyStroke; public record KeyBind(String name, String category, List combinations) { @@ -42,6 +48,12 @@ public void setFrom(KeyBind other) { this.combinations.addAll(other.combinations); } + /** + * Prefer using component's {@link InputMap}s and {@link ActionMap}s to checking this in {@link KeyListener}s. + * + * @see GuiUtil#putKeyBindAction(KeyBind, JComponent, GuiUtil.FocusCondition, ActionListener) + */ + @Deprecated public boolean matches(KeyEvent e) { return this.combinations.stream().anyMatch(c -> c.matches(e)); } diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/config/keybind/KeyBinds.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/config/keybind/KeyBinds.java index 0755c6860..036ecd838 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/config/keybind/KeyBinds.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/config/keybind/KeyBinds.java @@ -19,12 +19,14 @@ public final class KeyBinds { public static final KeyBind EXIT = KeyBind.builder("close").key(KeyEvent.VK_ESCAPE).build(); public static final KeyBind SELECT = KeyBind.builder("select").key(KeyEvent.VK_ENTER).build(); public static final KeyBind DIALOG_SAVE = KeyBind.builder("dialog_save").key(KeyEvent.VK_ENTER).build(); + public static final KeyBind MULTILINE_DIALOG_SAVE = KeyBind.builder("multiline_dialog_save").mod(InputEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_ENTER).build(); public static final KeyBind QUICK_FIND_DIALOG_IGNORE_CASE = KeyBind.builder("ignore_case", QUICK_FIND_DIALOG_CATEGORY).key(KeyEvent.VK_C).build(); public static final KeyBind QUICK_FIND_DIALOG_REGEX = KeyBind.builder("regex", QUICK_FIND_DIALOG_CATEGORY).key(KeyEvent.VK_R).build(); public static final KeyBind QUICK_FIND_DIALOG_WRAP = KeyBind.builder("wrap", QUICK_FIND_DIALOG_CATEGORY).key(KeyEvent.VK_W).build(); public static final KeyBind QUICK_FIND_DIALOG_NEXT = KeyBind.builder("next", QUICK_FIND_DIALOG_CATEGORY).key(KeyEvent.VK_ENTER).build(); public static final KeyBind QUICK_FIND_DIALOG_PREVIOUS = KeyBind.builder("previous", QUICK_FIND_DIALOG_CATEGORY).mod(InputEvent.SHIFT_DOWN_MASK).key(KeyEvent.VK_ENTER).build(); + public static final KeyBind QUICK_FIND_DIALOG_CLOSE = KeyBind.builder("close_quick_find", QUICK_FIND_DIALOG_CATEGORY).key(KeyEvent.VK_ESCAPE).build(); public static final KeyBind SEARCH_DIALOG_NEXT = KeyBind.builder("next", SEARCH_DIALOG_CATEGORY).key(KeyEvent.VK_DOWN).build(); public static final KeyBind SEARCH_DIALOG_PREVIOUS = KeyBind.builder("previous", SEARCH_DIALOG_CATEGORY).key(KeyEvent.VK_UP).build(); @@ -35,7 +37,7 @@ public final class KeyBinds { public static final KeyBind EDITOR_SEARCH_STRUCTURE = KeyBind.builder("search_structure", EDITOR_CATEGORY).mod(InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK).key(KeyEvent.VK_T).build(); public static final KeyBind EDITOR_SHOW_INHERITANCE = KeyBind.builder("show_inheritance", EDITOR_CATEGORY).mod(InputEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_I).build(); public static final KeyBind EDITOR_SHOW_IMPLEMENTATIONS = KeyBind.builder("show_implementations", EDITOR_CATEGORY).mod(InputEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_M).build(); - public static final KeyBind EDITOR_SHOW_CALLS = KeyBind.builder("show_calls", EDITOR_CATEGORY).mod(InputEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_C).build(); + public static final KeyBind EDITOR_SHOW_CALLS = KeyBind.builder("show_calls", EDITOR_CATEGORY).mod(InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK).key(KeyEvent.VK_C).build(); public static final KeyBind EDITOR_SHOW_CALLS_SPECIFIC = KeyBind.builder("show_calls_specific", EDITOR_CATEGORY).mod(InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK).key(KeyEvent.VK_C).build(); public static final KeyBind EDITOR_OPEN_ENTRY = KeyBind.builder("open_entry", EDITOR_CATEGORY).mod(InputEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_N).build(); public static final KeyBind EDITOR_OPEN_PREVIOUS = KeyBind.builder("open_previous", EDITOR_CATEGORY).mod(InputEvent.CTRL_DOWN_MASK).key(KeyEvent.VK_P).build(); @@ -61,10 +63,10 @@ public final class KeyBinds { public static final KeyBind SEARCH_METHOD = KeyBind.builder("search_method", MENU_CATEGORY).build(); public static final KeyBind SEARCH_FIELD = KeyBind.builder("search_field", MENU_CATEGORY).build(); - private static final List DEFAULT_KEY_BINDS = Stream.of(EXIT, SELECT, DIALOG_SAVE, QUICK_FIND_DIALOG_NEXT, - QUICK_FIND_DIALOG_PREVIOUS, SEARCH_DIALOG_NEXT, SEARCH_DIALOG_PREVIOUS, EDITOR_RENAME, EDITOR_PASTE, - EDITOR_EDIT_JAVADOC, EDITOR_SHOW_INHERITANCE, EDITOR_SHOW_IMPLEMENTATIONS, EDITOR_SHOW_CALLS, - EDITOR_SHOW_CALLS_SPECIFIC, EDITOR_OPEN_ENTRY, EDITOR_OPEN_PREVIOUS, EDITOR_OPEN_NEXT, + private static final List DEFAULT_KEY_BINDS = Stream.of(EXIT, SELECT, DIALOG_SAVE, MULTILINE_DIALOG_SAVE, + QUICK_FIND_DIALOG_NEXT, QUICK_FIND_DIALOG_PREVIOUS, QUICK_FIND_DIALOG_CLOSE, SEARCH_DIALOG_NEXT, SEARCH_DIALOG_PREVIOUS, + EDITOR_RENAME, EDITOR_PASTE, EDITOR_EDIT_JAVADOC, EDITOR_SHOW_INHERITANCE, EDITOR_SHOW_IMPLEMENTATIONS, + EDITOR_SHOW_CALLS, EDITOR_SHOW_CALLS_SPECIFIC, EDITOR_OPEN_ENTRY, EDITOR_OPEN_PREVIOUS, EDITOR_OPEN_NEXT, EDITOR_TOGGLE_MAPPING, EDITOR_ZOOM_IN, EDITOR_ZOOM_OUT, EDITOR_CLOSE_TAB, EDITOR_RELOAD_CLASS, EDITOR_QUICK_FIND, EDITOR_SHOW_STRUCTURE, EDITOR_SEARCH_STRUCTURE, SAVE_MAPPINGS, DROP_MAPPINGS, RELOAD_MAPPINGS, RELOAD_ALL, MAPPING_STATS, SEARCH, SEARCH_ALL, SEARCH_CLASS, SEARCH_METHOD, SEARCH_FIELD).map(KeyBind::toImmutable).toList(); @@ -72,9 +74,10 @@ public final class KeyBinds { private static final List CONFIGURABLE_KEY_BINDS = List.of(EDITOR_RENAME, EDITOR_PASTE, EDITOR_EDIT_JAVADOC, EDITOR_SHOW_INHERITANCE, EDITOR_SHOW_IMPLEMENTATIONS, EDITOR_SHOW_CALLS, EDITOR_SHOW_CALLS_SPECIFIC, EDITOR_OPEN_ENTRY, EDITOR_OPEN_PREVIOUS, EDITOR_OPEN_NEXT, EDITOR_TOGGLE_MAPPING, EDITOR_ZOOM_IN, - EDITOR_ZOOM_OUT, EDITOR_CLOSE_TAB, EDITOR_RELOAD_CLASS, EDITOR_SHOW_STRUCTURE, EDITOR_SEARCH_STRUCTURE, - SAVE_MAPPINGS, DROP_MAPPINGS, RELOAD_MAPPINGS, RELOAD_ALL, MAPPING_STATS, SEARCH, SEARCH_ALL, SEARCH_CLASS, - SEARCH_METHOD, SEARCH_FIELD); + EDITOR_ZOOM_OUT, EDITOR_CLOSE_TAB, EDITOR_RELOAD_CLASS, EDITOR_QUICK_FIND, EDITOR_SHOW_STRUCTURE, + EDITOR_SEARCH_STRUCTURE, SAVE_MAPPINGS, DROP_MAPPINGS, RELOAD_MAPPINGS, RELOAD_ALL, MAPPING_STATS, SEARCH, + SEARCH_ALL, SEARCH_CLASS, SEARCH_METHOD, SEARCH_FIELD + ); // Editing entries in CONFIGURABLE_KEY_BINDS directly wouldn't allow to revert the changes instead of saving private static List editableKeyBinds; diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/dialog/ChangeDialog.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/dialog/ChangeDialog.java index 75a63e7c0..5465d69c5 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/dialog/ChangeDialog.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/dialog/ChangeDialog.java @@ -1,7 +1,6 @@ package org.quiltmc.enigma.gui.dialog; import org.quiltmc.enigma.gui.config.keybind.KeyBinds; -import org.quiltmc.enigma.gui.util.GuiUtil; import org.quiltmc.enigma.util.I18n; import java.awt.BorderLayout; @@ -12,6 +11,8 @@ import javax.swing.JLabel; import javax.swing.JPanel; +import static org.quiltmc.enigma.gui.util.GuiUtil.putKeyBindAction; + public class ChangeDialog { public static void show(Window parent) { // init frame @@ -31,11 +32,7 @@ public static void show(Window parent) { JButton okButton = new JButton(I18n.translate("prompt.ok")); buttonPanel.add(okButton); okButton.addActionListener(event -> frame.dispose()); - okButton.addKeyListener(GuiUtil.onKeyPress(e -> { - if (KeyBinds.EXIT.matches(e)) { - frame.dispose(); - } - })); + putKeyBindAction(KeyBinds.EXIT, okButton, e -> frame.dispose()); // show the frame frame.pack(); diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/dialog/EnigmaQuickFindDialog.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/dialog/EnigmaQuickFindDialog.java deleted file mode 100644 index 264677c3c..000000000 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/dialog/EnigmaQuickFindDialog.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2008 Ayman Al-Sairafi ayman.alsairafi@gmail.com - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.quiltmc.enigma.gui.dialog; - -import org.quiltmc.enigma.gui.config.keybind.KeyBinds; -import org.quiltmc.enigma.gui.util.GuiUtil; -import org.quiltmc.enigma.util.I18n; -import org.quiltmc.syntaxpain.QuickFindDialog; - -import javax.swing.text.JTextComponent; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; - -/** - * Extension of {@link QuickFindDialog} to allow using keybindings, and improve UI. - */ -public class EnigmaQuickFindDialog extends QuickFindDialog { - public EnigmaQuickFindDialog(JTextComponent target) { - super(target); - } - - @Override - protected void initComponents() { - super.initComponents(); - - // keybinding support - this.searchField.addKeyListener(new KeyAdapter() { - @Override - public void keyPressed(KeyEvent e) { - if (KeyBinds.QUICK_FIND_DIALOG_PREVIOUS.matches(e)) { - EnigmaQuickFindDialog.this.prevButton.doClick(); - } else if (KeyBinds.QUICK_FIND_DIALOG_NEXT.matches(e)) { - EnigmaQuickFindDialog.this.nextButton.doClick(); - } - } - }); - - this.ignoreCaseCheckBox.setMnemonic(KeyBinds.QUICK_FIND_DIALOG_IGNORE_CASE.getKeyCode()); - this.regexCheckBox.setMnemonic(KeyBinds.QUICK_FIND_DIALOG_REGEX.getKeyCode()); - this.wrapCheckBox.setMnemonic(KeyBinds.QUICK_FIND_DIALOG_WRAP.getKeyCode()); - - // make buttons icon-only - this.nextButton.setIcon(GuiUtil.getDownChevron()); - this.prevButton.setIcon(GuiUtil.getUpChevron()); - - // translations - this.ignoreCase = I18n.translate("editor.quick_find.ignore_case"); - this.useRegex = I18n.translate("editor.quick_find.use_regex"); - this.wrap = I18n.translate("editor.quick_find.wrap"); - this.next = ""; - this.prev = ""; - this.notFound = I18n.translate("editor.quick_find.not_found"); - this.translate(); - } -} diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/dialog/EnigmaQuickFindToolBar.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/dialog/EnigmaQuickFindToolBar.java new file mode 100644 index 000000000..131289707 --- /dev/null +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/dialog/EnigmaQuickFindToolBar.java @@ -0,0 +1,118 @@ +/* + * Copyright 2008 Ayman Al-Sairafi ayman.alsairafi@gmail.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.enigma.gui.dialog; + +import org.quiltmc.enigma.gui.config.Config; +import org.quiltmc.enigma.gui.config.keybind.KeyBinds; +import org.quiltmc.enigma.gui.util.GuiUtil; +import org.quiltmc.enigma.gui.util.GuiUtil.FocusCondition; +import org.quiltmc.enigma.util.I18n; +import org.quiltmc.syntaxpain.QuickFindToolBar; + +import javax.swing.Box; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.SwingConstants; + +import static org.quiltmc.enigma.gui.util.GuiUtil.putKeyBindAction; + +/** + * Extension of {@link QuickFindToolBar} to allow using keybindings, and improve UI. + */ +public class EnigmaQuickFindToolBar extends QuickFindToolBar { + protected JCheckBox persistentCheckBox; + protected JButton closeButton; + + public EnigmaQuickFindToolBar() { + super(); + // keybinding support + this.reloadKeyBinds(); + + // configure parent components + this.ignoreCaseCheckBox.setMnemonic(KeyBinds.QUICK_FIND_DIALOG_IGNORE_CASE.getKeyCode()); + this.regexCheckBox.setMnemonic(KeyBinds.QUICK_FIND_DIALOG_REGEX.getKeyCode()); + this.wrapCheckBox.setMnemonic(KeyBinds.QUICK_FIND_DIALOG_WRAP.getKeyCode()); + + // make buttons icon-only + this.nextButton.setText(""); + this.nextButton.setIcon(GuiUtil.getDownChevron()); + this.prevButton.setText(""); + this.prevButton.setIcon(GuiUtil.getUpChevron()); + + // add custom components + // push the rest of the components to the right + this.add(Box.createHorizontalGlue()); + + this.addSeparator(); + + this.persistentCheckBox = new JCheckBox(); + this.persistentCheckBox.setFocusable(false); + this.persistentCheckBox.setOpaque(false); + this.persistentCheckBox.setVerticalTextPosition(SwingConstants.BOTTOM); + this.persistentCheckBox.setHorizontalTextPosition(SwingConstants.LEADING); + this.persistentCheckBox.addActionListener(this); + this.persistentCheckBox.addItemListener(e -> { + final boolean selected = this.persistentCheckBox.isSelected(); + if (selected != Config.main().persistentEditorQuickFind.value()) { + Config.main().persistentEditorQuickFind.setValue(selected); + } + + // request focus so when it's lost this may be dismissed + this.requestFocus(); + }); + this.persistentCheckBox.setSelected(Config.main().persistentEditorQuickFind.value()); + Config.main().persistentEditorQuickFind.registerCallback(callback -> { + final Boolean configured = callback.value(); + if (this.persistentCheckBox.isSelected() != configured) { + this.persistentCheckBox.setSelected(configured); + } + }); + this.add(this.persistentCheckBox); + + this.addSeparator(); + + this.closeButton = new JButton(); + this.closeButton.setIcon(GuiUtil.getCloseIcon()); + this.closeButton.setFocusable(false); + this.closeButton.setOpaque(false); + this.closeButton.addActionListener(e -> this.dismiss()); + this.add(this.closeButton); + + this.translate(); + } + + @Override + protected boolean dismissOnFocusLost() { + return !this.persistentCheckBox.isSelected(); + } + + public void translate() { + this.notFound = I18n.translate("editor.quick_find.not_found"); + + this.ignoreCaseCheckBox.setText(I18n.translate("editor.quick_find.ignore_case")); + this.regexCheckBox.setText(I18n.translate("editor.quick_find.use_regex")); + this.wrapCheckBox.setText(I18n.translate("editor.quick_find.wrap")); + + this.persistentCheckBox.setText(I18n.translate("editor.quick_find.persistent")); + } + + public void reloadKeyBinds() { + putKeyBindAction(KeyBinds.QUICK_FIND_DIALOG_PREVIOUS, this.searchField, e -> this.prevButton.doClick()); + putKeyBindAction(KeyBinds.QUICK_FIND_DIALOG_NEXT, this.searchField, e -> this.nextButton.doClick()); + putKeyBindAction( + KeyBinds.QUICK_FIND_DIALOG_CLOSE, this, FocusCondition.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, + e -> this.setVisible(false) + ); + } +} diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/dialog/JavadocDialog.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/dialog/JavadocDialog.java index d715e7e90..8e61ac8b3 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/dialog/JavadocDialog.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/dialog/JavadocDialog.java @@ -29,6 +29,8 @@ import javax.swing.WindowConstants; import javax.swing.text.html.HTML; +import static org.quiltmc.enigma.gui.util.GuiUtil.putKeyBindAction; + public class JavadocDialog { private final JDialog ui; private final GuiController controller; @@ -53,18 +55,16 @@ private JavadocDialog(JFrame parent, GuiController controller, Entry entry, S this.text.setText(preset); this.text.setTabSize(2); contentPane.add(new JScrollPane(this.text), BorderLayout.CENTER); - this.text.addKeyListener(GuiUtil.onKeyPress(event -> { - if (KeyBinds.DIALOG_SAVE.matches(event)) { - if (event.isControlDown()) { - this.doSave(); - if (this.vc.canProceed()) { - this.close(); - } - } - } else if (KeyBinds.EXIT.matches(event)) { + + putKeyBindAction(KeyBinds.MULTILINE_DIALOG_SAVE, this.text, e -> { + this.doSave(); + if (this.vc.canProceed()) { this.close(); } - })); + }); + + putKeyBindAction(KeyBinds.EXIT, this.text, e -> this.close()); + this.text.setFont(ScaleUtil.scaleFont(Config.currentFonts().editor.value())); // buttons panel diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/dialog/SearchDialog.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/dialog/SearchDialog.java index 8c530d745..bc2be70aa 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/dialog/SearchDialog.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/dialog/SearchDialog.java @@ -25,7 +25,6 @@ import java.awt.Font; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; -import java.awt.event.KeyEvent; import java.awt.event.MouseListener; import java.util.ArrayList; import java.util.Arrays; @@ -48,6 +47,8 @@ import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; +import static org.quiltmc.enigma.gui.util.GuiUtil.putKeyBindAction; + public class SearchDialog { private final JTextField searchField; private final JCheckBox onlyExactMatchesCheckbox; @@ -90,7 +91,6 @@ public void changedUpdate(DocumentEvent e) { SearchDialog.this.updateList(); } }); - this.searchField.addKeyListener(GuiUtil.onKeyPress(this::onKeyPressed)); this.searchField.addActionListener(e -> this.openSelected()); this.onlyExactMatchesCheckbox = new JCheckBox(I18n.translate("menu.search.only_exact_matches")); @@ -156,6 +156,22 @@ public void componentShown(ComponentEvent e) { this.dialog.setContentPane(contentPane); this.dialog.setSize(ScaleUtil.getDimension(400, 500)); this.dialog.setLocationRelativeTo(gui.getFrame()); + + putKeyBindAction(KeyBinds.SEARCH_DIALOG_NEXT, contentPane, e -> { + final int next = this.classList.isSelectionEmpty() ? 0 : this.classList.getSelectedIndex() + 1; + this.classList.setSelectedIndex(next); + this.classList.ensureIndexIsVisible(next); + }); + + putKeyBindAction(KeyBinds.SEARCH_DIALOG_PREVIOUS, contentPane, e -> { + final int prev = this.classList.isSelectionEmpty() + ? this.classList.getModel().getSize() + : this.classList.getSelectedIndex() - 1; + this.classList.setSelectedIndex(prev); + this.classList.ensureIndexIsVisible(prev); + }); + + putKeyBindAction(KeyBinds.EXIT, contentPane, e -> this.close()); } private MouseListener createCheckboxListener(Type type) { @@ -313,20 +329,6 @@ public void dispose() { this.dialog.dispose(); } - private void onKeyPressed(KeyEvent e) { - if (KeyBinds.SEARCH_DIALOG_NEXT.matches(e)) { - int next = this.classList.isSelectionEmpty() ? 0 : this.classList.getSelectedIndex() + 1; - this.classList.setSelectedIndex(next); - this.classList.ensureIndexIsVisible(next); - } else if (KeyBinds.SEARCH_DIALOG_PREVIOUS.matches(e)) { - int prev = this.classList.isSelectionEmpty() ? this.classList.getModel().getSize() : this.classList.getSelectedIndex() - 1; - this.classList.setSelectedIndex(prev); - this.classList.ensureIndexIsVisible(prev); - } else if (KeyBinds.EXIT.matches(e)) { - this.close(); - } - } - private record SearchEntryImpl(ParentedEntry obf, ParentedEntry deobf) implements SearchEntry { @Override public List getSearchableNames() { diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/dialog/keybind/EditKeyBindDialog.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/dialog/keybind/EditKeyBindDialog.java index 48bd8de10..ffd812c9a 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/dialog/keybind/EditKeyBindDialog.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/dialog/keybind/EditKeyBindDialog.java @@ -170,7 +170,7 @@ private void reset() { } private boolean isNewCombination(CombinationPanel panel) { - return panel.getOriginalCombination() != KeyBind.Combination.EMPTY; + return panel.getOriginalCombination() == KeyBind.Combination.EMPTY; } // Stop editing all combination panels but the excluded one diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/docker/StructureDocker.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/docker/StructureDocker.java index 644077d70..a15640ad2 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/docker/StructureDocker.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/docker/StructureDocker.java @@ -34,6 +34,8 @@ import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; +import static org.quiltmc.enigma.gui.util.GuiUtil.putKeyBindAction; + public class StructureDocker extends Docker { private final JPanel optionsPanel; @@ -91,7 +93,8 @@ public void keyReleased(KeyEvent e) { this.structureTree.setCellRenderer(new StructureTreeCellRenderer(gui)); this.structureTree.setSelectionModel(new SingleTreeSelectionModel()); this.structureTree.setShowsRootHandles(true); - this.structureTree.addKeyListener(GuiUtil.onKeyPress(this::onKeyPress)); + putKeyBindAction(KeyBinds.SELECT, this.structureTree, e -> this.navigateToSelectedNode()); + this.structureTree.addMouseListener(GuiUtil.onMouseClick(this::onClick)); this.retranslateUi(); @@ -135,12 +138,6 @@ public void focusTree() { this.structureTree.requestFocus(); } - private void onKeyPress(KeyEvent e) { - if (KeyBinds.SELECT.matches(e)) { - this.navigateToSelectedNode(); - } - } - private void onClick(MouseEvent event) { if (event.getClickCount() >= 2 && event.getButton() == MouseEvent.BUTTON1) { this.navigateToSelectedNode(); diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/element/ConvertingTextField.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/element/ConvertingTextField.java index b4ebeba9a..a4d020aa2 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/element/ConvertingTextField.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/element/ConvertingTextField.java @@ -16,6 +16,8 @@ import javax.swing.JTextField; import javax.swing.text.Document; +import static org.quiltmc.enigma.gui.util.GuiUtil.putKeyBindAction; + /** * A label that converts into an editable text field when you click it. */ @@ -47,13 +49,8 @@ public void focusLost(FocusEvent e) { } }); - this.textField.addKeyListener(GuiUtil.onKeyPress(e -> { - if (KeyBinds.EXIT.matches(e)) { - this.stopEditing(true); - } else if (KeyBinds.DIALOG_SAVE.matches(e)) { - this.stopEditing(false); - } - })); + putKeyBindAction(KeyBinds.EXIT, this.textField, e -> this.stopEditing(true)); + putKeyBindAction(KeyBinds.DIALOG_SAVE, this.textField, e -> this.stopEditing(false)); this.ui.add(this.label); } diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/element/EditorPopupMenu.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/element/EditorPopupMenu.java index 922ad7d65..4c0a0c3b2 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/element/EditorPopupMenu.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/element/EditorPopupMenu.java @@ -1,10 +1,12 @@ package org.quiltmc.enigma.gui.element; +import com.google.common.collect.ImmutableMap; import org.quiltmc.enigma.api.analysis.EntryReference; import org.quiltmc.enigma.api.source.TokenType; import org.quiltmc.enigma.gui.EditableType; import org.quiltmc.enigma.gui.Gui; import org.quiltmc.enigma.gui.GuiController; +import org.quiltmc.enigma.gui.config.keybind.KeyBind; import org.quiltmc.enigma.gui.config.keybind.KeyBinds; import org.quiltmc.enigma.gui.docker.StructureDocker; import org.quiltmc.enigma.gui.panel.EditorPanel; @@ -15,9 +17,10 @@ import org.quiltmc.enigma.api.translation.representation.entry.MethodEntry; import org.quiltmc.enigma.util.I18n; -import java.awt.event.KeyEvent; +import javax.swing.AbstractButton; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; +import java.util.Map; public class EditorPopupMenu { private final JPopupMenu ui = new JPopupMenu(); @@ -39,6 +42,22 @@ public class EditorPopupMenu { private final JMenuItem zoomOutMenu = new JMenuItem(); private final JMenuItem resetZoomItem = new JMenuItem(); + private final ImmutableMap buttonKeyBinds = ImmutableMap.ofEntries( + Map.entry(KeyBinds.EDITOR_SHOW_INHERITANCE, this.showInheritanceItem), + Map.entry(KeyBinds.EDITOR_SHOW_IMPLEMENTATIONS, this.showImplementationsItem), + Map.entry(KeyBinds.EDITOR_OPEN_ENTRY, this.openEntryItem), + Map.entry(KeyBinds.EDITOR_OPEN_PREVIOUS, this.openPreviousItem), + Map.entry(KeyBinds.EDITOR_OPEN_NEXT, this.openNextItem), + Map.entry(KeyBinds.EDITOR_SHOW_CALLS_SPECIFIC, this.showCallsSpecificItem), + Map.entry(KeyBinds.EDITOR_SHOW_CALLS, this.showCallsItem), + Map.entry(KeyBinds.EDITOR_TOGGLE_MAPPING, this.toggleMappingItem), + Map.entry(KeyBinds.EDITOR_RENAME, this.renameItem), + Map.entry(KeyBinds.EDITOR_EDIT_JAVADOC, this.editJavadocItem), + Map.entry(KeyBinds.EDITOR_PASTE, this.pasteItem), + Map.entry(KeyBinds.EDITOR_SEARCH_STRUCTURE, this.searchStructureItem), + Map.entry(KeyBinds.EDITOR_SHOW_STRUCTURE, this.showStructureItem) + ); + private final EditorPanel editor; private final Gui gui; @@ -102,70 +121,8 @@ public EditorPopupMenu(EditorPanel editor, Gui gui) { this.resetZoomItem.addActionListener(event -> editor.resetEditorZoom()); } - public void setKeyBinds() { - this.renameItem.setAccelerator(KeyBinds.EDITOR_RENAME.toKeyStroke()); - this.pasteItem.setAccelerator(KeyBinds.EDITOR_PASTE.toKeyStroke()); - this.editJavadocItem.setAccelerator(KeyBinds.EDITOR_EDIT_JAVADOC.toKeyStroke()); - this.showStructureItem.setAccelerator(KeyBinds.EDITOR_SHOW_STRUCTURE.toKeyStroke()); - this.searchStructureItem.setAccelerator(KeyBinds.EDITOR_SEARCH_STRUCTURE.toKeyStroke()); - this.showInheritanceItem.setAccelerator(KeyBinds.EDITOR_SHOW_INHERITANCE.toKeyStroke()); - this.showImplementationsItem.setAccelerator(KeyBinds.EDITOR_SHOW_IMPLEMENTATIONS.toKeyStroke()); - this.showCallsItem.setAccelerator(KeyBinds.EDITOR_SHOW_CALLS.toKeyStroke()); - this.showCallsSpecificItem.setAccelerator(KeyBinds.EDITOR_SHOW_CALLS_SPECIFIC.toKeyStroke()); - this.openEntryItem.setAccelerator(KeyBinds.EDITOR_OPEN_ENTRY.toKeyStroke()); - this.openPreviousItem.setAccelerator(KeyBinds.EDITOR_OPEN_PREVIOUS.toKeyStroke()); - this.openNextItem.setAccelerator(KeyBinds.EDITOR_OPEN_NEXT.toKeyStroke()); - this.toggleMappingItem.setAccelerator(KeyBinds.EDITOR_TOGGLE_MAPPING.toKeyStroke()); - this.zoomInItem.setAccelerator(KeyBinds.EDITOR_ZOOM_IN.toKeyStroke()); - this.zoomOutMenu.setAccelerator(KeyBinds.EDITOR_ZOOM_OUT.toKeyStroke()); - } - - // TODO have editor redirect key event to menu so that the actions get - // triggered without having to hardcode them here, because this - // is a hack - public boolean handleKeyEvent(KeyEvent event) { - if (KeyBinds.EDITOR_SHOW_INHERITANCE.matches(event)) { - this.showInheritanceItem.doClick(); - return true; - } else if (KeyBinds.EDITOR_SHOW_IMPLEMENTATIONS.matches(event)) { - this.showImplementationsItem.doClick(); - return true; - } else if (KeyBinds.EDITOR_OPEN_ENTRY.matches(event)) { - this.openEntryItem.doClick(); - return true; - } else if (KeyBinds.EDITOR_OPEN_PREVIOUS.matches(event)) { - this.openPreviousItem.doClick(); - return true; - } else if (KeyBinds.EDITOR_OPEN_NEXT.matches(event)) { - this.openNextItem.doClick(); - return true; - } else if (KeyBinds.EDITOR_SHOW_CALLS_SPECIFIC.matches(event)) { - this.showCallsSpecificItem.doClick(); - return true; - } else if (KeyBinds.EDITOR_SHOW_CALLS.matches(event)) { - this.showCallsItem.doClick(); - return true; - } else if (KeyBinds.EDITOR_TOGGLE_MAPPING.matches(event)) { - this.toggleMappingItem.doClick(); - return true; - } else if (KeyBinds.EDITOR_RENAME.matches(event)) { - this.renameItem.doClick(); - return true; - } else if (KeyBinds.EDITOR_EDIT_JAVADOC.matches(event)) { - this.editJavadocItem.doClick(); - return true; - } else if (KeyBinds.EDITOR_PASTE.matches(event)) { - this.pasteItem.doClick(); - return true; - } else if (KeyBinds.EDITOR_SEARCH_STRUCTURE.matches(event)) { - this.searchStructureItem.doClick(); - return true; - } else if (KeyBinds.EDITOR_SHOW_STRUCTURE.matches(event)) { - this.showStructureItem.doClick(); - return true; - } - - return false; + public ImmutableMap getButtonKeyBinds() { + return this.buttonKeyBinds; } public void updateUiState() { diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/element/EditorTabbedPane.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/element/EditorTabbedPane.java index 221b60224..745ca5b08 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/element/EditorTabbedPane.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/element/EditorTabbedPane.java @@ -16,10 +16,15 @@ import java.awt.event.MouseEvent; import java.util.Iterator; import javax.annotation.Nullable; +import javax.swing.InputMap; +import javax.swing.JComponent; import javax.swing.JTabbedPane; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; +import static org.quiltmc.enigma.gui.util.GuiUtil.putKeyBindAction; +import static javax.swing.SwingUtilities.getUIInputMap; + public class EditorTabbedPane { private final JTabbedPane openFiles = new JTabbedPane(SwingConstants.TOP, JTabbedPane.SCROLL_TAB_LAYOUT); private final HashBiMap editors = HashBiMap.create(); @@ -34,23 +39,28 @@ public EditorTabbedPane(Gui gui) { this.navigator = new NavigatorPanel(this.gui); this.openFiles.addMouseListener(GuiUtil.onMousePress(this::onTabPressed)); + final InputMap openFilesInputMap = getUIInputMap(this.openFiles, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + if (openFilesInputMap != null) { + // remove default JTabbedPane binding that conflicts with KeyBind for editors + openFilesInputMap.remove(KeyBinds.ENTRY_NAVIGATOR_LAST.toKeyStroke()); + } } public EditorPanel openClass(ClassEntry entry) { EditorPanel activeEditor = this.getActiveEditor(); - EditorPanel editorPanel = this.editors.computeIfAbsent(entry, e -> { - ClassHandle ch = this.gui.getController().getClassHandleProvider().openClass(entry); + EditorPanel editorPanel = this.editors.computeIfAbsent(entry, classEntry -> { + ClassHandle ch = this.gui.getController().getClassHandleProvider().openClass(classEntry); if (ch == null) return null; this.navigator = new NavigatorPanel(this.gui); - EditorPanel ed = new EditorPanel(this.gui, this.navigator); - ed.setClassHandle(ch); - this.openFiles.addTab(ed.getFileName(), ed.getUi()); + EditorPanel newEditor = new EditorPanel(this.gui, this.navigator); + newEditor.setClassHandle(ch); + this.openFiles.addTab(newEditor.getFileName(), newEditor.getUi()); - ClosableTabTitlePane titlePane = new ClosableTabTitlePane(ed.getFileName(), () -> this.closeEditor(ed)); - this.openFiles.setTabComponentAt(this.openFiles.indexOfComponent(ed.getUi()), titlePane.getUi()); + ClosableTabTitlePane titlePane = new ClosableTabTitlePane(newEditor.getFileName(), () -> this.closeEditor(newEditor)); + this.openFiles.setTabComponentAt(this.openFiles.indexOfComponent(newEditor.getUi()), titlePane.getUi()); titlePane.setTabbedPane(this.openFiles); - ed.addListener(new EditorActionListener() { + newEditor.addListener(new EditorActionListener() { @Override public void onCursorReferenceChanged(EditorPanel editor, EntryReference, Entry> ref) { if (editor == EditorTabbedPane.this.getActiveEditor()) { @@ -70,19 +80,11 @@ public void onTitleChanged(EditorPanel editor, String title) { } }); - ed.getEditor().addKeyListener(GuiUtil.onKeyPress(keyEvent -> { - if (KeyBinds.EDITOR_CLOSE_TAB.matches(keyEvent)) { - this.closeEditor(ed); - } else if (KeyBinds.ENTRY_NAVIGATOR_NEXT.matches(keyEvent)) { - ed.getNavigatorPanel().navigateDown(); - keyEvent.consume(); - } else if (KeyBinds.ENTRY_NAVIGATOR_LAST.matches(keyEvent)) { - ed.getNavigatorPanel().navigateUp(); - keyEvent.consume(); - } - })); + putKeyBindAction(KeyBinds.EDITOR_CLOSE_TAB, newEditor.getEditor(), e -> this.closeEditor(newEditor)); + putKeyBindAction(KeyBinds.ENTRY_NAVIGATOR_NEXT, newEditor.getEditor(), e -> newEditor.getNavigatorPanel().navigateDown()); + putKeyBindAction(KeyBinds.ENTRY_NAVIGATOR_LAST, newEditor.getEditor(), e -> newEditor.getNavigatorPanel().navigateUp()); - return ed; + return newEditor; }); if (editorPanel != null && activeEditor != editorPanel) { diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/panel/ClosableTabTitlePane.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/panel/ClosableTabTitlePane.java index 137bd9b77..877e5f374 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/panel/ClosableTabTitlePane.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/panel/ClosableTabTitlePane.java @@ -8,7 +8,6 @@ import javax.swing.JPanel; import javax.swing.JTabbedPane; import javax.swing.SwingUtilities; -import javax.swing.UIManager; import javax.swing.border.EmptyBorder; import javax.swing.event.ChangeListener; import java.awt.Dimension; @@ -36,7 +35,7 @@ public ClosableTabTitlePane(String text, Runnable onClose) { this.closeButton.setFocusPainted(false); this.closeButton.setFocusable(false); this.closeButton.setOpaque(true); - this.closeButton.setIcon(UIManager.getIcon("InternalFrame.closeIcon")); + this.closeButton.setIcon(GuiUtil.getCloseIcon()); this.closeButton.putClientProperty("paintActive", Boolean.TRUE); this.closeButton.setBorder(new EmptyBorder(0, 0, 0, 0)); this.closeButton.putClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY, "Close"); diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/panel/EditorPanel.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/panel/EditorPanel.java index 772f65763..7c75f7b12 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/panel/EditorPanel.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/panel/EditorPanel.java @@ -13,6 +13,7 @@ import org.quiltmc.enigma.gui.config.theme.ThemeUtil; import org.quiltmc.enigma.gui.config.Config; import org.quiltmc.enigma.gui.config.keybind.KeyBinds; +import org.quiltmc.enigma.gui.dialog.EnigmaQuickFindToolBar; import org.quiltmc.enigma.gui.element.EditorPopupMenu; import org.quiltmc.enigma.gui.element.NavigatorPanel; import org.quiltmc.enigma.gui.event.EditorActionListener; @@ -23,13 +24,13 @@ import org.quiltmc.enigma.api.source.DecompiledClassSource; import org.quiltmc.enigma.api.source.TokenType; import org.quiltmc.enigma.api.source.Token; -import org.quiltmc.enigma.api.translation.mapping.EntryRemapper; -import org.quiltmc.enigma.api.translation.mapping.EntryResolver; import org.quiltmc.enigma.api.translation.mapping.ResolutionStrategy; import org.quiltmc.enigma.api.translation.representation.entry.ClassEntry; import org.quiltmc.enigma.api.translation.representation.entry.Entry; import org.quiltmc.enigma.util.I18n; import org.quiltmc.enigma.util.Result; +import org.quiltmc.syntaxpain.DefaultSyntaxAction; +import org.quiltmc.syntaxpain.SyntaxDocument; import org.tinylog.Logger; import java.awt.Color; @@ -43,6 +44,9 @@ import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; @@ -68,11 +72,16 @@ import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.Highlighter.HighlightPainter; +import javax.swing.text.JTextComponent; + +import static org.quiltmc.enigma.gui.util.GuiUtil.putKeyBindAction; +import static java.awt.event.InputEvent.CTRL_DOWN_MASK; 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 EnigmaQuickFindToolBar quickFindToolBar = new EnigmaQuickFindToolBar(); private final EditorPopupMenu popupMenu; // progress UI @@ -93,8 +102,6 @@ public class EditorPanel { private EntryReference, Entry> cursorReference; private EntryReference, Entry> nextReference; - private boolean mouseIsPressed = false; - private boolean shouldNavigateOnClick; private int fontSize = 12; private final BoxHighlightPainter obfuscatedPainter; @@ -119,11 +126,32 @@ public EditorPanel(Gui gui, NavigatorPanel navigator) { this.editor.setSelectionColor(new Color(31, 46, 90)); this.editor.setCaret(new BrowserCaret()); this.editor.setFont(ScaleUtil.getFont(this.editor.getFont().getFontName(), Font.PLAIN, this.fontSize)); - this.editor.addCaretListener(event -> this.onCaretMove(event.getDot(), this.mouseIsPressed)); + this.editor.addCaretListener(event -> this.onCaretMove(event.getDot())); this.editor.setCaretColor(Config.getCurrentSyntaxPaneColors().caret.value()); this.editor.setContentType("text/enigma-sources"); this.editor.setBackground(Config.getCurrentSyntaxPaneColors().editorBackground.value()); + // HACK to prevent DefaultCaret from calling setSelectionVisible(false) when quickFind gains focus + if (this.editor.getCaret() instanceof FocusListener caretFocusListener) { + this.editor.removeFocusListener(caretFocusListener); + this.editor.addFocusListener(new FocusAdapter() { + @Override + public void focusGained(FocusEvent e) { + caretFocusListener.focusGained(e); + } + + @Override + public void focusLost(FocusEvent e) { + EditorPanel.this.editor.getCaret().setVisible(false); + EditorPanel.this.editor.getCaret().setSelectionVisible( + EditorPanel.this.editor.hasFocus() || EditorPanel.this.quickFindToolBar.isVisible() + ); + } + }); + } + + this.quickFindToolBar.setVisible(false); + // set unit increment to height of one line, the amount scrolled per // mouse wheel rotation is then controlled by OS settings this.editorScrollPane.getVerticalScrollBar().setUnitIncrement(this.editor.getFontMetrics(this.editor.getFont()).getHeight()); @@ -145,8 +173,11 @@ public EditorPanel(Gui gui, NavigatorPanel navigator) { this.editor.addMouseListener(new MouseAdapter() { @Override - public void mousePressed(MouseEvent mouseEvent) { - EditorPanel.this.mouseIsPressed = true; + public void mouseClicked(MouseEvent e) { + if ((e.getModifiersEx() & CTRL_DOWN_MASK) != 0 && e.getButton() == MouseEvent.BUTTON1) { + // ctrl + left click + EditorPanel.this.navigateToCursorReference(); + } } @Override @@ -159,31 +190,10 @@ public void mouseReleased(MouseEvent e) { case 5 -> // Forward navigation gui.getController().openNextReference(); } - - EditorPanel.this.mouseIsPressed = false; } }); - this.editor.addKeyListener(new KeyAdapter() { - @Override - public void keyPressed(KeyEvent event) { - if (EditorPanel.this.popupMenu.handleKeyEvent(event)) return; - - if (KeyBinds.EDITOR_RELOAD_CLASS.matches(event)) { - if (EditorPanel.this.classHandle != null) { - EditorPanel.this.classHandle.invalidate(); - } - } else if (KeyBinds.EDITOR_QUICK_FIND.matches(event)) { - // prevent navigating on click when quick find activated - EditorPanel.this.shouldNavigateOnClick = false; // CTRL - } else if (KeyBinds.EDITOR_ZOOM_IN.matches(event)) { - EditorPanel.this.offsetEditorZoom(2); - } else if (KeyBinds.EDITOR_ZOOM_OUT.matches(event)) { - EditorPanel.this.offsetEditorZoom(-2); - } else if (event.isControlDown()) { - EditorPanel.this.shouldNavigateOnClick = true; // CTRL - } - } + this.editor.addKeyListener(new KeyAdapter() { @Override public void keyTyped(KeyEvent event) { EntryReference, Entry> ref = EditorPanel.this.getCursorReference(); @@ -206,13 +216,10 @@ public void keyTyped(KeyEvent event) { gui.startRename(EditorPanel.this, name); } } - - @Override - public void keyReleased(KeyEvent event) { - EditorPanel.this.shouldNavigateOnClick = event.isControlDown(); - } }); + this.reloadKeyBinds(); + this.retryButton.addActionListener(e -> this.redecompileClass()); this.ui.putClientProperty(EditorPanel.class, this); @@ -220,7 +227,9 @@ public void keyReleased(KeyEvent event) { public void onRename(boolean isNewMapping) { this.navigatorPanel.updateAllTokenTypes(); - if (isNewMapping) this.navigatorPanel.decrementIndex(); + if (isNewMapping) { + this.navigatorPanel.decrementIndex(); + } } @Nullable @@ -346,24 +355,39 @@ public boolean isOptimizedDrawingEnabled() { }; editorPane.setLayout(new GridBagLayout()); - GridBagConstraints constraints = new GridBagConstraints(); - constraints.gridx = 0; - constraints.gridy = 0; - constraints.weightx = 1.0; - constraints.weighty = 1.0; - constraints.anchor = GridBagConstraints.FIRST_LINE_END; - constraints.insets = new Insets(32, 32, 32, 32); - constraints.ipadx = 16; - constraints.ipady = 16; - editorPane.add(this.navigatorPanel, constraints); - - constraints = new GridBagConstraints(); - constraints.gridx = 0; - constraints.gridy = 0; - constraints.weightx = 1.0; - constraints.weighty = 1.0; - constraints.fill = GridBagConstraints.BOTH; - editorPane.add(this.editorScrollPane, constraints); + { + final var navigatorConstraints = new GridBagConstraints(); + navigatorConstraints.gridx = 0; + navigatorConstraints.gridy = 0; + navigatorConstraints.weightx = 1.0; + navigatorConstraints.weighty = 1.0; + navigatorConstraints.anchor = GridBagConstraints.FIRST_LINE_END; + navigatorConstraints.insets = new Insets(32, 32, 32, 32); + navigatorConstraints.ipadx = 16; + navigatorConstraints.ipady = 16; + editorPane.add(this.navigatorPanel, navigatorConstraints); + } + + { + final var scrollConstraints = new GridBagConstraints(); + scrollConstraints.gridx = 0; + scrollConstraints.gridy = 0; + scrollConstraints.weightx = 1.0; + scrollConstraints.weighty = 1.0; + scrollConstraints.fill = GridBagConstraints.BOTH; + editorPane.add(this.editorScrollPane, scrollConstraints); + } + + { + final var quickFindConstraints = new GridBagConstraints(); + quickFindConstraints.gridx = 0; + quickFindConstraints.weightx = 1.0; + quickFindConstraints.weighty = 0; + quickFindConstraints.anchor = GridBagConstraints.PAGE_END; + quickFindConstraints.fill = GridBagConstraints.HORIZONTAL; + editorPane.add(this.quickFindToolBar, quickFindConstraints); + } + this.ui.add(editorPane); break; } @@ -396,25 +420,21 @@ public void resetEditorZoom() { this.editor.setFont(ScaleUtil.getFont(this.editor.getFont().getFontName(), Font.PLAIN, this.fontSize)); } - public void onCaretMove(int pos, boolean fromClick) { + private void onCaretMove(int pos) { if (this.settingSource || this.controller.getProject() == null) { return; } - EntryRemapper mapper = this.controller.getProject().getRemapper(); - Token token = this.getToken(pos); - - this.setCursorReference(this.getReference(token)); - - Entry referenceEntry = this.cursorReference != null ? this.cursorReference.entry : null; + this.setCursorReference(this.getReference(this.getToken(pos))); + } - if (referenceEntry != null && this.shouldNavigateOnClick && fromClick) { - this.shouldNavigateOnClick = false; - Entry navigationEntry = referenceEntry; - if (this.cursorReference.context == null) { - EntryResolver resolver = mapper.getObfResolver(); - navigationEntry = resolver.resolveFirstEntry(referenceEntry, ResolutionStrategy.RESOLVE_ROOT); - } + private void navigateToCursorReference() { + if (this.cursorReference != null) { + final Entry referenceEntry = this.cursorReference.entry; + final Entry navigationEntry = this.cursorReference.context == null + ? this.controller.getProject().getRemapper().getObfResolver() + .resolveFirstEntry(referenceEntry, ResolutionStrategy.RESOLVE_ROOT) + : referenceEntry; this.controller.navigateTo(navigationEntry); } @@ -660,10 +680,26 @@ public String getFileName() { public void retranslateUi() { this.popupMenu.retranslateUi(); + this.quickFindToolBar.translate(); } public void reloadKeyBinds() { - this.popupMenu.setKeyBinds(); + putKeyBindAction(KeyBinds.EDITOR_RELOAD_CLASS, this.editor, e -> { + if (this.classHandle != null) { + this.classHandle.invalidate(); + } + }); + putKeyBindAction(KeyBinds.EDITOR_ZOOM_IN, this.editor, e -> this.offsetEditorZoom(2)); + putKeyBindAction(KeyBinds.EDITOR_ZOOM_OUT, this.editor, e -> this.offsetEditorZoom(-2)); + putKeyBindAction(KeyBinds.EDITOR_QUICK_FIND, this.editor, new DefaultSyntaxAction("quick-find-tool-bar") { + @Override + public void actionPerformed(JTextComponent target, SyntaxDocument sDoc, int dot, ActionEvent e) { + EditorPanel.this.quickFindToolBar.showFor(target); + } + }); + this.quickFindToolBar.reloadKeyBinds(); + + this.popupMenu.getButtonKeyBinds().forEach((key, button) -> putKeyBindAction(key, this.editor, e -> button.doClick())); } private enum DisplayMode { diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/GuiUtil.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/GuiUtil.java index 1608156af..09c93bb77 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/GuiUtil.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/GuiUtil.java @@ -7,18 +7,24 @@ import org.quiltmc.enigma.api.translation.representation.AccessFlags; import org.quiltmc.enigma.api.translation.representation.entry.ClassEntry; import org.quiltmc.enigma.api.translation.representation.entry.MethodEntry; +import org.quiltmc.enigma.gui.config.keybind.KeyBind; import org.quiltmc.enigma.gui.config.theme.ThemeUtil; import org.quiltmc.enigma.util.Os; +import javax.swing.Action; +import javax.swing.ActionMap; import javax.swing.Icon; +import javax.swing.InputMap; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JToolTip; import javax.swing.JTree; +import javax.swing.KeyStroke; import javax.swing.Popup; import javax.swing.PopupFactory; import javax.swing.Timer; +import javax.swing.UIManager; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.TreeNode; @@ -31,6 +37,7 @@ import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; @@ -52,7 +59,11 @@ import java.util.Objects; import java.util.function.Consumer; -public class GuiUtil { +public final class GuiUtil { + private GuiUtil() { + throw new UnsupportedOperationException(); + } + public static final Icon CLASS_ICON = loadIcon("class"); public static final Icon INTERFACE_ICON = loadIcon("interface"); public static final Icon ENUM_ICON = loadIcon("enum"); @@ -207,6 +218,10 @@ public static Icon getMethodIcon(MethodEntry entry) { return METHOD_ICON; } + public static Icon getCloseIcon() { + return UIManager.getIcon("InternalFrame.closeIcon"); + } + public static TreePath getPathToRoot(TreeNode node) { List nodes = new ArrayList<>(); TreeNode n = node; @@ -274,4 +289,65 @@ public static Icon getUpChevron() { public static Icon getDownChevron() { return ThemeUtil.isDarkLaf() ? CHEVRON_DOWN_WHITE : CHEVRON_DOWN_BLACK; } + + public static void putKeyBindAction(KeyBind keyBind, JComponent component, ActionListener listener) { + putKeyBindAction(keyBind, component, FocusCondition.WHEN_IN_FOCUSED_WINDOW, listener); + } + + public static void putKeyBindAction( + KeyBind keyBind, JComponent component, FocusCondition condition, ActionListener listener + ) { + putKeyBindAction(keyBind, component, condition, new SimpleAction(listener)); + } + + public static void putKeyBindAction( + KeyBind keyBind, JComponent component, FocusCondition condition, Action action + ) { + final InputMap inputMap = component.getInputMap(condition.value); + + if (inputMap != null) { + final String actionKey = keyBind.name(); + + final KeyStroke[] keys = inputMap.keys(); + if (keys != null) { + for (final KeyStroke key : keys) { + final Object value = inputMap.get(key); + if (actionKey.equals(value)) { + // remove previous bindings to action + inputMap.remove(key); + } + } + } + + keyBind.combinations().stream().map(combo -> combo.toKeyStroke(0)) + .forEach(key -> inputMap.put(key, actionKey)); + + final ActionMap actionMap = component.getActionMap(); + if (actionMap != null) { + actionMap.remove(actionKey); + actionMap.put(actionKey, action); + } + } + } + + public enum FocusCondition { + /** + * @see JComponent#WHEN_IN_FOCUSED_WINDOW + */ + WHEN_IN_FOCUSED_WINDOW(JComponent.WHEN_IN_FOCUSED_WINDOW), + /** + * @see JComponent#WHEN_FOCUSED + */ + WHEN_FOCUSED(JComponent.WHEN_FOCUSED), + /** + * @see JComponent#WHEN_ANCESTOR_OF_FOCUSED_COMPONENT + */ + WHEN_ANCESTOR_OF_FOCUSED_COMPONENT(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + + private final int value; + + FocusCondition(int value) { + this.value = value; + } + } } diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/SimpleAction.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/SimpleAction.java new file mode 100644 index 000000000..1c44bd47a --- /dev/null +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/SimpleAction.java @@ -0,0 +1,18 @@ +package org.quiltmc.enigma.gui.util; + +import javax.swing.AbstractAction; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class SimpleAction extends AbstractAction { + private final ActionListener listener; + + public SimpleAction(ActionListener listener) { + this.listener = listener; + } + + @Override + public void actionPerformed(ActionEvent e) { + this.listener.actionPerformed(e); + } +} diff --git a/enigma/src/main/resources/lang/en_us.json b/enigma/src/main/resources/lang/en_us.json index 001898785..2467cc8eb 100644 --- a/enigma/src/main/resources/lang/en_us.json +++ b/enigma/src/main/resources/lang/en_us.json @@ -148,6 +148,7 @@ "editor.quick_find.use_regex": "Use regex", "editor.quick_find.wrap": "Wrap", "editor.quick_find.not_found": "Not found!", + "editor.quick_find.persistent": "Persistent", "info_panel.identifier": "Identifier Info", "info_panel.identifier.none": "No identifier selected", @@ -338,6 +339,7 @@ "keybind.editor.zoom_out": "Zoom in", "keybind.editor.close_tab": "Close editor tab", "keybind.editor.reload_class": "Reload open class", + "keybind.editor.quick_find": "Quick find", "keybind.editor.navigate_next": "Navigate to next entry", "keybind.editor.navigate_last": "Navigate to last entry", "keybind.menu.search": "Search", diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ac23c5adf..67e37ad9e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ gson = "2.10.1" asm = "9.7.1" jopt = "6.0-alpha-3" flatlaf = "3.4" -syntaxpain = "0.1.5" +syntaxpain = "0.1.5+local" swing_dpi = "0.10" fontchooser = "2.5.2" tinylog = "2.6.2"