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

* feat(gui): allow user to customize shortcuts

* internal: fixed other constructor for jadx action

* make code area actions customizable

* show warning dialog when mouse button is commonly used

* applied code formatting

* code formatting and and moved string to resources

* moved action related classes to their own package

* added fix for actions with modifiers in macos

* ignore left click in shortcut edit

* applied code formatting

* warn user when a duplicate shortcut is entered

* save shortcut when key is pressed (instead of typed)

* fix node under mouse being ignored

Co-authored-by: skylot <118523+skylot@users.noreply.github.com>

* add missing import

* applied code formatting

* added custom shortcuts support to script content panel

* save shortcut when key is released (instead of pressed)

* enable custom shortcut in script autocomplete

* fix duplicate shortcut warning when the shortcut is set again at the same action

* fixed mouse buttons shortcut not working for code area

* fix exception with mouse button shortcuts

* fix action getting fired twice

* added variants for forward and back nav actions

* fix exception when shortcut is not saved

* fix mouse button shortcut for auto complete action

* consume mouse event if bound to an action

* workaround not being able to extend HashMap

* fix exception in script code area when using mouse button shortcut

* minor pref serialiazation improvement

* fix action buttons not working (like run action)

* fix exception with plugin actinos

* fixed nullptr when adding an action with null actionmodel to jadxmenu

* fix plugin action name not showing

---------

Co-authored-by: skylot <118523+skylot@users.noreply.github.com>
This commit is contained in:
Mino 2023-08-03 20:05:25 +01:00 committed by GitHub
parent 868e99e0b5
commit 68b84ea786
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 1434 additions and 400 deletions

View File

