From 2486c981a86b1f06f94a9f43d9a6ff7c8743109b Mon Sep 17 00:00:00 2001 From: Yaroslav <43380144+MrIkso@users.noreply.github.com> Date: Sat, 24 May 2025 01:15:16 +0300 Subject: [PATCH] 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 --- .../gui/settings/ui/JadxSettingsWindow.java | 1 + .../PathHighlightTreeCellRenderer.java | 77 ++++ .../jadx/gui/ui/dialog/ExcludePkgDialog.java | 20 +- .../jadx/gui/ui/dialog/UsageDialogPlus.java | 338 ++---------------- .../jadx/gui/ui/panel/SimpleCodePanel.java | 223 ++++++++++++ .../src/main/java/jadx/gui/utils/UiUtils.java | 46 +++ .../resources/i18n/Messages_de_DE.properties | 9 +- .../resources/i18n/Messages_en_US.properties | 13 +- .../resources/i18n/Messages_es_ES.properties | 11 +- .../resources/i18n/Messages_id_ID.properties | 9 +- .../resources/i18n/Messages_ko_KR.properties | 9 +- .../resources/i18n/Messages_pt_BR.properties | 9 +- .../resources/i18n/Messages_ru_RU.properties | 9 +- .../resources/i18n/Messages_zh_CN.properties | 3 +- .../resources/i18n/Messages_zh_TW.properties | 3 +- 15 files changed, 419 insertions(+), 361 deletions(-) create mode 100644 jadx-gui/src/main/java/jadx/gui/ui/cellrenders/PathHighlightTreeCellRenderer.java create mode 100644 jadx-gui/src/main/java/jadx/gui/ui/panel/SimpleCodePanel.java diff --git a/jadx-gui/src/main/java/jadx/gui/settings/ui/JadxSettingsWindow.java b/jadx-gui/src/main/java/jadx/gui/settings/ui/JadxSettingsWindow.java index a0e8b0676..b796c42fc 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/ui/JadxSettingsWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/ui/JadxSettingsWindow.java @@ -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); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/cellrenders/PathHighlightTreeCellRenderer.java b/jadx-gui/src/main/java/jadx/gui/ui/cellrenders/PathHighlightTreeCellRenderer.java new file mode 100644 index 000000000..13a3a9fab --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/cellrenders/PathHighlightTreeCellRenderer.java @@ -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; + } + +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/dialog/ExcludePkgDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/dialog/ExcludePkgDialog.java index 9e093f7ab..c786adb90 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/dialog/ExcludePkgDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/dialog/ExcludePkgDialog.java @@ -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); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/dialog/UsageDialogPlus.java b/jadx-gui/src/main/java/jadx/gui/ui/dialog/UsageDialogPlus.java index c57c9ef24..bde05c77f 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/dialog/UsageDialogPlus.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/dialog/UsageDialogPlus.java @@ -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() { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/panel/SimpleCodePanel.java b/jadx-gui/src/main/java/jadx/gui/ui/panel/SimpleCodePanel.java new file mode 100644 index 000000000..c296f83ff --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/panel/SimpleCodePanel.java @@ -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()); + } + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/utils/UiUtils.java b/jadx-gui/src/main/java/jadx/gui/utils/UiUtils.java index f38905947..a67ebe730 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/UiUtils.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/UiUtils.java @@ -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. + * + *

+ * 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. + *

+ * + *

+ * The new brightness value is capped at {@code 1.0f} (maximum HSB brightness) to prevent + * colors from becoming invalid or exceeding full brightness. + *

+ * + * How to use: + * + * + *
{@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);
+	 * }
+ * + * @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 diff --git a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties index b086aac56..95bf5b60f 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties @@ -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 diff --git a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties index 82cd5f900..f355868a4 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -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 diff --git a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties index fb6f92a7a..7b01db1b0 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties @@ -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 diff --git a/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties b/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties index 452d43019..792fbb632 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties @@ -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 diff --git a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties index 66a54ee5f..fe3b5e59d 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties @@ -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=반전 diff --git a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties index df71de212..ca4c72945 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties @@ -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 diff --git a/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties b/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties index fe811776b..e6eb7be48 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties @@ -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=Инвертировать diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties index 80ec57e84..9a1f06c5c 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties @@ -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=反选 diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties index 05bc0312c..235f930cf 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties @@ -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=反轉選取