Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gui): allow user to set custom shortcuts #1980

Merged
merged 33 commits into from
Aug 3, 2023
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
44fe5a7
feat(gui): allow user to customize shortcuts
Mino260806 Aug 1, 2023
f087de4
internal: fixed other constructor for jadx action
Mino260806 Aug 1, 2023
2f2737f
make code area actions customizable
Mino260806 Aug 2, 2023
15e4b47
show warning dialog when mouse button is commonly used
Mino260806 Aug 2, 2023
107c3f8
applied code formatting
Mino260806 Aug 2, 2023
6bef0fe
code formatting and and moved string to resources
Mino260806 Aug 2, 2023
51ca5e3
moved action related classes to their own package
Mino260806 Aug 2, 2023
db182ee
added fix for actions with modifiers in macos
Mino260806 Aug 2, 2023
b85b847
ignore left click in shortcut edit
Mino260806 Aug 2, 2023
ada8c9d
applied code formatting
Mino260806 Aug 2, 2023
fa21b1c
warn user when a duplicate shortcut is entered
Mino260806 Aug 2, 2023
32ef389
save shortcut when key is pressed (instead of typed)
Mino260806 Aug 2, 2023
52472d8
fix node under mouse being ignored
Mino260806 Aug 2, 2023
8f65691
add missing import
Mino260806 Aug 2, 2023
1157b88
applied code formatting
Mino260806 Aug 2, 2023
d1e7420
added custom shortcuts support to script content panel
Mino260806 Aug 2, 2023
31a53d8
save shortcut when key is released (instead of pressed)
Mino260806 Aug 2, 2023
d83d69c
enable custom shortcut in script autocomplete
Mino260806 Aug 2, 2023
06782f8
fix duplicate shortcut warning when the shortcut is set again at the …
Mino260806 Aug 2, 2023
aa15eb6
fixed mouse buttons shortcut not working for code area
Mino260806 Aug 2, 2023
b35c8b9
fix exception with mouse button shortcuts
Mino260806 Aug 2, 2023
f9a2a31
fix action getting fired twice
Mino260806 Aug 2, 2023
e44dfb3
added variants for forward and back nav actions
Mino260806 Aug 2, 2023
1662f79
fix exception when shortcut is not saved
Mino260806 Aug 3, 2023
6ed5071
fix mouse button shortcut for auto complete action
Mino260806 Aug 3, 2023
0f5b378
consume mouse event if bound to an action
Mino260806 Aug 3, 2023
7ea464c
workaround not being able to extend HashMap
Mino260806 Aug 3, 2023
e833548
fix exception in script code area when using mouse button shortcut
Mino260806 Aug 3, 2023
c64dee5
minor pref serialiazation improvement
Mino260806 Aug 3, 2023
b0cb8ce
fix action buttons not working (like run action)
Mino260806 Aug 3, 2023
90e84e1
fix exception with plugin actinos
Mino260806 Aug 3, 2023
90ac9ad
fixed nullptr when adding an action with null actionmodel to jadxmenu
Mino260806 Aug 3, 2023
952bd99
fix plugin action name not showing
Mino260806 Aug 3, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,13 @@ private static class NodeAction extends JNodeAction {

public NodeAction(CodePopupAction data, CodeArea codeArea) {
super(data.name, codeArea);
setShortcutComponent(codeArea);
skylot marked this conversation as resolved.
Show resolved Hide resolved
if (data.keyBinding != null) {
KeyStroke key = KeyStroke.getKeyStroke(data.keyBinding);
if (key == null) {
throw new IllegalArgumentException("Failed to parse key stroke: " + data.keyBinding);
}
addKeyBinding(key, data.name);
setKeyBinding(key);
}
this.data = data;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
package jadx.gui.plugins.script;

import java.awt.event.KeyEvent;

import javax.swing.KeyStroke;

import org.fife.ui.autocomplete.AutoCompletion;
import org.jetbrains.annotations.NotNull;

import jadx.api.ICodeInfo;
import jadx.gui.settings.JadxSettings;
import jadx.gui.treemodel.JInputScript;
import jadx.gui.ui.action.JadxAutoCompletion;
import jadx.gui.ui.codearea.AbstractCodeArea;
import jadx.gui.ui.panel.ContentPanel;
import jadx.gui.utils.UiUtils;
import jadx.gui.utils.shortcut.ShortcutsController;

public class ScriptCodeArea extends AbstractCodeArea {

private final JInputScript scriptNode;
private final AutoCompletion autoCompletion;
private final ShortcutsController shortcutsController;

public ScriptCodeArea(ContentPanel contentPanel, JInputScript node) {
super(contentPanel, node);
Expand All @@ -27,19 +25,20 @@ public ScriptCodeArea(ContentPanel contentPanel, JInputScript node) {
setCodeFoldingEnabled(true);
setCloseCurlyBraces(true);

shortcutsController = contentPanel.getTabbedPane().getMainWindow().getShortcutsController();
JadxSettings settings = contentPanel.getTabbedPane().getMainWindow().getSettings();
autoCompletion = addAutoComplete(settings);
}

private AutoCompletion addAutoComplete(JadxSettings settings) {
ScriptCompleteProvider provider = new ScriptCompleteProvider(this);
provider.setAutoActivationRules(false, ".");
AutoCompletion ac = new AutoCompletion(provider);
JadxAutoCompletion ac = new JadxAutoCompletion(provider);
ac.setListCellRenderer(new ScriptCompletionRenderer(settings));
ac.setTriggerKey(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, UiUtils.ctrlButton()));
ac.setAutoActivationEnabled(true);
ac.setAutoCompleteSingleChoices(true);
ac.install(this);
shortcutsController.bindImmediate(ac);
return ac;
}

Expand Down Expand Up @@ -80,6 +79,7 @@ public JInputScript getScriptNode() {

@Override
public void dispose() {
shortcutsController.unbindActionsForComponent(this);
autoCompletion.uninstall();
super.dispose();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,14 @@
import jadx.gui.treemodel.JInputScript;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.TabbedPane;
import jadx.gui.ui.action.ActionModel;
import jadx.gui.ui.action.JadxGuiAction;
import jadx.gui.ui.codearea.AbstractCodeArea;
import jadx.gui.ui.codearea.AbstractCodeContentPanel;
import jadx.gui.ui.codearea.SearchBar;
import jadx.gui.utils.Icons;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
import jadx.gui.utils.ui.ActionHandler;
import jadx.gui.utils.ui.NodeLabel;
import jadx.plugins.script.ide.ScriptAnalyzeResult;
import jadx.plugins.script.ide.ScriptServices;
Expand Down Expand Up @@ -89,15 +90,13 @@ private void initUI() {
}

private JPanel buildScriptActionsPanel() {
ActionHandler runAction = new ActionHandler(this::runScript);
runAction.setNameAndDesc(NLS.str("script.run"));
runAction.setIcon(Icons.RUN);
runAction.attachKeyBindingFor(scriptArea, KeyStroke.getKeyStroke(KeyEvent.VK_F8, 0));

ActionHandler saveAction = new ActionHandler(scriptArea::save);
saveAction.setNameAndDesc(NLS.str("script.save"));
saveAction.setIcon(Icons.SAVE_ALL);
saveAction.setKeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_S, UiUtils.ctrlButton()));
JadxGuiAction runAction = new JadxGuiAction(ActionModel.SCRIPT_RUN, this::runScript);
JadxGuiAction saveAction = new JadxGuiAction(ActionModel.SCRIPT_SAVE, scriptArea::save);

runAction.setShortcutComponent(scriptArea);
skylot marked this conversation as resolved.
Show resolved Hide resolved

tabbedPane.getMainWindow().getShortcutsController().bindImmediate(runAction);
tabbedPane.getMainWindow().getShortcutsController().bindImmediate(saveAction);

JButton save = saveAction.makeButton();
scriptArea.getScriptNode().addChangeListener(save::setEnabled);
Expand Down
15 changes: 15 additions & 0 deletions jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,15 @@
import jadx.cli.LogHelper;
import jadx.gui.cache.code.CodeCacheMode;
import jadx.gui.cache.usage.UsageCacheMode;
import jadx.gui.settings.data.ShortcutsWrapper;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.action.ActionModel;
import jadx.gui.ui.codearea.EditorTheme;
import jadx.gui.utils.FontUtils;
import jadx.gui.utils.LafManager;
import jadx.gui.utils.LangLocale;
import jadx.gui.utils.NLS;
import jadx.gui.utils.shortcut.Shortcut;

public class JadxSettings extends JadxCLIArgs {
private static final Logger LOG = LoggerFactory.getLogger(JadxSettings.class);
Expand Down Expand Up @@ -73,6 +76,10 @@ public class JadxSettings extends JadxCLIArgs {
private boolean autoStartJobs = false;
private String excludedPackages = "";
private boolean autoSaveProject = true;
private Map<ActionModel, Shortcut> shortcuts = new HashMap<>();

@JadxSettingsAdapter.GsonExclude
private ShortcutsWrapper shortcutsWrapper = null;

private boolean showHeapUsageBar = false;
private boolean alwaysSelectOpened = false;
Expand Down Expand Up @@ -442,6 +449,14 @@ public void setAutoSaveProject(boolean autoSaveProject) {
this.autoSaveProject = autoSaveProject;
}

public ShortcutsWrapper getShortcuts() {
if (shortcutsWrapper == null) {
shortcutsWrapper = new ShortcutsWrapper();
shortcutsWrapper.updateShortcuts(shortcuts);
}
return shortcutsWrapper;
}

public void setExportAsGradleProject(boolean exportAsGradleProject) {
this.exportAsGradleProject = exportAsGradleProject;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package jadx.gui.settings.data;

import java.util.Map;

import jadx.gui.ui.action.ActionModel;
import jadx.gui.utils.shortcut.Shortcut;

public class ShortcutsWrapper {
private Map<ActionModel, Shortcut> shortcuts;

public void updateShortcuts(Map<ActionModel, Shortcut> shortcuts) {
this.shortcuts = shortcuts;
}

public Shortcut get(ActionModel actionModel) {
return shortcuts.getOrDefault(actionModel, actionModel.getDefaultShortcut());
}

public void put(ActionModel actionModel, Shortcut shortcut) {
shortcuts.put(actionModel, shortcut);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
import jadx.gui.settings.LineNumbersMode;
import jadx.gui.settings.ui.cache.CacheSettingsGroup;
import jadx.gui.settings.ui.plugins.PluginsSettings;
import jadx.gui.settings.ui.shortcut.ShortcutsSettingsGroup;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.codearea.EditorTheme;
import jadx.gui.utils.FontUtils;
Expand Down Expand Up @@ -126,6 +127,7 @@ private void initUI() {
groups.add(makeRenameGroup());
groups.add(new CacheSettingsGroup(this));
groups.add(makeAppearanceGroup());
groups.add(new ShortcutsSettingsGroup(this, settings));
groups.add(makeSearchResGroup());
groups.add(makeProjectGroup());
groups.add(new PluginsSettings(mainWindow, settings).build());
Expand Down Expand Up @@ -640,6 +642,7 @@ private void save() {
enableComponents(this, false);
SwingUtilities.invokeLater(() -> {
if (shouldReload()) {
mainWindow.getShortcutsController().loadSettings();
mainWindow.reopen();
}
if (!settings.getLangLocale().equals(prevLang)) {
Expand Down
193 changes: 193 additions & 0 deletions jadx-gui/src/main/java/jadx/gui/settings/ui/shortcut/ShortcutEdit.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package jadx.gui.settings.ui.shortcut;

import java.awt.AWTEvent;
import java.awt.KeyboardFocusManager;
import java.awt.Toolkit;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;

import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.UIManager;

import jadx.gui.settings.JadxSettings;
import jadx.gui.settings.ui.JadxSettingsWindow;
import jadx.gui.ui.action.ActionModel;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
import jadx.gui.utils.shortcut.Shortcut;

public class ShortcutEdit extends JPanel {
private static final Icon CLEAR_ICON = UiUtils.openSvgIcon("ui/close");

private final ActionModel actionModel;
private final JadxSettingsWindow settingsWindow;
private final JadxSettings settings;
private final TextField textField;

public Shortcut shortcut;

public ShortcutEdit(ActionModel actionModel, JadxSettingsWindow settingsWindow, JadxSettings settings) {
this.actionModel = actionModel;
this.settings = settings;
this.settingsWindow = settingsWindow;

textField = new TextField();
JButton clearButton = new JButton(CLEAR_ICON);

setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
add(textField);
add(clearButton);

clearButton.addActionListener(e -> {
setShortcut(Shortcut.none());
saveShortcut();
});
}

public void setShortcut(Shortcut shortcut) {
this.shortcut = shortcut;
textField.reload();
}

private void saveShortcut() {
settings.getShortcuts().put(actionModel, shortcut);
settingsWindow.needReload();
}

private boolean verifyShortcut(Shortcut shortcut) {
ActionModel otherAction = null;
for (ActionModel a : ActionModel.values()) {
if (actionModel != a && shortcut.equals(settings.getShortcuts().get(a))) {
otherAction = a;
break;
}
}

if (otherAction != null) {
int dialogResult = JOptionPane.showConfirmDialog(
this,
NLS.str("msg.duplicate_shortcut",
shortcut,
otherAction.getName(),
otherAction.getCategory().getName()),
NLS.str("msg.warning_title"),
JOptionPane.YES_NO_OPTION);
if (dialogResult != 0) {
return false;
}
}

return true;
}

private class TextField extends JTextField {
private Shortcut tempShortcut;

public TextField() {
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(ev -> {
if (!isListening()) {
return false;
}

if (ev.getID() == KeyEvent.KEY_PRESSED) {
Shortcut pressedShortcut = Shortcut.keyboard(ev.getKeyCode(), ev.getModifiersEx());
if (pressedShortcut.isValidKeyboard()) {
tempShortcut = pressedShortcut;
refresh(tempShortcut);
} else {
tempShortcut = null;
}
} else if (ev.getID() == KeyEvent.KEY_RELEASED) {
removeFocus();
}
ev.consume();
return true;
});

addFocusListener(new FocusListener() {
@Override
public void focusGained(FocusEvent ev) {
}

@Override
public void focusLost(FocusEvent ev) {
if (tempShortcut != null) {
if (verifyShortcut(tempShortcut)) {
shortcut = tempShortcut;
saveShortcut();
} else {
reload();
}
tempShortcut = null;
}
}
});

Toolkit.getDefaultToolkit().addAWTEventListener(event -> {
if (!isListening()) {
return;
}

if (event instanceof MouseEvent) {
MouseEvent mouseEvent = (MouseEvent) event;
if (mouseEvent.getID() == MouseEvent.MOUSE_PRESSED) {
int mouseButton = mouseEvent.getButton();

if (mouseButton <= MouseEvent.BUTTON1) {
return;
}

if (mouseButton <= MouseEvent.BUTTON3) {
int dialogResult = JOptionPane.showConfirmDialog(
this,
NLS.str("msg.common_mouse_shortcut"),
NLS.str("msg.warning_title"),
JOptionPane.YES_NO_OPTION);
if (dialogResult != 0) {
((MouseEvent) event).consume();
tempShortcut = null;
removeFocus();
return;
}
}

((MouseEvent) event).consume();
tempShortcut = Shortcut.mouse(mouseButton);
refresh(tempShortcut);
removeFocus();
}
}
}, AWTEvent.MOUSE_EVENT_MASK);
}

public void reload() {
refresh(shortcut);
}

private void refresh(Shortcut displayedShortcut) {
if (displayedShortcut == null || displayedShortcut.isNone()) {
setText("None");
setForeground(UIManager.getColor("TextArea.inactiveForeground"));
return;
}
setText(displayedShortcut.toString());
setForeground(UIManager.getColor("TextArea.foreground"));
}

private void removeFocus() {
// triggers focusLost
getRootPane().requestFocus();
}

private boolean isListening() {
return isFocusOwner();
}
}
}
Loading