@ -34,12 +34,14 @@ public class CodePopupAction {
public NodeAction(CodePopupAction data, CodeArea codeArea) { public NodeAction(CodePopupAction data, CodeArea codeArea) {
super(data.name, codeArea); super(data.name, codeArea);
setName(data.name);
setShortcutComponent(codeArea);
if (data.keyBinding != null) { if (data.keyBinding != null) {
KeyStroke key = KeyStroke.getKeyStroke(data.keyBinding); KeyStroke key = KeyStroke.getKeyStroke(data.keyBinding);
if (key == null) { if (key == null) {
throw new IllegalArgumentException("Failed to parse key stroke: " + data.keyBinding); throw new IllegalArgumentException("Failed to parse key stroke: " + data.keyBinding);
} }
addKeyBinding(key, data.name); setKeyBinding(key);
} }
this.data = data; this.data = data;
} }

View File

@ -1,23 +1,21 @@
package jadx.gui.plugins.script; package jadx.gui.plugins.script;
import java.awt.event.KeyEvent;
import javax.swing.KeyStroke;
import org.fife.ui.autocomplete.AutoCompletion; import org.fife.ui.autocomplete.AutoCompletion;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import jadx.api.ICodeInfo; import jadx.api.ICodeInfo;
import jadx.gui.settings.JadxSettings; import jadx.gui.settings.JadxSettings;
import jadx.gui.treemodel.JInputScript; import jadx.gui.treemodel.JInputScript;
import jadx.gui.ui.action.JadxAutoCompletion;
import jadx.gui.ui.codearea.AbstractCodeArea; import jadx.gui.ui.codearea.AbstractCodeArea;
import jadx.gui.ui.panel.ContentPanel; import jadx.gui.ui.panel.ContentPanel;
import jadx.gui.utils.UiUtils; import jadx.gui.utils.shortcut.ShortcutsController;
public class ScriptCodeArea extends AbstractCodeArea { public class ScriptCodeArea extends AbstractCodeArea {
private final JInputScript scriptNode; private final JInputScript scriptNode;
private final AutoCompletion autoCompletion; private final AutoCompletion autoCompletion;
private final ShortcutsController shortcutsController;
public ScriptCodeArea(ContentPanel contentPanel, JInputScript node) { public ScriptCodeArea(ContentPanel contentPanel, JInputScript node) {
super(contentPanel, node); super(contentPanel, node);
@ -27,6 +25,7 @@ public class ScriptCodeArea extends AbstractCodeArea {
setCodeFoldingEnabled(true); setCodeFoldingEnabled(true);
setCloseCurlyBraces(true); setCloseCurlyBraces(true);
shortcutsController = contentPanel.getTabbedPane().getMainWindow().getShortcutsController();
JadxSettings settings = contentPanel.getTabbedPane().getMainWindow().getSettings(); JadxSettings settings = contentPanel.getTabbedPane().getMainWindow().getSettings();
autoCompletion = addAutoComplete(settings); autoCompletion = addAutoComplete(settings);
} }
@ -34,12 +33,12 @@ public class ScriptCodeArea extends AbstractCodeArea {
private AutoCompletion addAutoComplete(JadxSettings settings) { private AutoCompletion addAutoComplete(JadxSettings settings) {
ScriptCompleteProvider provider = new ScriptCompleteProvider(this); ScriptCompleteProvider provider = new ScriptCompleteProvider(this);
provider.setAutoActivationRules(false, "."); provider.setAutoActivationRules(false, ".");
AutoCompletion ac = new AutoCompletion(provider); JadxAutoCompletion ac = new JadxAutoCompletion(provider);
ac.setListCellRenderer(new ScriptCompletionRenderer(settings)); ac.setListCellRenderer(new ScriptCompletionRenderer(settings));
ac.setTriggerKey(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, UiUtils.ctrlButton()));
ac.setAutoActivationEnabled(true); ac.setAutoActivationEnabled(true);
ac.setAutoCompleteSingleChoices(true); ac.setAutoCompleteSingleChoices(true);
ac.install(this); ac.install(this);
shortcutsController.bindImmediate(ac);
return ac; return ac;
} }
@ -80,6 +79,7 @@ public class ScriptCodeArea extends AbstractCodeArea {
@Override @Override
public void dispose() { public void dispose() {
shortcutsController.unbindActionsForComponent(this);
autoCompletion.uninstall(); autoCompletion.uninstall();
super.dispose(); super.dispose();
} }

View File

@ -29,13 +29,14 @@ import jadx.gui.settings.LineNumbersMode;
import jadx.gui.treemodel.JInputScript; import jadx.gui.treemodel.JInputScript;
import jadx.gui.ui.MainWindow; import jadx.gui.ui.MainWindow;
import jadx.gui.ui.TabbedPane; 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.AbstractCodeArea;
import jadx.gui.ui.codearea.AbstractCodeContentPanel; import jadx.gui.ui.codearea.AbstractCodeContentPanel;
import jadx.gui.ui.codearea.SearchBar; import jadx.gui.ui.codearea.SearchBar;
import jadx.gui.utils.Icons; import jadx.gui.utils.Icons;
import jadx.gui.utils.NLS; import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils; import jadx.gui.utils.UiUtils;
import jadx.gui.utils.ui.ActionHandler;
import jadx.gui.utils.ui.NodeLabel; import jadx.gui.utils.ui.NodeLabel;
import jadx.plugins.script.ide.ScriptAnalyzeResult; import jadx.plugins.script.ide.ScriptAnalyzeResult;
import jadx.plugins.script.ide.ScriptServices; import jadx.plugins.script.ide.ScriptServices;
@ -89,15 +90,14 @@ public class ScriptContentPanel extends AbstractCodeContentPanel {
} }
private JPanel buildScriptActionsPanel() { private JPanel buildScriptActionsPanel() {
ActionHandler runAction = new ActionHandler(this::runScript); JadxGuiAction runAction = new JadxGuiAction(ActionModel.SCRIPT_RUN, this::runScript);
runAction.setNameAndDesc(NLS.str("script.run")); JadxGuiAction saveAction = new JadxGuiAction(ActionModel.SCRIPT_SAVE, scriptArea::save);
runAction.setIcon(Icons.RUN);
runAction.attachKeyBindingFor(scriptArea, KeyStroke.getKeyStroke(KeyEvent.VK_F8, 0));
ActionHandler saveAction = new ActionHandler(scriptArea::save); runAction.setShortcutComponent(scriptArea);
saveAction.setNameAndDesc(NLS.str("script.save")); saveAction.setShortcutComponent(scriptArea);
saveAction.setIcon(Icons.SAVE_ALL);
saveAction.setKeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_S, UiUtils.ctrlButton())); tabbedPane.getMainWindow().getShortcutsController().bindImmediate(runAction);
tabbedPane.getMainWindow().getShortcutsController().bindImmediate(saveAction);
JButton save = saveAction.makeButton(); JButton save = saveAction.makeButton();
scriptArea.getScriptNode().addChangeListener(save::setEnabled); scriptArea.getScriptNode().addChangeListener(save::setEnabled);

View File

@ -37,12 +37,15 @@ import jadx.cli.JadxCLIArgs;
import jadx.cli.LogHelper; import jadx.cli.LogHelper;
import jadx.gui.cache.code.CodeCacheMode; import jadx.gui.cache.code.CodeCacheMode;
import jadx.gui.cache.usage.UsageCacheMode; import jadx.gui.cache.usage.UsageCacheMode;
import jadx.gui.settings.data.ShortcutsWrapper;
import jadx.gui.ui.MainWindow; import jadx.gui.ui.MainWindow;
import jadx.gui.ui.action.ActionModel;
import jadx.gui.ui.codearea.EditorTheme; import jadx.gui.ui.codearea.EditorTheme;
import jadx.gui.utils.FontUtils; import jadx.gui.utils.FontUtils;
import jadx.gui.utils.LafManager; import jadx.gui.utils.LafManager;
import jadx.gui.utils.LangLocale; import jadx.gui.utils.LangLocale;
import jadx.gui.utils.NLS; import jadx.gui.utils.NLS;
import jadx.gui.utils.shortcut.Shortcut;
public class JadxSettings extends JadxCLIArgs { public class JadxSettings extends JadxCLIArgs {
private static final Logger LOG = LoggerFactory.getLogger(JadxSettings.class); private static final Logger LOG = LoggerFactory.getLogger(JadxSettings.class);
@ -73,6 +76,10 @@ public class JadxSettings extends JadxCLIArgs {
private boolean autoStartJobs = false; private boolean autoStartJobs = false;
private String excludedPackages = ""; private String excludedPackages = "";
private boolean autoSaveProject = true; private boolean autoSaveProject = true;
private Map<ActionModel, Shortcut> shortcuts = new HashMap<>();
@JadxSettingsAdapter.GsonExclude
private ShortcutsWrapper shortcutsWrapper = null;
private boolean showHeapUsageBar = false; private boolean showHeapUsageBar = false;
private boolean alwaysSelectOpened = false; private boolean alwaysSelectOpened = false;
@ -442,6 +449,14 @@ public class JadxSettings extends JadxCLIArgs {
this.autoSaveProject = autoSaveProject; this.autoSaveProject = autoSaveProject;
} }
public ShortcutsWrapper getShortcuts() {
if (shortcutsWrapper == null) {
shortcutsWrapper = new ShortcutsWrapper();
shortcutsWrapper.updateShortcuts(shortcuts);
}
return shortcutsWrapper;
}
public void setExportAsGradleProject(boolean exportAsGradleProject) { public void setExportAsGradleProject(boolean exportAsGradleProject) {
this.exportAsGradleProject = exportAsGradleProject; this.exportAsGradleProject = exportAsGradleProject;
} }

View File

@ -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);
}
}

View File

@ -62,6 +62,7 @@ import jadx.gui.settings.JadxSettingsAdapter;
import jadx.gui.settings.LineNumbersMode; import jadx.gui.settings.LineNumbersMode;
import jadx.gui.settings.ui.cache.CacheSettingsGroup; import jadx.gui.settings.ui.cache.CacheSettingsGroup;
import jadx.gui.settings.ui.plugins.PluginsSettings; import jadx.gui.settings.ui.plugins.PluginsSettings;
import jadx.gui.settings.ui.shortcut.ShortcutsSettingsGroup;
import jadx.gui.ui.MainWindow; import jadx.gui.ui.MainWindow;
import jadx.gui.ui.codearea.EditorTheme; import jadx.gui.ui.codearea.EditorTheme;
import jadx.gui.utils.FontUtils; import jadx.gui.utils.FontUtils;
@ -126,6 +127,7 @@ public class JadxSettingsWindow extends JDialog {
groups.add(makeRenameGroup()); groups.add(makeRenameGroup());
groups.add(new CacheSettingsGroup(this)); groups.add(new CacheSettingsGroup(this));
groups.add(makeAppearanceGroup()); groups.add(makeAppearanceGroup());
groups.add(new ShortcutsSettingsGroup(this, settings));
groups.add(makeSearchResGroup()); groups.add(makeSearchResGroup());
groups.add(makeProjectGroup()); groups.add(makeProjectGroup());
groups.add(new PluginsSettings(mainWindow, settings).build()); groups.add(new PluginsSettings(mainWindow, settings).build());
@ -640,6 +642,7 @@ public class JadxSettingsWindow extends JDialog {
enableComponents(this, false); enableComponents(this, false);
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
if (shouldReload()) { if (shouldReload()) {
mainWindow.getShortcutsController().loadSettings();
mainWindow.reopen(); mainWindow.reopen();
} }
if (!settings.getLangLocale().equals(prevLang)) { if (!settings.getLangLocale().equals(prevLang)) {

View File

@ -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();
}
}
}

View File

@ -0,0 +1,60 @@
package jadx.gui.settings.ui.shortcut;
import java.awt.BorderLayout;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import jadx.api.plugins.gui.ISettingsGroup;
import jadx.gui.settings.JadxSettings;
import jadx.gui.settings.ui.JadxSettingsWindow;
import jadx.gui.settings.ui.SettingsGroup;
import jadx.gui.ui.action.ActionCategory;
import jadx.gui.ui.action.ActionModel;
import jadx.gui.utils.NLS;
import jadx.gui.utils.shortcut.Shortcut;
public class ShortcutsSettingsGroup implements ISettingsGroup {
private final JadxSettingsWindow settingsWindow;
private final JadxSettings settings;
public ShortcutsSettingsGroup(JadxSettingsWindow settingsWindow, JadxSettings settings) {
this.settingsWindow = settingsWindow;
this.settings = settings;
}
@Override
public String getTitle() {
return NLS.str("preferences.shortcuts");
}
@Override
public JComponent buildComponent() {
JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
panel.add(new JLabel(NLS.str("preferences.select_shortcuts")), BorderLayout.NORTH);
return panel;
}
@Override
public List<ISettingsGroup> getSubGroups() {
return Arrays.stream(ActionCategory.values())
.map(this::makeShortcutsGroup)
.collect(Collectors.toUnmodifiableList());
}
private SettingsGroup makeShortcutsGroup(ActionCategory category) {
SettingsGroup group = new SettingsGroup(category.getName());
for (ActionModel actionModel : ActionModel.select(category)) {
Shortcut shortcut = settings.getShortcuts().get(actionModel);
ShortcutEdit edit = new ShortcutEdit(actionModel, settingsWindow, settings);
edit.setShortcut(shortcut);
group.addRow(actionModel.getName(), edit);
}
return group;
}
}

View File

@ -1,6 +1,5 @@
package jadx.gui.ui; package jadx.gui.ui;
import java.awt.AWTEvent;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.Component; import java.awt.Component;
import java.awt.Dimension; import java.awt.Dimension;
@ -9,14 +8,12 @@ import java.awt.Font;
import java.awt.GraphicsDevice; import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment; import java.awt.GraphicsEnvironment;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.dnd.DnDConstants; import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget; import java.awt.dnd.DropTarget;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter; import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent; import java.awt.event.FocusEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyAdapter; import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter; import java.awt.event.MouseAdapter;
@ -45,10 +42,10 @@ import javax.swing.Action;
import javax.swing.Box; import javax.swing.Box;
import javax.swing.ImageIcon; import javax.swing.ImageIcon;
import javax.swing.JCheckBoxMenuItem; import javax.swing.JCheckBoxMenuItem;
import javax.swing.JDialog;
import javax.swing.JFileChooser; import javax.swing.JFileChooser;
import javax.swing.JFrame; import javax.swing.JFrame;
import javax.swing.JMenu; import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane; import javax.swing.JOptionPane;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.JPopupMenu; import javax.swing.JPopupMenu;
@ -118,6 +115,8 @@ import jadx.gui.treemodel.JNode;
import jadx.gui.treemodel.JPackage; import jadx.gui.treemodel.JPackage;
import jadx.gui.treemodel.JResource; import jadx.gui.treemodel.JResource;
import jadx.gui.treemodel.JRoot; import jadx.gui.treemodel.JRoot;
import jadx.gui.ui.action.ActionModel;
import jadx.gui.ui.action.JadxGuiAction;
import jadx.gui.ui.codearea.AbstractCodeArea; import jadx.gui.ui.codearea.AbstractCodeArea;
import jadx.gui.ui.codearea.AbstractCodeContentPanel; import jadx.gui.ui.codearea.AbstractCodeContentPanel;
import jadx.gui.ui.codearea.EditorTheme; import jadx.gui.ui.codearea.EditorTheme;
@ -128,6 +127,9 @@ import jadx.gui.ui.dialog.LogViewerDialog;
import jadx.gui.ui.dialog.SearchDialog; import jadx.gui.ui.dialog.SearchDialog;
import jadx.gui.ui.filedialog.FileDialogWrapper; import jadx.gui.ui.filedialog.FileDialogWrapper;
import jadx.gui.ui.filedialog.FileOpenMode; import jadx.gui.ui.filedialog.FileOpenMode;
import jadx.gui.ui.menu.HiddenMenuItem;
import jadx.gui.ui.menu.JadxMenu;
import jadx.gui.ui.menu.JadxMenuBar;
import jadx.gui.ui.panel.ContentPanel; import jadx.gui.ui.panel.ContentPanel;
import jadx.gui.ui.panel.IssuesPanel; import jadx.gui.ui.panel.IssuesPanel;
import jadx.gui.ui.panel.JDebuggerPanel; import jadx.gui.ui.panel.JDebuggerPanel;
@ -141,18 +143,16 @@ import jadx.gui.update.data.Release;
import jadx.gui.utils.CacheObject; import jadx.gui.utils.CacheObject;
import jadx.gui.utils.FontUtils; import jadx.gui.utils.FontUtils;
import jadx.gui.utils.ILoadListener; import jadx.gui.utils.ILoadListener;
import jadx.gui.utils.Icons;
import jadx.gui.utils.LafManager; import jadx.gui.utils.LafManager;
import jadx.gui.utils.Link; import jadx.gui.utils.Link;
import jadx.gui.utils.NLS; import jadx.gui.utils.NLS;
import jadx.gui.utils.SystemInfo;
import jadx.gui.utils.UiUtils; import jadx.gui.utils.UiUtils;
import jadx.gui.utils.fileswatcher.LiveReloadWorker; import jadx.gui.utils.fileswatcher.LiveReloadWorker;
import jadx.gui.utils.shortcut.ShortcutsController;
import jadx.gui.utils.ui.ActionHandler; import jadx.gui.utils.ui.ActionHandler;
import jadx.gui.utils.ui.NodeLabel; import jadx.gui.utils.ui.NodeLabel;
import static io.reactivex.internal.functions.Functions.EMPTY_RUNNABLE; import static io.reactivex.internal.functions.Functions.EMPTY_RUNNABLE;
import static javax.swing.KeyStroke.getKeyStroke;
public class MainWindow extends JFrame { public class MainWindow extends JFrame {
private static final Logger LOG = LoggerFactory.getLogger(MainWindow.class); private static final Logger LOG = LoggerFactory.getLogger(MainWindow.class);
@ -191,8 +191,8 @@ public class MainWindow extends JFrame {
private transient @NotNull JadxProject project; private transient @NotNull JadxProject project;
private transient Action newProjectAction; private transient JadxGuiAction newProjectAction;
private transient Action saveProjectAction; private transient JadxGuiAction saveProjectAction;
private transient JPanel mainPanel; private transient JPanel mainPanel;
private transient JSplitPane treeSplitPane; private transient JSplitPane treeSplitPane;
@ -227,7 +227,10 @@ public class MainWindow extends JFrame {
private final List<ILoadListener> loadListeners = new ArrayList<>(); private final List<ILoadListener> loadListeners = new ArrayList<>();
private final List<Consumer<JRoot>> treeUpdateListener = new ArrayList<>(); private final List<Consumer<JRoot>> treeUpdateListener = new ArrayList<>();
private boolean loaded; private boolean loaded;
private boolean settingsOpen = false;
private ShortcutsController shortcutsController;
private JadxMenuBar menuBar;
private JMenu pluginsMenu; private JMenu pluginsMenu;
private final transient RenameMappingsGui renameMappings; private final transient RenameMappingsGui renameMappings;
@ -240,6 +243,7 @@ public class MainWindow extends JFrame {
this.liveReloadWorker = new LiveReloadWorker(this); this.liveReloadWorker = new LiveReloadWorker(this);
this.renameMappings = new RenameMappingsGui(this); this.renameMappings = new RenameMappingsGui(this);
this.cacheManager = new CacheManager(settings); this.cacheManager = new CacheManager(settings);
this.shortcutsController = new ShortcutsController(settings);
resetCache(); resetCache();
FontUtils.registerBundledFonts(); FontUtils.registerBundledFonts();
@ -247,8 +251,8 @@ public class MainWindow extends JFrame {
initUI(); initUI();
this.backgroundExecutor = new BackgroundExecutor(settings, progressPane); this.backgroundExecutor = new BackgroundExecutor(settings, progressPane);
initMenuAndToolbar(); initMenuAndToolbar();
registerMouseNavigationButtons();
UiUtils.setWindowIcons(this); UiUtils.setWindowIcons(this);
shortcutsController.registerMouseEventListener(this);
loadSettings(); loadSettings();
update(); update();
@ -472,6 +476,8 @@ public class MainWindow extends JFrame {
saveAll(); saveAll();
closeAll(); closeAll();
loadFiles(EMPTY_RUNNABLE); loadFiles(EMPTY_RUNNABLE);
menuBar.reloadShortcuts();
} }
private void openProject(Path path, Runnable onFinish) { private void openProject(Path path, Runnable onFinish) {
@ -865,99 +871,75 @@ public class MainWindow extends JFrame {
tree.requestFocus(); tree.requestFocus();
} }
public void textSearch() {
ContentPanel panel = tabbedPane.getSelectedContentPanel();
if (panel instanceof AbstractCodeContentPanel) {
AbstractCodeArea codeArea = ((AbstractCodeContentPanel) panel).getCodeArea();
String preferText = codeArea.getSelectedText();
if (StringUtils.isEmpty(preferText)) {
preferText = codeArea.getWordUnderCaret();
}
if (!StringUtils.isEmpty(preferText)) {
SearchDialog.searchText(MainWindow.this, preferText);
return;
}
}
SearchDialog.search(MainWindow.this, SearchDialog.SearchPreset.TEXT);
}
public void gotoMainActivity() {
AndroidManifestParser parser = new AndroidManifestParser(
AndroidManifestParser.getAndroidManifest(getWrapper().getResources()),
EnumSet.of(AppAttribute.MAIN_ACTIVITY));
if (!parser.isManifestFound()) {
JOptionPane.showMessageDialog(MainWindow.this,
NLS.str("error_dialog.not_found", "AndroidManifest.xml"),
NLS.str("error_dialog.title"),
JOptionPane.ERROR_MESSAGE);
return;
}
try {
ApplicationParams results = parser.parse();
if (results.getMainActivityName() == null) {
throw new JadxRuntimeException("Failed to get main activity name from manifest");
}
JavaClass mainActivityClass = results.getMainActivity(getWrapper().getDecompiler());
if (mainActivityClass == null) {
throw new JadxRuntimeException("Failed to find main activity class: " + results.getMainActivityName());
}
tabbedPane.codeJump(getCacheObject().getNodeCache().makeFrom(mainActivityClass));
} catch (Exception e) {
LOG.error("Main activity not found", e);
JOptionPane.showMessageDialog(MainWindow.this,
NLS.str("error_dialog.not_found", "Main Activity"),
NLS.str("error_dialog.title"),
JOptionPane.ERROR_MESSAGE);
}
}
private void initMenuAndToolbar() { private void initMenuAndToolbar() {
ActionHandler openAction = new ActionHandler(this::openFileDialog); JadxGuiAction openAction = new JadxGuiAction(ActionModel.OPEN, this::openFileDialog);
openAction.setNameAndDesc(NLS.str("file.open_action")); JadxGuiAction openProject = new JadxGuiAction(ActionModel.OPEN_PROJECT, this::openProjectDialog);
openAction.setIcon(Icons.OPEN);
openAction.setKeyBinding(getKeyStroke(KeyEvent.VK_O, UiUtils.ctrlButton()));
ActionHandler openProject = new ActionHandler(this::openProjectDialog); JadxGuiAction addFilesAction = new JadxGuiAction(ActionModel.ADD_FILES, () -> addFiles());
openProject.setNameAndDesc(NLS.str("file.open_project")); newProjectAction = new JadxGuiAction(ActionModel.NEW_PROJECT, this::newProject);
openProject.setIcon(Icons.OPEN_PROJECT); saveProjectAction = new JadxGuiAction(ActionModel.SAVE_PROJECT, this::saveProject);
openProject.setKeyBinding(getKeyStroke(KeyEvent.VK_O, InputEvent.SHIFT_DOWN_MASK | UiUtils.ctrlButton())); JadxGuiAction saveProjectAsAction = new JadxGuiAction(ActionModel.SAVE_PROJECT_AS, this::saveProjectAs);
JadxGuiAction reloadAction = new JadxGuiAction(ActionModel.RELOAD, () -> UiUtils.uiRun(this::reopen));
JadxGuiAction liveReloadAction = new JadxGuiAction(ActionModel.LIVE_RELOAD,
() -> updateLiveReload(!project.isEnableLiveReload()));
Action addFilesAction = new AbstractAction(NLS.str("file.add_files_action"), ICON_ADD_FILES) { liveReloadMenuItem = new JCheckBoxMenuItem(liveReloadAction);
@Override
public void actionPerformed(ActionEvent e) {
addFiles();
}
};
addFilesAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("file.add_files_action"));
newProjectAction = new AbstractAction(NLS.str("file.new_project"), Icons.NEW_PROJECT) {
@Override
public void actionPerformed(ActionEvent e) {
newProject();
}
};
newProjectAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("file.new_project"));
saveProjectAction = new AbstractAction(NLS.str("file.save_project")) {
@Override
public void actionPerformed(ActionEvent e) {
saveProject();
}
};
saveProjectAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("file.save_project"));
Action saveProjectAsAction = new AbstractAction(NLS.str("file.save_project_as")) {
@Override
public void actionPerformed(ActionEvent e) {
saveProjectAs();
}
};
saveProjectAsAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("file.save_project_as"));
ActionHandler reload = new ActionHandler(ev -> UiUtils.uiRun(this::reopen));
reload.setNameAndDesc(NLS.str("file.reload"));
reload.setIcon(ICON_RELOAD);
reload.setKeyBinding(getKeyStroke(KeyEvent.VK_F5, 0));
ActionHandler liveReload = new ActionHandler(ev -> updateLiveReload(!project.isEnableLiveReload()));
liveReload.setName(NLS.str("file.live_reload"));
liveReload.setShortDescription(NLS.str("file.live_reload_desc"));
liveReload.setKeyBinding(getKeyStroke(KeyEvent.VK_F5, InputEvent.SHIFT_DOWN_MASK));
liveReloadMenuItem = new JCheckBoxMenuItem(liveReload);
liveReloadMenuItem.setState(project.isEnableLiveReload()); liveReloadMenuItem.setState(project.isEnableLiveReload());
Action saveAllAction = new AbstractAction(NLS.str("file.save_all"), Icons.SAVE_ALL) { JadxGuiAction saveAllAction = new JadxGuiAction(ActionModel.SAVE_ALL, () -> saveAll(false));
@Override JadxGuiAction exportAction = new JadxGuiAction(ActionModel.EXPORT, () -> saveAll(true));
public void actionPerformed(ActionEvent e) {
saveAll(false);
}
};
saveAllAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("file.save_all"));
saveAllAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_E, UiUtils.ctrlButton()));
Action exportAction = new AbstractAction(NLS.str("file.export_gradle"), ICON_EXPORT) { JMenu recentProjects = new JadxMenu(NLS.str("menu.recent_projects"), shortcutsController);
@Override
public void actionPerformed(ActionEvent e) {
saveAll(true);
}
};
exportAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("file.export_gradle"));
exportAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_E, UiUtils.ctrlButton() | KeyEvent.SHIFT_DOWN_MASK));
JMenu recentProjects = new JMenu(NLS.str("menu.recent_projects"));
recentProjects.addMenuListener(new RecentProjectsMenuListener(this, recentProjects)); recentProjects.addMenuListener(new RecentProjectsMenuListener(this, recentProjects));
Action prefsAction = new AbstractAction(NLS.str("menu.preferences"), ICON_PREF) { JadxGuiAction prefsAction = new JadxGuiAction(ActionModel.PREFS, this::openSettings);
@Override JadxGuiAction exitAction = new JadxGuiAction(ActionModel.EXIT, this::closeWindow);
public void actionPerformed(ActionEvent e) {
new JadxSettingsWindow(MainWindow.this, settings).setVisible(true);
}
};
prefsAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("menu.preferences"));
prefsAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_P,
UiUtils.ctrlButton() | KeyEvent.SHIFT_DOWN_MASK));
Action exitAction = new AbstractAction(NLS.str("file.exit"), ICON_EXIT) {
@Override
public void actionPerformed(ActionEvent e) {
closeWindow();
}
};
isFlattenPackage = settings.isFlattenPackage(); isFlattenPackage = settings.isFlattenPackage();
flatPkgMenuItem = new JCheckBoxMenuItem(NLS.str("menu.flatten"), ICON_FLAT_PKG); flatPkgMenuItem = new JCheckBoxMenuItem(NLS.str("menu.flatten"), ICON_FLAT_PKG);
@ -983,109 +965,17 @@ public class MainWindow extends JFrame {
dockLog.setState(settings.isDockLogViewer()); dockLog.setState(settings.isDockLogViewer());
dockLog.addActionListener(event -> settings.setDockLogViewer(!settings.isDockLogViewer())); dockLog.addActionListener(event -> settings.setDockLogViewer(!settings.isDockLogViewer()));
Action syncAction = new AbstractAction(NLS.str("menu.sync"), ICON_SYNC) { JadxGuiAction syncAction = new JadxGuiAction(ActionModel.SYNC, this::syncWithEditor);
@Override JadxGuiAction textSearchAction = new JadxGuiAction(ActionModel.TEXT_SEARCH, this::textSearch);
public void actionPerformed(ActionEvent e) { JadxGuiAction clsSearchAction = new JadxGuiAction(ActionModel.CLASS_SEARCH,
syncWithEditor(); () -> SearchDialog.search(MainWindow.this, SearchDialog.SearchPreset.CLASS));
} JadxGuiAction commentSearchAction = new JadxGuiAction(ActionModel.COMMENT_SEARCH,
}; () -> SearchDialog.search(MainWindow.this, SearchDialog.SearchPreset.COMMENT));
syncAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("menu.sync")); JadxGuiAction gotoMainActivityAction = new JadxGuiAction(ActionModel.GOTO_MAIN_ACTIVITY,
syncAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_T, UiUtils.ctrlButton())); this::gotoMainActivity);
JadxGuiAction decompileAllAction = new JadxGuiAction(ActionModel.DECOMPILE_ALL, this::requestFullDecompilation);
Action textSearchAction = new AbstractAction(NLS.str("menu.text_search"), ICON_SEARCH) { JadxGuiAction resetCacheAction = new JadxGuiAction(ActionModel.RESET_CACHE, this::resetCodeCache);
@Override JadxGuiAction deobfAction = new JadxGuiAction(ActionModel.DEOBF, this::toggleDeobfuscation);
public void actionPerformed(ActionEvent e) {
ContentPanel panel = tabbedPane.getSelectedContentPanel();
if (panel instanceof AbstractCodeContentPanel) {
AbstractCodeArea codeArea = ((AbstractCodeContentPanel) panel).getCodeArea();
String preferText = codeArea.getSelectedText();
if (StringUtils.isEmpty(preferText)) {
preferText = codeArea.getWordUnderCaret();
}
if (!StringUtils.isEmpty(preferText)) {
SearchDialog.searchText(MainWindow.this, preferText);
return;
}
}
SearchDialog.search(MainWindow.this, SearchDialog.SearchPreset.TEXT);
}
};
textSearchAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("menu.text_search"));
textSearchAction.putValue(Action.ACCELERATOR_KEY,
getKeyStroke(KeyEvent.VK_F, UiUtils.ctrlButton() | KeyEvent.SHIFT_DOWN_MASK));
Action clsSearchAction = new AbstractAction(NLS.str("menu.class_search"), ICON_FIND) {
@Override
public void actionPerformed(ActionEvent e) {
SearchDialog.search(MainWindow.this, SearchDialog.SearchPreset.CLASS);
}
};
clsSearchAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("menu.class_search"));
clsSearchAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_N, UiUtils.ctrlButton()));
Action commentSearchAction = new AbstractAction(NLS.str("menu.comment_search"), ICON_COMMENT_SEARCH) {
@Override
public void actionPerformed(ActionEvent e) {
SearchDialog.search(MainWindow.this, SearchDialog.SearchPreset.COMMENT);
}
};
commentSearchAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("menu.comment_search"));
commentSearchAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_SEMICOLON,
UiUtils.ctrlButton() | KeyEvent.SHIFT_DOWN_MASK));
Action gotoMainActivityAction = new AbstractAction(NLS.str("menu.goto_main_activity"), ICON_MAIN_ACTIVITY) {
@Override
public void actionPerformed(ActionEvent ev) {
AndroidManifestParser parser = new AndroidManifestParser(
AndroidManifestParser.getAndroidManifest(getWrapper().getResources()),
EnumSet.of(AppAttribute.MAIN_ACTIVITY));
if (!parser.isManifestFound()) {
JOptionPane.showMessageDialog(MainWindow.this,
NLS.str("error_dialog.not_found", "AndroidManifest.xml"),
NLS.str("error_dialog.title"),
JOptionPane.ERROR_MESSAGE);
return;
}
try {
ApplicationParams results = parser.parse();
if (results.getMainActivityName() == null) {
throw new JadxRuntimeException("Failed to get main activity name from manifest");
}
JavaClass mainActivityClass = results.getMainActivity(getWrapper().getDecompiler());
if (mainActivityClass == null) {
throw new JadxRuntimeException("Failed to find main activity class: " + results.getMainActivityName());
}
tabbedPane.codeJump(getCacheObject().getNodeCache().makeFrom(mainActivityClass));
} catch (Exception e) {
LOG.error("Main activity not found", e);
JOptionPane.showMessageDialog(MainWindow.this,
NLS.str("error_dialog.not_found", "Main Activity"),
NLS.str("error_dialog.title"),
JOptionPane.ERROR_MESSAGE);
}
}
};
gotoMainActivityAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("menu.goto_main_activity"));
gotoMainActivityAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_M,
UiUtils.ctrlButton() | KeyEvent.SHIFT_DOWN_MASK));
ActionHandler decompileAllAction = new ActionHandler(ev -> requestFullDecompilation());
decompileAllAction.setNameAndDesc(NLS.str("menu.decompile_all"));
decompileAllAction.setIcon(ICON_DECOMPILE_ALL);
ActionHandler resetCacheAction = new ActionHandler(ev -> resetCodeCache());
resetCacheAction.setNameAndDesc(NLS.str("menu.reset_cache"));
resetCacheAction.setIcon(Icons.RESET);
Action deobfAction = new AbstractAction(NLS.str("menu.deobfuscation"), ICON_DEOBF) {
@Override
public void actionPerformed(ActionEvent e) {
toggleDeobfuscation();
}
};
deobfAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("preferences.deobfuscation"));
deobfAction.putValue(Action.ACCELERATOR_KEY,
getKeyStroke(KeyEvent.VK_D, UiUtils.ctrlButton() | KeyEvent.ALT_DOWN_MASK));
deobfToggleBtn = new JToggleButton(deobfAction); deobfToggleBtn = new JToggleButton(deobfAction);
deobfToggleBtn.setSelected(settings.isDeobfuscationOn()); deobfToggleBtn.setSelected(settings.isDeobfuscationOn());
@ -1094,54 +984,19 @@ public class MainWindow extends JFrame {
deobfMenuItem = new JCheckBoxMenuItem(deobfAction); deobfMenuItem = new JCheckBoxMenuItem(deobfAction);
deobfMenuItem.setState(settings.isDeobfuscationOn()); deobfMenuItem.setState(settings.isDeobfuscationOn());
ActionHandler showLog = new ActionHandler(ev -> showLogViewer(LogOptions.current())); JadxGuiAction showLogAction = new JadxGuiAction(ActionModel.SHOW_LOG,
showLog.setNameAndDesc(NLS.str("menu.log")); () -> showLogViewer(LogOptions.current()));
showLog.setIcon(ICON_LOG); JadxGuiAction aboutAction = new JadxGuiAction(ActionModel.ABOUT, () -> new AboutDialog().setVisible(true));
showLog.setKeyBinding(getKeyStroke(KeyEvent.VK_L, UiUtils.ctrlButton() | KeyEvent.SHIFT_DOWN_MASK)); JadxGuiAction backAction = new JadxGuiAction(ActionModel.BACK, tabbedPane::navBack);
JadxGuiAction backVariantAction = new JadxGuiAction(ActionModel.BACK_V, tabbedPane::navBack);
JadxGuiAction forwardAction = new JadxGuiAction(ActionModel.FORWARD, tabbedPane::navForward);
JadxGuiAction forwardVariantAction = new JadxGuiAction(ActionModel.FORWARD_V, tabbedPane::navForward);
JadxGuiAction quarkAction = new JadxGuiAction(ActionModel.QUARK,
() -> new QuarkDialog(MainWindow.this).setVisible(true));
JadxGuiAction openDeviceAction = new JadxGuiAction(ActionModel.OPEN_DEVICE,
() -> new ADBDialog(MainWindow.this).setVisible(true));
Action aboutAction = new AbstractAction(NLS.str("menu.about"), ICON_INFO) { JMenu file = new JadxMenu(NLS.str("menu.file"), shortcutsController);
@Override
public void actionPerformed(ActionEvent e) {
new AboutDialog().setVisible(true);
}
};
Action backAction = new AbstractAction(NLS.str("nav.back"), ICON_BACK) {
@Override
public void actionPerformed(ActionEvent e) {
tabbedPane.navBack();
}
};
backAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("nav.back"));
backAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_ESCAPE, 0));
Action forwardAction = new AbstractAction(NLS.str("nav.forward"), ICON_FORWARD) {
@Override
public void actionPerformed(ActionEvent e) {
tabbedPane.navForward();
}
};
forwardAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("nav.forward"));
forwardAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_RIGHT, KeyEvent.ALT_DOWN_MASK, SystemInfo.IS_MAC));
Action quarkAction = new AbstractAction("Quark Engine", ICON_QUARK) {
@Override
public void actionPerformed(ActionEvent e) {
new QuarkDialog(MainWindow.this).setVisible(true);
}
};
quarkAction.putValue(Action.SHORT_DESCRIPTION, "Quark Engine");
Action openDeviceAction = new AbstractAction(NLS.str("debugger.process_selector"), ICON_DEBUGGER) {
@Override
public void actionPerformed(ActionEvent e) {
ADBDialog dialog = new ADBDialog(MainWindow.this);
dialog.setVisible(true);
}
};
openDeviceAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("debugger.process_selector"));
JMenu file = new JMenu(NLS.str("menu.file"));
file.setMnemonic(KeyEvent.VK_F); file.setMnemonic(KeyEvent.VK_F);
file.add(openAction); file.add(openAction);
file.add(openProject); file.add(openProject);
@ -1151,7 +1006,7 @@ public class MainWindow extends JFrame {
file.add(saveProjectAction); file.add(saveProjectAction);
file.add(saveProjectAsAction); file.add(saveProjectAsAction);
file.addSeparator(); file.addSeparator();
file.add(reload); file.add(reloadAction);
file.add(liveReloadMenuItem); file.add(liveReloadMenuItem);
renameMappings.addMenuActions(file); renameMappings.addMenuActions(file);
file.addSeparator(); file.addSeparator();
@ -1164,7 +1019,7 @@ public class MainWindow extends JFrame {
file.addSeparator(); file.addSeparator();
file.add(exitAction); file.add(exitAction);
JMenu view = new JMenu(NLS.str("menu.view")); JMenu view = new JadxMenu(NLS.str("menu.view"), shortcutsController);
view.setMnemonic(KeyEvent.VK_V); view.setMnemonic(KeyEvent.VK_V);
view.add(flatPkgMenuItem); view.add(flatPkgMenuItem);
view.add(syncAction); view.add(syncAction);
@ -1172,7 +1027,7 @@ public class MainWindow extends JFrame {
view.add(alwaysSelectOpened); view.add(alwaysSelectOpened);
view.add(dockLog); view.add(dockLog);
JMenu nav = new JMenu(NLS.str("menu.navigation")); JMenu nav = new JadxMenu(NLS.str("menu.navigation"), shortcutsController);
nav.setMnemonic(KeyEvent.VK_N); nav.setMnemonic(KeyEvent.VK_N);
nav.add(textSearchAction); nav.add(textSearchAction);
nav.add(clsSearchAction); nav.add(clsSearchAction);
@ -1182,11 +1037,11 @@ public class MainWindow extends JFrame {
nav.add(backAction); nav.add(backAction);
nav.add(forwardAction); nav.add(forwardAction);
pluginsMenu = new JMenu(NLS.str("menu.plugins")); pluginsMenu = new JadxMenu(NLS.str("menu.plugins"), shortcutsController);
pluginsMenu.setMnemonic(KeyEvent.VK_P); pluginsMenu.setMnemonic(KeyEvent.VK_P);
resetPluginsMenu(); resetPluginsMenu();
JMenu tools = new JMenu(NLS.str("menu.tools")); JMenu tools = new JadxMenu(NLS.str("menu.tools"), shortcutsController);
tools.setMnemonic(KeyEvent.VK_T); tools.setMnemonic(KeyEvent.VK_T);
tools.add(decompileAllAction); tools.add(decompileAllAction);
tools.add(resetCacheAction); tools.add(resetCacheAction);
@ -1194,9 +1049,9 @@ public class MainWindow extends JFrame {
tools.add(quarkAction); tools.add(quarkAction);
tools.add(openDeviceAction); tools.add(openDeviceAction);
JMenu help = new JMenu(NLS.str("menu.help")); JMenu help = new JadxMenu(NLS.str("menu.help"), shortcutsController);
help.setMnemonic(KeyEvent.VK_H); help.setMnemonic(KeyEvent.VK_H);
help.add(showLog); help.add(showLogAction);
if (Jadx.isDevVersion()) { if (Jadx.isDevVersion()) {
help.add(new AbstractAction("Show sample error report") { help.add(new AbstractAction("Show sample error report") {
@Override @Override
@ -1207,7 +1062,7 @@ public class MainWindow extends JFrame {
} }
help.add(aboutAction); help.add(aboutAction);
JMenuBar menuBar = new JMenuBar(); menuBar = new JadxMenuBar();
menuBar.add(file); menuBar.add(file);
menuBar.add(view); menuBar.add(view);
menuBar.add(nav); menuBar.add(nav);
@ -1231,7 +1086,7 @@ public class MainWindow extends JFrame {
toolbar.add(openAction); toolbar.add(openAction);
toolbar.add(addFilesAction); toolbar.add(addFilesAction);
toolbar.addSeparator(); toolbar.addSeparator();
toolbar.add(reload); toolbar.add(reloadAction);
toolbar.addSeparator(); toolbar.addSeparator();
toolbar.add(saveAllAction); toolbar.add(saveAllAction);
toolbar.add(exportAction); toolbar.add(exportAction);
@ -1251,7 +1106,7 @@ public class MainWindow extends JFrame {
toolbar.add(quarkAction); toolbar.add(quarkAction);
toolbar.add(openDeviceAction); toolbar.add(openDeviceAction);
toolbar.addSeparator(); toolbar.addSeparator();
toolbar.add(showLog); toolbar.add(showLogAction);
toolbar.addSeparator(); toolbar.addSeparator();
toolbar.add(prefsAction); toolbar.add(prefsAction);
toolbar.addSeparator(); toolbar.addSeparator();
@ -1260,18 +1115,26 @@ public class MainWindow extends JFrame {
mainPanel.add(toolbar, BorderLayout.NORTH); mainPanel.add(toolbar, BorderLayout.NORTH);
nav.add(new HiddenMenuItem(backVariantAction));
nav.add(new HiddenMenuItem(forwardVariantAction));
shortcutsController.bind(backVariantAction);
shortcutsController.bind(forwardVariantAction);
addLoadListener(loaded -> { addLoadListener(loaded -> {
textSearchAction.setEnabled(loaded); textSearchAction.setEnabled(loaded);
clsSearchAction.setEnabled(loaded); clsSearchAction.setEnabled(loaded);
commentSearchAction.setEnabled(loaded); commentSearchAction.setEnabled(loaded);
gotoMainActivityAction.setEnabled(loaded); gotoMainActivityAction.setEnabled(loaded);
backAction.setEnabled(loaded); backAction.setEnabled(loaded);
backVariantAction.setEnabled(loaded);
forwardAction.setEnabled(loaded); forwardAction.setEnabled(loaded);
forwardVariantAction.setEnabled(loaded);
syncAction.setEnabled(loaded); syncAction.setEnabled(loaded);
saveAllAction.setEnabled(loaded); saveAllAction.setEnabled(loaded);
exportAction.setEnabled(loaded); exportAction.setEnabled(loaded);
saveProjectAsAction.setEnabled(loaded); saveProjectAsAction.setEnabled(loaded);
reload.setEnabled(loaded); reloadAction.setEnabled(loaded);
decompileAllAction.setEnabled(loaded); decompileAllAction.setEnabled(loaded);
deobfAction.setEnabled(loaded); deobfAction.setEnabled(loaded);
quarkAction.setEnabled(loaded); quarkAction.setEnabled(loaded);
@ -1402,42 +1265,6 @@ public class MainWindow extends JFrame {
setTitle(DEFAULT_TITLE); setTitle(DEFAULT_TITLE);
} }
private void registerMouseNavigationButtons() {
Toolkit toolkit = Toolkit.getDefaultToolkit();
toolkit.addAWTEventListener(event -> {
if (event instanceof MouseEvent) {
MouseEvent mouseEvent = (MouseEvent) event;
if (mouseEvent.getID() == MouseEvent.MOUSE_PRESSED) {
int rawButton = mouseEvent.getButton();
if (rawButton <= 3) {
return;
}
int button = remapMouseButton(rawButton);
switch (button) {
case 4:
tabbedPane.navBack();
break;
case 5:
tabbedPane.navForward();
break;
}
}
}
}, AWTEvent.MOUSE_EVENT_MASK);
}
private static int remapMouseButton(int rawButton) {
if (SystemInfo.IS_LINUX) {
if (rawButton == 6) {
return 4;
}
if (rawButton == 7) {
return 5;
}
}
return rawButton;
}
private static String[] getPathExpansion(TreePath path) { private static String[] getPathExpansion(TreePath path) {
List<String> pathList = new ArrayList<>(); List<String> pathList = new ArrayList<>();
while (path != null) { while (path != null) {
@ -1515,6 +1342,23 @@ public class MainWindow extends JFrame {
return editorTheme; return editorTheme;
} }
private void openSettings() {
settingsOpen = true;
JDialog settingsWindow = new JadxSettingsWindow(MainWindow.this, settings);
settingsWindow.setVisible(true);
settingsWindow.addWindowListener(new WindowAdapter() {
@Override
public void windowClosed(WindowEvent e) {
settingsOpen = false;
}
});
}
public boolean isSettingsOpen() {
return settingsOpen;
}
public void loadSettings() { public void loadSettings() {
// queue update to not interrupt current UI tasks // queue update to not interrupt current UI tasks
UiUtils.uiRun(this::updateUiSettings); UiUtils.uiRun(this::updateUiSettings);
@ -1535,6 +1379,8 @@ public class MainWindow extends JFrame {
if (logPanel != null) { if (logPanel != null) {
logPanel.loadSettings(); logPanel.loadSettings();
} }
shortcutsController.loadSettings();
} }
private void closeWindow() { private void closeWindow() {
@ -1638,6 +1484,10 @@ public class MainWindow extends JFrame {
return debuggerPanel; return debuggerPanel;
} }
public ShortcutsController getShortcutsController() {
return shortcutsController;
}
public void showDebuggerPanel() { public void showDebuggerPanel() {
initDebuggerPanel(); initDebuggerPanel();
} }

View File

@ -0,0 +1,22 @@
package jadx.gui.ui.action;
import jadx.gui.utils.NLS;
public enum ActionCategory {
MENU_TOOLBAR("action_category.menu_toolbar"),
CODE_AREA("action_category.code_area"),
PLUGIN_SCRIPT("action_category.plugin_script");
private final String nameRes;
ActionCategory(String nameRes) {
this.nameRes = nameRes;
}
public String getName() {
if (nameRes != null) {
return NLS.str(nameRes);
}
return null;
}
}

View File

@ -0,0 +1,154 @@
package jadx.gui.ui.action;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javax.swing.ImageIcon;
import io.reactivex.annotations.NonNull;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
import jadx.gui.utils.shortcut.Shortcut;
import static jadx.gui.ui.action.ActionCategory.*;
public enum ActionModel {
ABOUT(MENU_TOOLBAR, "menu.about", "menu.about", "ui/showInfos",
Shortcut.keyboard(KeyEvent.VK_F1)),
OPEN(MENU_TOOLBAR, "file.open_action", "file.open_action", "ui/openDisk",
Shortcut.keyboard(KeyEvent.VK_O, KeyEvent.CTRL_DOWN_MASK)),
OPEN_PROJECT(MENU_TOOLBAR, "file.open_project", "file.open_project", "ui/projectDirectory",
Shortcut.keyboard(KeyEvent.VK_O, InputEvent.SHIFT_DOWN_MASK | UiUtils.ctrlButton())),
ADD_FILES(MENU_TOOLBAR, "file.add_files_action", "file.add_files_action", "ui/addFile",
Shortcut.none()),
NEW_PROJECT(MENU_TOOLBAR, "file.new_project", "file.new_project", "ui/newFolder",
Shortcut.none()),
SAVE_PROJECT(MENU_TOOLBAR, "file.save_project", "file.save_project", null,
Shortcut.none()),
SAVE_PROJECT_AS(MENU_TOOLBAR, "file.save_project_as", "file.save_project_as", null,
Shortcut.none()),
RELOAD(MENU_TOOLBAR, "file.reload", "file.reload", "ui/refresh",
Shortcut.keyboard(KeyEvent.VK_F5)),
LIVE_RELOAD(MENU_TOOLBAR, "file.live_reload", "file.live_reload_desc", null,
Shortcut.keyboard(KeyEvent.VK_F5, InputEvent.SHIFT_DOWN_MASK)),
SAVE_ALL(MENU_TOOLBAR, "file.save_all", "file.save_all", "ui/menu-saveall",
Shortcut.keyboard(KeyEvent.VK_E, UiUtils.ctrlButton())),
EXPORT(MENU_TOOLBAR, "file.export_gradle", "file.export_gradle", "ui/export",
Shortcut.keyboard(KeyEvent.VK_E, UiUtils.ctrlButton() | KeyEvent.SHIFT_DOWN_MASK)),
PREFS(MENU_TOOLBAR, "menu.preferences", "menu.preferences", "ui/settings",
Shortcut.keyboard(KeyEvent.VK_P, UiUtils.ctrlButton() | KeyEvent.SHIFT_DOWN_MASK)),
EXIT(MENU_TOOLBAR, "file.exit", "file.exit", "ui/exit",
Shortcut.none()),
SYNC(MENU_TOOLBAR, "menu.sync", "menu.sync", "ui/pagination",
Shortcut.keyboard(KeyEvent.VK_T, UiUtils.ctrlButton())),
TEXT_SEARCH(MENU_TOOLBAR, "menu.text_search", "menu.text_search", "ui/find",
Shortcut.keyboard(KeyEvent.VK_F, UiUtils.ctrlButton() | KeyEvent.SHIFT_DOWN_MASK)),
CLASS_SEARCH(MENU_TOOLBAR, "menu.class_search", "menu.class_search", "ui/ejbFinderMethod",
Shortcut.keyboard(KeyEvent.VK_N, UiUtils.ctrlButton())),
COMMENT_SEARCH(MENU_TOOLBAR, "menu.comment_search", "menu.comment_search", "ui/usagesFinder",
Shortcut.keyboard(KeyEvent.VK_SEMICOLON, UiUtils.ctrlButton() | KeyEvent.SHIFT_DOWN_MASK)),
GOTO_MAIN_ACTIVITY(MENU_TOOLBAR, "menu.goto_main_activity", "menu.goto_main_activity", "ui/home",
Shortcut.keyboard(KeyEvent.VK_M, UiUtils.ctrlButton() | KeyEvent.SHIFT_DOWN_MASK)),
DECOMPILE_ALL(MENU_TOOLBAR, "menu.decompile_all", "menu.decompile_all", "ui/runAll",
Shortcut.none()),
RESET_CACHE(MENU_TOOLBAR, "menu.reset_cache", "menu.reset_cache", "ui/reset",
Shortcut.none()),
DEOBF(MENU_TOOLBAR, "menu.deobfuscation", "preferences.deobfuscation", "ui/helmChartLock",
Shortcut.keyboard(KeyEvent.VK_D, UiUtils.ctrlButton() | KeyEvent.ALT_DOWN_MASK)),
SHOW_LOG(MENU_TOOLBAR, "menu.log", "menu.log", "ui/logVerbose",
Shortcut.keyboard(KeyEvent.VK_L, UiUtils.ctrlButton() | KeyEvent.SHIFT_DOWN_MASK)),
BACK(MENU_TOOLBAR, "nav.back", "nav.back", "ui/left",
Shortcut.keyboard(KeyEvent.VK_ESCAPE)),
BACK_V(MENU_TOOLBAR, "nav.back", "nav.back", "ui/left",
Shortcut.none()),
FORWARD(MENU_TOOLBAR, "nav.forward", "nav.forward", "ui/right",
Shortcut.keyboard(KeyEvent.VK_RIGHT, KeyEvent.ALT_DOWN_MASK)),
FORWARD_V(MENU_TOOLBAR, "nav.forward", "nav.forward", "ui/right",
Shortcut.none()),
QUARK(MENU_TOOLBAR, "menu.quark", "menu.quark", "ui/quark",
Shortcut.none()),
OPEN_DEVICE(MENU_TOOLBAR, "debugger.process_selector", "debugger.process_selector", "ui/startDebugger",
Shortcut.none()),
FIND_USAGE(CODE_AREA, "popup.find_usage", "popup.find_usage", null,
Shortcut.keyboard(KeyEvent.VK_X)),
GOTO_DECLARATION(CODE_AREA, "popup.go_to_declaration", "popup.go_to_declaration", null,
Shortcut.keyboard(KeyEvent.VK_D)),
CODE_COMMENT(CODE_AREA, "popup.add_comment", "popup.add_comment", null,
Shortcut.keyboard(KeyEvent.VK_SEMICOLON)),
CODE_COMMENT_SEARCH(CODE_AREA, "popup.search_comment", "popup.search_comment", null,
Shortcut.keyboard(KeyEvent.VK_SEMICOLON, UiUtils.ctrlButton())),
CODE_RENAME(CODE_AREA, "popup.rename", "popup.rename", null,
Shortcut.keyboard(KeyEvent.VK_N)),
FRIDA_COPY(CODE_AREA, "popup.frida", "popup.frida", null,
Shortcut.keyboard(KeyEvent.VK_F)),
XPOSED_COPY(CODE_AREA, "popup.xposed", "popup.xposed", null,
Shortcut.keyboard(KeyEvent.VK_Y)),
JSON_PRETTIFY(CODE_AREA, "popup.json_prettify", "popup.json_prettify", null,
Shortcut.none()),
SCRIPT_RUN(PLUGIN_SCRIPT, "script.run", "script.run", "ui/run",
Shortcut.keyboard(KeyEvent.VK_F8)),
SCRIPT_SAVE(PLUGIN_SCRIPT, "script.save", "script.save", "ui/menu-saveall",
Shortcut.keyboard(KeyEvent.VK_S, UiUtils.ctrlButton())),
SCRIPT_AUTO_COMPLETE(PLUGIN_SCRIPT, "script.auto_complete", "script.auto_complete", null,
Shortcut.keyboard(KeyEvent.VK_SPACE, UiUtils.ctrlButton()));
private final ActionCategory category;
private final String nameRes;
private final String descRes;
private final String iconPath;
private final Shortcut defaultShortcut;
ActionModel(ActionCategory category, String nameRes, String descRes, String iconPath, Shortcut defaultShortcut) {
this.category = category;
this.nameRes = nameRes;
this.descRes = descRes;
this.iconPath = iconPath;
this.defaultShortcut = defaultShortcut;
}
public static List<ActionModel> select(ActionCategory category) {
return Arrays.stream(values())
.filter(actionModel -> actionModel.category == category)
.collect(Collectors.toUnmodifiableList());
}
public ActionCategory getCategory() {
return category;
}
public String getName() {
if (nameRes != null) {
String name = NLS.str(nameRes);
if (name().endsWith("_V")) {
name = NLS.str("action.variant", name);
}
return name;
}
return null;
}
public String getDescription() {
if (descRes != null) {
return NLS.str(descRes);
}
return null;
}
public ImageIcon getIcon() {
if (iconPath != null) {
return UiUtils.openSvgIcon(iconPath);
}
return null;
}
@NonNull
public Shortcut getDefaultShortcut() {
return defaultShortcut;
}
}

View File

@ -0,0 +1,15 @@
package jadx.gui.ui.action;
import javax.swing.JComponent;
import jadx.gui.utils.shortcut.Shortcut;
public interface IShortcutAction {
ActionModel getActionModel();
JComponent getShortcutComponent();
void performAction();
void setShortcut(Shortcut shortcut);
}

View File

@ -0,0 +1,51 @@
package jadx.gui.ui.action;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import org.fife.ui.autocomplete.AutoCompletion;
import org.fife.ui.autocomplete.CompletionProvider;
import jadx.gui.utils.shortcut.Shortcut;
public class JadxAutoCompletion extends AutoCompletion
implements IShortcutAction {
public static final String COMMAND = "JadxAutoCompletion.Command";
/**
* Constructor.
*
* @param provider The completion provider. This cannot be <code>null</code>
*/
public JadxAutoCompletion(CompletionProvider provider) {
super(provider);
}
@Override
public ActionModel getActionModel() {
return ActionModel.SCRIPT_AUTO_COMPLETE;
}
@Override
public JComponent getShortcutComponent() {
return getTextComponent();
}
@Override
public void performAction() {
createAutoCompleteAction().actionPerformed(
new ActionEvent(this, ActionEvent.ACTION_PERFORMED, COMMAND));
}
@Override
public void setShortcut(Shortcut shortcut) {
if (shortcut != null && shortcut.isKeyboard()) {
setTriggerKey(shortcut.toKeyStroke());
} else {
setTriggerKey(KeyStroke.getKeyStroke(KeyEvent.VK_UNDEFINED, 0));
}
}
}

View File

@ -0,0 +1,141 @@
package jadx.gui.ui.action;
import java.awt.event.ActionEvent;
import java.util.function.Consumer;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import org.jetbrains.annotations.Nullable;
import jadx.gui.utils.UiUtils;
import jadx.gui.utils.shortcut.Shortcut;
import jadx.gui.utils.ui.ActionHandler;
public class JadxGuiAction extends ActionHandler
implements IShortcutAction {
private static final String COMMAND = "JadxGuiAction.Command.%s";
private final ActionModel actionModel;
private final String id;
private JComponent shortcutComponent = null;
private KeyStroke addedKeyStroke = null;
private Shortcut shortcut;
public JadxGuiAction(ActionModel actionModel) {
super();
this.actionModel = actionModel;
this.id = actionModel.name();
updateProperties();
}
public JadxGuiAction(ActionModel actionModel, Runnable action) {
super(action);
this.actionModel = actionModel;
this.id = actionModel.name();
updateProperties();
}
public JadxGuiAction(ActionModel actionModel, Consumer<ActionEvent> consumer) {
super(consumer);
this.actionModel = actionModel;
this.id = actionModel.name();
updateProperties();
}
public JadxGuiAction(String id) {
super();
this.actionModel = null;
this.id = id;
updateProperties();
}
private void updateProperties() {
if (actionModel == null) {
return;
}
String name = actionModel.getName();
String description = actionModel.getDescription();
ImageIcon icon = actionModel.getIcon();
if (name != null) {
setName(name);
}
if (description != null) {
setShortDescription(description);
}
if (icon != null) {
setIcon(icon);
}
}
@Nullable
public ActionModel getActionModel() {
return actionModel;
}
@Override
public void setShortcut(Shortcut shortcut) {
this.shortcut = shortcut;
if (shortcut != null) {
setKeyBinding(shortcut.toKeyStroke());
} else {
setKeyBinding(null);
}
}
public void setShortcutComponent(JComponent component) {
this.shortcutComponent = component;
}
@Override
public JComponent getShortcutComponent() {
return shortcutComponent;
}
@Override
public void actionPerformed(ActionEvent e) {
super.actionPerformed(e);
}
@Override
public void performAction() {
if (shortcutComponent != null && !shortcutComponent.isShowing()) {
return;
}
String shortcutType = "null";
if (shortcut != null) {
shortcutType = shortcut.getTypeString();
}
actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED,
String.format(COMMAND, shortcutType)));
}
public static boolean isSource(ActionEvent event) {
return event.getActionCommand() != null
&& event.getActionCommand().startsWith(String.format(COMMAND, ""));
}
@Override
public void setKeyBinding(KeyStroke keyStroke) {
if (shortcutComponent == null) {
super.setKeyBinding(keyStroke);
} else {
// We just set the keyStroke for it to appear in the menu item
// (grayed out in the right)
super.setKeyBinding(keyStroke);
if (addedKeyStroke != null) {
UiUtils.removeKeyBinding(shortcutComponent, addedKeyStroke, id);
}
UiUtils.addKeyBinding(shortcutComponent, keyStroke, id, this::performAction);
addedKeyStroke = keyStroke;
}
}
}

View File

@ -31,6 +31,7 @@ import jadx.gui.utils.DefaultPopupMenuListener;
import jadx.gui.utils.JNodeCache; import jadx.gui.utils.JNodeCache;
import jadx.gui.utils.JumpPosition; import jadx.gui.utils.JumpPosition;
import jadx.gui.utils.UiUtils; import jadx.gui.utils.UiUtils;
import jadx.gui.utils.shortcut.ShortcutsController;
/** /**
* The {@link AbstractCodeArea} implementation used for displaying Java code and text based * The {@link AbstractCodeArea} implementation used for displaying Java code and text based
@ -42,9 +43,12 @@ public final class CodeArea extends AbstractCodeArea {
private static final long serialVersionUID = 6312736869579635796L; private static final long serialVersionUID = 6312736869579635796L;
private @Nullable ICodeInfo cachedCodeInfo; private @Nullable ICodeInfo cachedCodeInfo;
private final ShortcutsController shortcutsController;
CodeArea(ContentPanel contentPanel, JNode node) { CodeArea(ContentPanel contentPanel, JNode node) {
super(contentPanel, node); super(contentPanel, node);
this.shortcutsController = getMainWindow().getShortcutsController();
setSyntaxEditingStyle(node.getSyntaxName()); setSyntaxEditingStyle(node.getSyntaxName());
boolean isJavaCode = node instanceof JClass; boolean isJavaCode = node instanceof JClass;
if (isJavaCode) { if (isJavaCode) {
@ -115,7 +119,8 @@ public final class CodeArea extends AbstractCodeArea {
} }
private void addMenuItems() { private void addMenuItems() {
JNodePopupBuilder popup = new JNodePopupBuilder(this, getPopupMenu()); ShortcutsController shortcutsController = getMainWindow().getShortcutsController();
JNodePopupBuilder popup = new JNodePopupBuilder(this, getPopupMenu(), shortcutsController);
popup.addSeparator(); popup.addSeparator();
popup.add(new FindUsageAction(this)); popup.add(new FindUsageAction(this));
popup.add(new GoToDeclarationAction(this)); popup.add(new GoToDeclarationAction(this));
@ -143,7 +148,8 @@ public final class CodeArea extends AbstractCodeArea {
} }
private void addMenuForJsonFile() { private void addMenuForJsonFile() {
JNodePopupBuilder popup = new JNodePopupBuilder(this, getPopupMenu()); ShortcutsController shortcutsController = getMainWindow().getShortcutsController();
JNodePopupBuilder popup = new JNodePopupBuilder(this, getPopupMenu(), shortcutsController);
popup.addSeparator(); popup.addSeparator();
popup.add(new JsonPrettifyAction(this)); popup.add(new JsonPrettifyAction(this));
} }
@ -339,6 +345,8 @@ public final class CodeArea extends AbstractCodeArea {
@Override @Override
public void dispose() { public void dispose() {
shortcutsController.unbindActionsForComponent(this);
super.dispose(); super.dispose();
cachedCodeInfo = null; cachedCodeInfo = null;
} }

View File

@ -0,0 +1,24 @@
package jadx.gui.ui.codearea;
import jadx.gui.ui.action.ActionModel;
import jadx.gui.ui.action.JadxGuiAction;
public class CodeAreaAction extends JadxGuiAction {
protected transient CodeArea codeArea;
public CodeAreaAction(ActionModel actionModel, CodeArea codeArea) {
super(actionModel);
this.codeArea = codeArea;
setShortcutComponent(codeArea);
}
public CodeAreaAction(String id, CodeArea codeArea) {
super(id);
this.codeArea = codeArea;
setShortcutComponent(codeArea);
}
public void dispose() {
codeArea = null;
}
}

View File

@ -1,9 +1,7 @@
package jadx.gui.ui.codearea; package jadx.gui.ui.codearea;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuEvent;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -25,30 +23,24 @@ import jadx.api.metadata.annotations.InsnCodeOffset;
import jadx.api.metadata.annotations.NodeDeclareRef; import jadx.api.metadata.annotations.NodeDeclareRef;
import jadx.gui.JadxWrapper; import jadx.gui.JadxWrapper;
import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JClass;
import jadx.gui.ui.action.ActionModel;
import jadx.gui.ui.action.JadxGuiAction;
import jadx.gui.ui.dialog.CommentDialog; import jadx.gui.ui.dialog.CommentDialog;
import jadx.gui.utils.DefaultPopupMenuListener; import jadx.gui.utils.DefaultPopupMenuListener;
import jadx.gui.utils.NLS; import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils; import jadx.gui.utils.UiUtils;
import static javax.swing.KeyStroke.getKeyStroke; public class CommentAction extends CodeAreaAction implements DefaultPopupMenuListener {
public class CommentAction extends AbstractAction implements DefaultPopupMenuListener {
private static final long serialVersionUID = 4753838562204629112L; private static final long serialVersionUID = 4753838562204629112L;
private static final Logger LOG = LoggerFactory.getLogger(CommentAction.class); private static final Logger LOG = LoggerFactory.getLogger(CommentAction.class);
private final CodeArea codeArea;
private final boolean enabled; private final boolean enabled;
private ICodeComment actionComment; private ICodeComment actionComment;
public CommentAction(CodeArea codeArea) { public CommentAction(CodeArea codeArea) {
super(NLS.str("popup.add_comment") + " (;)"); super(ActionModel.CODE_COMMENT, codeArea);
this.codeArea = codeArea;
this.enabled = codeArea.getNode() instanceof JClass; this.enabled = codeArea.getNode() instanceof JClass;
if (enabled) {
UiUtils.addKeyBinding(codeArea, getKeyStroke(KeyEvent.VK_SEMICOLON, 0), "popup.add_comment",
() -> showCommentDialog(getCommentRef(codeArea.getCaretPosition())));
}
} }
@Override @Override
@ -64,7 +56,15 @@ public class CommentAction extends AbstractAction implements DefaultPopupMenuLis
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
showCommentDialog(this.actionComment); if (!enabled) {
return;
}
if (JadxGuiAction.isSource(e)) {
showCommentDialog(getCommentRef(codeArea.getCaretPosition()));
} else {
showCommentDialog(this.actionComment);
}
} }
private void showCommentDialog(ICodeComment codeComment) { private void showCommentDialog(ICodeComment codeComment) {

View File

@ -1,28 +1,15 @@
package jadx.gui.ui.codearea; package jadx.gui.ui.codearea;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.KeyStroke;
import jadx.gui.ui.action.ActionModel;
import jadx.gui.ui.dialog.SearchDialog; import jadx.gui.ui.dialog.SearchDialog;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
import static javax.swing.KeyStroke.getKeyStroke; public class CommentSearchAction extends CodeAreaAction {
public class CommentSearchAction extends AbstractAction {
private static final long serialVersionUID = -3646341661734961590L; private static final long serialVersionUID = -3646341661734961590L;
private final CodeArea codeArea;
public CommentSearchAction(CodeArea codeArea) { public CommentSearchAction(CodeArea codeArea) {
super(NLS.str("popup.search_comment") + " (Ctrl + ;)"); super(ActionModel.CODE_COMMENT_SEARCH, codeArea);
this.codeArea = codeArea;
KeyStroke key = getKeyStroke(KeyEvent.VK_SEMICOLON, UiUtils.ctrlButton());
UiUtils.addKeyBinding(codeArea, key, "popup.search_comment", this::startSearch);
} }
@Override @Override

View File

@ -1,20 +1,15 @@
package jadx.gui.ui.codearea; package jadx.gui.ui.codearea;
import java.awt.event.KeyEvent;
import jadx.gui.treemodel.JNode; import jadx.gui.treemodel.JNode;
import jadx.gui.ui.MainWindow; import jadx.gui.ui.MainWindow;
import jadx.gui.ui.action.ActionModel;
import jadx.gui.ui.dialog.UsageDialog; import jadx.gui.ui.dialog.UsageDialog;
import jadx.gui.utils.NLS;
import static javax.swing.KeyStroke.getKeyStroke;
public final class FindUsageAction extends JNodeAction { public final class FindUsageAction extends JNodeAction {
private static final long serialVersionUID = 4692546569977976384L; private static final long serialVersionUID = 4692546569977976384L;
public FindUsageAction(CodeArea codeArea) { public FindUsageAction(CodeArea codeArea) {
super(NLS.str("popup.find_usage") + " (x)", codeArea); super(ActionModel.FIND_USAGE, codeArea);
addKeyBinding(getKeyStroke(KeyEvent.VK_X, 0), "trigger usage");
} }
@Override @Override

View File

@ -1,6 +1,5 @@
package jadx.gui.ui.codearea; package jadx.gui.ui.codearea;
import java.awt.event.KeyEvent;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -23,18 +22,16 @@ import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JField; import jadx.gui.treemodel.JField;
import jadx.gui.treemodel.JMethod; import jadx.gui.treemodel.JMethod;
import jadx.gui.treemodel.JNode; import jadx.gui.treemodel.JNode;
import jadx.gui.ui.action.ActionModel;
import jadx.gui.utils.NLS; import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils; import jadx.gui.utils.UiUtils;
import static javax.swing.KeyStroke.getKeyStroke;
public final class FridaAction extends JNodeAction { public final class FridaAction extends JNodeAction {
private static final Logger LOG = LoggerFactory.getLogger(FridaAction.class); private static final Logger LOG = LoggerFactory.getLogger(FridaAction.class);
private static final long serialVersionUID = -3084073927621269039L; private static final long serialVersionUID = -3084073927621269039L;
public FridaAction(CodeArea codeArea) { public FridaAction(CodeArea codeArea) {
super(NLS.str("popup.frida") + " (f)", codeArea); super(ActionModel.FRIDA_COPY, codeArea);
addKeyBinding(getKeyStroke(KeyEvent.VK_F, 0), "trigger frida");
} }
@Override @Override

View File

@ -1,18 +1,13 @@
package jadx.gui.ui.codearea; package jadx.gui.ui.codearea;
import java.awt.event.KeyEvent;
import jadx.gui.treemodel.JNode; import jadx.gui.treemodel.JNode;
import jadx.gui.utils.NLS; import jadx.gui.ui.action.ActionModel;
import static javax.swing.KeyStroke.getKeyStroke;
public final class GoToDeclarationAction extends JNodeAction { public final class GoToDeclarationAction extends JNodeAction {
private static final long serialVersionUID = -1186470538894941301L; private static final long serialVersionUID = -1186470538894941301L;
public GoToDeclarationAction(CodeArea codeArea) { public GoToDeclarationAction(CodeArea codeArea) {
super(NLS.str("popup.go_to_declaration") + " (d)", codeArea); super(ActionModel.GOTO_DECLARATION, codeArea);
addKeyBinding(getKeyStroke(KeyEvent.VK_D, 0), "trigger goto decl");
} }
@Override @Override

View File

@ -3,26 +3,26 @@ package jadx.gui.ui.codearea;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import javax.swing.AbstractAction;
import javax.swing.KeyStroke;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import jadx.gui.treemodel.JNode; import jadx.gui.treemodel.JNode;
import jadx.gui.utils.UiUtils; import jadx.gui.ui.action.ActionModel;
import jadx.gui.ui.action.JadxGuiAction;
/** /**
* Add menu and key binding actions for JNode in code area * Add menu and key binding actions for JNode in code area
*/ */
public abstract class JNodeAction extends AbstractAction { public abstract class JNodeAction extends CodeAreaAction {
private static final long serialVersionUID = -2600154727884853550L; private static final long serialVersionUID = -2600154727884853550L;
private transient CodeArea codeArea;
private transient @Nullable JNode node; private transient @Nullable JNode node;
public JNodeAction(String name, CodeArea codeArea) { public JNodeAction(ActionModel actionModel, CodeArea codeArea) {
super(name); super(actionModel, codeArea);
this.codeArea = codeArea; }
public JNodeAction(String id, CodeArea codeArea) {
super(id, codeArea);
} }
public abstract void runAction(JNode node); public abstract void runAction(JNode node);
@ -31,21 +31,16 @@ public abstract class JNodeAction extends AbstractAction {
return node != null; return node != null;
} }
public void addKeyBinding(KeyStroke key, String id) {
UiUtils.addKeyBinding(codeArea, key, id, new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
node = codeArea.getNodeUnderCaret();
if (isActionEnabled(node)) {
runAction(node);
}
}
});
}
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
runAction(node); if (JadxGuiAction.isSource(e)) {
node = codeArea.getNodeUnderCaret();
if (isActionEnabled(node)) {
runAction(node);
}
} else {
runAction(node);
}
} }
public void changeNode(@Nullable JNode node) { public void changeNode(@Nullable JNode node) {
@ -57,9 +52,10 @@ public abstract class JNodeAction extends AbstractAction {
return codeArea; return codeArea;
} }
@Override
public void dispose() { public void dispose() {
super.dispose();
node = null; node = null;
codeArea = null;
for (PropertyChangeListener changeListener : getPropertyChangeListeners()) { for (PropertyChangeListener changeListener : getPropertyChangeListeners()) {
removePropertyChangeListener(changeListener); removePropertyChangeListener(changeListener);
} }

View File

@ -1,14 +1,18 @@
package jadx.gui.ui.codearea; package jadx.gui.ui.codearea;
import javax.swing.Action;
import javax.swing.JPopupMenu; import javax.swing.JPopupMenu;
import javax.swing.event.PopupMenuListener; import javax.swing.event.PopupMenuListener;
import jadx.gui.ui.action.JadxGuiAction;
import jadx.gui.utils.shortcut.ShortcutsController;
public class JNodePopupBuilder { public class JNodePopupBuilder {
private final JPopupMenu menu; private final JPopupMenu menu;
private final JNodePopupListener popupListener; private final JNodePopupListener popupListener;
private final ShortcutsController shortcutsController;
public JNodePopupBuilder(CodeArea codeArea, JPopupMenu popupMenu) { public JNodePopupBuilder(CodeArea codeArea, JPopupMenu popupMenu, ShortcutsController shortcutsController) {
this.shortcutsController = shortcutsController;
menu = popupMenu; menu = popupMenu;
popupListener = new JNodePopupListener(codeArea); popupListener = new JNodePopupListener(codeArea);
popupMenu.addPopupMenuListener(popupListener); popupMenu.addPopupMenuListener(popupListener);
@ -19,11 +23,23 @@ public class JNodePopupBuilder {
} }
public void add(JNodeAction nodeAction) { public void add(JNodeAction nodeAction) {
// We set the shortcut immediately for two reasons
// - there might be multiple instances of this action with
// same ActionModel across different codeAreas, while
// ShortcutController only supports one instance
// - This action will be recreated when shortcuts are changed,
// so no need to bind it
if (nodeAction.getActionModel() != null) {
shortcutsController.bindImmediate(nodeAction);
}
menu.add(nodeAction); menu.add(nodeAction);
popupListener.addActions(nodeAction); popupListener.addActions(nodeAction);
} }
public void add(Action action) { public void add(JadxGuiAction action) {
if (action.getActionModel() != null) {
shortcutsController.bindImmediate(action);
}
menu.add(action); menu.add(action);
if (action instanceof PopupMenuListener) { if (action instanceof PopupMenuListener) {
menu.addPopupMenuListener((PopupMenuListener) action); menu.addPopupMenuListener((PopupMenuListener) action);

View File

@ -6,7 +6,7 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import jadx.gui.treemodel.JNode; import jadx.gui.treemodel.JNode;
import jadx.gui.utils.NLS; import jadx.gui.ui.action.ActionModel;
public class JsonPrettifyAction extends JNodeAction { public class JsonPrettifyAction extends JNodeAction {
@ -14,7 +14,7 @@ public class JsonPrettifyAction extends JNodeAction {
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
public JsonPrettifyAction(CodeArea codeArea) { public JsonPrettifyAction(CodeArea codeArea) {
super(NLS.str("popup.json_prettify"), codeArea); super(ActionModel.JSON_PRETTIFY, codeArea);
} }
@Override @Override

View File

@ -2,18 +2,14 @@ package jadx.gui.ui.codearea;
import jadx.gui.treemodel.JNode; import jadx.gui.treemodel.JNode;
import jadx.gui.treemodel.JRenameNode; import jadx.gui.treemodel.JRenameNode;
import jadx.gui.ui.action.ActionModel;
import jadx.gui.ui.dialog.RenameDialog; import jadx.gui.ui.dialog.RenameDialog;
import jadx.gui.utils.NLS;
import static java.awt.event.KeyEvent.VK_N;
import static javax.swing.KeyStroke.getKeyStroke;
public final class RenameAction extends JNodeAction { public final class RenameAction extends JNodeAction {
private static final long serialVersionUID = -4680872086148463289L; private static final long serialVersionUID = -4680872086148463289L;
public RenameAction(CodeArea codeArea) { public RenameAction(CodeArea codeArea) {
super(NLS.str("popup.rename") + " (n)", codeArea); super(ActionModel.CODE_RENAME, codeArea);
addKeyBinding(getKeyStroke(VK_N, 0), "trigger rename");
} }
@Override @Override

View File

@ -1,6 +1,5 @@
package jadx.gui.ui.codearea; package jadx.gui.ui.codearea;
import java.awt.event.KeyEvent;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -17,18 +16,16 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JMethod; import jadx.gui.treemodel.JMethod;
import jadx.gui.treemodel.JNode; import jadx.gui.treemodel.JNode;
import jadx.gui.ui.action.ActionModel;
import jadx.gui.utils.NLS; import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils; import jadx.gui.utils.UiUtils;
import static javax.swing.KeyStroke.getKeyStroke;
public class XposedAction extends JNodeAction { public class XposedAction extends JNodeAction {
private static final Logger LOG = LoggerFactory.getLogger(XposedAction.class); private static final Logger LOG = LoggerFactory.getLogger(XposedAction.class);
private static final long serialVersionUID = 2641585141624592578L; private static final long serialVersionUID = 2641585141624592578L;
public XposedAction(CodeArea codeArea) { public XposedAction(CodeArea codeArea) {
super(NLS.str("popup.xposed") + " (y)", codeArea); super(ActionModel.XPOSED_COPY, codeArea);
addKeyBinding(getKeyStroke(KeyEvent.VK_Y, 0), "trigger xposed");
} }
@Override @Override

View File

@ -0,0 +1,22 @@
package jadx.gui.ui.menu;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.Action;
import javax.swing.JMenuItem;
public class HiddenMenuItem extends JMenuItem {
public HiddenMenuItem(Action a) {
super(a);
}
@Override
protected void paintComponent(Graphics g) {
}
@Override
public Dimension getPreferredSize() {
return new Dimension(0, 0);
}
}

View File

@ -0,0 +1,64 @@
package jadx.gui.ui.menu;
import javax.swing.Action;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import jadx.gui.ui.action.ActionModel;
import jadx.gui.ui.action.JadxGuiAction;
import jadx.gui.utils.shortcut.Shortcut;
import jadx.gui.utils.shortcut.ShortcutsController;
public class JadxMenu extends JMenu {
private final ShortcutsController shortcutsController;
public JadxMenu(String name, ShortcutsController shortcutsController) {
super(name);
this.shortcutsController = shortcutsController;
}
@Override
public JMenuItem add(JMenuItem menuItem) {
Action action = menuItem.getAction();
bindAction(action);
return super.add(menuItem);
}
@Override
public JMenuItem add(Action action) {
bindAction(action);
return super.add(action);
}
public void bindAction(Action action) {
if (action instanceof JadxGuiAction) {
shortcutsController.bind((JadxGuiAction) action);
}
}
public void reloadShortcuts() {
for (int i = 0; i < getItemCount(); i++) {
// TODO only repaint the items whose shortcut changed
JMenuItem item = getItem(i);
if (item == null) {
continue;
}
Action action = item.getAction();
if (!(action instanceof JadxGuiAction) || ((JadxGuiAction) action).getActionModel() == null) {
continue;
}
ActionModel actionModel = ((JadxGuiAction) action).getActionModel();
Shortcut shortcut = shortcutsController.get(actionModel);
if (shortcut != null) {
item.setAccelerator(shortcut.toKeyStroke());
} else {
item.setAccelerator(null);
}
item.repaint();
item.revalidate();
}
}
}

View File

@ -0,0 +1,15 @@
package jadx.gui.ui.menu;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
public class JadxMenuBar extends JMenuBar {
public void reloadShortcuts() {
for (int i = 0; i < getMenuCount(); i++) {
JMenu menu = getMenu(i);
if (menu instanceof JadxMenu) {
((JadxMenu) menu).reloadShortcuts();
}
}
}
}

View File

@ -0,0 +1,150 @@
package jadx.gui.utils.shortcut;
import java.awt.event.KeyEvent;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import javax.swing.KeyStroke;
import jadx.gui.utils.SystemInfo;
public class Shortcut {
private static final Set<Integer> FORBIDDEN_KEY_CODES = new HashSet<>(List.of(
KeyEvent.VK_UNDEFINED, KeyEvent.VK_SHIFT, KeyEvent.VK_ALT, KeyEvent.VK_META, KeyEvent.VK_ALT_GRAPH));
private static final Set<Integer> ALLOWED_MODIFIERS = new HashSet<>(List.of(
KeyEvent.CTRL_DOWN_MASK, KeyEvent.META_DOWN_MASK, KeyEvent.ALT_DOWN_MASK, KeyEvent.ALT_GRAPH_DOWN_MASK,
KeyEvent.SHIFT_DOWN_MASK));
private Integer keyCode = null;
private Integer modifiers = null;
private Integer mouseButton = null;
private Shortcut() {
}
public static Shortcut keyboard(int keyCode) {
return keyboard(keyCode, 0);
}
public static Shortcut keyboard(int keyCode, int modifiers) {
Shortcut shortcut = new Shortcut();
shortcut.keyCode = keyCode;
shortcut.modifiers = modifiers;
return shortcut;
}
public static Shortcut mouse(int mouseButton) {
Shortcut shortcut = new Shortcut();
shortcut.mouseButton = mouseButton;
return shortcut;
}
public static Shortcut none() {
Shortcut shortcut = new Shortcut();
// Must have at least one non-null attribute in order to be serialized
// otherwise will roll back to default Shortcut
shortcut.modifiers = 0;
return shortcut;
}
public Integer getKeyCode() {
return keyCode;
}
public Integer getModifiers() {
return modifiers;
}
public Integer getMouseButton() {
return mouseButton;
}
public boolean isKeyboard() {
return keyCode != null;
}
public boolean isMouse() {
return mouseButton != null;
}
public boolean isNone() {
return !isMouse() && !isKeyboard();
}
public boolean isValidKeyboard() {
return isKeyboard() && !FORBIDDEN_KEY_CODES.contains(keyCode) && isValidModifiers();
}
public boolean isValidModifiers() {
int modifiersTest = modifiers;
for (Integer modifier : ALLOWED_MODIFIERS) {
modifiersTest &= ~modifier;
}
return modifiersTest == 0;
}
public KeyStroke toKeyStroke() {
return isKeyboard()
? KeyStroke.getKeyStroke(keyCode, modifiers,
modifiers != 0 && SystemInfo.IS_MAC)
: null;
}
@Override
public String toString() {
if (isKeyboard()) {
return keyToString();
} else if (isMouse()) {
return mouseToString();
}
return "NONE";
}
public String getTypeString() {
if (isKeyboard()) {
return "Keyboard";
} else if (isMouse()) {
return "Mouse";
}
return null;
}
private String mouseToString() {
return "MouseButton" + mouseButton;
}
private String keyToString() {
StringBuilder sb = new StringBuilder();
if (modifiers != null && modifiers > 0) {
sb.append(KeyEvent.getModifiersExText(modifiers));
sb.append('+');
}
if (keyCode != null && keyCode != 0) {
sb.append(KeyEvent.getKeyText(keyCode));
} else {
sb.append("UNDEFINED");
}
return sb.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Shortcut shortcut = (Shortcut) o;
return Objects.equals(keyCode, shortcut.keyCode)
&& Objects.equals(modifiers, shortcut.modifiers)
&& Objects.equals(mouseButton, shortcut.mouseButton);
}
@Override
public int hashCode() {
return Objects.hash(keyCode, modifiers, mouseButton);
}
}

View File

@ -0,0 +1,143 @@
package jadx.gui.utils.shortcut;
import java.awt.AWTEvent;
import java.awt.Toolkit;
import java.awt.event.MouseEvent;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import org.jetbrains.annotations.Nullable;
import jadx.gui.settings.JadxSettings;
import jadx.gui.settings.data.ShortcutsWrapper;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.action.ActionModel;
import jadx.gui.ui.action.IShortcutAction;
import jadx.gui.utils.UiUtils;
public class ShortcutsController {
private ShortcutsWrapper shortcuts;
private final JadxSettings settings;
private final Map<ActionModel, Set<IShortcutAction>> boundActions = new HashMap<>();
private Set<ActionModel> mouseActions = null;
public ShortcutsController(JadxSettings settings) {
this.settings = settings;
}
public void loadSettings() {
this.shortcuts = settings.getShortcuts();
indexMouseActions();
for (Map.Entry<ActionModel, Set<IShortcutAction>> actionsEntry : boundActions.entrySet()) {
ActionModel actionModel = actionsEntry.getKey();
Set<IShortcutAction> actions = actionsEntry.getValue();
Shortcut shortcut = get(actionModel);
if (actions != null) {
for (IShortcutAction action : actions) {
action.setShortcut(shortcut);
}
}
}
}
@Nullable
public Shortcut get(ActionModel actionModel) {
return shortcuts.get(actionModel);
}
public KeyStroke getKeyStroke(ActionModel actionModel) {
Shortcut shortcut = get(actionModel);
KeyStroke keyStroke = null;
if (shortcut != null && shortcut.isKeyboard()) {
keyStroke = shortcut.toKeyStroke();
}
return keyStroke;
}
/*
* Binds to an action and updates its shortcut every time loadSettings is called
*/
public void bind(IShortcutAction action) {
boundActions.computeIfAbsent(action.getActionModel(), k -> new HashSet<>());
boundActions.get(action.getActionModel()).add(action);
}
/*
* Immediately sets the shortcut for an action
*/
public void bindImmediate(IShortcutAction action) {
bind(action);
Shortcut shortcut = get(action.getActionModel());
action.setShortcut(shortcut);
}
public static Map<ActionModel, Shortcut> getDefault() {
Map<ActionModel, Shortcut> shortcuts = new HashMap<>();
for (ActionModel actionModel : ActionModel.values()) {
shortcuts.put(actionModel, actionModel.getDefaultShortcut());
}
return shortcuts;
}
public void registerMouseEventListener(MainWindow mw) {
Toolkit.getDefaultToolkit().addAWTEventListener(event -> {
if (mw.isSettingsOpen()) {
return;
}
if (!(event instanceof MouseEvent)) {
return;
}
MouseEvent mouseEvent = (MouseEvent) event;
if (mouseEvent.getID() != MouseEvent.MOUSE_PRESSED) {
return;
}
int mouseButton = mouseEvent.getButton();
for (ActionModel actionModel : mouseActions) {
Shortcut shortcut = shortcuts.get(actionModel);
if (shortcut != null && shortcut.getMouseButton() == mouseButton) {
Set<IShortcutAction> actions = boundActions.get(actionModel);
if (actions != null) {
for (IShortcutAction action : actions) {
if (action != null) {
mouseEvent.consume();
UiUtils.uiRun(action::performAction);
}
}
}
}
}
}, AWTEvent.MOUSE_EVENT_MASK);
}
private void indexMouseActions() {
mouseActions = new HashSet<>();
for (ActionModel actionModel : ActionModel.values()) {
Shortcut shortcut = shortcuts.get(actionModel);
if (shortcut != null && shortcut.isMouse()) {
mouseActions.add(actionModel);
} else {
mouseActions.remove(actionModel);
}
}
}
public void unbindActionsForComponent(JComponent component) {
for (ActionModel actionModel : ActionModel.values()) {
Set<IShortcutAction> actions = boundActions.get(actionModel);
if (actions != null) {
actions.removeIf(action -> action != null && action.getShortcutComponent() == component);
}
}
}
}

View File

@ -1,7 +1,6 @@
package jadx.gui.utils.ui; package jadx.gui.utils.ui;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.function.Consumer; import java.util.function.Consumer;
import javax.swing.AbstractAction; import javax.swing.AbstractAction;
@ -11,6 +10,7 @@ import javax.swing.JComponent;
import javax.swing.KeyStroke; import javax.swing.KeyStroke;
import jadx.gui.utils.UiUtils; import jadx.gui.utils.UiUtils;
import jadx.gui.utils.shortcut.Shortcut;
public class ActionHandler extends AbstractAction { public class ActionHandler extends AbstractAction {
@ -24,6 +24,11 @@ public class ActionHandler extends AbstractAction {
this.consumer = consumer; this.consumer = consumer;
} }
public ActionHandler() {
this.consumer = ev -> {
};
}
public void setName(String name) { public void setName(String name) {
putValue(NAME, name); putValue(NAME, name);
} }
@ -58,7 +63,7 @@ public class ActionHandler extends AbstractAction {
public void addKeyBindToDescription() { public void addKeyBindToDescription() {
KeyStroke keyStroke = (KeyStroke) getValue(ACCELERATOR_KEY); KeyStroke keyStroke = (KeyStroke) getValue(ACCELERATOR_KEY);
if (keyStroke != null) { if (keyStroke != null) {
String keyText = KeyEvent.getKeyText(keyStroke.getKeyCode()); String keyText = Shortcut.keyboard(keyStroke.getKeyCode(), keyStroke.getModifiers()).toString();
String desc = (String) getValue(SHORT_DESCRIPTION); String desc = (String) getValue(SHORT_DESCRIPTION);
setShortDescription(desc + " (" + keyText + ")"); setShortDescription(desc + " (" + keyText + ")");
} }

View File

@ -23,6 +23,7 @@ menu.deobfuscation=Deobfuskierung
menu.log=Log-Anzeige menu.log=Log-Anzeige
menu.help=Hilfe menu.help=Hilfe
menu.about=Über menu.about=Über
#menu.quark=
menu.update_label=Neue Version %s verfügbar! menu.update_label=Neue Version %s verfügbar!
file.open_action=Datei öffnen… file.open_action=Datei öffnen…
@ -161,6 +162,8 @@ about_dialog.title=Über JADX
preferences.title=Einstellungen preferences.title=Einstellungen
preferences.deobfuscation=Deobfuskierung preferences.deobfuscation=Deobfuskierung
preferences.appearance=Aussehen preferences.appearance=Aussehen
#preferences.shortcuts=
#preferences.select_shortcuts=
preferences.decompile=Dekompilierung preferences.decompile=Dekompilierung
preferences.plugins=Plugins preferences.plugins=Plugins
preferences.project=Projekt preferences.project=Projekt
@ -257,6 +260,9 @@ msg.open_file=Bitte Datei öffnen
msg.saving_sources=Quelltexte speichern msg.saving_sources=Quelltexte speichern
msg.language_changed_title=Sprache speichern msg.language_changed_title=Sprache speichern
msg.language_changed=Die neue Sprache wird beim nächsten Start der Anwendung angezeigt. msg.language_changed=Die neue Sprache wird beim nächsten Start der Anwendung angezeigt.
#msg.warning_title=
#msg.common_mouse_shortcut=
#msg.duplicate_shortcut=
msg.project_error_title=Fehler msg.project_error_title=Fehler
msg.project_error=Projekt konnte nicht geladen werden msg.project_error=Projekt konnte nicht geladen werden
msg.cmd_select_class_error=Klasse\n%s auswählen nicht möglich\nSie existiert nicht. msg.cmd_select_class_error=Klasse\n%s auswählen nicht möglich\nSie existiert nicht.
@ -290,6 +296,7 @@ popup.search_global=Globale Suche "%s"
#script.run=Run #script.run=Run
#script.save=Save #script.save=Save
#script.auto_complete=
#script.check=Check #script.check=Check
#script.format=Reformat #script.format=Reformat
#script.log=Show log #script.log=Show log
@ -405,3 +412,8 @@ adb_dialog.no_devices=Ich kann kein Gerät finden, um die App zu starten.
adb_dialog.restart_while_debugging_title=Neustart während der Fehlersuche adb_dialog.restart_while_debugging_title=Neustart während der Fehlersuche
adb_dialog.restart_while_debugging_msg=Sie debuggen eine App, sind Sie sicher, dass Sie eine Sitzung neu starten können? adb_dialog.restart_while_debugging_msg=Sie debuggen eine App, sind Sie sicher, dass Sie eine Sitzung neu starten können?
adb_dialog.starting_debugger=Debugger starten… adb_dialog.starting_debugger=Debugger starten…
#action.variant=
#action_category.menu_toolbar=
#action_category.code_area=
#action_category.plugin_script=

View File

@ -23,6 +23,7 @@ menu.deobfuscation=Deobfuscation
menu.log=Log Viewer menu.log=Log Viewer
menu.help=Help menu.help=Help
menu.about=About menu.about=About
menu.quark=Quark Engine
menu.update_label=New version %s available! menu.update_label=New version %s available!
file.open_action=Open files ... file.open_action=Open files ...
@ -161,6 +162,8 @@ about_dialog.title=About JADX
preferences.title=Preferences preferences.title=Preferences
preferences.deobfuscation=Deobfuscation preferences.deobfuscation=Deobfuscation
preferences.appearance=Appearance preferences.appearance=Appearance
preferences.shortcuts=Shortcuts
preferences.select_shortcuts=Select a specific shortcuts group
preferences.decompile=Decompilation preferences.decompile=Decompilation
preferences.plugins=Plugins preferences.plugins=Plugins
preferences.project=Project preferences.project=Project
@ -257,6 +260,9 @@ msg.open_file=Please open file
msg.saving_sources=Saving sources msg.saving_sources=Saving sources
msg.language_changed_title=Language changed msg.language_changed_title=Language changed
msg.language_changed=New language will be displayed the next time application starts. msg.language_changed=New language will be displayed the next time application starts.
msg.warning_title=Warning
msg.common_mouse_shortcut=This is a commonly used key, are you sure you would like to bind it to an action?
msg.duplicate_shortcut=The shortcut %s is already set in action "%s" from category "%s", continue ?
msg.project_error_title=Error msg.project_error_title=Error
msg.project_error=Project could not be loaded msg.project_error=Project could not be loaded
msg.cmd_select_class_error=Failed to select the class\n%s\nThe class does not exist. msg.cmd_select_class_error=Failed to select the class\n%s\nThe class does not exist.
@ -290,6 +296,7 @@ popup.json_prettify=JSON Prettify
script.run=Run script.run=Run
script.save=Save script.save=Save
script.auto_complete=Auto Complete
script.check=Check script.check=Check
script.format=Reformat script.format=Reformat
script.log=Show log script.log=Show log
@ -405,3 +412,8 @@ adb_dialog.no_devices=Can't found any device to start app.
adb_dialog.restart_while_debugging_title=Restart while debugging adb_dialog.restart_while_debugging_title=Restart while debugging
adb_dialog.restart_while_debugging_msg=You're debugging an app, are you sure to restart a session? adb_dialog.restart_while_debugging_msg=You're debugging an app, are you sure to restart a session?
adb_dialog.starting_debugger=Starting debugger... adb_dialog.starting_debugger=Starting debugger...
action.variant=%s (variant)
action_category.menu_toolbar=Menu / Toolbar
action_category.code_area=Code Area
action_category.plugin_script=Plugin Script

View File

@ -23,6 +23,7 @@ menu.deobfuscation=Desofuscación
menu.log=Visor log menu.log=Visor log
menu.help=Ayuda menu.help=Ayuda
menu.about=Acerca de... menu.about=Acerca de...
#menu.quark=
menu.update_label=¡Nueva versión %s disponible! menu.update_label=¡Nueva versión %s disponible!
file.open_action=Abrir archivo... file.open_action=Abrir archivo...
@ -161,6 +162,8 @@ about_dialog.title=Sobre JADX
preferences.title=Preferencias preferences.title=Preferencias
preferences.deobfuscation=Desofuscación preferences.deobfuscation=Desofuscación
#preferences.appearance=Appearance #preferences.appearance=Appearance
#preferences.shortcuts=
#preferences.select_shortcuts=
preferences.decompile=Descompilación preferences.decompile=Descompilación
#preferences.plugins=Plugins #preferences.plugins=Plugins
#preferences.project= #preferences.project=
@ -257,6 +260,9 @@ msg.open_file=Por favor, abra un archivo
msg.saving_sources=Guardando fuente msg.saving_sources=Guardando fuente
msg.language_changed_title=Idioma cambiado msg.language_changed_title=Idioma cambiado
msg.language_changed=El nuevo idioma se mostrará la próxima vez que la aplicación se inicie. msg.language_changed=El nuevo idioma se mostrará la próxima vez que la aplicación se inicie.
#msg.warning_title=
#msg.common_mouse_shortcut=
#msg.duplicate_shortcut=
#msg.project_error_title= #msg.project_error_title=
#msg.project_error= #msg.project_error=
#msg.cmd_select_class_error= #msg.cmd_select_class_error=
@ -290,6 +296,7 @@ popup.rename=Nimeta ümber
#script.run=Run #script.run=Run
#script.save=Save #script.save=Save
#script.auto_complete=
#script.check=Check #script.check=Check
#script.format=Reformat #script.format=Reformat
#script.log=Show log #script.log=Show log
@ -405,3 +412,8 @@ certificate.serialPubKeyY=Y
#adb_dialog.restart_while_debugging_title=Restart while debugging #adb_dialog.restart_while_debugging_title=Restart while debugging
#adb_dialog.restart_while_debugging_msg=You're debugging an app, are you sure to restart a session? #adb_dialog.restart_while_debugging_msg=You're debugging an app, are you sure to restart a session?
#adb_dialog.starting_debugger=Starting debugger... #adb_dialog.starting_debugger=Starting debugger...
#action.variant=
#action_category.menu_toolbar=
#action_category.code_area=
#action_category.plugin_script=

View File

@ -23,6 +23,7 @@ menu.deobfuscation=난독화 해제
menu.log=로그 뷰어 menu.log=로그 뷰어
menu.help=도움말 menu.help=도움말
menu.about=정보 menu.about=정보
#menu.quark=
menu.update_label=새 버전 %s 이(가) 존재합니다! menu.update_label=새 버전 %s 이(가) 존재합니다!
file.open_action=파일 열기 ... file.open_action=파일 열기 ...
@ -161,6 +162,8 @@ about_dialog.title=JADX 정보
preferences.title=설정 preferences.title=설정
preferences.deobfuscation=난독화 해제 preferences.deobfuscation=난독화 해제
preferences.appearance=외관 preferences.appearance=외관
#preferences.shortcuts=
#preferences.select_shortcuts=
preferences.decompile=디컴파일 preferences.decompile=디컴파일
preferences.plugins=플러그인 preferences.plugins=플러그인
preferences.project=프로젝트 preferences.project=프로젝트
@ -257,6 +260,9 @@ msg.open_file=파일을 여십시오
msg.saving_sources=소스 저장 중 msg.saving_sources=소스 저장 중
msg.language_changed_title=언어 변경됨 msg.language_changed_title=언어 변경됨
msg.language_changed=다음에 응용 프로그램이 시작되면 새 언어가 표시됩니다. msg.language_changed=다음에 응용 프로그램이 시작되면 새 언어가 표시됩니다.
#msg.warning_title=
#msg.common_mouse_shortcut=
#msg.duplicate_shortcut=
msg.project_error_title=오류 msg.project_error_title=오류
msg.project_error=프로젝트를 로드 할 수 없습니다. msg.project_error=프로젝트를 로드 할 수 없습니다.
msg.cmd_select_class_error=클래스를 선택하지 못했습니다.\n%s\n클래스가 없습니다. msg.cmd_select_class_error=클래스를 선택하지 못했습니다.\n%s\n클래스가 없습니다.
@ -290,6 +296,7 @@ popup.search_global="%s" 전역 검색
#script.run=Run #script.run=Run
#script.save=Save #script.save=Save
#script.auto_complete=
#script.check=Check #script.check=Check
#script.format=Reformat #script.format=Reformat
#script.log=Show log #script.log=Show log
@ -405,3 +412,8 @@ adb_dialog.no_devices=앱을 실행할 기기를 찾을 수 없습니다.
adb_dialog.restart_while_debugging_title=디버깅중 다시 시작 adb_dialog.restart_while_debugging_title=디버깅중 다시 시작
adb_dialog.restart_while_debugging_msg=앱을 디버깅하고 있습니다. 세션을 다시 시작 하시겠습니까? adb_dialog.restart_while_debugging_msg=앱을 디버깅하고 있습니다. 세션을 다시 시작 하시겠습니까?
adb_dialog.starting_debugger=디버거 시작 중 ... adb_dialog.starting_debugger=디버거 시작 중 ...
#action.variant=
#action_category.menu_toolbar=
#action_category.code_area=
#action_category.plugin_script=

View File

@ -23,6 +23,7 @@ menu.deobfuscation=Desofuscar
menu.log=Visualizador de log menu.log=Visualizador de log
menu.help=Ajuda menu.help=Ajuda
menu.about=Sobre menu.about=Sobre
#menu.quark=
menu.update_label=Nova versão %s disponível! menu.update_label=Nova versão %s disponível!
file.open_action=Abrir arquivos... file.open_action=Abrir arquivos...
@ -161,6 +162,8 @@ about_dialog.title=Sobre o JADX
preferences.title=Preferências preferences.title=Preferências
preferences.deobfuscation=Desofuscar preferences.deobfuscation=Desofuscar
preferences.appearance=Aparência preferences.appearance=Aparência
#preferences.shortcuts=
#preferences.select_shortcuts=
preferences.decompile=Descompilação preferences.decompile=Descompilação
preferences.plugins=Plugins preferences.plugins=Plugins
preferences.project=Projeto preferences.project=Projeto
@ -257,6 +260,9 @@ msg.open_file=Abra um arquivo
msg.saving_sources=Salvando recursos msg.saving_sources=Salvando recursos
msg.language_changed_title=Idioma alterado msg.language_changed_title=Idioma alterado
msg.language_changed=Novo idioma será mostrado na próxima inicialização. msg.language_changed=Novo idioma será mostrado na próxima inicialização.
#msg.warning_title=
#msg.common_mouse_shortcut=
#msg.duplicate_shortcut=
msg.project_error_title=Erro msg.project_error_title=Erro
msg.project_error=Projeto não pôde ser carregado msg.project_error=Projeto não pôde ser carregado
msg.cmd_select_class_error=Falha ao selecionar classe\n%s\nA classe não existe. msg.cmd_select_class_error=Falha ao selecionar classe\n%s\nA classe não existe.
@ -290,6 +296,7 @@ popup.search_global=Busca global "%s"
#script.run=Run #script.run=Run
#script.save=Save #script.save=Save
#script.auto_complete=
#script.check=Check #script.check=Check
#script.format=Reformat #script.format=Reformat
#script.log=Show log #script.log=Show log
@ -405,3 +412,8 @@ adb_dialog.no_devices=Não foi possível encontrar nenhum dispositivo para inici
adb_dialog.restart_while_debugging_title=Reiniciar enquanto depura adb_dialog.restart_while_debugging_title=Reiniciar enquanto depura
adb_dialog.restart_while_debugging_msg=Você está depurando um aplicativo, você tem certeza que deseja reiniciar a sessão? adb_dialog.restart_while_debugging_msg=Você está depurando um aplicativo, você tem certeza que deseja reiniciar a sessão?
adb_dialog.starting_debugger=Iniciando depurador... adb_dialog.starting_debugger=Iniciando depurador...
#action.variant=
#action_category.menu_toolbar=
#action_category.code_area=
#action_category.plugin_script=

View File

@ -23,6 +23,7 @@ menu.deobfuscation=Деобфускация
menu.log=Просмотр логов menu.log=Просмотр логов
menu.help=Помощь menu.help=Помощь
menu.about=О программе menu.about=О программе
#menu.quark=
menu.update_label=Версия %s уже доступна! menu.update_label=Версия %s уже доступна!
file.open_action=Открыть файлы... file.open_action=Открыть файлы...
@ -161,6 +162,8 @@ about_dialog.title=О программе JADX
preferences.title=Параметры preferences.title=Параметры
preferences.deobfuscation=Деобфускация preferences.deobfuscation=Деобфускация
preferences.appearance=Внешний вид preferences.appearance=Внешний вид
#preferences.shortcuts=
#preferences.select_shortcuts=
preferences.decompile=Декомпиляция preferences.decompile=Декомпиляция
preferences.plugins=Плагины preferences.plugins=Плагины
preferences.project=Проект preferences.project=Проект
@ -257,6 +260,9 @@ msg.open_file=Пожалуйста, откройте файл
msg.saving_sources=Сохранение ресурсов msg.saving_sources=Сохранение ресурсов
msg.language_changed_title=Язык изменен msg.language_changed_title=Язык изменен
msg.language_changed=Новый язык применится при следующем запуске программы msg.language_changed=Новый язык применится при следующем запуске программы
#msg.warning_title=
#msg.common_mouse_shortcut=
#msg.duplicate_shortcut=
msg.project_error_title=Ошибка msg.project_error_title=Ошибка
msg.project_error=Проект не может быть загружен msg.project_error=Проект не может быть загружен
msg.cmd_select_class_error=Ошибка выбора класса\n%s\nЭтот класс не существует. msg.cmd_select_class_error=Ошибка выбора класса\n%s\nЭтот класс не существует.
@ -290,6 +296,7 @@ popup.search_global=Глобальный поиск "%s"
#script.run=Run #script.run=Run
#script.save=Save #script.save=Save
#script.auto_complete=
#script.check=Check #script.check=Check
#script.format=Reformat #script.format=Reformat
#script.log=Show log #script.log=Show log
@ -405,3 +412,8 @@ adb_dialog.no_devices=Нет устройств для запуска прило
adb_dialog.restart_while_debugging_title=Перезапустить отладчик adb_dialog.restart_while_debugging_title=Перезапустить отладчик
adb_dialog.restart_while_debugging_msg=Запущен процесс отдадки приложения. Вы действительно хотите перезапустить сессию? adb_dialog.restart_while_debugging_msg=Запущен процесс отдадки приложения. Вы действительно хотите перезапустить сессию?
adb_dialog.starting_debugger=Запуск отладки... adb_dialog.starting_debugger=Запуск отладки...
#action.variant=
#action_category.menu_toolbar=
#action_category.code_area=
#action_category.plugin_script=

View File

@ -23,6 +23,7 @@ menu.deobfuscation=反混淆
menu.log=日志查看器 menu.log=日志查看器
menu.help=帮助 menu.help=帮助
menu.about=关于 menu.about=关于
#menu.quark=
menu.update_label=发现新版本 %s menu.update_label=发现新版本 %s
file.open_action=打开文件… file.open_action=打开文件…
@ -161,6 +162,8 @@ about_dialog.title=关于 JADX
preferences.title=首选项 preferences.title=首选项
preferences.deobfuscation=反混淆 preferences.deobfuscation=反混淆
preferences.appearance=界面 preferences.appearance=界面
#preferences.shortcuts=
#preferences.select_shortcuts=
preferences.decompile=反编译 preferences.decompile=反编译
preferences.plugins=插件 preferences.plugins=插件
preferences.project=项目 preferences.project=项目
@ -257,6 +260,9 @@ msg.open_file=请打开文件
msg.saving_sources=正在导出源代码 msg.saving_sources=正在导出源代码
msg.language_changed_title=语言已更改 msg.language_changed_title=语言已更改
msg.language_changed=新的语言将在下次应用程序启动时显示。 msg.language_changed=新的语言将在下次应用程序启动时显示。
#msg.warning_title=
#msg.common_mouse_shortcut=
#msg.duplicate_shortcut=
msg.project_error_title=错误 msg.project_error_title=错误
msg.project_error=项目无法加载 msg.project_error=项目无法加载
msg.cmd_select_class_error=无法选择类\n%s\n该类不存在。 msg.cmd_select_class_error=无法选择类\n%s\n该类不存在。
@ -290,6 +296,7 @@ popup.json_prettify=JSON 格式化
script.run=运行 script.run=运行
script.save=保存 script.save=保存
#script.auto_complete=
script.check=检查 script.check=检查
script.format=重新格式化 script.format=重新格式化
script.log=显示日志 script.log=显示日志
@ -405,3 +412,8 @@ adb_dialog.no_devices=找不到任何设备用来启动APP。
adb_dialog.restart_while_debugging_title=调试时重新启动 adb_dialog.restart_while_debugging_title=调试时重新启动
adb_dialog.restart_while_debugging_msg=你正在调试一个APP确定要重新启动一个会话吗 adb_dialog.restart_while_debugging_msg=你正在调试一个APP确定要重新启动一个会话吗
adb_dialog.starting_debugger=正在启动调试器… adb_dialog.starting_debugger=正在启动调试器…
#action.variant=
#action_category.menu_toolbar=
#action_category.code_area=
#action_category.plugin_script=

View File

@ -23,6 +23,7 @@ menu.deobfuscation=去模糊化
menu.log=記錄檔檢視器 menu.log=記錄檔檢視器
menu.help=幫助 menu.help=幫助
menu.about=關於 menu.about=關於
#menu.quark=
menu.update_label=新版本 %s 可供下載! menu.update_label=新版本 %s 可供下載!
file.open_action=開啟檔案... file.open_action=開啟檔案...
@ -161,6 +162,8 @@ about_dialog.title=關於 JADX
preferences.title=選項 preferences.title=選項
preferences.deobfuscation=去模糊化 preferences.deobfuscation=去模糊化
preferences.appearance=外觀 preferences.appearance=外觀
#preferences.shortcuts=
#preferences.select_shortcuts=
preferences.decompile=反編譯 preferences.decompile=反編譯
preferences.plugins=外掛程式 preferences.plugins=外掛程式
preferences.project=專案 preferences.project=專案
@ -257,6 +260,9 @@ msg.open_file=請開啟檔案
msg.saving_sources=正在儲存原始碼 msg.saving_sources=正在儲存原始碼
msg.language_changed_title=已更改語言 msg.language_changed_title=已更改語言
msg.language_changed=新語言將於下次應用程式啟動時套用。 msg.language_changed=新語言將於下次應用程式啟動時套用。
#msg.warning_title=
#msg.common_mouse_shortcut=
#msg.duplicate_shortcut=
msg.project_error_title=錯誤 msg.project_error_title=錯誤
msg.project_error=無法載入專案 msg.project_error=無法載入專案
msg.cmd_select_class_error=無法選擇類別\n%s\n類別不存在。 msg.cmd_select_class_error=無法選擇類別\n%s\n類別不存在。
@ -290,6 +296,7 @@ popup.json_prettify=JSON 格式化
script.run=執行 script.run=執行
script.save=儲存 script.save=儲存
#script.auto_complete=
script.check=檢查 script.check=檢查
script.format=重新格式化 script.format=重新格式化
script.log=顯示記錄檔 script.log=顯示記錄檔
@ -405,3 +412,8 @@ adb_dialog.no_devices=無法找到任何可以啟動應用程式的裝置。
adb_dialog.restart_while_debugging_title=偵錯時重新啟動 adb_dialog.restart_while_debugging_title=偵錯時重新啟動
adb_dialog.restart_while_debugging_msg=您正在為應用程式偵錯,您確定要重新啟動工作階段嗎? adb_dialog.restart_while_debugging_msg=您正在為應用程式偵錯,您確定要重新啟動工作階段嗎?
adb_dialog.starting_debugger=正在啟動偵錯工具... adb_dialog.starting_debugger=正在啟動偵錯工具...
#action.variant=
#action_category.menu_toolbar=
#action_category.code_area=
#action_category.plugin_script=