fix(gui): split loading and UI update for code area (#2682)
This commit is contained in:
@@ -514,9 +514,9 @@ public class Utils {
|
||||
}
|
||||
|
||||
private static final class SimpleThreadFactory implements ThreadFactory {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Utils.class);
|
||||
|
||||
private static final AtomicInteger POOL = new AtomicInteger(0);
|
||||
private static final Thread.UncaughtExceptionHandler EXC_HANDLER = new SimpleUncaughtExceptionHandler();
|
||||
|
||||
private final AtomicInteger number = new AtomicInteger(0);
|
||||
private final String name;
|
||||
|
||||
@@ -529,15 +529,22 @@ public class Utils {
|
||||
Thread thread = new Thread(r, "jadx-" + name
|
||||
+ '-' + POOL.incrementAndGet()
|
||||
+ '-' + number.incrementAndGet());
|
||||
thread.setUncaughtExceptionHandler((t, e) -> {
|
||||
thread.setUncaughtExceptionHandler(EXC_HANDLER);
|
||||
return thread;
|
||||
}
|
||||
|
||||
private static class SimpleUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SimpleUncaughtExceptionHandler.class);
|
||||
|
||||
@Override
|
||||
public void uncaughtException(Thread thread, Throwable e) {
|
||||
if (e instanceof OutOfMemoryError) {
|
||||
thread.interrupt();
|
||||
LOG.error("OutOfMemoryError in thread: {}, forcing interrupt", t.getName());
|
||||
LOG.error("OutOfMemoryError in thread: {}, forcing interrupt", thread.getName());
|
||||
} else {
|
||||
LOG.error("Uncaught thread exception, thread: {}", t.getName(), e);
|
||||
LOG.error("Uncaught thread exception, thread: {}", thread.getName(), e);
|
||||
}
|
||||
});
|
||||
return thread;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -182,7 +182,7 @@ public class BackgroundExecutor {
|
||||
jobsCount = taskExecutor.getTasksCount();
|
||||
LOG.debug("Starting background task '{}', jobs count: {}, time limit: {} ms, memory check: {}",
|
||||
task.getTitle(), jobsCount, task.timeLimit(), task.checkMemoryUsage());
|
||||
if (jobsCount != 1) {
|
||||
if (jobsCount != 1 && !task.isSilent()) {
|
||||
progressPane.changeVisibility(this, true);
|
||||
}
|
||||
status = TaskStatus.STARTED;
|
||||
@@ -202,6 +202,10 @@ public class BackgroundExecutor {
|
||||
if (!taskExecutor.isRunning()) {
|
||||
return TaskStatus.COMPLETE;
|
||||
}
|
||||
if (task.isSilent()) {
|
||||
Thread.sleep(50);
|
||||
continue;
|
||||
}
|
||||
TaskStatus cancelStatus = cancelCheck.get();
|
||||
if (cancelStatus != null) {
|
||||
performCancel();
|
||||
|
||||
@@ -55,4 +55,11 @@ public interface IBackgroundTask extends Cancelable {
|
||||
default @Nullable Consumer<ITaskProgress> getProgressListener() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Silent task: don't show progress
|
||||
*/
|
||||
default boolean isSilent() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package jadx.gui.jobs;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import jadx.api.utils.tasks.ITaskExecutor;
|
||||
import jadx.core.utils.tasks.TaskExecutor;
|
||||
import jadx.gui.utils.NLS;
|
||||
|
||||
/**
|
||||
* Load task: prepare data in background task and use that data in UI task
|
||||
*/
|
||||
public class LoadTask<T> extends CancelableBackgroundTask {
|
||||
private final String title;
|
||||
private final AtomicReference<T> taskData;
|
||||
private final Runnable bgTask;
|
||||
private final Runnable uiTask;
|
||||
|
||||
public LoadTask(Supplier<T> loadBgTask, Consumer<T> uiTask) {
|
||||
this(NLS.str("progress.load"), loadBgTask, uiTask);
|
||||
}
|
||||
|
||||
public LoadTask(String title, Supplier<T> loadBgTask, Consumer<T> uiTask) {
|
||||
this.title = title;
|
||||
this.taskData = new AtomicReference<>();
|
||||
this.bgTask = () -> taskData.set(loadBgTask.get());
|
||||
this.uiTask = () -> uiTask.accept(taskData.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITaskExecutor scheduleTasks() {
|
||||
TaskExecutor executor = new TaskExecutor();
|
||||
executor.addSequentialTask(bgTask);
|
||||
return executor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish(ITaskInfo taskInfo) {
|
||||
uiTask.run();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package jadx.gui.jobs;
|
||||
|
||||
import jadx.api.utils.tasks.ITaskExecutor;
|
||||
import jadx.core.utils.tasks.TaskExecutor;
|
||||
|
||||
/**
|
||||
* Simple and short task, will not show progress
|
||||
*/
|
||||
public class SilentTask extends CancelableBackgroundTask {
|
||||
private final Runnable task;
|
||||
|
||||
public SilentTask(Runnable task) {
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSilent() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return "<silent>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITaskExecutor scheduleTasks() {
|
||||
TaskExecutor executor = new TaskExecutor();
|
||||
executor.addSequentialTask(task);
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package jadx.gui.jobs;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -13,15 +14,15 @@ public class TaskWithExtraOnFinish implements IBackgroundTask {
|
||||
private final IBackgroundTask task;
|
||||
private final Consumer<TaskStatus> extraOnFinish;
|
||||
|
||||
public TaskWithExtraOnFinish(IBackgroundTask task, Consumer<TaskStatus> extraOnFinish) {
|
||||
this.task = task;
|
||||
this.extraOnFinish = extraOnFinish;
|
||||
}
|
||||
|
||||
public TaskWithExtraOnFinish(IBackgroundTask task, Runnable extraOnFinish) {
|
||||
this(task, s -> extraOnFinish.run());
|
||||
}
|
||||
|
||||
public TaskWithExtraOnFinish(IBackgroundTask task, Consumer<TaskStatus> extraOnFinish) {
|
||||
this.task = Objects.requireNonNull(task);
|
||||
this.extraOnFinish = Objects.requireNonNull(extraOnFinish);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish(ITaskInfo taskInfo) {
|
||||
task.onFinish(taskInfo);
|
||||
|
||||
@@ -38,6 +38,11 @@ public class JInputMapping extends JEditableNode {
|
||||
this.name = mappingPath.getFileName().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasContent() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContentPanel getContentPanel(TabbedPane tabbedPane) {
|
||||
return new CodeContentPanel(tabbedPane, this);
|
||||
|
||||
@@ -53,6 +53,11 @@ public class QuarkReportNode extends JNode {
|
||||
return "Quark analysis report";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasContent() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContentPanel getContentPanel(TabbedPane tabbedPane) {
|
||||
try {
|
||||
|
||||
@@ -4,6 +4,8 @@ import org.fife.ui.autocomplete.AutoCompletion;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.gui.jobs.IBackgroundTask;
|
||||
import jadx.gui.jobs.LoadTask;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.treemodel.JInputScript;
|
||||
import jadx.gui.ui.action.JadxAutoCompletion;
|
||||
@@ -48,12 +50,14 @@ public class ScriptCodeArea extends AbstractCodeArea {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
if (getText().isEmpty()) {
|
||||
setText(getCodeInfo().getCodeStr());
|
||||
setCaretPosition(0);
|
||||
setLoaded();
|
||||
}
|
||||
public IBackgroundTask getLoadTask() {
|
||||
return new LoadTask<>(
|
||||
() -> node.getCodeInfo().getCodeStr(),
|
||||
code -> {
|
||||
setText(code);
|
||||
setCaretPosition(0);
|
||||
setLoaded();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -83,6 +83,11 @@ public class ApkSignatureNode extends JNode {
|
||||
return "APK signature";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasContent() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContentPanel getContentPanel(TabbedPane tabbedPane) {
|
||||
ApkSignatureNode.tabbedPane = tabbedPane;
|
||||
|
||||
@@ -135,6 +135,11 @@ public class JClass extends JLoadableNode implements JRenameNode {
|
||||
return cls.getCodeInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasContent() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContentPanel getContentPanel(TabbedPane tabbedPane) {
|
||||
return new ClassCodeContentPanel(tabbedPane, this);
|
||||
|
||||
@@ -36,6 +36,11 @@ public class JInputScript extends JEditableNode {
|
||||
this.name = scriptPath.getFileName().toString().replace(".jadx.kts", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasContent() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContentPanel getContentPanel(TabbedPane tabbedPane) {
|
||||
return new ScriptContentPanel(tabbedPane, this);
|
||||
|
||||
@@ -35,6 +35,11 @@ public class JInputSmaliFile extends JEditableNode {
|
||||
return JInputFile.buildInputFilePopupMenu(mainWindow, filePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasContent() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ContentPanel getContentPanel(TabbedPane tabbedPane) {
|
||||
return new CodeContentPanel(tabbedPane, this);
|
||||
|
||||
@@ -45,6 +45,10 @@ public abstract class JNode extends DefaultMutableTreeNode implements ITreeNode,
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean hasContent() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public @Nullable ContentPanel getContentPanel(TabbedPane tabbedPane) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -152,6 +152,11 @@ public class JResource extends JLoadableNode {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasContent() {
|
||||
return resFile != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ContentPanel getContentPanel(TabbedPane tabbedPane) {
|
||||
if (resFile == null) {
|
||||
|
||||
@@ -120,15 +120,11 @@ import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.settings.ui.JadxSettingsWindow;
|
||||
import jadx.gui.tree.TreeExpansionService;
|
||||
import jadx.gui.treemodel.ApkSignatureNode;
|
||||
import jadx.gui.treemodel.JInputFiles;
|
||||
import jadx.gui.treemodel.JInputScripts;
|
||||
import jadx.gui.treemodel.JInputs;
|
||||
import jadx.gui.treemodel.JLoadableNode;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.treemodel.JPackage;
|
||||
import jadx.gui.treemodel.JResource;
|
||||
import jadx.gui.treemodel.JRoot;
|
||||
import jadx.gui.treemodel.JSources;
|
||||
import jadx.gui.ui.action.ActionModel;
|
||||
import jadx.gui.ui.action.JadxGuiAction;
|
||||
import jadx.gui.ui.codearea.AbstractCodeArea;
|
||||
@@ -920,11 +916,7 @@ public class MainWindow extends JFrame {
|
||||
}
|
||||
} else if (obj instanceof JNode) {
|
||||
JNode treeNode = (JNode) obj;
|
||||
if (!(treeNode instanceof JPackage)
|
||||
&& !(treeNode instanceof JSources)
|
||||
&& !(treeNode instanceof JInputs)
|
||||
&& !(treeNode instanceof JInputFiles)
|
||||
&& !(treeNode instanceof JInputScripts)) {
|
||||
if (treeNode.hasContent() || treeNode.getJParent() != null) {
|
||||
tabsController.codeJump(treeNode, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import java.awt.event.KeyEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.awt.event.MouseMotionListener;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.Action;
|
||||
@@ -45,6 +46,7 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.gui.jobs.IBackgroundTask;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JEditableNode;
|
||||
@@ -83,7 +85,7 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
|
||||
protected ContentPanel contentPanel;
|
||||
protected JNode node;
|
||||
|
||||
protected volatile boolean loaded = false;
|
||||
private final AtomicBoolean loaded = new AtomicBoolean(false);
|
||||
|
||||
public AbstractCodeArea(ContentPanel contentPanel, JNode node) {
|
||||
this.contentPanel = contentPanel;
|
||||
@@ -254,7 +256,7 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
|
||||
|
||||
private void addChangeUpdates(JEditableNode editableNode) {
|
||||
getDocument().addDocumentListener(new DocumentUpdateListener(ev -> {
|
||||
if (loaded) {
|
||||
if (loaded.get()) {
|
||||
editableNode.setChanged(true);
|
||||
}
|
||||
}));
|
||||
@@ -349,17 +351,33 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
|
||||
|
||||
public abstract ICodeInfo getCodeInfo();
|
||||
|
||||
public void load() {
|
||||
if (isLoaded()) {
|
||||
return;
|
||||
}
|
||||
IBackgroundTask loadTask = getLoadTask();
|
||||
contentPanel.getMainWindow().getBackgroundExecutor().execute(loadTask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement in this method the code that loads and sets the content to be displayed
|
||||
* Call `setLoaded()` on load finish.
|
||||
*/
|
||||
public abstract void load();
|
||||
public abstract IBackgroundTask getLoadTask();
|
||||
|
||||
public void setLoaded() {
|
||||
this.loaded = true;
|
||||
this.loaded.set(true);
|
||||
discardAllEdits(); // disable 'undo' action to empty state (before load)
|
||||
}
|
||||
|
||||
public void setUnLoaded() {
|
||||
this.loaded.set(false);
|
||||
}
|
||||
|
||||
public boolean isLoaded() {
|
||||
return loaded.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement in this method the code that reloads node from cache and sets the new content to be
|
||||
* displayed
|
||||
|
||||
@@ -15,6 +15,8 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.api.ResourcesLoader;
|
||||
import jadx.api.resources.ResourceContentType;
|
||||
import jadx.gui.jobs.BackgroundExecutor;
|
||||
import jadx.gui.jobs.IBackgroundTask;
|
||||
import jadx.gui.jobs.TaskWithExtraOnFinish;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.settings.LineNumbersMode;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
@@ -89,7 +91,7 @@ public class BinaryContentPanel extends AbstractCodeContentPanel {
|
||||
Component codePanel = getSelectedPanel();
|
||||
if (codePanel instanceof CodeArea) {
|
||||
CodeArea codeArea = (CodeArea) codePanel;
|
||||
bgExec.startLoading(codeArea::load);
|
||||
codeArea.load();
|
||||
} else {
|
||||
bgExec.startLoading(this::loadHexView);
|
||||
}
|
||||
@@ -106,11 +108,17 @@ public class BinaryContentPanel extends AbstractCodeContentPanel {
|
||||
|
||||
@Override
|
||||
public void scrollToPos(int pos) {
|
||||
UiUtils.uiThreadGuard();
|
||||
BackgroundExecutor bgExec = getMainWindow().getBackgroundExecutor();
|
||||
if (getNode().getContentType() == ResourceContentType.CONTENT_TEXT) {
|
||||
areaTabbedPane.setSelectedComponent(textCodePanel);
|
||||
AbstractCodeArea codeArea = textCodePanel.getCodeArea();
|
||||
bgExec.startLoading(codeArea::load, () -> codeArea.scrollToPos(pos));
|
||||
if (codeArea.isLoaded()) {
|
||||
codeArea.scrollToPos(pos);
|
||||
} else {
|
||||
IBackgroundTask loadTask = codeArea.getLoadTask();
|
||||
bgExec.execute(new TaskWithExtraOnFinish(loadTask, () -> codeArea.scrollToPos(pos)));
|
||||
}
|
||||
} else {
|
||||
areaTabbedPane.setSelectedComponent(hexPreviewPanel);
|
||||
bgExec.startLoading(this::loadHexView, () -> hexPreviewPanel.scrollToOffset(pos));
|
||||
|
||||
@@ -21,8 +21,12 @@ import jadx.api.JavaClass;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.gui.JadxWrapper;
|
||||
import jadx.gui.jobs.IBackgroundTask;
|
||||
import jadx.gui.jobs.LoadTask;
|
||||
import jadx.gui.jobs.TaskWithExtraOnFinish;
|
||||
import jadx.gui.settings.JadxProject;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JLoadableNode;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.treemodel.JResource;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
@@ -120,12 +124,24 @@ public final class CodeArea extends AbstractCodeArea {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
if (getText().isEmpty()) {
|
||||
setText(getCodeInfo().getCodeStr());
|
||||
setCaretPosition(0);
|
||||
setLoaded();
|
||||
public IBackgroundTask getLoadTask() {
|
||||
if (node instanceof JLoadableNode) {
|
||||
IBackgroundTask loadTask = ((JLoadableNode) node).getLoadTask();
|
||||
if (loadTask != null) {
|
||||
return new TaskWithExtraOnFinish(loadTask, () -> {
|
||||
setText(getCodeInfo().getCodeStr());
|
||||
setCaretPosition(0);
|
||||
setLoaded();
|
||||
});
|
||||
}
|
||||
}
|
||||
return new LoadTask<>(
|
||||
() -> getCodeInfo().getCodeStr(),
|
||||
code -> {
|
||||
setText(code);
|
||||
setCaretPosition(0);
|
||||
setLoaded();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -37,6 +37,8 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.gui.device.debugger.BreakpointManager;
|
||||
import jadx.gui.device.debugger.DbgUtils;
|
||||
import jadx.gui.jobs.IBackgroundTask;
|
||||
import jadx.gui.jobs.LoadTask;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
@@ -88,13 +90,15 @@ public final class SmaliArea extends AbstractCodeArea {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
if (getText().isEmpty() || curVersion != shouldUseSmaliPrinterV2()) {
|
||||
curVersion = shouldUseSmaliPrinterV2();
|
||||
model.load();
|
||||
setCaretPosition(0);
|
||||
setLoaded();
|
||||
}
|
||||
public IBackgroundTask getLoadTask() {
|
||||
return new LoadTask<>(
|
||||
() -> model.loadCode(),
|
||||
code -> {
|
||||
curVersion = shouldUseSmaliPrinterV2();
|
||||
model.loadUI(code);
|
||||
setCaretPosition(0);
|
||||
setLoaded();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -121,7 +125,10 @@ public final class SmaliArea extends AbstractCodeArea {
|
||||
if (model != null) {
|
||||
model.unload();
|
||||
}
|
||||
model = shouldUseSmaliPrinterV2() ? new DebugModel() : new NormalModel(this);
|
||||
curVersion = shouldUseSmaliPrinterV2();
|
||||
model = curVersion ? new DebugModel() : new NormalModel(this);
|
||||
setUnLoaded();
|
||||
load();
|
||||
}
|
||||
|
||||
public void scrollToDebugPos(int pos) {
|
||||
@@ -153,7 +160,9 @@ public final class SmaliArea extends AbstractCodeArea {
|
||||
}
|
||||
|
||||
private abstract class SmaliModel {
|
||||
abstract void load();
|
||||
abstract String loadCode();
|
||||
|
||||
abstract void loadUI(String code);
|
||||
|
||||
abstract void unload();
|
||||
|
||||
@@ -173,20 +182,23 @@ public final class SmaliArea extends AbstractCodeArea {
|
||||
}
|
||||
|
||||
private class NormalModel extends SmaliModel {
|
||||
|
||||
public NormalModel(SmaliArea smaliArea) {
|
||||
getContentPanel().getMainWindow().getEditorThemeManager().apply(smaliArea);
|
||||
setSyntaxEditingStyle(SYNTAX_STYLE_SMALI);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
setText(getJClass().getSmali());
|
||||
public String loadCode() {
|
||||
return getJClass().getSmali();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadUI(String code) {
|
||||
setText(code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +222,12 @@ public final class SmaliArea extends AbstractCodeArea {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
String loadCode() {
|
||||
return DbgUtils.getSmaliCode(((JClass) node).getCls().getClassNode());
|
||||
}
|
||||
|
||||
@Override
|
||||
void loadUI(String code) {
|
||||
if (gutter == null) {
|
||||
gutter = RSyntaxUtilities.getGutter(SmaliArea.this);
|
||||
gutter.setBookmarkingEnabled(true);
|
||||
@@ -218,7 +235,7 @@ public final class SmaliArea extends AbstractCodeArea {
|
||||
Font baseFont = SmaliArea.super.getFont();
|
||||
gutter.setLineNumberFont(baseFont.deriveFont(baseFont.getSize2D() - 1.0f));
|
||||
}
|
||||
setText(DbgUtils.getSmaliCode(((JClass) node).getCls().getClassNode()));
|
||||
setText(code);
|
||||
loadV2Style();
|
||||
loadBreakpoints();
|
||||
}
|
||||
|
||||
@@ -12,6 +12,11 @@ import jadx.gui.utils.NLS;
|
||||
public class StartPageNode extends JNode {
|
||||
private static final long serialVersionUID = 8983134608645736174L;
|
||||
|
||||
@Override
|
||||
public boolean hasContent() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContentPanel getContentPanel(TabbedPane tabbedPane) {
|
||||
return new StartPagePanel(tabbedPane, this);
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.gui.jobs.SilentTask;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
@@ -221,7 +222,7 @@ public class TabbedPane extends JTabbedPane implements ITabStatesListener {
|
||||
|
||||
private @Nullable ContentPanel showCode(JumpPosition jumpPos) {
|
||||
JNode jumpNode = jumpPos.getNode();
|
||||
ContentPanel contentPanel = getContentPanel(jumpNode);
|
||||
ContentPanel contentPanel = getTabByNode(jumpNode);
|
||||
if (contentPanel == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -314,11 +315,6 @@ public class TabbedPane extends JTabbedPane implements ITabStatesListener {
|
||||
return (TabComponent) component;
|
||||
}
|
||||
|
||||
private @Nullable ContentPanel getContentPanel(JNode node) {
|
||||
controller.openTab(node);
|
||||
return getTabByNode(node);
|
||||
}
|
||||
|
||||
public void refresh(JNode node) {
|
||||
ContentPanel panel = getTabByNode(node);
|
||||
if (panel != null) {
|
||||
@@ -421,7 +417,7 @@ public class TabbedPane extends JTabbedPane implements ITabStatesListener {
|
||||
|
||||
@Override
|
||||
public void onTabSelect(TabBlueprint blueprint) {
|
||||
ContentPanel contentPanel = getContentPanel(blueprint.getNode());
|
||||
ContentPanel contentPanel = getTabByNode(blueprint.getNode());
|
||||
if (contentPanel != null) {
|
||||
setSelectedComponent(contentPanel);
|
||||
}
|
||||
@@ -429,7 +425,8 @@ public class TabbedPane extends JTabbedPane implements ITabStatesListener {
|
||||
|
||||
@Override
|
||||
public void onTabCodeJump(TabBlueprint blueprint, @Nullable JumpPosition prevPos, JumpPosition position) {
|
||||
showCode(position);
|
||||
// queue task to wait completion of loading tasks
|
||||
mainWindow.getBackgroundExecutor().execute(new SilentTask(() -> showCode(position)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,6 +15,7 @@ import jadx.api.JavaClass;
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
import jadx.api.metadata.annotations.NodeDeclareRef;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.gui.jobs.SimpleTask;
|
||||
import jadx.gui.jobs.TaskWithExtraOnFinish;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
@@ -36,7 +37,9 @@ public class TabsController {
|
||||
|
||||
public TabsController(MainWindow mainWindow) {
|
||||
this.mainWindow = mainWindow;
|
||||
// addListener(new LogTabStates());
|
||||
if (UiUtils.JADX_GUI_DEBUG) {
|
||||
addListener(new LogTabStates());
|
||||
}
|
||||
}
|
||||
|
||||
public MainWindow getMainWindow() {
|
||||
@@ -59,11 +62,10 @@ public class TabsController {
|
||||
return openTab(node, false, false);
|
||||
}
|
||||
|
||||
public TabBlueprint openTab(JNode node, boolean hidden) {
|
||||
return openTab(node, hidden, false);
|
||||
}
|
||||
|
||||
public TabBlueprint openTab(JNode node, boolean hidden, boolean preview) {
|
||||
if (!node.hasContent()) {
|
||||
throw new JadxRuntimeException("Can't open tab for node without content");
|
||||
}
|
||||
TabBlueprint blueprint = getTabByNode(node);
|
||||
if (blueprint == null) {
|
||||
TabBlueprint newBlueprint = new TabBlueprint(node);
|
||||
@@ -77,10 +79,6 @@ public class TabsController {
|
||||
blueprint = newBlueprint;
|
||||
}
|
||||
setTabHiddenInternal(blueprint, hidden);
|
||||
if (!blueprint.isCreated()) {
|
||||
LOG.warn("No content panel for node: {}", node);
|
||||
closeTabForce(blueprint);
|
||||
}
|
||||
return blueprint;
|
||||
}
|
||||
|
||||
@@ -89,10 +87,7 @@ public class TabsController {
|
||||
if (blueprint != null) {
|
||||
closeTab(blueprint.getNode());
|
||||
}
|
||||
|
||||
blueprint = openTab(node, false, true);
|
||||
|
||||
return blueprint;
|
||||
return openTab(node, false, true);
|
||||
}
|
||||
|
||||
public void selectTab(JNode node) {
|
||||
@@ -125,6 +120,9 @@ public class TabsController {
|
||||
public void codeJump(JNode node, boolean fromTree) {
|
||||
JClass parentCls = node.getJParent();
|
||||
if (parentCls != null) {
|
||||
// handle jump to inner class, method or field:
|
||||
// - load parent
|
||||
// - search position and jump to it
|
||||
JavaClass cls = node.getJParent().getCls();
|
||||
JavaClass origTopCls = cls.getOriginalTopParentClass();
|
||||
JavaClass codeParent = cls.getTopParentClass();
|
||||
@@ -134,19 +132,12 @@ public class TabsController {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Not an inline node, jump normally
|
||||
if (node.getPos() > 0) {
|
||||
codeJump(new JumpPosition(node), fromTree);
|
||||
return;
|
||||
}
|
||||
if (node.getRootClass() == null) {
|
||||
// not a class, select tab without position scroll
|
||||
// not a class, select tab (without position scroll)
|
||||
selectTab(node, fromTree);
|
||||
return;
|
||||
}
|
||||
// node need loading
|
||||
loadCodeWithUIAction(node.getRootClass(), () -> codeJump(new JumpPosition(node), fromTree));
|
||||
codeJump(new JumpPosition(node), fromTree);
|
||||
}
|
||||
|
||||
private void loadCodeWithUIAction(JClass cls, Runnable action) {
|
||||
|
||||
@@ -194,6 +194,11 @@ public class SummaryNode extends JNode {
|
||||
return String.format("%d (%.2f%%)", value, value * 100 / ((double) total));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasContent() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContentPanel getContentPanel(TabbedPane tabbedPane) {
|
||||
return new HtmlPanel(tabbedPane, this);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package jadx.gui.ui.treenodes;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.Icon;
|
||||
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
@@ -19,6 +19,11 @@ public class UndisplayedStringsNode extends JNode {
|
||||
this.undisplayedStings = undisplayedStings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasContent() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContentPanel getContentPanel(TabbedPane tabbedPane) {
|
||||
return new UndisplayedStringsPanel(tabbedPane, this);
|
||||
|
||||
@@ -430,17 +430,17 @@ public class UiUtils {
|
||||
* Uses single thread, so all tasks are ordered.
|
||||
*/
|
||||
public static void bgRun(Runnable runnable) {
|
||||
BACKGROUND_THREAD.submit(runnable);
|
||||
BACKGROUND_THREAD.execute(runnable);
|
||||
}
|
||||
|
||||
public static void uiThreadGuard() {
|
||||
if (!SwingUtilities.isEventDispatchThread()) {
|
||||
if (JADX_GUI_DEBUG && !SwingUtilities.isEventDispatchThread()) {
|
||||
LOG.warn("Expect UI thread, got: {}", Thread.currentThread(), new JadxRuntimeException());
|
||||
}
|
||||
}
|
||||
|
||||
public static void notUiThreadGuard() {
|
||||
if (SwingUtilities.isEventDispatchThread()) {
|
||||
if (JADX_GUI_DEBUG && SwingUtilities.isEventDispatchThread()) {
|
||||
LOG.warn("Expect background thread, got: {}", Thread.currentThread(), new JadxRuntimeException());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user