fix: various UI improvements (#419)
* fixed wait time for background jobs * enable multi-threaded decompiling * added preference for excluding certain packages from decompiling and indexing * show message dialog in case classes are not indexed because of low memory * added heap usage bar for visualizing Java memory usage
This commit is contained in:
@@ -5,6 +5,7 @@ import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -64,10 +65,34 @@ public class JadxWrapper {
|
||||
new Thread(save).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the complete list of classes
|
||||
* @return
|
||||
*/
|
||||
public List<JavaClass> getClasses() {
|
||||
return decompiler.getClasses();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all classes that are not excluded by the excluded packages settings
|
||||
* @return
|
||||
*/
|
||||
public List<JavaClass> getIncludedClasses() {
|
||||
List<JavaClass> classList = decompiler.getClasses();
|
||||
String excludedPackages = settings.getExcludedPackages().trim();
|
||||
if (excludedPackages.length() == 0)
|
||||
return classList;
|
||||
String[] excluded = excludedPackages.split("[ ]+");
|
||||
|
||||
return classList.stream().filter(cls -> {
|
||||
for (String exclude : excluded) {
|
||||
if (cls.getFullName().startsWith(exclude))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<JavaPackage> getPackages() {
|
||||
return decompiler.getPackages();
|
||||
}
|
||||
@@ -79,4 +104,8 @@ public class JadxWrapper {
|
||||
public File getOpenFile() {
|
||||
return openFile;
|
||||
}
|
||||
|
||||
public JadxSettings getSettings() {
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package jadx.gui.jobs;
|
||||
import javax.swing.*;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import jadx.gui.utils.NLS;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -62,6 +63,9 @@ public class BackgroundWorker extends SwingWorker<Void, Void> {
|
||||
if (searchIndex != null && searchIndex.getSkippedCount() > 0) {
|
||||
LOG.warn("Indexing of some classes skipped, count: {}, low memory: {}",
|
||||
searchIndex.getSkippedCount(), Utils.memoryInfo());
|
||||
String msg = NLS.str("message.indexingClassesSkipped");
|
||||
msg = String.format(msg, searchIndex.getSkippedCount());
|
||||
JOptionPane.showMessageDialog(null, msg);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Exception in background worker", e);
|
||||
|
||||
@@ -10,7 +10,7 @@ public class DecompileJob extends BackgroundJob {
|
||||
}
|
||||
|
||||
protected void runJob() {
|
||||
for (final JavaClass cls : wrapper.getClasses()) {
|
||||
for (final JavaClass cls : wrapper.getIncludedClasses()) {
|
||||
addTask(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
@@ -33,7 +33,7 @@ public class IndexJob extends BackgroundJob {
|
||||
final CodeUsageInfo usageInfo = new CodeUsageInfo(nodeCache);
|
||||
cache.setTextIndex(index);
|
||||
cache.setUsageInfo(usageInfo);
|
||||
for (final JavaClass cls : wrapper.getClasses()) {
|
||||
for (final JavaClass cls : wrapper.getIncludedClasses()) {
|
||||
addTask(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
@@ -42,6 +42,9 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
private String editorThemePath = "";
|
||||
private LangLocale langLocale = NLS.defaultLocale();
|
||||
private boolean autoStartJobs = false;
|
||||
protected String excludedPackages = "";
|
||||
|
||||
private boolean showHeapUsageBar = true;
|
||||
|
||||
private int settingsVersion = 0;
|
||||
|
||||
@@ -147,6 +150,24 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public boolean isShowHeapUsageBar() {
|
||||
return showHeapUsageBar;
|
||||
}
|
||||
|
||||
public void setShowHeapUsageBar(boolean showHeapUsageBar) {
|
||||
this.showHeapUsageBar = showHeapUsageBar;
|
||||
partialSync(settings -> settings.showHeapUsageBar = showHeapUsageBar);
|
||||
}
|
||||
|
||||
public String getExcludedPackages() {
|
||||
return excludedPackages;
|
||||
}
|
||||
|
||||
public void setExcludedPackages(String excludedPackages) {
|
||||
this.excludedPackages = excludedPackages;
|
||||
}
|
||||
|
||||
public void setThreadsCount(int threadsCount) {
|
||||
this.threadsCount = threadsCount;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package jadx.gui.settings;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
@@ -242,6 +244,16 @@ public class JadxSettingsWindow extends JDialog {
|
||||
needReload();
|
||||
});
|
||||
|
||||
JButton editExcludedPackages = new JButton(NLS.str("preferences.excludedPackages.button"));
|
||||
editExcludedPackages.addActionListener( event -> {
|
||||
|
||||
String result = JOptionPane.showInputDialog(this, NLS.str("preferences.excludedPackages.editDialog"),
|
||||
settings.getExcludedPackages());
|
||||
if (result !=null) {
|
||||
settings.setExcludedPackages(result);
|
||||
}
|
||||
});
|
||||
|
||||
JCheckBox autoStartJobs = new JCheckBox();
|
||||
autoStartJobs.setSelected(settings.isAutoStartJobs());
|
||||
autoStartJobs.addItemListener(e -> settings.setAutoStartJobs(e.getStateChange() == ItemEvent.SELECTED));
|
||||
@@ -269,6 +281,8 @@ public class JadxSettingsWindow extends JDialog {
|
||||
|
||||
SettingsGroup other = new SettingsGroup(NLS.str("preferences.decompile"));
|
||||
other.addRow(NLS.str("preferences.threads"), threadsCount);
|
||||
other.addRow(NLS.str("preferences.excludedPackages"), NLS.str("preferences.excludedPackages.tooltip"),
|
||||
editExcludedPackages);
|
||||
other.addRow(NLS.str("preferences.start_jobs"), autoStartJobs);
|
||||
other.addRow(NLS.str("preferences.showInconsistentCode"), showInconsistentCode);
|
||||
other.addRow(NLS.str("preferences.escapeUnicode"), escapeUnicode);
|
||||
@@ -334,6 +348,10 @@ public class JadxSettingsWindow extends JDialog {
|
||||
}
|
||||
|
||||
public void addRow(String label, JComponent comp) {
|
||||
addRow(label, null, comp);
|
||||
}
|
||||
|
||||
public void addRow(String label, String tooltip, JComponent comp) {
|
||||
c.gridy = row++;
|
||||
JLabel jLabel = new JLabel(label);
|
||||
jLabel.setLabelFor(comp);
|
||||
@@ -349,6 +367,12 @@ public class JadxSettingsWindow extends JDialog {
|
||||
c.anchor = GridBagConstraints.CENTER;
|
||||
c.weightx = 0.2;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
|
||||
if (tooltip != null) {
|
||||
jLabel.setToolTipText(tooltip);
|
||||
comp.setToolTipText(tooltip);
|
||||
}
|
||||
|
||||
add(comp, c);
|
||||
|
||||
comp.addPropertyChangeListener("enabled", evt -> jLabel.setEnabled((boolean) evt.getNewValue()));
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package jadx.gui.ui;
|
||||
|
||||
import jadx.gui.utils.NLS;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
public class HeapUsageBar extends JProgressBar implements ActionListener {
|
||||
|
||||
private static final double TWO_TO_20 = 1048576d; // 1024 * 1024
|
||||
|
||||
private final Color GREEN = new Color(0, 180, 0);
|
||||
private final Color RED = new Color(200, 0, 0);
|
||||
|
||||
private final Runtime r;
|
||||
|
||||
private String maxHeapStr;
|
||||
|
||||
private final Timer timer;
|
||||
|
||||
private final double maxGB;
|
||||
|
||||
private final String textFormat;
|
||||
|
||||
public HeapUsageBar() {
|
||||
super();
|
||||
textFormat = NLS.str("heapUsage.text");
|
||||
r = Runtime.getRuntime();
|
||||
setBorderPainted(false);
|
||||
setStringPainted(true);
|
||||
setValue(10);
|
||||
int maxKB = (int) (r.maxMemory() / 1024);
|
||||
setMaximum(maxKB);
|
||||
maxGB = maxKB / TWO_TO_20;
|
||||
update();
|
||||
timer = new Timer(1000, this);
|
||||
}
|
||||
|
||||
public void update() {
|
||||
long used = r.totalMemory() - r.freeMemory();
|
||||
int usedKB = (int) (used / 1024);
|
||||
setValue(usedKB);
|
||||
setString(String.format(textFormat, (usedKB / TWO_TO_20), maxGB));
|
||||
|
||||
if (used > r.totalMemory() * 0.8) {
|
||||
setForeground(RED);
|
||||
} else {
|
||||
setForeground(GREEN);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisible(boolean aFlag) {
|
||||
super.setVisible(aFlag);
|
||||
if (aFlag) {
|
||||
timer.start();
|
||||
} else {
|
||||
timer.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,10 +90,12 @@ public class MainWindow extends JFrame {
|
||||
private DefaultTreeModel treeModel;
|
||||
private JRoot treeRoot;
|
||||
private TabbedPane tabbedPane;
|
||||
private HeapUsageBar heapUsageBar;
|
||||
|
||||
private boolean isFlattenPackage;
|
||||
private JToggleButton flatPkgButton;
|
||||
private JCheckBoxMenuItem flatPkgMenuItem;
|
||||
private JCheckBoxMenuItem heapUsageBarMenuItem;
|
||||
|
||||
private JToggleButton deobfToggleBtn;
|
||||
private JCheckBoxMenuItem deobfMenuItem;
|
||||
@@ -124,6 +126,7 @@ public class MainWindow extends JFrame {
|
||||
public void open() {
|
||||
pack();
|
||||
setLocationAndPosition();
|
||||
heapUsageBar.setVisible(settings.isShowHeapUsageBar());
|
||||
setVisible(true);
|
||||
setLocationRelativeTo(null);
|
||||
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
|
||||
@@ -185,7 +188,7 @@ public class MainWindow extends JFrame {
|
||||
protected void resetCache() {
|
||||
cacheObject.reset();
|
||||
// TODO: decompilation freezes sometime with several threads
|
||||
int threadsCount = 1; // settings.getThreadsCount();
|
||||
int threadsCount = settings.getThreadsCount();
|
||||
cacheObject.setDecompileJob(new DecompileJob(wrapper, threadsCount));
|
||||
cacheObject.setIndexJob(new IndexJob(wrapper, cacheObject, threadsCount));
|
||||
}
|
||||
@@ -385,6 +388,13 @@ public class MainWindow extends JFrame {
|
||||
flatPkgMenuItem = new JCheckBoxMenuItem(NLS.str("menu.flatten"), ICON_FLAT_PKG);
|
||||
flatPkgMenuItem.setState(isFlattenPackage);
|
||||
|
||||
heapUsageBarMenuItem = new JCheckBoxMenuItem(NLS.str("menu.heapUsageBar"));
|
||||
heapUsageBarMenuItem.setState(settings.isShowHeapUsageBar());
|
||||
heapUsageBarMenuItem.addActionListener(event -> {
|
||||
settings.setShowHeapUsageBar(!settings.isShowHeapUsageBar());
|
||||
heapUsageBar.setVisible(settings.isShowHeapUsageBar());
|
||||
});
|
||||
|
||||
Action syncAction = new AbstractAction(NLS.str("menu.sync"), ICON_SYNC) {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
@@ -483,6 +493,7 @@ public class MainWindow extends JFrame {
|
||||
view.setMnemonic(KeyEvent.VK_V);
|
||||
view.add(flatPkgMenuItem);
|
||||
view.add(syncAction);
|
||||
view.add(heapUsageBarMenuItem);
|
||||
|
||||
JMenu nav = new JMenu(NLS.str("menu.navigation"));
|
||||
nav.setMnemonic(KeyEvent.VK_N);
|
||||
@@ -609,6 +620,9 @@ public class MainWindow extends JFrame {
|
||||
|
||||
new DropTarget(this, DnDConstants.ACTION_COPY, new MainDropTarget(this));
|
||||
|
||||
heapUsageBar = new HeapUsageBar();
|
||||
mainPanel.add(heapUsageBar, BorderLayout.SOUTH);
|
||||
|
||||
setContentPane(mainPanel);
|
||||
setTitle(DEFAULT_TITLE);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user