From fe91d774fab98512747d8dd6e513e5c85f488b20 Mon Sep 17 00:00:00 2001 From: Skylot Date: Tue, 22 Mar 2022 10:59:15 +0000 Subject: [PATCH] feat(gui): add split view for different decompilation modes --- .../jadx/gui/jobs/BackgroundExecutor.java | 8 +- .../java/jadx/gui/settings/JadxSettings.java | 3 +- .../main/java/jadx/gui/treemodel/JClass.java | 3 +- .../main/java/jadx/gui/treemodel/JMethod.java | 8 -- .../main/java/jadx/gui/treemodel/JNode.java | 4 - .../gui/ui/codearea/AbstractCodeArea.java | 19 +++- .../ui/codearea/ClassCodeContentPanel.java | 86 +++++++++++++++---- .../java/jadx/gui/ui/codearea/CodeArea.java | 5 +- .../gui/ui/codearea/CodeContentPanel.java | 2 +- .../java/jadx/gui/ui/codearea/SmaliArea.java | 12 ++- .../jadx/gui/ui/codearea/mode/JCodeMode.java | 61 +++++++++++++ 11 files changed, 167 insertions(+), 44 deletions(-) create mode 100644 jadx-gui/src/main/java/jadx/gui/ui/codearea/mode/JCodeMode.java diff --git a/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java b/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java index e4c5df0b2..0b0020d33 100644 --- a/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java +++ b/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java @@ -105,7 +105,6 @@ public class BackgroundExecutor { protected TaskStatus doInBackground() throws Exception { progressPane.changeLabel(this, task.getTitle() + "… "); progressPane.changeCancelBtnVisible(this, task.canBeCanceled()); - progressPane.changeVisibility(this, true); runJobs(); return status; @@ -116,6 +115,9 @@ public class BackgroundExecutor { jobsCount = jobs.size(); LOG.debug("Starting background task '{}', jobs count: {}, time limit: {} ms, memory check: {}", task.getTitle(), jobsCount, task.timeLimit(), task.checkMemoryUsage()); + if (jobsCount != 1) { + progressPane.changeVisibility(this, true); + } status = TaskStatus.STARTED; int threadsCount = mainWindow.getSettings().getThreadsCount(); executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(threadsCount); @@ -146,6 +148,10 @@ public class BackgroundExecutor { setProgress(calcProgress(executor.getCompletedTaskCount())); k++; Thread.sleep(k < 20 ? 100 : 1000); // faster update for short tasks + if (jobsCount == 1 && k == 3) { + // small delay before show progress to reduce blinking on short tasks + progressPane.changeVisibility(this, true); + } } } catch (InterruptedException e) { LOG.debug("Task wait interrupted"); diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java index 625e36824..dc79e26b3 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java @@ -32,7 +32,6 @@ import jadx.api.JadxArgs; import jadx.api.args.DeobfuscationMapFileMode; import jadx.cli.JadxCLIArgs; import jadx.cli.LogHelper; -import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.gui.ui.MainWindow; import jadx.gui.ui.codearea.EditorTheme; import jadx.gui.utils.FontUtils; @@ -686,7 +685,7 @@ public class JadxSettings extends JadxCLIArgs { fromVersion++; } if (fromVersion != CURRENT_SETTINGS_VERSION) { - throw new JadxRuntimeException("Incorrect settings upgrade"); + LOG.warn("Incorrect settings upgrade. Expected version: {}, got: {}", CURRENT_SETTINGS_VERSION, fromVersion); } settingsVersion = CURRENT_SETTINGS_VERSION; sync(); diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java index 394c366c6..764738915 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java @@ -63,7 +63,7 @@ public class JClass extends JLoadableNode implements Comparable { return !cls.getClassNode().contains(AFlag.DONT_RENAME); } - public synchronized void load() { + private synchronized void load() { if (loaded) { return; } @@ -122,7 +122,6 @@ public class JClass extends JLoadableNode implements Comparable { return new ClassCodeContentPanel(tabbedPane, this); } - @Override public String getSmali() { return cls.getSmali(); } diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JMethod.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JMethod.java index d8596c921..156404213 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JMethod.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JMethod.java @@ -12,9 +12,6 @@ import jadx.api.JavaNode; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.info.AccessInfo; import jadx.core.dex.instructions.args.ArgType; -import jadx.gui.ui.TabbedPane; -import jadx.gui.ui.codearea.ClassCodeContentPanel; -import jadx.gui.ui.panel.ContentPanel; import jadx.gui.utils.OverlayIcon; import jadx.gui.utils.UiUtils; @@ -65,11 +62,6 @@ public class JMethod extends JNode { return mth.getDecompiledLine(); } - @Override - public ContentPanel getContentPanel(TabbedPane tabbedPane) { - return new ClassCodeContentPanel(tabbedPane, this); - } - @Override public Icon getIcon() { AccessInfo accessFlags = mth.getAccessFlags(); diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java index 70c7adc93..5af0c390a 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java @@ -38,10 +38,6 @@ public abstract class JNode extends DefaultMutableTreeNode { return null; } - public String getSmali() { - return null; - } - public String getSyntaxName() { return SyntaxConstants.SYNTAX_STYLE_NONE; } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeArea.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeArea.java index 681087adb..4090a9fd9 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeArea.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeArea.java @@ -23,7 +23,9 @@ import javax.swing.text.BadLocationException; import javax.swing.text.Caret; import javax.swing.text.DefaultCaret; +import org.fife.ui.rsyntaxtextarea.AbstractTokenMakerFactory; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; +import org.fife.ui.rsyntaxtextarea.TokenMakerFactory; import org.fife.ui.rtextarea.SearchContext; import org.fife.ui.rtextarea.SearchEngine; import org.jetbrains.annotations.Nullable; @@ -31,6 +33,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import jadx.core.utils.StringUtils; +import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.gui.settings.JadxSettings; import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JNode; @@ -47,12 +50,24 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea { private static final Logger LOG = LoggerFactory.getLogger(AbstractCodeArea.class); + public static final String SYNTAX_STYLE_SMALI = "text/smali"; + + static { + TokenMakerFactory tokenMakerFactory = TokenMakerFactory.getDefaultInstance(); + if (tokenMakerFactory instanceof AbstractTokenMakerFactory) { + AbstractTokenMakerFactory atmf = (AbstractTokenMakerFactory) tokenMakerFactory; + atmf.putMapping(SYNTAX_STYLE_SMALI, "jadx.gui.ui.codearea.SmaliTokenMaker"); + } else { + throw new JadxRuntimeException("Unexpected TokenMakerFactory instance: " + tokenMakerFactory.getClass()); + } + } + protected final ContentPanel contentPanel; protected final JNode node; - public AbstractCodeArea(ContentPanel contentPanel) { + public AbstractCodeArea(ContentPanel contentPanel, JNode node) { this.contentPanel = contentPanel; - this.node = contentPanel.getNode(); + this.node = node; setMarkOccurrences(false); setEditable(false); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/ClassCodeContentPanel.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/ClassCodeContentPanel.java index 15a4ad5a0..859cede5f 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/ClassCodeContentPanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/ClassCodeContentPanel.java @@ -1,21 +1,28 @@ package jadx.gui.ui.codearea; import java.awt.BorderLayout; +import java.awt.Dimension; import java.awt.Point; +import javax.swing.JCheckBox; +import javax.swing.JSplitPane; import javax.swing.JTabbedPane; +import javax.swing.JToolBar; import javax.swing.border.EmptyBorder; -import org.fife.ui.rsyntaxtextarea.AbstractTokenMakerFactory; -import org.fife.ui.rsyntaxtextarea.TokenMakerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import jadx.gui.treemodel.JNode; +import jadx.api.DecompilationMode; +import jadx.gui.jobs.BackgroundExecutor; +import jadx.gui.treemodel.JClass; import jadx.gui.ui.TabbedPane; +import jadx.gui.ui.codearea.mode.JCodeMode; import jadx.gui.ui.panel.IViewStateSupport; import jadx.gui.utils.NLS; +import static com.formdev.flatlaf.FlatClientProperties.TABBED_PANE_TRAILING_COMPONENT; + /** * Displays one class with two different view: * @@ -32,32 +39,77 @@ public final class ClassCodeContentPanel extends AbstractCodeContentPanel implem private final transient CodePanel smaliCodePanel; private final transient JTabbedPane areaTabbedPane; - public ClassCodeContentPanel(TabbedPane panel, JNode jnode) { - super(panel, jnode); + private boolean splitView = false; - // FIXME I don't know the project very well, so need to get the right place - AbstractTokenMakerFactory atmf = (AbstractTokenMakerFactory) TokenMakerFactory.getDefaultInstance(); - atmf.putMapping("text/smali", "jadx.gui.ui.codearea.SmaliTokenMaker"); + public ClassCodeContentPanel(TabbedPane panel, JClass jCls) { + super(panel, jCls); - javaCodePanel = new CodePanel(new CodeArea(this)); - smaliCodePanel = new CodePanel(new SmaliArea(this)); + javaCodePanel = new CodePanel(new CodeArea(this, jCls)); + smaliCodePanel = new CodePanel(new SmaliArea(this, jCls)); + areaTabbedPane = buildTabbedPane(jCls, false); + addCustomControls(areaTabbedPane); + initView(); + javaCodePanel.load(); + } + + private void initView() { + removeAll(); setLayout(new BorderLayout()); setBorder(new EmptyBorder(0, 0, 0, 0)); + if (splitView) { + JTabbedPane splitPaneView = buildTabbedPane(((JClass) node), true); + JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, areaTabbedPane, splitPaneView); + add(splitPane); + splitPane.setDividerLocation(0.5); + splitPaneView.setSelectedIndex(1); + } else { + add(areaTabbedPane); + } + invalidate(); + } - areaTabbedPane = new JTabbedPane(JTabbedPane.BOTTOM); + private JTabbedPane buildTabbedPane(JClass jCls, boolean split) { + JTabbedPane areaTabbedPane = new JTabbedPane(JTabbedPane.BOTTOM); areaTabbedPane.setBorder(new EmptyBorder(0, 0, 0, 0)); areaTabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); - areaTabbedPane.add(javaCodePanel, NLS.str("tabs.code")); - areaTabbedPane.add(smaliCodePanel, NLS.str("tabs.smali")); - add(areaTabbedPane); - - javaCodePanel.load(); - + if (split) { + areaTabbedPane.add(new CodePanel(new CodeArea(this, jCls)), NLS.str("tabs.code")); + areaTabbedPane.add(new CodePanel(new SmaliArea(this, jCls)), NLS.str("tabs.smali")); + } else { + areaTabbedPane.add(javaCodePanel, NLS.str("tabs.code")); + areaTabbedPane.add(smaliCodePanel, NLS.str("tabs.smali")); + } + areaTabbedPane.add(new CodePanel(new CodeArea(this, new JCodeMode(jCls, DecompilationMode.SIMPLE))), "Simple"); + areaTabbedPane.add(new CodePanel(new CodeArea(this, new JCodeMode(jCls, DecompilationMode.FALLBACK))), "Fallback"); areaTabbedPane.addChangeListener(e -> { CodePanel selectedPanel = (CodePanel) areaTabbedPane.getSelectedComponent(); + // TODO: to run background load extract ui update to other method selectedPanel.load(); + // execInBackground(selectedPanel::load); }); + return areaTabbedPane; + } + + private void addCustomControls(JTabbedPane tabbedPane) { + JCheckBox splitCheckBox = new JCheckBox("Split view", splitView); + splitCheckBox.addItemListener(e -> { + splitView = splitCheckBox.isSelected(); + this.initView(); + }); + + JToolBar trailing = new JToolBar(); + trailing.setFloatable(false); + trailing.setBorder(null); + // trailing.add(Box.createHorizontalGlue()); + trailing.addSeparator(new Dimension(50, 1)); + trailing.add(splitCheckBox); + tabbedPane.putClientProperty(TABBED_PANE_TRAILING_COMPONENT, trailing); + } + + private void execInBackground(Runnable runnable) { + BackgroundExecutor bgExec = this.tabbedPane.getMainWindow().getBackgroundExecutor(); + bgExec.execute("Loading", runnable); } @Override diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java index 5fcbf4e06..ec1440628 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java @@ -39,10 +39,9 @@ public final class CodeArea extends AbstractCodeArea { private static final long serialVersionUID = 6312736869579635796L; - CodeArea(ContentPanel contentPanel) { - super(contentPanel); + CodeArea(ContentPanel contentPanel, JNode node) { + super(contentPanel, node); setSyntaxEditingStyle(node.getSyntaxName()); - boolean isJavaCode = node instanceof JClass; if (isJavaCode) { ((RSyntaxDocument) getDocument()).setSyntaxStyle(new JadxTokenMaker(this)); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeContentPanel.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeContentPanel.java index 9a507c066..5c67c4209 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeContentPanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeContentPanel.java @@ -15,7 +15,7 @@ public final class CodeContentPanel extends AbstractCodeContentPanel implements public CodeContentPanel(TabbedPane panel, JNode jnode) { super(panel, jnode); setLayout(new BorderLayout()); - codePanel = new CodePanel(new CodeArea(this)); + codePanel = new CodePanel(new CodeArea(this, jnode)); add(codePanel, BorderLayout.CENTER); codePanel.load(); } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/SmaliArea.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/SmaliArea.java index aac02b748..9382bfc1f 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/SmaliArea.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/SmaliArea.java @@ -60,8 +60,8 @@ public final class SmaliArea extends AbstractCodeArea { private boolean curVersion = false; private SmaliModel model; - SmaliArea(ContentPanel contentPanel) { - super(contentPanel); + SmaliArea(ContentPanel contentPanel, JClass node) { + super(contentPanel, node); this.textNode = new TextNode(node.getName()); cbUseSmaliV2 = new JCheckBoxMenuItem(NLS.str("popup.bytecode_col"), @@ -106,6 +106,10 @@ public final class SmaliArea extends AbstractCodeArea { return textNode; } + public JClass getJClass() { + return ((JClass) node); + } + private void switchModel() { if (model != null) { model.unload(); @@ -166,12 +170,12 @@ public final class SmaliArea extends AbstractCodeArea { public NormalModel() { Theme theme = getContentPanel().getTabbedPane().getMainWindow().getEditorTheme(); setSyntaxScheme(theme.scheme); - setSyntaxEditingStyle("text/smali"); + setSyntaxEditingStyle(SYNTAX_STYLE_SMALI); } @Override public void load() { - setText(node.getSmali()); + setText(getJClass().getSmali()); } @Override diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/mode/JCodeMode.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/mode/JCodeMode.java new file mode 100644 index 000000000..61b7f2e5a --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/mode/JCodeMode.java @@ -0,0 +1,61 @@ +package jadx.gui.ui.codearea.mode; + +import javax.swing.Icon; + +import org.fife.ui.rsyntaxtextarea.SyntaxConstants; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import jadx.api.DecompilationMode; +import jadx.api.ICodeInfo; +import jadx.core.dex.nodes.ClassNode; +import jadx.gui.treemodel.JClass; +import jadx.gui.treemodel.JNode; + +public class JCodeMode extends JNode { + + private final JClass jCls; + private final DecompilationMode mode; + + private @Nullable ICodeInfo codeInfo; + + public JCodeMode(JClass jClass, DecompilationMode mode) { + this.jCls = jClass; + this.mode = mode; + } + + @Override + public JClass getJParent() { + return jCls.getJParent(); + } + + @Override + public Icon getIcon() { + return jCls.getIcon(); + } + + @Override + public String makeString() { + return jCls.makeString(); + } + + @Override + public @NotNull ICodeInfo getCodeInfo() { + if (codeInfo != null) { + return codeInfo; + } + ClassNode cls = jCls.getCls().getClassNode(); + codeInfo = cls.decompileWithMode(mode); + return codeInfo; + } + + @Override + public String getContent() { + return getCodeInfo().getCodeStr(); + } + + @Override + public String getSyntaxName() { + return SyntaxConstants.SYNTAX_STYLE_JAVA; + } +}