mirror of
https://github.com/skylot/jadx.git
synced 2024-11-23 21:00:06 +00:00
* 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:
parent
868e99e0b5
commit
68b84ea786
@ -34,12 +34,14 @@ public class CodePopupAction {
|
||||
|
||||
public NodeAction(CodePopupAction data, CodeArea codeArea) {
|
||||
super(data.name, codeArea);
|
||||
setName(data.name);
|
||||
setShortcutComponent(codeArea);
|
||||
if (data.keyBinding != null) {
|
||||
KeyStroke key = KeyStroke.getKeyStroke(data.keyBinding);
|
||||
if (key == null) {
|
||||
throw new IllegalArgumentException("Failed to parse key stroke: " + data.keyBinding);
|
||||
}
|
||||
addKeyBinding(key, data.name);
|
||||
setKeyBinding(key);
|
||||
}
|
||||
this.data = data;
|
||||
}
|
||||
|
@ -1,23 +1,21 @@
|
||||
package jadx.gui.plugins.script;
|
||||
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
import org.fife.ui.autocomplete.AutoCompletion;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.treemodel.JInputScript;
|
||||
import jadx.gui.ui.action.JadxAutoCompletion;
|
||||
import jadx.gui.ui.codearea.AbstractCodeArea;
|
||||
import jadx.gui.ui.panel.ContentPanel;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.shortcut.ShortcutsController;
|
||||
|
||||
public class ScriptCodeArea extends AbstractCodeArea {
|
||||
|
||||
private final JInputScript scriptNode;
|
||||
private final AutoCompletion autoCompletion;
|
||||
private final ShortcutsController shortcutsController;
|
||||
|
||||
public ScriptCodeArea(ContentPanel contentPanel, JInputScript node) {
|
||||
super(contentPanel, node);
|
||||
@ -27,6 +25,7 @@ public class ScriptCodeArea extends AbstractCodeArea {
|
||||
setCodeFoldingEnabled(true);
|
||||
setCloseCurlyBraces(true);
|
||||
|
||||
shortcutsController = contentPanel.getTabbedPane().getMainWindow().getShortcutsController();
|
||||
JadxSettings settings = contentPanel.getTabbedPane().getMainWindow().getSettings();
|
||||
autoCompletion = addAutoComplete(settings);
|
||||
}
|
||||
@ -34,12 +33,12 @@ public class ScriptCodeArea extends AbstractCodeArea {
|
||||
private AutoCompletion addAutoComplete(JadxSettings settings) {
|
||||
ScriptCompleteProvider provider = new ScriptCompleteProvider(this);
|
||||
provider.setAutoActivationRules(false, ".");
|
||||
AutoCompletion ac = new AutoCompletion(provider);
|
||||
JadxAutoCompletion ac = new JadxAutoCompletion(provider);
|
||||
ac.setListCellRenderer(new ScriptCompletionRenderer(settings));
|
||||
ac.setTriggerKey(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, UiUtils.ctrlButton()));
|
||||
ac.setAutoActivationEnabled(true);
|
||||
ac.setAutoCompleteSingleChoices(true);
|
||||
ac.install(this);
|
||||
shortcutsController.bindImmediate(ac);
|
||||
return ac;
|
||||
}
|
||||
|
||||
@ -80,6 +79,7 @@ public class ScriptCodeArea extends AbstractCodeArea {
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
shortcutsController.unbindActionsForComponent(this);
|
||||
autoCompletion.uninstall();
|
||||
super.dispose();
|
||||
}
|
||||
|
@ -29,13 +29,14 @@ import jadx.gui.settings.LineNumbersMode;
|
||||
import jadx.gui.treemodel.JInputScript;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.TabbedPane;
|
||||
import jadx.gui.ui.action.ActionModel;
|
||||
import jadx.gui.ui.action.JadxGuiAction;
|
||||
import jadx.gui.ui.codearea.AbstractCodeArea;
|
||||
import jadx.gui.ui.codearea.AbstractCodeContentPanel;
|
||||
import jadx.gui.ui.codearea.SearchBar;
|
||||
import jadx.gui.utils.Icons;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.ui.ActionHandler;
|
||||
import jadx.gui.utils.ui.NodeLabel;
|
||||
import jadx.plugins.script.ide.ScriptAnalyzeResult;
|
||||
import jadx.plugins.script.ide.ScriptServices;
|
||||
@ -89,15 +90,14 @@ public class ScriptContentPanel extends AbstractCodeContentPanel {
|
||||
}
|
||||
|
||||
private JPanel buildScriptActionsPanel() {
|
||||
ActionHandler runAction = new ActionHandler(this::runScript);
|
||||
runAction.setNameAndDesc(NLS.str("script.run"));
|
||||
runAction.setIcon(Icons.RUN);
|
||||
runAction.attachKeyBindingFor(scriptArea, KeyStroke.getKeyStroke(KeyEvent.VK_F8, 0));
|
||||
JadxGuiAction runAction = new JadxGuiAction(ActionModel.SCRIPT_RUN, this::runScript);
|
||||
JadxGuiAction saveAction = new JadxGuiAction(ActionModel.SCRIPT_SAVE, scriptArea::save);
|
||||
|
||||
ActionHandler saveAction = new ActionHandler(scriptArea::save);
|
||||
saveAction.setNameAndDesc(NLS.str("script.save"));
|
||||
saveAction.setIcon(Icons.SAVE_ALL);
|
||||
saveAction.setKeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_S, UiUtils.ctrlButton()));
|
||||
runAction.setShortcutComponent(scriptArea);
|
||||
saveAction.setShortcutComponent(scriptArea);
|
||||
|
||||
tabbedPane.getMainWindow().getShortcutsController().bindImmediate(runAction);
|
||||
tabbedPane.getMainWindow().getShortcutsController().bindImmediate(saveAction);
|
||||
|
||||
JButton save = saveAction.makeButton();
|
||||
scriptArea.getScriptNode().addChangeListener(save::setEnabled);
|
||||
|
@ -37,12 +37,15 @@ import jadx.cli.JadxCLIArgs;
|
||||
import jadx.cli.LogHelper;
|
||||
import jadx.gui.cache.code.CodeCacheMode;
|
||||
import jadx.gui.cache.usage.UsageCacheMode;
|
||||
import jadx.gui.settings.data.ShortcutsWrapper;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.action.ActionModel;
|
||||
import jadx.gui.ui.codearea.EditorTheme;
|
||||
import jadx.gui.utils.FontUtils;
|
||||
import jadx.gui.utils.LafManager;
|
||||
import jadx.gui.utils.LangLocale;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.shortcut.Shortcut;
|
||||
|
||||
public class JadxSettings extends JadxCLIArgs {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxSettings.class);
|
||||
@ -73,6 +76,10 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
private boolean autoStartJobs = false;
|
||||
private String excludedPackages = "";
|
||||
private boolean autoSaveProject = true;
|
||||
private Map<ActionModel, Shortcut> shortcuts = new HashMap<>();
|
||||
|
||||
@JadxSettingsAdapter.GsonExclude
|
||||
private ShortcutsWrapper shortcutsWrapper = null;
|
||||
|
||||
private boolean showHeapUsageBar = false;
|
||||
private boolean alwaysSelectOpened = false;
|
||||
@ -442,6 +449,14 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
this.autoSaveProject = autoSaveProject;
|
||||
}
|
||||
|
||||
public ShortcutsWrapper getShortcuts() {
|
||||
if (shortcutsWrapper == null) {
|
||||
shortcutsWrapper = new ShortcutsWrapper();
|
||||
shortcutsWrapper.updateShortcuts(shortcuts);
|
||||
}
|
||||
return shortcutsWrapper;
|
||||
}
|
||||
|
||||
public void setExportAsGradleProject(boolean exportAsGradleProject) {
|
||||
this.exportAsGradleProject = exportAsGradleProject;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -62,6 +62,7 @@ import jadx.gui.settings.JadxSettingsAdapter;
|
||||
import jadx.gui.settings.LineNumbersMode;
|
||||
import jadx.gui.settings.ui.cache.CacheSettingsGroup;
|
||||
import jadx.gui.settings.ui.plugins.PluginsSettings;
|
||||
import jadx.gui.settings.ui.shortcut.ShortcutsSettingsGroup;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.codearea.EditorTheme;
|
||||
import jadx.gui.utils.FontUtils;
|
||||
@ -126,6 +127,7 @@ public class JadxSettingsWindow extends JDialog {
|
||||
groups.add(makeRenameGroup());
|
||||
groups.add(new CacheSettingsGroup(this));
|
||||
groups.add(makeAppearanceGroup());
|
||||
groups.add(new ShortcutsSettingsGroup(this, settings));
|
||||
groups.add(makeSearchResGroup());
|
||||
groups.add(makeProjectGroup());
|
||||
groups.add(new PluginsSettings(mainWindow, settings).build());
|
||||
@ -640,6 +642,7 @@ public class JadxSettingsWindow extends JDialog {
|
||||
enableComponents(this, false);
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
if (shouldReload()) {
|
||||
mainWindow.getShortcutsController().loadSettings();
|
||||
mainWindow.reopen();
|
||||
}
|
||||
if (!settings.getLangLocale().equals(prevLang)) {
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
package jadx.gui.ui;
|
||||
|
||||
import java.awt.AWTEvent;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
@ -9,14 +8,12 @@ import java.awt.Font;
|
||||
import java.awt.GraphicsDevice;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.dnd.DnDConstants;
|
||||
import java.awt.dnd.DropTarget;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.FocusAdapter;
|
||||
import java.awt.event.FocusEvent;
|
||||
import java.awt.event.InputEvent;
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
@ -45,10 +42,10 @@ import javax.swing.Action;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JCheckBoxMenuItem;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JMenu;
|
||||
import javax.swing.JMenuBar;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JPopupMenu;
|
||||
@ -118,6 +115,8 @@ import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.treemodel.JPackage;
|
||||
import jadx.gui.treemodel.JResource;
|
||||
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.AbstractCodeContentPanel;
|
||||
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.filedialog.FileDialogWrapper;
|
||||
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.IssuesPanel;
|
||||
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.FontUtils;
|
||||
import jadx.gui.utils.ILoadListener;
|
||||
import jadx.gui.utils.Icons;
|
||||
import jadx.gui.utils.LafManager;
|
||||
import jadx.gui.utils.Link;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.SystemInfo;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.fileswatcher.LiveReloadWorker;
|
||||
import jadx.gui.utils.shortcut.ShortcutsController;
|
||||
import jadx.gui.utils.ui.ActionHandler;
|
||||
import jadx.gui.utils.ui.NodeLabel;
|
||||
|
||||
import static io.reactivex.internal.functions.Functions.EMPTY_RUNNABLE;
|
||||
import static javax.swing.KeyStroke.getKeyStroke;
|
||||
|
||||
public class MainWindow extends JFrame {
|
||||
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 Action newProjectAction;
|
||||
private transient Action saveProjectAction;
|
||||
private transient JadxGuiAction newProjectAction;
|
||||
private transient JadxGuiAction saveProjectAction;
|
||||
|
||||
private transient JPanel mainPanel;
|
||||
private transient JSplitPane treeSplitPane;
|
||||
@ -227,7 +227,10 @@ public class MainWindow extends JFrame {
|
||||
private final List<ILoadListener> loadListeners = new ArrayList<>();
|
||||
private final List<Consumer<JRoot>> treeUpdateListener = new ArrayList<>();
|
||||
private boolean loaded;
|
||||
private boolean settingsOpen = false;
|
||||
|
||||
private ShortcutsController shortcutsController;
|
||||
private JadxMenuBar menuBar;
|
||||
private JMenu pluginsMenu;
|
||||
|
||||
private final transient RenameMappingsGui renameMappings;
|
||||
@ -240,6 +243,7 @@ public class MainWindow extends JFrame {
|
||||
this.liveReloadWorker = new LiveReloadWorker(this);
|
||||
this.renameMappings = new RenameMappingsGui(this);
|
||||
this.cacheManager = new CacheManager(settings);
|
||||
this.shortcutsController = new ShortcutsController(settings);
|
||||
|
||||
resetCache();
|
||||
FontUtils.registerBundledFonts();
|
||||
@ -247,8 +251,8 @@ public class MainWindow extends JFrame {
|
||||
initUI();
|
||||
this.backgroundExecutor = new BackgroundExecutor(settings, progressPane);
|
||||
initMenuAndToolbar();
|
||||
registerMouseNavigationButtons();
|
||||
UiUtils.setWindowIcons(this);
|
||||
shortcutsController.registerMouseEventListener(this);
|
||||
loadSettings();
|
||||
|
||||
update();
|
||||
@ -472,6 +476,8 @@ public class MainWindow extends JFrame {
|
||||
saveAll();
|
||||
closeAll();
|
||||
loadFiles(EMPTY_RUNNABLE);
|
||||
|
||||
menuBar.reloadShortcuts();
|
||||
}
|
||||
|
||||
private void openProject(Path path, Runnable onFinish) {
|
||||
@ -865,99 +871,75 @@ public class MainWindow extends JFrame {
|
||||
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() {
|
||||
ActionHandler openAction = new ActionHandler(this::openFileDialog);
|
||||
openAction.setNameAndDesc(NLS.str("file.open_action"));
|
||||
openAction.setIcon(Icons.OPEN);
|
||||
openAction.setKeyBinding(getKeyStroke(KeyEvent.VK_O, UiUtils.ctrlButton()));
|
||||
JadxGuiAction openAction = new JadxGuiAction(ActionModel.OPEN, this::openFileDialog);
|
||||
JadxGuiAction openProject = new JadxGuiAction(ActionModel.OPEN_PROJECT, this::openProjectDialog);
|
||||
|
||||
ActionHandler openProject = new ActionHandler(this::openProjectDialog);
|
||||
openProject.setNameAndDesc(NLS.str("file.open_project"));
|
||||
openProject.setIcon(Icons.OPEN_PROJECT);
|
||||
openProject.setKeyBinding(getKeyStroke(KeyEvent.VK_O, InputEvent.SHIFT_DOWN_MASK | UiUtils.ctrlButton()));
|
||||
JadxGuiAction addFilesAction = new JadxGuiAction(ActionModel.ADD_FILES, () -> addFiles());
|
||||
newProjectAction = new JadxGuiAction(ActionModel.NEW_PROJECT, this::newProject);
|
||||
saveProjectAction = new JadxGuiAction(ActionModel.SAVE_PROJECT, this::saveProject);
|
||||
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) {
|
||||
@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 = new JCheckBoxMenuItem(liveReloadAction);
|
||||
liveReloadMenuItem.setState(project.isEnableLiveReload());
|
||||
|
||||
Action saveAllAction = new AbstractAction(NLS.str("file.save_all"), Icons.SAVE_ALL) {
|
||||
@Override
|
||||
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()));
|
||||
JadxGuiAction saveAllAction = new JadxGuiAction(ActionModel.SAVE_ALL, () -> saveAll(false));
|
||||
JadxGuiAction exportAction = new JadxGuiAction(ActionModel.EXPORT, () -> saveAll(true));
|
||||
|
||||
Action exportAction = new AbstractAction(NLS.str("file.export_gradle"), ICON_EXPORT) {
|
||||
@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"));
|
||||
JMenu recentProjects = new JadxMenu(NLS.str("menu.recent_projects"), shortcutsController);
|
||||
recentProjects.addMenuListener(new RecentProjectsMenuListener(this, recentProjects));
|
||||
|
||||
Action prefsAction = new AbstractAction(NLS.str("menu.preferences"), ICON_PREF) {
|
||||
@Override
|
||||
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();
|
||||
}
|
||||
};
|
||||
JadxGuiAction prefsAction = new JadxGuiAction(ActionModel.PREFS, this::openSettings);
|
||||
JadxGuiAction exitAction = new JadxGuiAction(ActionModel.EXIT, this::closeWindow);
|
||||
|
||||
isFlattenPackage = settings.isFlattenPackage();
|
||||
flatPkgMenuItem = new JCheckBoxMenuItem(NLS.str("menu.flatten"), ICON_FLAT_PKG);
|
||||
@ -983,109 +965,17 @@ public class MainWindow extends JFrame {
|
||||
dockLog.setState(settings.isDockLogViewer());
|
||||
dockLog.addActionListener(event -> settings.setDockLogViewer(!settings.isDockLogViewer()));
|
||||
|
||||
Action syncAction = new AbstractAction(NLS.str("menu.sync"), ICON_SYNC) {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
syncWithEditor();
|
||||
}
|
||||
};
|
||||
syncAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("menu.sync"));
|
||||
syncAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_T, UiUtils.ctrlButton()));
|
||||
|
||||
Action textSearchAction = new AbstractAction(NLS.str("menu.text_search"), ICON_SEARCH) {
|
||||
@Override
|
||||
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));
|
||||
JadxGuiAction syncAction = new JadxGuiAction(ActionModel.SYNC, this::syncWithEditor);
|
||||
JadxGuiAction textSearchAction = new JadxGuiAction(ActionModel.TEXT_SEARCH, this::textSearch);
|
||||
JadxGuiAction clsSearchAction = new JadxGuiAction(ActionModel.CLASS_SEARCH,
|
||||
() -> SearchDialog.search(MainWindow.this, SearchDialog.SearchPreset.CLASS));
|
||||
JadxGuiAction commentSearchAction = new JadxGuiAction(ActionModel.COMMENT_SEARCH,
|
||||
() -> SearchDialog.search(MainWindow.this, SearchDialog.SearchPreset.COMMENT));
|
||||
JadxGuiAction gotoMainActivityAction = new JadxGuiAction(ActionModel.GOTO_MAIN_ACTIVITY,
|
||||
this::gotoMainActivity);
|
||||
JadxGuiAction decompileAllAction = new JadxGuiAction(ActionModel.DECOMPILE_ALL, this::requestFullDecompilation);
|
||||
JadxGuiAction resetCacheAction = new JadxGuiAction(ActionModel.RESET_CACHE, this::resetCodeCache);
|
||||
JadxGuiAction deobfAction = new JadxGuiAction(ActionModel.DEOBF, this::toggleDeobfuscation);
|
||||
|
||||
deobfToggleBtn = new JToggleButton(deobfAction);
|
||||
deobfToggleBtn.setSelected(settings.isDeobfuscationOn());
|
||||
@ -1094,54 +984,19 @@ public class MainWindow extends JFrame {
|
||||
deobfMenuItem = new JCheckBoxMenuItem(deobfAction);
|
||||
deobfMenuItem.setState(settings.isDeobfuscationOn());
|
||||
|
||||
ActionHandler showLog = new ActionHandler(ev -> showLogViewer(LogOptions.current()));
|
||||
showLog.setNameAndDesc(NLS.str("menu.log"));
|
||||
showLog.setIcon(ICON_LOG);
|
||||
showLog.setKeyBinding(getKeyStroke(KeyEvent.VK_L, UiUtils.ctrlButton() | KeyEvent.SHIFT_DOWN_MASK));
|
||||
JadxGuiAction showLogAction = new JadxGuiAction(ActionModel.SHOW_LOG,
|
||||
() -> showLogViewer(LogOptions.current()));
|
||||
JadxGuiAction aboutAction = new JadxGuiAction(ActionModel.ABOUT, () -> new AboutDialog().setVisible(true));
|
||||
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) {
|
||||
@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"));
|
||||
JMenu file = new JadxMenu(NLS.str("menu.file"), shortcutsController);
|
||||
file.setMnemonic(KeyEvent.VK_F);
|
||||
file.add(openAction);
|
||||
file.add(openProject);
|
||||
@ -1151,7 +1006,7 @@ public class MainWindow extends JFrame {
|
||||
file.add(saveProjectAction);
|
||||
file.add(saveProjectAsAction);
|
||||
file.addSeparator();
|
||||
file.add(reload);
|
||||
file.add(reloadAction);
|
||||
file.add(liveReloadMenuItem);
|
||||
renameMappings.addMenuActions(file);
|
||||
file.addSeparator();
|
||||
@ -1164,7 +1019,7 @@ public class MainWindow extends JFrame {
|
||||
file.addSeparator();
|
||||
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.add(flatPkgMenuItem);
|
||||
view.add(syncAction);
|
||||
@ -1172,7 +1027,7 @@ public class MainWindow extends JFrame {
|
||||
view.add(alwaysSelectOpened);
|
||||
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.add(textSearchAction);
|
||||
nav.add(clsSearchAction);
|
||||
@ -1182,11 +1037,11 @@ public class MainWindow extends JFrame {
|
||||
nav.add(backAction);
|
||||
nav.add(forwardAction);
|
||||
|
||||
pluginsMenu = new JMenu(NLS.str("menu.plugins"));
|
||||
pluginsMenu = new JadxMenu(NLS.str("menu.plugins"), shortcutsController);
|
||||
pluginsMenu.setMnemonic(KeyEvent.VK_P);
|
||||
resetPluginsMenu();
|
||||
|
||||
JMenu tools = new JMenu(NLS.str("menu.tools"));
|
||||
JMenu tools = new JadxMenu(NLS.str("menu.tools"), shortcutsController);
|
||||
tools.setMnemonic(KeyEvent.VK_T);
|
||||
tools.add(decompileAllAction);
|
||||
tools.add(resetCacheAction);
|
||||
@ -1194,9 +1049,9 @@ public class MainWindow extends JFrame {
|
||||
tools.add(quarkAction);
|
||||
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.add(showLog);
|
||||
help.add(showLogAction);
|
||||
if (Jadx.isDevVersion()) {
|
||||
help.add(new AbstractAction("Show sample error report") {
|
||||
@Override
|
||||
@ -1207,7 +1062,7 @@ public class MainWindow extends JFrame {
|
||||
}
|
||||
help.add(aboutAction);
|
||||
|
||||
JMenuBar menuBar = new JMenuBar();
|
||||
menuBar = new JadxMenuBar();
|
||||
menuBar.add(file);
|
||||
menuBar.add(view);
|
||||
menuBar.add(nav);
|
||||
@ -1231,7 +1086,7 @@ public class MainWindow extends JFrame {
|
||||
toolbar.add(openAction);
|
||||
toolbar.add(addFilesAction);
|
||||
toolbar.addSeparator();
|
||||
toolbar.add(reload);
|
||||
toolbar.add(reloadAction);
|
||||
toolbar.addSeparator();
|
||||
toolbar.add(saveAllAction);
|
||||
toolbar.add(exportAction);
|
||||
@ -1251,7 +1106,7 @@ public class MainWindow extends JFrame {
|
||||
toolbar.add(quarkAction);
|
||||
toolbar.add(openDeviceAction);
|
||||
toolbar.addSeparator();
|
||||
toolbar.add(showLog);
|
||||
toolbar.add(showLogAction);
|
||||
toolbar.addSeparator();
|
||||
toolbar.add(prefsAction);
|
||||
toolbar.addSeparator();
|
||||
@ -1260,18 +1115,26 @@ public class MainWindow extends JFrame {
|
||||
|
||||
mainPanel.add(toolbar, BorderLayout.NORTH);
|
||||
|
||||
nav.add(new HiddenMenuItem(backVariantAction));
|
||||
nav.add(new HiddenMenuItem(forwardVariantAction));
|
||||
|
||||
shortcutsController.bind(backVariantAction);
|
||||
shortcutsController.bind(forwardVariantAction);
|
||||
|
||||
addLoadListener(loaded -> {
|
||||
textSearchAction.setEnabled(loaded);
|
||||
clsSearchAction.setEnabled(loaded);
|
||||
commentSearchAction.setEnabled(loaded);
|
||||
gotoMainActivityAction.setEnabled(loaded);
|
||||
backAction.setEnabled(loaded);
|
||||
backVariantAction.setEnabled(loaded);
|
||||
forwardAction.setEnabled(loaded);
|
||||
forwardVariantAction.setEnabled(loaded);
|
||||
syncAction.setEnabled(loaded);
|
||||
saveAllAction.setEnabled(loaded);
|
||||
exportAction.setEnabled(loaded);
|
||||
saveProjectAsAction.setEnabled(loaded);
|
||||
reload.setEnabled(loaded);
|
||||
reloadAction.setEnabled(loaded);
|
||||
decompileAllAction.setEnabled(loaded);
|
||||
deobfAction.setEnabled(loaded);
|
||||
quarkAction.setEnabled(loaded);
|
||||
@ -1402,42 +1265,6 @@ public class MainWindow extends JFrame {
|
||||
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) {
|
||||
List<String> pathList = new ArrayList<>();
|
||||
while (path != null) {
|
||||
@ -1515,6 +1342,23 @@ public class MainWindow extends JFrame {
|
||||
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() {
|
||||
// queue update to not interrupt current UI tasks
|
||||
UiUtils.uiRun(this::updateUiSettings);
|
||||
@ -1535,6 +1379,8 @@ public class MainWindow extends JFrame {
|
||||
if (logPanel != null) {
|
||||
logPanel.loadSettings();
|
||||
}
|
||||
|
||||
shortcutsController.loadSettings();
|
||||
}
|
||||
|
||||
private void closeWindow() {
|
||||
@ -1638,6 +1484,10 @@ public class MainWindow extends JFrame {
|
||||
return debuggerPanel;
|
||||
}
|
||||
|
||||
public ShortcutsController getShortcutsController() {
|
||||
return shortcutsController;
|
||||
}
|
||||
|
||||
public void showDebuggerPanel() {
|
||||
initDebuggerPanel();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
154
jadx-gui/src/main/java/jadx/gui/ui/action/ActionModel.java
Normal file
154
jadx-gui/src/main/java/jadx/gui/ui/action/ActionModel.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
141
jadx-gui/src/main/java/jadx/gui/ui/action/JadxGuiAction.java
Normal file
141
jadx-gui/src/main/java/jadx/gui/ui/action/JadxGuiAction.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -31,6 +31,7 @@ import jadx.gui.utils.DefaultPopupMenuListener;
|
||||
import jadx.gui.utils.JNodeCache;
|
||||
import jadx.gui.utils.JumpPosition;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.shortcut.ShortcutsController;
|
||||
|
||||
/**
|
||||
* 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 @Nullable ICodeInfo cachedCodeInfo;
|
||||
private final ShortcutsController shortcutsController;
|
||||
|
||||
CodeArea(ContentPanel contentPanel, JNode node) {
|
||||
super(contentPanel, node);
|
||||
this.shortcutsController = getMainWindow().getShortcutsController();
|
||||
|
||||
setSyntaxEditingStyle(node.getSyntaxName());
|
||||
boolean isJavaCode = node instanceof JClass;
|
||||
if (isJavaCode) {
|
||||
@ -115,7 +119,8 @@ public final class CodeArea extends AbstractCodeArea {
|
||||
}
|
||||
|
||||
private void addMenuItems() {
|
||||
JNodePopupBuilder popup = new JNodePopupBuilder(this, getPopupMenu());
|
||||
ShortcutsController shortcutsController = getMainWindow().getShortcutsController();
|
||||
JNodePopupBuilder popup = new JNodePopupBuilder(this, getPopupMenu(), shortcutsController);
|
||||
popup.addSeparator();
|
||||
popup.add(new FindUsageAction(this));
|
||||
popup.add(new GoToDeclarationAction(this));
|
||||
@ -143,7 +148,8 @@ public final class CodeArea extends AbstractCodeArea {
|
||||
}
|
||||
|
||||
private void addMenuForJsonFile() {
|
||||
JNodePopupBuilder popup = new JNodePopupBuilder(this, getPopupMenu());
|
||||
ShortcutsController shortcutsController = getMainWindow().getShortcutsController();
|
||||
JNodePopupBuilder popup = new JNodePopupBuilder(this, getPopupMenu(), shortcutsController);
|
||||
popup.addSeparator();
|
||||
popup.add(new JsonPrettifyAction(this));
|
||||
}
|
||||
@ -339,6 +345,8 @@ public final class CodeArea extends AbstractCodeArea {
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
shortcutsController.unbindActionsForComponent(this);
|
||||
|
||||
super.dispose();
|
||||
cachedCodeInfo = null;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -1,9 +1,7 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.event.PopupMenuEvent;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@ -25,30 +23,24 @@ import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||
import jadx.api.metadata.annotations.NodeDeclareRef;
|
||||
import jadx.gui.JadxWrapper;
|
||||
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.utils.DefaultPopupMenuListener;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
import static javax.swing.KeyStroke.getKeyStroke;
|
||||
|
||||
public class CommentAction extends AbstractAction implements DefaultPopupMenuListener {
|
||||
public class CommentAction extends CodeAreaAction implements DefaultPopupMenuListener {
|
||||
private static final long serialVersionUID = 4753838562204629112L;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CommentAction.class);
|
||||
private final CodeArea codeArea;
|
||||
private final boolean enabled;
|
||||
|
||||
private ICodeComment actionComment;
|
||||
|
||||
public CommentAction(CodeArea codeArea) {
|
||||
super(NLS.str("popup.add_comment") + " (;)");
|
||||
this.codeArea = codeArea;
|
||||
super(ActionModel.CODE_COMMENT, codeArea);
|
||||
this.enabled = codeArea.getNode() instanceof JClass;
|
||||
if (enabled) {
|
||||
UiUtils.addKeyBinding(codeArea, getKeyStroke(KeyEvent.VK_SEMICOLON, 0), "popup.add_comment",
|
||||
() -> showCommentDialog(getCommentRef(codeArea.getCaretPosition())));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -64,7 +56,15 @@ public class CommentAction extends AbstractAction implements DefaultPopupMenuLis
|
||||
|
||||
@Override
|
||||
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) {
|
||||
|
@ -1,28 +1,15 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
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.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
import static javax.swing.KeyStroke.getKeyStroke;
|
||||
|
||||
public class CommentSearchAction extends AbstractAction {
|
||||
public class CommentSearchAction extends CodeAreaAction {
|
||||
private static final long serialVersionUID = -3646341661734961590L;
|
||||
|
||||
private final CodeArea codeArea;
|
||||
|
||||
public CommentSearchAction(CodeArea codeArea) {
|
||||
super(NLS.str("popup.search_comment") + " (Ctrl + ;)");
|
||||
this.codeArea = codeArea;
|
||||
|
||||
KeyStroke key = getKeyStroke(KeyEvent.VK_SEMICOLON, UiUtils.ctrlButton());
|
||||
UiUtils.addKeyBinding(codeArea, key, "popup.search_comment", this::startSearch);
|
||||
super(ActionModel.CODE_COMMENT_SEARCH, codeArea);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,20 +1,15 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.action.ActionModel;
|
||||
import jadx.gui.ui.dialog.UsageDialog;
|
||||
import jadx.gui.utils.NLS;
|
||||
|
||||
import static javax.swing.KeyStroke.getKeyStroke;
|
||||
|
||||
public final class FindUsageAction extends JNodeAction {
|
||||
private static final long serialVersionUID = 4692546569977976384L;
|
||||
|
||||
public FindUsageAction(CodeArea codeArea) {
|
||||
super(NLS.str("popup.find_usage") + " (x)", codeArea);
|
||||
addKeyBinding(getKeyStroke(KeyEvent.VK_X, 0), "trigger usage");
|
||||
super(ActionModel.FIND_USAGE, codeArea);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,6 +1,5 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
@ -23,18 +22,16 @@ import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JField;
|
||||
import jadx.gui.treemodel.JMethod;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.ui.action.ActionModel;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
import static javax.swing.KeyStroke.getKeyStroke;
|
||||
|
||||
public final class FridaAction extends JNodeAction {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FridaAction.class);
|
||||
private static final long serialVersionUID = -3084073927621269039L;
|
||||
|
||||
public FridaAction(CodeArea codeArea) {
|
||||
super(NLS.str("popup.frida") + " (f)", codeArea);
|
||||
addKeyBinding(getKeyStroke(KeyEvent.VK_F, 0), "trigger frida");
|
||||
super(ActionModel.FRIDA_COPY, codeArea);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,18 +1,13 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.utils.NLS;
|
||||
|
||||
import static javax.swing.KeyStroke.getKeyStroke;
|
||||
import jadx.gui.ui.action.ActionModel;
|
||||
|
||||
public final class GoToDeclarationAction extends JNodeAction {
|
||||
private static final long serialVersionUID = -1186470538894941301L;
|
||||
|
||||
public GoToDeclarationAction(CodeArea codeArea) {
|
||||
super(NLS.str("popup.go_to_declaration") + " (d)", codeArea);
|
||||
addKeyBinding(getKeyStroke(KeyEvent.VK_D, 0), "trigger goto decl");
|
||||
super(ActionModel.GOTO_DECLARATION, codeArea);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -3,26 +3,26 @@ package jadx.gui.ui.codearea;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
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
|
||||
*/
|
||||
public abstract class JNodeAction extends AbstractAction {
|
||||
public abstract class JNodeAction extends CodeAreaAction {
|
||||
private static final long serialVersionUID = -2600154727884853550L;
|
||||
|
||||
private transient CodeArea codeArea;
|
||||
private transient @Nullable JNode node;
|
||||
|
||||
public JNodeAction(String name, CodeArea codeArea) {
|
||||
super(name);
|
||||
this.codeArea = codeArea;
|
||||
public JNodeAction(ActionModel actionModel, CodeArea codeArea) {
|
||||
super(actionModel, codeArea);
|
||||
}
|
||||
|
||||
public JNodeAction(String id, CodeArea codeArea) {
|
||||
super(id, codeArea);
|
||||
}
|
||||
|
||||
public abstract void runAction(JNode node);
|
||||
@ -31,21 +31,16 @@ public abstract class JNodeAction extends AbstractAction {
|
||||
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
|
||||
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) {
|
||||
@ -57,9 +52,10 @@ public abstract class JNodeAction extends AbstractAction {
|
||||
return codeArea;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
node = null;
|
||||
codeArea = null;
|
||||
for (PropertyChangeListener changeListener : getPropertyChangeListeners()) {
|
||||
removePropertyChangeListener(changeListener);
|
||||
}
|
||||
|
@ -1,14 +1,18 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import javax.swing.Action;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.event.PopupMenuListener;
|
||||
|
||||
import jadx.gui.ui.action.JadxGuiAction;
|
||||
import jadx.gui.utils.shortcut.ShortcutsController;
|
||||
|
||||
public class JNodePopupBuilder {
|
||||
private final JPopupMenu menu;
|
||||
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;
|
||||
popupListener = new JNodePopupListener(codeArea);
|
||||
popupMenu.addPopupMenuListener(popupListener);
|
||||
@ -19,11 +23,23 @@ public class JNodePopupBuilder {
|
||||
}
|
||||
|
||||
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);
|
||||
popupListener.addActions(nodeAction);
|
||||
}
|
||||
|
||||
public void add(Action action) {
|
||||
public void add(JadxGuiAction action) {
|
||||
if (action.getActionModel() != null) {
|
||||
shortcutsController.bindImmediate(action);
|
||||
}
|
||||
menu.add(action);
|
||||
if (action instanceof PopupMenuListener) {
|
||||
menu.addPopupMenuListener((PopupMenuListener) action);
|
||||
|
@ -6,7 +6,7 @@ import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.ui.action.ActionModel;
|
||||
|
||||
public class JsonPrettifyAction extends JNodeAction {
|
||||
|
||||
@ -14,7 +14,7 @@ public class JsonPrettifyAction extends JNodeAction {
|
||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||
|
||||
public JsonPrettifyAction(CodeArea codeArea) {
|
||||
super(NLS.str("popup.json_prettify"), codeArea);
|
||||
super(ActionModel.JSON_PRETTIFY, codeArea);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -2,18 +2,14 @@ package jadx.gui.ui.codearea;
|
||||
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.treemodel.JRenameNode;
|
||||
import jadx.gui.ui.action.ActionModel;
|
||||
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 {
|
||||
private static final long serialVersionUID = -4680872086148463289L;
|
||||
|
||||
public RenameAction(CodeArea codeArea) {
|
||||
super(NLS.str("popup.rename") + " (n)", codeArea);
|
||||
addKeyBinding(getKeyStroke(VK_N, 0), "trigger rename");
|
||||
super(ActionModel.CODE_RENAME, codeArea);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,6 +1,5 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -17,18 +16,16 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JMethod;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.ui.action.ActionModel;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
import static javax.swing.KeyStroke.getKeyStroke;
|
||||
|
||||
public class XposedAction extends JNodeAction {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(XposedAction.class);
|
||||
private static final long serialVersionUID = 2641585141624592578L;
|
||||
|
||||
public XposedAction(CodeArea codeArea) {
|
||||
super(NLS.str("popup.xposed") + " (y)", codeArea);
|
||||
addKeyBinding(getKeyStroke(KeyEvent.VK_Y, 0), "trigger xposed");
|
||||
super(ActionModel.XPOSED_COPY, codeArea);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
22
jadx-gui/src/main/java/jadx/gui/ui/menu/HiddenMenuItem.java
Normal file
22
jadx-gui/src/main/java/jadx/gui/ui/menu/HiddenMenuItem.java
Normal 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);
|
||||
}
|
||||
}
|
64
jadx-gui/src/main/java/jadx/gui/ui/menu/JadxMenu.java
Normal file
64
jadx-gui/src/main/java/jadx/gui/ui/menu/JadxMenu.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
15
jadx-gui/src/main/java/jadx/gui/ui/menu/JadxMenuBar.java
Normal file
15
jadx-gui/src/main/java/jadx/gui/ui/menu/JadxMenuBar.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
150
jadx-gui/src/main/java/jadx/gui/utils/shortcut/Shortcut.java
Normal file
150
jadx-gui/src/main/java/jadx/gui/utils/shortcut/Shortcut.java
Normal 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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
package jadx.gui.utils.ui;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
@ -11,6 +10,7 @@ import javax.swing.JComponent;
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.shortcut.Shortcut;
|
||||
|
||||
public class ActionHandler extends AbstractAction {
|
||||
|
||||
@ -24,6 +24,11 @@ public class ActionHandler extends AbstractAction {
|
||||
this.consumer = consumer;
|
||||
}
|
||||
|
||||
public ActionHandler() {
|
||||
this.consumer = ev -> {
|
||||
};
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
putValue(NAME, name);
|
||||
}
|
||||
@ -58,7 +63,7 @@ public class ActionHandler extends AbstractAction {
|
||||
public void addKeyBindToDescription() {
|
||||
KeyStroke keyStroke = (KeyStroke) getValue(ACCELERATOR_KEY);
|
||||
if (keyStroke != null) {
|
||||
String keyText = KeyEvent.getKeyText(keyStroke.getKeyCode());
|
||||
String keyText = Shortcut.keyboard(keyStroke.getKeyCode(), keyStroke.getModifiers()).toString();
|
||||
String desc = (String) getValue(SHORT_DESCRIPTION);
|
||||
setShortDescription(desc + " (" + keyText + ")");
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ menu.deobfuscation=Deobfuskierung
|
||||
menu.log=Log-Anzeige
|
||||
menu.help=Hilfe
|
||||
menu.about=Über
|
||||
#menu.quark=
|
||||
menu.update_label=Neue Version %s verfügbar!
|
||||
|
||||
file.open_action=Datei öffnen…
|
||||
@ -161,6 +162,8 @@ about_dialog.title=Über JADX
|
||||
preferences.title=Einstellungen
|
||||
preferences.deobfuscation=Deobfuskierung
|
||||
preferences.appearance=Aussehen
|
||||
#preferences.shortcuts=
|
||||
#preferences.select_shortcuts=
|
||||
preferences.decompile=Dekompilierung
|
||||
preferences.plugins=Plugins
|
||||
preferences.project=Projekt
|
||||
@ -257,6 +260,9 @@ msg.open_file=Bitte Datei öffnen
|
||||
msg.saving_sources=Quelltexte speichern
|
||||
msg.language_changed_title=Sprache speichern
|
||||
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=Projekt konnte nicht geladen werden
|
||||
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.save=Save
|
||||
#script.auto_complete=
|
||||
#script.check=Check
|
||||
#script.format=Reformat
|
||||
#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_msg=Sie debuggen eine App, sind Sie sicher, dass Sie eine Sitzung neu starten können?
|
||||
adb_dialog.starting_debugger=Debugger starten…
|
||||
|
||||
#action.variant=
|
||||
#action_category.menu_toolbar=
|
||||
#action_category.code_area=
|
||||
#action_category.plugin_script=
|
||||
|
@ -23,6 +23,7 @@ menu.deobfuscation=Deobfuscation
|
||||
menu.log=Log Viewer
|
||||
menu.help=Help
|
||||
menu.about=About
|
||||
menu.quark=Quark Engine
|
||||
menu.update_label=New version %s available!
|
||||
|
||||
file.open_action=Open files ...
|
||||
@ -161,6 +162,8 @@ about_dialog.title=About JADX
|
||||
preferences.title=Preferences
|
||||
preferences.deobfuscation=Deobfuscation
|
||||
preferences.appearance=Appearance
|
||||
preferences.shortcuts=Shortcuts
|
||||
preferences.select_shortcuts=Select a specific shortcuts group
|
||||
preferences.decompile=Decompilation
|
||||
preferences.plugins=Plugins
|
||||
preferences.project=Project
|
||||
@ -257,6 +260,9 @@ msg.open_file=Please open file
|
||||
msg.saving_sources=Saving sources
|
||||
msg.language_changed_title=Language changed
|
||||
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=Project could not be loaded
|
||||
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.save=Save
|
||||
script.auto_complete=Auto Complete
|
||||
script.check=Check
|
||||
script.format=Reformat
|
||||
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_msg=You're debugging an app, are you sure to restart a session?
|
||||
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
|
||||
|
@ -23,6 +23,7 @@ menu.deobfuscation=Desofuscación
|
||||
menu.log=Visor log
|
||||
menu.help=Ayuda
|
||||
menu.about=Acerca de...
|
||||
#menu.quark=
|
||||
menu.update_label=¡Nueva versión %s disponible!
|
||||
|
||||
file.open_action=Abrir archivo...
|
||||
@ -161,6 +162,8 @@ about_dialog.title=Sobre JADX
|
||||
preferences.title=Preferencias
|
||||
preferences.deobfuscation=Desofuscación
|
||||
#preferences.appearance=Appearance
|
||||
#preferences.shortcuts=
|
||||
#preferences.select_shortcuts=
|
||||
preferences.decompile=Descompilación
|
||||
#preferences.plugins=Plugins
|
||||
#preferences.project=
|
||||
@ -257,6 +260,9 @@ msg.open_file=Por favor, abra un archivo
|
||||
msg.saving_sources=Guardando fuente
|
||||
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.warning_title=
|
||||
#msg.common_mouse_shortcut=
|
||||
#msg.duplicate_shortcut=
|
||||
#msg.project_error_title=
|
||||
#msg.project_error=
|
||||
#msg.cmd_select_class_error=
|
||||
@ -290,6 +296,7 @@ popup.rename=Nimeta ümber
|
||||
|
||||
#script.run=Run
|
||||
#script.save=Save
|
||||
#script.auto_complete=
|
||||
#script.check=Check
|
||||
#script.format=Reformat
|
||||
#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_msg=You're debugging an app, are you sure to restart a session?
|
||||
#adb_dialog.starting_debugger=Starting debugger...
|
||||
|
||||
#action.variant=
|
||||
#action_category.menu_toolbar=
|
||||
#action_category.code_area=
|
||||
#action_category.plugin_script=
|
||||
|
@ -23,6 +23,7 @@ menu.deobfuscation=난독화 해제
|
||||
menu.log=로그 뷰어
|
||||
menu.help=도움말
|
||||
menu.about=정보
|
||||
#menu.quark=
|
||||
menu.update_label=새 버전 %s 이(가) 존재합니다!
|
||||
|
||||
file.open_action=파일 열기 ...
|
||||
@ -161,6 +162,8 @@ about_dialog.title=JADX 정보
|
||||
preferences.title=설정
|
||||
preferences.deobfuscation=난독화 해제
|
||||
preferences.appearance=외관
|
||||
#preferences.shortcuts=
|
||||
#preferences.select_shortcuts=
|
||||
preferences.decompile=디컴파일
|
||||
preferences.plugins=플러그인
|
||||
preferences.project=프로젝트
|
||||
@ -257,6 +260,9 @@ msg.open_file=파일을 여십시오
|
||||
msg.saving_sources=소스 저장 중
|
||||
msg.language_changed_title=언어 변경됨
|
||||
msg.language_changed=다음에 응용 프로그램이 시작되면 새 언어가 표시됩니다.
|
||||
#msg.warning_title=
|
||||
#msg.common_mouse_shortcut=
|
||||
#msg.duplicate_shortcut=
|
||||
msg.project_error_title=오류
|
||||
msg.project_error=프로젝트를 로드 할 수 없습니다.
|
||||
msg.cmd_select_class_error=클래스를 선택하지 못했습니다.\n%s\n클래스가 없습니다.
|
||||
@ -290,6 +296,7 @@ popup.search_global="%s" 전역 검색
|
||||
|
||||
#script.run=Run
|
||||
#script.save=Save
|
||||
#script.auto_complete=
|
||||
#script.check=Check
|
||||
#script.format=Reformat
|
||||
#script.log=Show log
|
||||
@ -405,3 +412,8 @@ adb_dialog.no_devices=앱을 실행할 기기를 찾을 수 없습니다.
|
||||
adb_dialog.restart_while_debugging_title=디버깅중 다시 시작
|
||||
adb_dialog.restart_while_debugging_msg=앱을 디버깅하고 있습니다. 세션을 다시 시작 하시겠습니까?
|
||||
adb_dialog.starting_debugger=디버거 시작 중 ...
|
||||
|
||||
#action.variant=
|
||||
#action_category.menu_toolbar=
|
||||
#action_category.code_area=
|
||||
#action_category.plugin_script=
|
||||
|
@ -23,6 +23,7 @@ menu.deobfuscation=Desofuscar
|
||||
menu.log=Visualizador de log
|
||||
menu.help=Ajuda
|
||||
menu.about=Sobre
|
||||
#menu.quark=
|
||||
menu.update_label=Nova versão %s disponível!
|
||||
|
||||
file.open_action=Abrir arquivos...
|
||||
@ -161,6 +162,8 @@ about_dialog.title=Sobre o JADX
|
||||
preferences.title=Preferências
|
||||
preferences.deobfuscation=Desofuscar
|
||||
preferences.appearance=Aparência
|
||||
#preferences.shortcuts=
|
||||
#preferences.select_shortcuts=
|
||||
preferences.decompile=Descompilação
|
||||
preferences.plugins=Plugins
|
||||
preferences.project=Projeto
|
||||
@ -257,6 +260,9 @@ msg.open_file=Abra um arquivo
|
||||
msg.saving_sources=Salvando recursos
|
||||
msg.language_changed_title=Idioma alterado
|
||||
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=Projeto não pôde ser carregado
|
||||
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.save=Save
|
||||
#script.auto_complete=
|
||||
#script.check=Check
|
||||
#script.format=Reformat
|
||||
#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_msg=Você está depurando um aplicativo, você tem certeza que deseja reiniciar a sessão?
|
||||
adb_dialog.starting_debugger=Iniciando depurador...
|
||||
|
||||
#action.variant=
|
||||
#action_category.menu_toolbar=
|
||||
#action_category.code_area=
|
||||
#action_category.plugin_script=
|
||||
|
@ -23,6 +23,7 @@ menu.deobfuscation=Деобфускация
|
||||
menu.log=Просмотр логов
|
||||
menu.help=Помощь
|
||||
menu.about=О программе
|
||||
#menu.quark=
|
||||
menu.update_label=Версия %s уже доступна!
|
||||
|
||||
file.open_action=Открыть файлы...
|
||||
@ -161,6 +162,8 @@ about_dialog.title=О программе JADX
|
||||
preferences.title=Параметры
|
||||
preferences.deobfuscation=Деобфускация
|
||||
preferences.appearance=Внешний вид
|
||||
#preferences.shortcuts=
|
||||
#preferences.select_shortcuts=
|
||||
preferences.decompile=Декомпиляция
|
||||
preferences.plugins=Плагины
|
||||
preferences.project=Проект
|
||||
@ -257,6 +260,9 @@ msg.open_file=Пожалуйста, откройте файл
|
||||
msg.saving_sources=Сохранение ресурсов
|
||||
msg.language_changed_title=Язык изменен
|
||||
msg.language_changed=Новый язык применится при следующем запуске программы
|
||||
#msg.warning_title=
|
||||
#msg.common_mouse_shortcut=
|
||||
#msg.duplicate_shortcut=
|
||||
msg.project_error_title=Ошибка
|
||||
msg.project_error=Проект не может быть загружен
|
||||
msg.cmd_select_class_error=Ошибка выбора класса\n%s\nЭтот класс не существует.
|
||||
@ -290,6 +296,7 @@ popup.search_global=Глобальный поиск "%s"
|
||||
|
||||
#script.run=Run
|
||||
#script.save=Save
|
||||
#script.auto_complete=
|
||||
#script.check=Check
|
||||
#script.format=Reformat
|
||||
#script.log=Show log
|
||||
@ -405,3 +412,8 @@ adb_dialog.no_devices=Нет устройств для запуска прило
|
||||
adb_dialog.restart_while_debugging_title=Перезапустить отладчик
|
||||
adb_dialog.restart_while_debugging_msg=Запущен процесс отдадки приложения. Вы действительно хотите перезапустить сессию?
|
||||
adb_dialog.starting_debugger=Запуск отладки...
|
||||
|
||||
#action.variant=
|
||||
#action_category.menu_toolbar=
|
||||
#action_category.code_area=
|
||||
#action_category.plugin_script=
|
||||
|
@ -23,6 +23,7 @@ menu.deobfuscation=反混淆
|
||||
menu.log=日志查看器
|
||||
menu.help=帮助
|
||||
menu.about=关于
|
||||
#menu.quark=
|
||||
menu.update_label=发现新版本 %s!
|
||||
|
||||
file.open_action=打开文件…
|
||||
@ -161,6 +162,8 @@ about_dialog.title=关于 JADX
|
||||
preferences.title=首选项
|
||||
preferences.deobfuscation=反混淆
|
||||
preferences.appearance=界面
|
||||
#preferences.shortcuts=
|
||||
#preferences.select_shortcuts=
|
||||
preferences.decompile=反编译
|
||||
preferences.plugins=插件
|
||||
preferences.project=项目
|
||||
@ -257,6 +260,9 @@ msg.open_file=请打开文件
|
||||
msg.saving_sources=正在导出源代码
|
||||
msg.language_changed_title=语言已更改
|
||||
msg.language_changed=新的语言将在下次应用程序启动时显示。
|
||||
#msg.warning_title=
|
||||
#msg.common_mouse_shortcut=
|
||||
#msg.duplicate_shortcut=
|
||||
msg.project_error_title=错误
|
||||
msg.project_error=项目无法加载
|
||||
msg.cmd_select_class_error=无法选择类\n%s\n该类不存在。
|
||||
@ -290,6 +296,7 @@ popup.json_prettify=JSON 格式化
|
||||
|
||||
script.run=运行
|
||||
script.save=保存
|
||||
#script.auto_complete=
|
||||
script.check=检查
|
||||
script.format=重新格式化
|
||||
script.log=显示日志
|
||||
@ -405,3 +412,8 @@ adb_dialog.no_devices=找不到任何设备用来启动APP。
|
||||
adb_dialog.restart_while_debugging_title=调试时重新启动
|
||||
adb_dialog.restart_while_debugging_msg=你正在调试一个APP,确定要重新启动一个会话吗?
|
||||
adb_dialog.starting_debugger=正在启动调试器…
|
||||
|
||||
#action.variant=
|
||||
#action_category.menu_toolbar=
|
||||
#action_category.code_area=
|
||||
#action_category.plugin_script=
|
||||
|
@ -23,6 +23,7 @@ menu.deobfuscation=去模糊化
|
||||
menu.log=記錄檔檢視器
|
||||
menu.help=幫助
|
||||
menu.about=關於
|
||||
#menu.quark=
|
||||
menu.update_label=新版本 %s 可供下載!
|
||||
|
||||
file.open_action=開啟檔案...
|
||||
@ -161,6 +162,8 @@ about_dialog.title=關於 JADX
|
||||
preferences.title=選項
|
||||
preferences.deobfuscation=去模糊化
|
||||
preferences.appearance=外觀
|
||||
#preferences.shortcuts=
|
||||
#preferences.select_shortcuts=
|
||||
preferences.decompile=反編譯
|
||||
preferences.plugins=外掛程式
|
||||
preferences.project=專案
|
||||
@ -257,6 +260,9 @@ msg.open_file=請開啟檔案
|
||||
msg.saving_sources=正在儲存原始碼
|
||||
msg.language_changed_title=已更改語言
|
||||
msg.language_changed=新語言將於下次應用程式啟動時套用。
|
||||
#msg.warning_title=
|
||||
#msg.common_mouse_shortcut=
|
||||
#msg.duplicate_shortcut=
|
||||
msg.project_error_title=錯誤
|
||||
msg.project_error=無法載入專案
|
||||
msg.cmd_select_class_error=無法選擇類別\n%s\n類別不存在。
|
||||
@ -290,6 +296,7 @@ popup.json_prettify=JSON 格式化
|
||||
|
||||
script.run=執行
|
||||
script.save=儲存
|
||||
#script.auto_complete=
|
||||
script.check=檢查
|
||||
script.format=重新格式化
|
||||
script.log=顯示記錄檔
|
||||
@ -405,3 +412,8 @@ adb_dialog.no_devices=無法找到任何可以啟動應用程式的裝置。
|
||||
adb_dialog.restart_while_debugging_title=偵錯時重新啟動
|
||||
adb_dialog.restart_while_debugging_msg=您正在為應用程式偵錯,您確定要重新啟動工作階段嗎?
|
||||
adb_dialog.starting_debugger=正在啟動偵錯工具...
|
||||
|
||||
#action.variant=
|
||||
#action_category.menu_toolbar=
|
||||
#action_category.code_area=
|
||||
#action_category.plugin_script=
|
||||
|
Loading…
Reference in New Issue
Block a user