fix(gui): improve colors in usage tree, some UI fixes (PR #2506)

* fix(gui): refactor code in UsageDialogPlus

* fix(gui): added padding between buttons in package exclusion dialog & fixed crash

* fix(gui): added padding between buttons in settings dialog

* fix: fix javadoc

* fix: fix javadoc

* fix: fix javadoc
This commit is contained in:
Yaroslav
2025-05-24 01:15:16 +03:00
committed by GitHub
parent b7a8a2879e
commit 2486c981a8
15 changed files with 419 additions and 361 deletions
@@ -186,6 +186,7 @@ public class JadxSettingsWindow extends JDialog {
buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS));
buttonPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
buttonPane.add(resetBtn);
buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
buttonPane.add(copyBtn);
buttonPane.add(Box.createHorizontalGlue());
buttonPane.add(saveBtn);
@@ -0,0 +1,77 @@
package jadx.gui.ui.cellrenders;
import java.awt.Color;
import java.awt.Component;
import javax.swing.BorderFactory;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreePath;
import jadx.gui.treemodel.JNode;
import jadx.gui.utils.UiUtils;
public class PathHighlightTreeCellRenderer extends DefaultTreeCellRenderer {
private final boolean isDarkTheme;
public PathHighlightTreeCellRenderer() {
super();
Color themeBackground = UIManager.getColor("Panel.background");
isDarkTheme = UiUtils.isDarkTheme(themeBackground);
}
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected,
boolean expanded, boolean leaf, int row, boolean hasFocus) {
Component comp = super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
Object userObject = node.getUserObject();
// Calculate the node level
int level = node.getLevel();
// Set different colors according to the level
float hue = (level * 0.1f) % 1.0f; // Hue cycle
Color levelColor = Color.getHSBColor(hue, 0.1f, 0.95f);
// Check if it is on the selected path
boolean onSelectionPath = false;
TreePath selectionPath = tree.getSelectionPath();
if (selectionPath != null) {
// Check if the current node is on the selected path (whether it is part of the selected path)
Object[] selectedPathNodes = selectionPath.getPath();
for (Object pathNode : selectedPathNodes) {
if (pathNode == node) {
onSelectionPath = true;
break;
}
}
}
if (onSelectionPath && !selected) {
// If it is on the selected path but not the selected node, use a special foreground
setForeground(isDarkTheme ? Color.decode("#70AEFF") : Color.decode("#0033B3"));
} else if (!selected) {
// Only apply the background color when it is not selected
setBackground(levelColor);
// Normal border
setBorder(BorderFactory.createEmptyBorder(2, level * 2 + 1, 2, 1));
} else {
// The selected node also adds a border
setBorder(BorderFactory.createEmptyBorder(2, level * 2 + 1, 2, 1));
}
if (userObject instanceof JNode) {
JNode jNode = (JNode) userObject;
setText(jNode.makeLongString());
setIcon(jNode.getIcon());
setToolTipText(jNode.getTooltip());
}
return comp;
}
}
@@ -1,10 +1,9 @@
package jadx.gui.ui.dialog;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Label;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
@@ -17,6 +16,7 @@ import java.util.function.Consumer;
import java.util.stream.Collectors;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
@@ -61,12 +61,12 @@ public class ExcludePkgDialog extends JDialog {
tree.setModel(treeModel);
tree.setCellRenderer(new PkgListCellRenderer());
JScrollPane listPanel = new JScrollPane(tree);
listPanel.setBorder(BorderFactory.createLineBorder(Color.black));
listPanel.setBorder(BorderFactory.createEmptyBorder());
tree.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
TreePath path = tree.getPathForLocation(e.getX(), e.getY());
if (path != null) {
if (path != null && path.getLastPathComponent() instanceof PkgNode) {
PkgNode node = (PkgNode) path.getLastPathComponent();
node.toggle();
repaint();
@@ -75,18 +75,18 @@ public class ExcludePkgDialog extends JDialog {
});
JPanel actionPanel = new JPanel();
BoxLayout boxLayout = new BoxLayout(actionPanel, BoxLayout.LINE_AXIS);
actionPanel.setLayout(boxLayout);
actionPanel.add(new Label(" "));
JButton btnOk = new JButton(NLS.str("exclude_dialog.ok"));
actionPanel.setLayout(new BoxLayout(actionPanel, BoxLayout.LINE_AXIS));
JButton btnOk = new JButton(NLS.str("common_dialog.ok"));
JButton btnAll = new JButton(NLS.str("exclude_dialog.select_all"));
JButton btnInvert = new JButton(NLS.str("exclude_dialog.invert"));
JButton btnDeselect = new JButton(NLS.str("exclude_dialog.deselect"));
actionPanel.add(btnDeselect);
actionPanel.add(Box.createRigidArea(new Dimension(10, 0)));
actionPanel.add(btnInvert);
actionPanel.add(Box.createRigidArea(new Dimension(10, 0)));
actionPanel.add(btnAll);
actionPanel.add(new Label(" "));
actionPanel.add(btnOk);
actionPanel.add(Box.createHorizontalGlue());
actionPanel.add(btnOk, BorderLayout.PAGE_END);
JPanel mainPane = new JPanel(new BorderLayout(5, 5));
mainPane.add(listPanel, BorderLayout.CENTER);
@@ -5,8 +5,6 @@ import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
@@ -19,7 +17,6 @@ import java.util.Map;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
@@ -33,16 +30,12 @@ import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
@@ -63,8 +56,9 @@ import jadx.gui.treemodel.JField;
import jadx.gui.treemodel.JMethod;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.codearea.AbstractCodeArea;
import jadx.gui.ui.cellrenders.PathHighlightTreeCellRenderer;
import jadx.gui.ui.panel.ProgressPanel;
import jadx.gui.ui.panel.SimpleCodePanel;
import jadx.gui.utils.JNodeCache;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
@@ -74,14 +68,14 @@ public class UsageDialogPlus extends CommonSearchDialog {
private final JPanel mainPanel;
private final JSplitPane splitPane;
private final CodePanel codePanel;
private final SimpleCodePanel simpleCodePanel;
private final JTree usageTree;
private final DefaultTreeModel treeModel;
private final DefaultMutableTreeNode rootNode;
private final ProgressPanel localProgressPanel;
private final JLabel resultsInfoLabel;
private final JLabel progressInfoLabel;
private JNode initialNode;
private final JNode initialNode;
private static final Logger LOG = LoggerFactory.getLogger(UsageDialogPlus.class);
@@ -116,15 +110,15 @@ public class UsageDialogPlus extends CommonSearchDialog {
warnLabel.setVisible(false);
// Initialize result and progress info labels
resultsInfoLabel = new JLabel("");
progressInfoLabel = new JLabel("");
resultsInfoLabel = new JLabel();
progressInfoLabel = new JLabel();
localProgressPanel = new ProgressPanel(mainWindow, false);
mainPanel = new JPanel();
mainPanel.setLayout(new BorderLayout());
// Create the code panel
codePanel = new CodePanel(mainWindow);
simpleCodePanel = new SimpleCodePanel(mainWindow);
// Create the tree
rootNode = new DefaultMutableTreeNode(node);
@@ -136,31 +130,29 @@ public class UsageDialogPlus extends CommonSearchDialog {
usageTree.putClientProperty("JTree.lineStyle", "Horizontal");
usageTree.setRowHeight(22);
usageTree.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
usageTree.setFont(mainWindow.getSettings().getFont());
// Use a custom renderer instead of a custom UI
usageTree.setCellRenderer(new PathHighlightTreeCellRenderer());
// Add tree listeners
usageTree.addTreeSelectionListener(new TreeSelectionListener() {
@Override
public void valueChanged(TreeSelectionEvent e) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) usageTree.getLastSelectedPathComponent();
if (node == null) {
return;
}
Object nodeInfo = node.getUserObject();
if (nodeInfo instanceof CodeNode) {
CodeNode codeNode = (CodeNode) nodeInfo;
codePanel.showCode(codeNode, codeNode.makeDescString());
} else if (nodeInfo instanceof JNode) {
JNode jNode = (JNode) nodeInfo;
codePanel.showCode(jNode, jNode.makeDescString());
}
// Update the result information to display the number of child nodes of the currently selected node
updateResultsInfo(node);
usageTree.addTreeSelectionListener(e -> {
DefaultMutableTreeNode node1 = (DefaultMutableTreeNode) usageTree.getLastSelectedPathComponent();
if (node1 == null) {
return;
}
Object nodeInfo = node1.getUserObject();
if (nodeInfo instanceof CodeNode) {
CodeNode codeNode = (CodeNode) nodeInfo;
simpleCodePanel.showCode(codeNode, codeNode.makeDescString());
} else if (nodeInfo instanceof JNode) {
JNode jNode = (JNode) nodeInfo;
simpleCodePanel.showCode(jNode, jNode.makeDescString());
}
// Update the result information to display the number of child nodes of the currently selected node
updateResultsInfo(node1);
});
usageTree.addTreeExpansionListener(new TreeExpansionListener() {
@@ -206,7 +198,7 @@ public class UsageDialogPlus extends CommonSearchDialog {
usageTree.setSelectionPath(path);
// Handle the right-click menu
if (e.getButton() == MouseEvent.BUTTON3) {
if (SwingUtilities.isRightMouseButton(e)) {
DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) path.getLastPathComponent();
Object userObject = selectedNode.getUserObject();
@@ -218,7 +210,7 @@ public class UsageDialogPlus extends CommonSearchDialog {
}
// Handle left-click single/double click
if (e.getButton() == MouseEvent.BUTTON1) {
if (SwingUtilities.isLeftMouseButton(e)) {
long clickTime = System.currentTimeMillis();
// Double-click interval
long doubleClickInterval = 300;
@@ -299,7 +291,7 @@ public class UsageDialogPlus extends CommonSearchDialog {
leftPanel.add(statusPanel, BorderLayout.SOUTH);
splitPane.setLeftComponent(leftPanel);
splitPane.setRightComponent(codePanel);
splitPane.setRightComponent(simpleCodePanel);
mainPanel.add(splitPane, BorderLayout.CENTER);
@@ -547,14 +539,10 @@ public class UsageDialogPlus extends CommonSearchDialog {
});
JMenuItem jumpToItem = new JMenuItem(NLS.str("usage_dialog_plus.jump_to"));
jumpToItem.addActionListener(evt -> {
openItem(node);
});
jumpToItem.addActionListener(evt -> openItem(node));
JMenuItem copyPathItem = new JMenuItem(NLS.str("usage_dialog_plus.copy_path"));
copyPathItem.addActionListener(evt -> {
copyUsagePath(path);
});
copyPathItem.addActionListener(evt -> copyUsagePath(path));
popup.add(expandItem);
popup.addSeparator();
@@ -597,274 +585,6 @@ public class UsageDialogPlus extends CommonSearchDialog {
}
}
// Delete the PathHighlightTreeUI class, use the enhanced CellRenderer instead
private class PathHighlightTreeCellRenderer extends DefaultTreeCellRenderer {
private static final int INDENT_PER_LEVEL = 18; // Each level of indentation in pixels
private final Color selectedPathColor = new Color(220, 240, 255); // Light blue background
private final Color selectedPathTextColor = new Color(0, 0, 150); // Dark blue text
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected,
boolean expanded, boolean leaf, int row, boolean hasFocus) {
Component comp = super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
Object userObject = node.getUserObject();
// Calculate the node level
int level = node.getLevel();
// Set different colors according to the level
float hue = (level * 0.1f) % 1.0f; // Hue cycle
Color levelColor = Color.getHSBColor(hue, 0.1f, 0.95f);
// Check if it is on the selected path
boolean onSelectionPath = false;
TreePath selectionPath = tree.getSelectionPath();
if (selectionPath != null) {
// Check if the current node is on the selected path (whether it is part of the selected path)
Object[] selectedPathNodes = selectionPath.getPath();
for (Object pathNode : selectedPathNodes) {
if (pathNode == node) {
onSelectionPath = true;
break;
}
}
}
if (onSelectionPath && !selected) {
// If it is on the selected path but not the selected node, use a special background
setBackground(selectedPathColor);
setForeground(selectedPathTextColor);
// Set the highlighted border
setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(selectedPathTextColor, 1),
BorderFactory.createEmptyBorder(1, level * 2, 1, 1)));
} else if (!selected) {
// Only apply the background color when it is not selected
setBackground(levelColor);
// Normal border
setBorder(BorderFactory.createEmptyBorder(2, level * 2 + 1, 2, 1));
} else {
// The selected node also adds a border
setBorder(BorderFactory.createEmptyBorder(2, level * 2 + 1, 2, 1));
}
if (userObject instanceof JNode) {
JNode jNode = (JNode) userObject;
setText(jNode.makeLongString());
setIcon(jNode.getIcon());
setToolTipText(jNode.getTooltip());
} else if (userObject instanceof CodeNode) {
CodeNode codeNode = (CodeNode) userObject;
setText(codeNode.makeLongString());
setIcon(codeNode.getIcon());
setToolTipText(codeNode.getTooltip());
}
return comp;
}
// Add custom icons to enhance visual distinction
@Override
public Icon getLeafIcon() {
return super.getLeafIcon();
}
@Override
public Icon getOpenIcon() {
return super.getOpenIcon();
}
@Override
public Icon getClosedIcon() {
return super.getClosedIcon();
}
}
// The code panel class is used to display the code of the selected node.
private class CodePanel extends JPanel {
private final RSyntaxTextArea codeArea;
private final JLabel titleLabel;
public CodePanel(MainWindow mainWindow) {
setLayout(new BorderLayout(5, 5));
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
// Set the minimum size to ensure the panel is not completely minimized
setMinimumSize(new Dimension(300, 400));
setPreferredSize(new Dimension(800, 600));
// The title label
titleLabel = new JLabel(NLS.str("usage_dialog_plus.code_view"));
titleLabel.setFont(codeFont);
titleLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 10, 5));
// The code area
codeArea = AbstractCodeArea.getDefaultArea(mainWindow);
codeArea.setEditable(false);
codeArea.setText("// " + NLS.str("usage_dialog_plus.select_node"));
JScrollPane scrollPane = new JScrollPane(codeArea);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
add(titleLabel, BorderLayout.NORTH);
add(scrollPane, BorderLayout.CENTER);
}
public void showCode(JNode node, String codeLine) {
if (node != null) {
titleLabel.setText(NLS.str("usage_dialog_plus.code_for") + " " + node.makeLongString());
codeArea.setSyntaxEditingStyle(node.getSyntaxName());
// Get the complete code
String contextCode = getContextCode(node, codeLine);
codeArea.setText(contextCode);
// Highlight the key line and scroll to that position
scrollToCodeLine(codeArea, codeLine);
// If it is a CodeNode, we can get a more precise position
if (node instanceof CodeNode) {
CodeNode codeNode = (CodeNode) node;
int pos = codeNode.getPos();
if (pos > 0) {
// Try to use the position information to more accurately locate
try {
String text = codeArea.getText();
int lineNum = 0;
int curPos = 0;
// Calculate the line number corresponding to the position
for (int i = 0; i < text.length() && curPos <= pos; i++) {
if (text.charAt(i) == '\n') {
lineNum++;
}
curPos++;
}
if (lineNum > 0) {
// Scroll to the calculated line number
int finalLineNum = lineNum;
SwingUtilities.invokeLater(() -> {
try {
Rectangle lineRect = codeArea
.modelToView(codeArea.getLineStartOffset(finalLineNum));
if (lineRect != null) {
JScrollPane scrollPane = (JScrollPane) codeArea.getParent().getParent();
Rectangle viewRect = scrollPane.getViewport().getViewRect();
int y = lineRect.y - (viewRect.height - lineRect.height) / 2;
if (y < 0) {
y = 0;
}
scrollPane.getViewport().setViewPosition(new Point(0, y));
}
} catch (Exception e) {
// Fall back to using string matching
scrollToCodeLine(codeArea, codeLine);
}
});
}
} catch (Exception e) {
// Fall back to using string matching
scrollToCodeLine(codeArea, codeLine);
}
} else {
// If there is no position information, use string matching
scrollToCodeLine(codeArea, codeLine);
}
} else {
// Not a CodeNode, use string matching
scrollToCodeLine(codeArea, codeLine);
}
} else {
titleLabel.setText(NLS.str("usage_dialog_plus.code_view"));
codeArea.setText("// " + NLS.str("usage_dialog_plus.select_node"));
}
}
private String getContextCode(JNode node, String codeLine) {
// Always try to get the complete code
if (node instanceof CodeNode) {
CodeNode codeNode = (CodeNode) node;
JNode usageJNode = codeNode.getJParent();
if (usageJNode != null) {
// Try to get the complete code of the method or class
String fullCode = getFullNodeCode(usageJNode);
if (fullCode != null && !fullCode.isEmpty()) {
return fullCode;
}
}
}
// If you cannot get more context, at least add some empty lines and comments
return "// Unable to get complete context, only display related lines\n\n" + codeLine;
}
private String getFullNodeCode(JNode node) {
if (node != null) {
// Get the code information of the node
ICodeInfo codeInfo = node.getCodeInfo();
if (codeInfo != null && !codeInfo.equals(ICodeInfo.EMPTY)) {
return codeInfo.getCodeStr();
}
// If it is a class node, try to get the class code
if (node instanceof JClass) {
JClass jClass = (JClass) node;
return jClass.getCodeInfo().getCodeStr();
}
}
return null;
}
private void scrollToCodeLine(RSyntaxTextArea textArea, String lineToHighlight) {
// Try to find and highlight a specific line in the code and scroll to that position
try {
String fullText = textArea.getText();
int lineIndex = fullText.indexOf(lineToHighlight);
if (lineIndex >= 0) {
// Ensure the text area has updated the layout
textArea.revalidate();
// Highlight the code line
textArea.setCaretPosition(lineIndex);
int endIndex = lineIndex + lineToHighlight.length();
textArea.select(lineIndex, endIndex);
textArea.getCaret().setSelectionVisible(true);
// Use SwingUtilities.invokeLater to ensure the scroll is executed after the UI is updated
SwingUtilities.invokeLater(() -> {
try {
// Get the line number
int lineNum = textArea.getLineOfOffset(lineIndex);
// Ensure the line is centered in the view
Rectangle lineRect = textArea.modelToView(textArea.getLineStartOffset(lineNum));
if (lineRect != null) {
// Calculate the center point of the view
JScrollPane scrollPane = (JScrollPane) textArea.getParent().getParent();
Rectangle viewRect = scrollPane.getViewport().getViewRect();
int y = lineRect.y - (viewRect.height - lineRect.height) / 2;
if (y < 0) {
y = 0;
}
// Scroll to the calculated position
scrollPane.getViewport().setViewPosition(new Point(0, y));
}
} catch (Exception e) {
LOG.debug("Error scrolling to line: {}", e.getMessage());
}
});
} else {
LOG.debug("Could not find line to highlight: {}", lineToHighlight);
}
} catch (Exception e) {
LOG.debug("Error highlighting line: {}", e.getMessage());
}
}
}
@NotNull
@Override
protected JPanel initButtonsPanel() {
@@ -0,0 +1,223 @@
package jadx.gui.ui.panel;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rtextarea.RTextScrollPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.ICodeInfo;
import jadx.gui.settings.JadxSettings;
import jadx.gui.settings.LineNumbersMode;
import jadx.gui.treemodel.CodeNode;
import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.codearea.AbstractCodeArea;
import jadx.gui.utils.NLS;
// The code panel class is used to display the code of the selected node.
public class SimpleCodePanel extends JPanel {
private static final long serialVersionUID = -4073178549744330905L;
private static final Logger LOG = LoggerFactory.getLogger(SimpleCodePanel.class);
private final RSyntaxTextArea codeArea;
private final RTextScrollPane codeScrollPane;
private final JLabel titleLabel;
public SimpleCodePanel(MainWindow mainWindow) {
JadxSettings settings = mainWindow.getSettings();
setLayout(new BorderLayout(5, 5));
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
// Set the minimum size to ensure the panel is not completely minimized
setMinimumSize(new Dimension(300, 400));
setPreferredSize(new Dimension(800, 600));
// The title label
titleLabel = new JLabel(NLS.str("usage_dialog_plus.code_view"));
titleLabel.setFont(settings.getFont());
titleLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 10, 5));
// The code area
codeArea = AbstractCodeArea.getDefaultArea(mainWindow);
codeArea.setText("// " + NLS.str("usage_dialog_plus.select_node"));
codeScrollPane = new RTextScrollPane(codeArea);
codeScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
codeScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
add(titleLabel, BorderLayout.NORTH);
add(codeScrollPane, BorderLayout.CENTER);
applySettings(settings);
}
private void applySettings(JadxSettings settings) {
codeScrollPane.setLineNumbersEnabled(settings.getLineNumbersMode() != LineNumbersMode.DISABLE);
codeScrollPane.getGutter().setLineNumberFont(settings.getFont());
codeArea.setFont(settings.getFont());
}
public void showCode(JNode node, String codeLine) {
if (node != null) {
titleLabel.setText(NLS.str("usage_dialog_plus.code_for", node.makeLongString()));
codeArea.setSyntaxEditingStyle(node.getSyntaxName());
// Get the complete code
String contextCode = getContextCode(node, codeLine);
codeArea.setText(contextCode);
// Highlight the key line and scroll to that position
scrollToCodeLine(codeArea, codeLine);
// If it is a CodeNode, we can get a more precise position
if (node instanceof CodeNode) {
CodeNode codeNode = (CodeNode) node;
int pos = codeNode.getPos();
if (pos > 0) {
// Try to use the position information to more accurately locate
try {
String text = codeArea.getText();
int lineNum = 0;
int curPos = 0;
// Calculate the line number corresponding to the position
for (int i = 0; i < text.length() && curPos <= pos; i++) {
if (text.charAt(i) == '\n') {
lineNum++;
}
curPos++;
}
if (lineNum > 0) {
// Scroll to the calculated line number
int finalLineNum = lineNum;
SwingUtilities.invokeLater(() -> {
try {
Rectangle2D lineRect = codeArea
.modelToView2D(codeArea.getLineStartOffset(finalLineNum));
if (lineRect != null) {
JScrollPane scrollPane = (JScrollPane) codeArea.getParent().getParent();
Rectangle viewRect = scrollPane.getViewport().getViewRect();
int y = (int) (lineRect.getY() - (viewRect.height - lineRect.getHeight()) / 2);
if (y < 0) {
y = 0;
}
scrollPane.getViewport().setViewPosition(new Point(0, y));
}
} catch (Exception e) {
// Fall back to using string matching
scrollToCodeLine(codeArea, codeLine);
}
});
}
} catch (Exception e) {
// Fall back to using string matching
scrollToCodeLine(codeArea, codeLine);
}
} else {
// If there is no position information, use string matching
scrollToCodeLine(codeArea, codeLine);
}
} else {
// Not a CodeNode, use string matching
scrollToCodeLine(codeArea, codeLine);
}
} else {
titleLabel.setText(NLS.str("usage_dialog_plus.code_view"));
codeArea.setText("// " + NLS.str("usage_dialog_plus.select_node"));
}
}
private String getContextCode(JNode node, String codeLine) {
// Always try to get the complete code
if (node instanceof CodeNode) {
CodeNode codeNode = (CodeNode) node;
JNode usageJNode = codeNode.getJParent();
if (usageJNode != null) {
// Try to get the complete code of the method or class
String fullCode = getFullNodeCode(usageJNode);
if (fullCode != null && !fullCode.isEmpty()) {
return fullCode;
}
}
}
// If you cannot get more context, at least add some empty lines and comments
return "// Unable to get complete context, only display related lines\n\n" + codeLine;
}
private String getFullNodeCode(JNode node) {
if (node != null) {
// Get the code information of the node
ICodeInfo codeInfo = node.getCodeInfo();
if (codeInfo != null && !codeInfo.equals(ICodeInfo.EMPTY)) {
return codeInfo.getCodeStr();
}
// If it is a class node, try to get the class code
if (node instanceof JClass) {
JClass jClass = (JClass) node;
return jClass.getCodeInfo().getCodeStr();
}
}
return null;
}
private void scrollToCodeLine(RSyntaxTextArea textArea, String lineToHighlight) {
// Try to find and highlight a specific line in the code and scroll to that position
try {
String fullText = textArea.getText();
int lineIndex = fullText.indexOf(lineToHighlight);
if (lineIndex >= 0) {
// Ensure the text area has updated the layout
textArea.revalidate();
// Highlight the code line
textArea.setCaretPosition(lineIndex);
int endIndex = lineIndex + lineToHighlight.length();
textArea.select(lineIndex, endIndex);
textArea.getCaret().setSelectionVisible(true);
// Use SwingUtilities.invokeLater to ensure the scroll is executed after the UI is updated
SwingUtilities.invokeLater(() -> {
try {
// Get the line number
int lineNum = textArea.getLineOfOffset(lineIndex);
// Ensure the line is centered in the view
Rectangle2D lineRect = textArea.modelToView2D(textArea.getLineStartOffset(lineNum));
if (lineRect != null) {
// Calculate the center point of the view
JScrollPane scrollPane = (JScrollPane) textArea.getParent().getParent();
Rectangle viewRect = scrollPane.getViewport().getViewRect();
int y = (int) (lineRect.getY() - (viewRect.height - lineRect.getHeight()) / 2);
if (y < 0) {
y = 0;
}
// Scroll to the calculated position
scrollPane.getViewport().setViewPosition(new Point(0, y));
}
} catch (Exception e) {
LOG.debug("Error scrolling to line: {}", e.getMessage());
}
});
} else {
LOG.debug("Could not find line to highlight: {}", lineToHighlight);
}
} catch (Exception e) {
LOG.debug("Error highlighting line: {}", e.getMessage());
}
}
}
@@ -463,6 +463,52 @@ public class UiUtils {
return brightness < 0.5;
}
/**
* Adjusts the brightness of a given {@code Color} object without altering its hue or saturation.
*
* <p>
* This method converts the input RGB color to the HSB (Hue, Saturation, Brightness) color model,
* multiplies its brightness component by the specified {@code factor}, and then converts it back
* to a new RGB {@code Color} object.
* </p>
*
* <p>
* The new brightness value is capped at {@code 1.0f} (maximum HSB brightness) to prevent
* colors from becoming invalid or exceeding full brightness.
* </p>
*
* How to use:
* <ul>
* <li>To make a color **brighter**: Use a {@code factor} greater than {@code 1.0f} (e.g.,
* {@code 1.2f}, {@code 1.5f}).</li>
* <li>To make a color **darker**: Use a {@code factor} less than {@code 1.0f} (e.g., {@code 0.8f},
* {@code 0.5f}).</li>
* <li>To keep the brightness **unchanged**: Use a {@code factor} of {@code 1.0f}.</li>
* </ul>
*
* <pre>{@code
* // Example usage:
* Color originalColor = Color.BLUE;
*
* // Make the blue color 50% brighter (factor 1.5)
* Color brighterBlue = adjustBrightness(originalColor, 1.5f);
*
* // Make the blue color 30% darker (factor 0.7)
* Color darkerBlue = adjustBrightness(originalColor, 0.7f);
*
* // Get the brightest possible version of the color (will cap at 1.0 brightness)
* Color maxBrightnessBlue = adjustBrightness(originalColor, 10.0f);
*
* // Get a very dark, almost black version
* Color veryDarkBlue = adjustBrightness(originalColor, 0.1f);
* }</pre>
*
* @param color The original {@code Color} object whose brightness needs to be adjusted.
* @param factor The multiplier for the brightness.
* @return A new {@code Color} object with the adjusted brightness.
* @see java.awt.Color#RGBtoHSB(int, int, int, float[])
* @see java.awt.Color#getHSBColor(float, float, float)
*/
public static Color adjustBrightness(Color color, float factor) {
float[] hsb = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
hsb[2] = Math.min(1.0f, hsb[2] * factor); // Adjust brightness
@@ -188,10 +188,10 @@ usage_dialog.label=Verwendungen von:
usage_dialog_plus.jump_to=Zur aktuellen Position springen
usage_dialog_plus.copy_path=Verwendungspfad kopieren
usage_dialog_plus.search_complete=Suche abgeschlossen
#usage_dialog_plus.code_view=code view
#usage_dialog_plus.select_node=select node
#usage_dialog_plus.code_for=code for
#usage_dialog_plus.expand_usages=expand data
#usage_dialog_plus.code_view=Code View
#usage_dialog_plus.select_node=Select Node
#usage_dialog_plus.code_for=Code for %s
#usage_dialog_plus.expand_usages=Expand Data
comment_dialog.title.add=Code-Kommentar hinzufügen
comment_dialog.title.update=Code-Kommentar aktualisieren
@@ -393,7 +393,6 @@ script.log=Protokoll anzeigen
#encoding_dialog.message=Select encoding:
exclude_dialog.title=Paketauswahl
exclude_dialog.ok=OK
exclude_dialog.select_all=Alles auswählen
exclude_dialog.deselect=Abwählen
exclude_dialog.invert=Invertieren
@@ -136,7 +136,7 @@ message.unable_preview_font=Unable preview font
heapUsage.text=JADX memory usage: %.2f GB of %.2f GB
common_dialog.ok=Ok
common_dialog.ok=OK
common_dialog.cancel=Cancel
common_dialog.add=Add
common_dialog.update=Update
@@ -179,7 +179,7 @@ search_dialog.keep_open=Keep open
search_dialog.tip_searching=Searching
search_dialog.limit_package=Limit to package:
search_dialog.package_not_found=No matching package found
search_dialog.copy=copy all
search_dialog.copy=Copy All
usage_dialog.title=Usage search
usage_dialog.label=Usage for:
@@ -188,10 +188,10 @@ usage_dialog_plus.title=Usage tree search
usage_dialog_plus.jump_to=Jump to current location
usage_dialog_plus.copy_path=Copy usage tree path
usage_dialog_plus.search_complete=Search completed
usage_dialog_plus.code_view=code view
usage_dialog_plus.select_node=select node
usage_dialog_plus.code_for=code for
usage_dialog_plus.expand_usages=expand data
usage_dialog_plus.code_view=Code View
usage_dialog_plus.select_node=Select Node
usage_dialog_plus.code_for=Code for %s
usage_dialog_plus.expand_usages=Expand Data
comment_dialog.title.add=Add code comment
comment_dialog.title.update=Update code comment
@@ -393,7 +393,6 @@ encoding_dialog.title=Encoding
encoding_dialog.message=Select encoding:
exclude_dialog.title=Package Selector
exclude_dialog.ok=OK
exclude_dialog.select_all=Select all
exclude_dialog.deselect=Deselect
exclude_dialog.invert=Invert
@@ -136,7 +136,7 @@ nav.forward=Adelante
#heapUsage.text=JADX memory usage: %.2f GB of %.2f GB
#common_dialog.ok=Ok
#common_dialog.ok=OK
#common_dialog.cancel=Cancel
#common_dialog.add=Add
#common_dialog.update=Update
@@ -188,10 +188,10 @@ usage_dialog.label=Usage for:
#usage_dialog_plus.jump_to=Jump to current location
#usage_dialog_plus.copy_path=Copy usage tree path
#usage_dialog_plus.search_complete=Search completed
#usage_dialog_plus.code_view=code view
#usage_dialog_plus.select_node=select node
#usage_dialog_plus.code_for=code for
#usage_dialog_plus.expand_usages=expand data
#usage_dialog_plus.code_view=Code View
#usage_dialog_plus.select_node=Select Node
#usage_dialog_plus.code_for=Code for %s
#usage_dialog_plus.expand_usages=Expand Data
#comment_dialog.title.add=Add code comment
#comment_dialog.title.update=Update code comment
@@ -393,7 +393,6 @@ popup.rename=Renombrar
#encoding_dialog.message=Select encoding:
#exclude_dialog.title=Package Selector
#exclude_dialog.ok=OK
#exclude_dialog.select_all=Select all
#exclude_dialog.deselect=Deselect
#exclude_dialog.invert=Invert
@@ -188,10 +188,10 @@ usage_dialog.label=Penggunaan untuk:
#usage_dialog_plus.jump_to=Jump to current location
#usage_dialog_plus.copy_path=Copy usage tree path
#usage_dialog_plus.search_complete=Search completed
#usage_dialog_plus.code_view=code view
#usage_dialog_plus.select_node=select node
#usage_dialog_plus.code_for=code for
#usage_dialog_plus.expand_usages=expand data
#usage_dialog_plus.code_view=Code View
#usage_dialog_plus.select_node=Select Node
#usage_dialog_plus.code_for=Code for %s
#usage_dialog_plus.expand_usages=Expand Data
comment_dialog.title.add=Tambahkan komentar kode
comment_dialog.title.update=Perbarui komentar kode
@@ -393,7 +393,6 @@ script.log=Tampilkan log
#encoding_dialog.message=Select encoding:
exclude_dialog.title=Selektor Paket
exclude_dialog.ok=OK
exclude_dialog.select_all=Pilih semua
exclude_dialog.deselect=Batal pilih
exclude_dialog.invert=Balik pilihan
@@ -188,10 +188,10 @@ usage_dialog.label=다음의 사용 검색 결과:
#usage_dialog_plus.jump_to=Jump to current location
#usage_dialog_plus.copy_path=Copy usage tree path
#usage_dialog_plus.search_complete=Search completed
#usage_dialog_plus.code_view=code view
#usage_dialog_plus.select_node=select node
#usage_dialog_plus.code_for=code for
#usage_dialog_plus.expand_usages=expand data
#usage_dialog_plus.code_view=Code View
#usage_dialog_plus.select_node=Select Node
#usage_dialog_plus.code_for=Code for %s
#usage_dialog_plus.expand_usages=Expand Data
comment_dialog.title.add=주석 추가
comment_dialog.title.update=주석 업데이트
@@ -393,7 +393,6 @@ popup.search_global="%s" 전역 검색
#encoding_dialog.message=Select encoding:
exclude_dialog.title=패키지 선택기
exclude_dialog.ok=확인
exclude_dialog.select_all=모두 선택
exclude_dialog.deselect=선택 해제
exclude_dialog.invert=반전
@@ -188,10 +188,10 @@ usage_dialog.label=Usado por:
#usage_dialog_plus.jump_to=Jump to current location
#usage_dialog_plus.copy_path=Copy usage tree path
#usage_dialog_plus.search_complete=Search completed
#usage_dialog_plus.code_view=code view
#usage_dialog_plus.select_node=select node
#usage_dialog_plus.code_for=code for
#usage_dialog_plus.expand_usages=expand data
#usage_dialog_plus.code_view=Code View
#usage_dialog_plus.select_node=Select Node
#usage_dialog_plus.code_for=Code for %s
#usage_dialog_plus.expand_usages=Expand Data
comment_dialog.title.add=Adicionar comentário ao código
comment_dialog.title.update=Atualizar comentário do código
@@ -393,7 +393,6 @@ popup.search_global=Busca global "%s"
#encoding_dialog.message=Select encoding:
exclude_dialog.title=Selecionar pacote
exclude_dialog.ok=OK
exclude_dialog.select_all=Selecionar tudo
exclude_dialog.deselect=Remover seleção
exclude_dialog.invert=Inverter
@@ -188,10 +188,10 @@ usage_dialog.label=Использования:
#usage_dialog_plus.jump_to=Jump to current location
#usage_dialog_plus.copy_path=Copy usage tree path
#usage_dialog_plus.search_complete=Search completed
#usage_dialog_plus.code_view=code view
#usage_dialog_plus.select_node=select node
#usage_dialog_plus.code_for=code for
#usage_dialog_plus.expand_usages=expand data
#usage_dialog_plus.code_view=Code View
#usage_dialog_plus.select_node=Select Node
#usage_dialog_plus.code_for=Code for %s
#usage_dialog_plus.expand_usages=Expand Data
comment_dialog.title.add=Добавить комментарий
comment_dialog.title.update=Обновить
@@ -393,7 +393,6 @@ script.log=Показать лог
#encoding_dialog.message=Select encoding:
exclude_dialog.title=Выбор пакетов
exclude_dialog.ok=OK
exclude_dialog.select_all=Выбрать все
exclude_dialog.deselect=Убрать
exclude_dialog.invert=Инвертировать
@@ -190,7 +190,7 @@ usage_dialog_plus.copy_path=复制用例路径
usage_dialog_plus.search_complete=搜索完成
usage_dialog_plus.code_view=代码预览
usage_dialog_plus.select_node=选择节点
usage_dialog_plus.code_for=代码信息
usage_dialog_plus.code_for=代码信息 %s
usage_dialog_plus.expand_usages=展开数据
comment_dialog.title.add=添加代码注释
@@ -393,7 +393,6 @@ script.log=显示日志
#encoding_dialog.message=Select encoding:
exclude_dialog.title=选择要排除的包
exclude_dialog.ok=确定
exclude_dialog.select_all=全选
exclude_dialog.deselect=取消选择
exclude_dialog.invert=反选
@@ -190,7 +190,7 @@ usage_dialog_plus.copy_path=複製使用情況路徑
usage_dialog_plus.search_complete=搜尋完成
usage_dialog_plus.code_view=程式碼預覽
usage_dialog_plus.select_node=選擇節點
usage_dialog_plus.code_for=程式碼資訊
usage_dialog_plus.code_for=程式碼資訊 %s
usage_dialog_plus.expand_usages=展開資料
comment_dialog.title.add=新增程式碼註解
@@ -393,7 +393,6 @@ script.log=顯示記錄檔
#encoding_dialog.message=Select encoding:
exclude_dialog.title=套件選擇
exclude_dialog.ok=OK
exclude_dialog.select_all=全選
exclude_dialog.deselect=取消選取
exclude_dialog.invert=反轉選取