From bcd0c949dcd92341a05da86c4a33fc580e903c64 Mon Sep 17 00:00:00 2001 From: Yaroslav <43380144+MrIkso@users.noreply.github.com> Date: Sun, 25 May 2025 22:35:35 +0300 Subject: [PATCH] feat(gui): use list component for recent projects on start page (PR #2513) * feat(gui): use list component witch recent projects on start page * fix: spotless check * move classes into one package and some minor changes --------- Co-authored-by: Skylot <118523+skylot@users.noreply.github.com> --- .../src/main/java/jadx/gui/ui/MainWindow.java | 2 +- .../jadx/gui/ui/panel/StartPagePanel.java | 127 --------- .../gui/ui/startpage/RecentProjectItem.java | 51 ++++ .../RecentProjectListCellRenderer.java | 125 ++++++++ .../StartPageNode.java | 3 +- .../jadx/gui/ui/startpage/StartPagePanel.java | 267 ++++++++++++++++++ .../resources/i18n/Messages_de_DE.properties | 2 + .../resources/i18n/Messages_en_US.properties | 2 + .../resources/i18n/Messages_es_ES.properties | 2 + .../resources/i18n/Messages_id_ID.properties | 2 + .../resources/i18n/Messages_ko_KR.properties | 2 + .../resources/i18n/Messages_pt_BR.properties | 2 + .../resources/i18n/Messages_ru_RU.properties | 2 + .../resources/i18n/Messages_zh_CN.properties | 2 + .../resources/i18n/Messages_zh_TW.properties | 2 + 15 files changed, 463 insertions(+), 130 deletions(-) delete mode 100644 jadx-gui/src/main/java/jadx/gui/ui/panel/StartPagePanel.java create mode 100644 jadx-gui/src/main/java/jadx/gui/ui/startpage/RecentProjectItem.java create mode 100644 jadx-gui/src/main/java/jadx/gui/ui/startpage/RecentProjectListCellRenderer.java rename jadx-gui/src/main/java/jadx/gui/ui/{treenodes => startpage}/StartPageNode.java (91%) create mode 100644 jadx-gui/src/main/java/jadx/gui/ui/startpage/StartPagePanel.java diff --git a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java index 5cc9490ae..8a3693788 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -155,13 +155,13 @@ import jadx.gui.ui.panel.IssuesPanel; import jadx.gui.ui.panel.JDebuggerPanel; import jadx.gui.ui.panel.ProgressPanel; import jadx.gui.ui.popupmenu.RecentProjectsMenuListener; +import jadx.gui.ui.startpage.StartPageNode; import jadx.gui.ui.tab.EditorSyncManager; import jadx.gui.ui.tab.NavigationController; import jadx.gui.ui.tab.QuickTabsTree; import jadx.gui.ui.tab.TabbedPane; import jadx.gui.ui.tab.TabsController; import jadx.gui.ui.tab.dnd.TabDndController; -import jadx.gui.ui.treenodes.StartPageNode; import jadx.gui.ui.treenodes.SummaryNode; import jadx.gui.ui.treenodes.UndisplayedStringsNode; import jadx.gui.update.JadxUpdate; diff --git a/jadx-gui/src/main/java/jadx/gui/ui/panel/StartPagePanel.java b/jadx-gui/src/main/java/jadx/gui/ui/panel/StartPagePanel.java deleted file mode 100644 index c06b7a24d..000000000 --- a/jadx-gui/src/main/java/jadx/gui/ui/panel/StartPagePanel.java +++ /dev/null @@ -1,127 +0,0 @@ -package jadx.gui.ui.panel; - -import java.awt.BorderLayout; -import java.awt.Dimension; -import java.awt.Font; -import java.nio.file.Path; -import java.util.List; - -import javax.swing.BorderFactory; -import javax.swing.Box; -import javax.swing.BoxLayout; -import javax.swing.JButton; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.ScrollPaneConstants; -import javax.swing.border.Border; -import javax.swing.border.TitledBorder; - -import jadx.gui.settings.JadxSettings; -import jadx.gui.ui.MainWindow; -import jadx.gui.ui.tab.TabbedPane; -import jadx.gui.ui.treenodes.StartPageNode; -import jadx.gui.utils.Icons; -import jadx.gui.utils.NLS; - -public class StartPagePanel extends ContentPanel { - - public StartPagePanel(TabbedPane tabbedPane, StartPageNode node) { - super(tabbedPane, node); - MainWindow mainWindow = tabbedPane.getMainWindow(); - Font baseFont = mainWindow.getSettings().getFont(); - - JButton openFile = new JButton(NLS.str("file.open_title"), Icons.OPEN); - openFile.addActionListener(ev -> mainWindow.openFileDialog()); - - JButton openProject = new JButton(NLS.str("file.open_project"), Icons.OPEN_PROJECT); - openProject.addActionListener(ev -> mainWindow.openProjectDialog()); - - JPanel start = new JPanel(); - start.setBorder(sectionFrame(NLS.str("start_page.start"), baseFont)); - start.setLayout(new BoxLayout(start, BoxLayout.LINE_AXIS)); - start.add(openFile); - start.add(Box.createRigidArea(new Dimension(10, 0))); - start.add(openProject); - start.add(Box.createHorizontalGlue()); - - JPanel recentPanel = new JPanel(); - JScrollPane scrollPane = new JScrollPane(recentPanel); - scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); - scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); - scrollPane.setPreferredSize(new Dimension(400, 200)); - scrollPane.setBorder(BorderFactory.createEmptyBorder()); - - fillRecentPanel(recentPanel, scrollPane, mainWindow); - - JPanel recent = new JPanel(); - recent.setBorder(sectionFrame(NLS.str("start_page.recent"), baseFont)); - recent.setLayout(new BoxLayout(recent, BoxLayout.PAGE_AXIS)); - recent.add(scrollPane); - - JPanel center = new JPanel(); - center.setLayout(new BorderLayout(10, 10)); - center.add(start, BorderLayout.PAGE_START); - center.add(recent, BorderLayout.CENTER); - center.setMaximumSize(new Dimension(700, 600)); - center.setAlignmentX(CENTER_ALIGNMENT); - - setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS)); - setBorder(BorderFactory.createEmptyBorder(50, 50, 50, 50)); - add(Box.createVerticalGlue()); - add(center); - add(Box.createVerticalGlue()); - } - - private void fillRecentPanel(JPanel panel, JScrollPane scrollPane, MainWindow mainWindow) { - JadxSettings settings = mainWindow.getSettings(); - List recentProjects = settings.getRecentProjects(); - panel.removeAll(); - panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); - - Font baseFont = settings.getFont(); - Font font = baseFont.deriveFont(baseFont.getSize() - 1f); - for (Path path : recentProjects) { - JButton openBtn = new JButton(path.getFileName().toString()); - openBtn.setToolTipText(path.toAbsolutePath().toString()); - openBtn.setFont(font); - openBtn.setBorderPainted(false); - openBtn.addActionListener(ev -> mainWindow.open(path)); - - JButton removeBtn = new JButton(); - removeBtn.setIcon(Icons.CLOSE_INACTIVE); - removeBtn.setRolloverIcon(Icons.CLOSE); - removeBtn.setRolloverEnabled(true); - removeBtn.setFocusable(false); - removeBtn.setBorder(null); - removeBtn.setBorderPainted(false); - removeBtn.setContentAreaFilled(false); - removeBtn.setOpaque(true); - removeBtn.addActionListener(e -> { - mainWindow.getSettings().removeRecentProject(path); - fillRecentPanel(panel, scrollPane, mainWindow); - panel.revalidate(); - scrollPane.repaint(); - }); - JPanel linePanel = new JPanel(); - linePanel.setLayout(new BoxLayout(linePanel, BoxLayout.LINE_AXIS)); - linePanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - linePanel.add(openBtn); - linePanel.add(Box.createHorizontalGlue()); - linePanel.add(removeBtn); - - panel.add(linePanel); - } - panel.add(Box.createVerticalGlue()); - } - - private static Border sectionFrame(String title, Font font) { - TitledBorder titledBorder = BorderFactory.createTitledBorder(title); - titledBorder.setTitleFont(font.deriveFont(Font.BOLD, font.getSize() + 1)); - Border spacing = BorderFactory.createEmptyBorder(10, 10, 10, 10); - return BorderFactory.createCompoundBorder(titledBorder, spacing); - } - - @Override - public void loadSettings() { - } -} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/startpage/RecentProjectItem.java b/jadx-gui/src/main/java/jadx/gui/ui/startpage/RecentProjectItem.java new file mode 100644 index 000000000..4191dde66 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/startpage/RecentProjectItem.java @@ -0,0 +1,51 @@ +package jadx.gui.ui.startpage; + +import java.nio.file.Path; +import java.util.Objects; + +import jadx.api.plugins.utils.CommonFileUtils; + +/** + * Represents an item in the recent projects list. + */ +public class RecentProjectItem { + private final Path path; + + public RecentProjectItem(Path path) { + this.path = Objects.requireNonNull(path); + } + + public Path getPath() { + return path; + } + + public String getProjectName() { + return CommonFileUtils.removeFileExtension(path.getFileName().toString()); + } + + public String getAbsolutePath() { + return path.toAbsolutePath().toString(); + } + + @Override + public String toString() { + return getProjectName(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RecentProjectItem that = (RecentProjectItem) o; + return Objects.equals(path, that.path); + } + + @Override + public int hashCode() { + return Objects.hash(path); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/startpage/RecentProjectListCellRenderer.java b/jadx-gui/src/main/java/jadx/gui/ui/startpage/RecentProjectListCellRenderer.java new file mode 100644 index 000000000..e88151f6e --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/startpage/RecentProjectListCellRenderer.java @@ -0,0 +1,125 @@ +package jadx.gui.ui.startpage; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Rectangle; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.ListCellRenderer; +import javax.swing.UIManager; +import javax.swing.plaf.basic.BasicButtonUI; + +import jadx.gui.utils.Icons; + +public class RecentProjectListCellRenderer extends JPanel implements ListCellRenderer { + private static final long serialVersionUID = 5550591869239586857L; + + private final JLabel fileNameLabel; + private final JLabel pathLabel; + private final JButton removeProjectBtn; + + private final Color defaultBackground; + private final Color defaultForeground; + + private final Color selectedBackground; + private final Color selectedForeground; + + private Rectangle removeIconBounds; + + public RecentProjectListCellRenderer(Font baseFont) { + super(new BorderLayout(5, 0)); + setOpaque(true); + setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 5)); + + this.fileNameLabel = new JLabel(); + fileNameLabel.setFont(baseFont.deriveFont(Font.BOLD, baseFont.getSize())); + + this.pathLabel = new JLabel(); + pathLabel.setFont(baseFont.deriveFont(baseFont.getSize() - 2f)); + pathLabel.setForeground(UIManager.getColor("Label.disabledForeground")); + + JPanel textPanel = new JPanel(new BorderLayout()); + textPanel.setOpaque(false); + textPanel.add(fileNameLabel, BorderLayout.NORTH); + textPanel.add(pathLabel, BorderLayout.SOUTH); + + removeProjectBtn = new JButton(); + removeProjectBtn.setIcon(Icons.CLOSE_INACTIVE); + removeProjectBtn.setOpaque(false); + removeProjectBtn.setUI(new BasicButtonUI()); + removeProjectBtn.setContentAreaFilled(false); + removeProjectBtn.setFocusable(false); + removeProjectBtn.setBorder(null); + removeProjectBtn.setBorderPainted(false); + + add(textPanel, BorderLayout.CENTER); + add(removeProjectBtn, BorderLayout.EAST); + + defaultBackground = UIManager.getColor("List.background"); + defaultForeground = UIManager.getColor("List.foreground"); + + selectedBackground = UIManager.getColor("List.selectionBackground"); + selectedForeground = UIManager.getColor("List.selectionForeground"); + } + + @Override + public Component getListCellRendererComponent(JList list, + RecentProjectItem value, int index, boolean isSelected, boolean cellHasFocus) { + + fileNameLabel.setText(value.getProjectName()); + pathLabel.setText(value.getAbsolutePath()); + + boolean isThisRemoveButtonHovered = (index == StartPagePanel.hoveredRemoveBtnIndex); + removeProjectBtn.setIcon(isThisRemoveButtonHovered ? Icons.CLOSE : Icons.CLOSE_INACTIVE); + removeProjectBtn.setRolloverEnabled(isThisRemoveButtonHovered); + + if (isSelected) { + setBackground(selectedBackground); + fileNameLabel.setForeground(selectedForeground); + pathLabel.setForeground(selectedForeground.darker()); + removeProjectBtn.setForeground(selectedForeground); + } else { + setBackground(defaultBackground); + fileNameLabel.setForeground(defaultForeground); + pathLabel.setForeground(UIManager.getColor("Label.disabledForeground")); + removeProjectBtn.setForeground(defaultForeground); + } + + setToolTipText(value.getAbsolutePath()); + + return this; + } + + /** + * Overriding paint to calculate the bounds of the remove button. + * This is crucial for the MouseListener on the JList to determine if a click/hover was on the + * button. + */ + @Override + public void paint(Graphics g) { + super.paint(g); + // Ensure the button's layout is valid before getting bounds + removeProjectBtn.doLayout(); + // Calculate bounds of the remove button relative to this renderer panel + int x = getWidth() - removeProjectBtn.getWidth() - getBorder().getBorderInsets(this).right; + int y = (getHeight() - removeProjectBtn.getHeight()) / 2; + removeIconBounds = new Rectangle(x, y, removeProjectBtn.getWidth(), removeProjectBtn.getHeight()); + } + + /** + * Returns the bounds of the remove button within the renderer component's coordinate system. + * This is crucial for the MouseListener on the JList to determine if a click was on the icon. + * + * @return Rectangle representing the bounds of the remove icon. + */ + public Rectangle getRemoveIconBounds() { + return removeIconBounds; + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/treenodes/StartPageNode.java b/jadx-gui/src/main/java/jadx/gui/ui/startpage/StartPageNode.java similarity index 91% rename from jadx-gui/src/main/java/jadx/gui/ui/treenodes/StartPageNode.java rename to jadx-gui/src/main/java/jadx/gui/ui/startpage/StartPageNode.java index c61070893..db0e59a42 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/treenodes/StartPageNode.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/startpage/StartPageNode.java @@ -1,11 +1,10 @@ -package jadx.gui.ui.treenodes; +package jadx.gui.ui.startpage; import javax.swing.Icon; import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JNode; import jadx.gui.ui.panel.ContentPanel; -import jadx.gui.ui.panel.StartPagePanel; import jadx.gui.ui.tab.TabbedPane; import jadx.gui.utils.Icons; import jadx.gui.utils.NLS; diff --git a/jadx-gui/src/main/java/jadx/gui/ui/startpage/StartPagePanel.java b/jadx-gui/src/main/java/jadx/gui/ui/startpage/StartPagePanel.java new file mode 100644 index 000000000..05ab22b08 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/startpage/StartPagePanel.java @@ -0,0 +1,267 @@ +package jadx.gui.ui.startpage; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Rectangle; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseMotionAdapter; +import java.nio.file.Path; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.DefaultListModel; +import javax.swing.JButton; +import javax.swing.JList; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.ListSelectionModel; +import javax.swing.ScrollPaneConstants; +import javax.swing.SwingUtilities; +import javax.swing.border.Border; +import javax.swing.border.TitledBorder; + +import jadx.gui.settings.JadxSettings; +import jadx.gui.ui.MainWindow; +import jadx.gui.ui.panel.ContentPanel; +import jadx.gui.ui.tab.TabbedPane; +import jadx.gui.utils.Icons; +import jadx.gui.utils.NLS; + +public class StartPagePanel extends ContentPanel { + private static final long serialVersionUID = 2457805175218770732L; + + private final RecentProjectsJList recentList; + private final DefaultListModel recentListModel; + private final MainWindow mainWindow; + private final JadxSettings settings; + + public static int hoveredRemoveBtnIndex = -1; + + public StartPagePanel(TabbedPane tabbedPane, StartPageNode node) { + super(tabbedPane, node); + this.mainWindow = tabbedPane.getMainWindow(); + this.settings = mainWindow.getSettings(); + Font baseFont = settings.getFont(); + + JButton openFile = new JButton(NLS.str("file.open_title"), Icons.OPEN); + openFile.addActionListener(ev -> mainWindow.openFileDialog()); + + JButton openProject = new JButton(NLS.str("file.open_project"), Icons.OPEN_PROJECT); + openProject.addActionListener(ev -> mainWindow.openProjectDialog()); + + JPanel start = new JPanel(); + start.setBorder(sectionFrame(NLS.str("start_page.start"), baseFont)); + start.setLayout(new BoxLayout(start, BoxLayout.LINE_AXIS)); + start.add(openFile); + start.add(Box.createRigidArea(new Dimension(10, 0))); + start.add(openProject); + start.add(Box.createHorizontalGlue()); + + this.recentListModel = new DefaultListModel<>(); + this.recentList = new RecentProjectsJList(recentListModel); + this.recentList.setCellRenderer(new RecentProjectListCellRenderer(baseFont)); + this.recentList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + + JScrollPane scrollPane = new JScrollPane(recentList); + scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); + scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + scrollPane.setPreferredSize(new Dimension(400, 250)); + scrollPane.setBorder(BorderFactory.createEmptyBorder()); + + recentList.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + int index = recentList.locationToIndex(e.getPoint()); + if (index == -1) { + return; + } + + RecentProjectItem item = recentListModel.getElementAt(index); + if (item == null) { + return; + } + + RecentProjectListCellRenderer renderer = (RecentProjectListCellRenderer) recentList.getCellRenderer() + .getListCellRendererComponent(recentList, item, index, false, false); + + Rectangle cellBounds = recentList.getCellBounds(index, index); + if (cellBounds != null) { + int xInCell = e.getX() - cellBounds.x; + int yInCell = e.getY() - cellBounds.y; + + Rectangle removeIconBounds = renderer.getRemoveIconBounds(); + if (removeIconBounds != null && removeIconBounds.contains(xInCell, yInCell)) { + removeRecentProject(item.getPath()); + return; + } + } + + if (e.getClickCount() == 2 && SwingUtilities.isLeftMouseButton(e)) { + openRecentProject(item.getPath()); + } else if (SwingUtilities.isRightMouseButton(e)) { + recentList.setSelectedIndex(index); + showRecentProjectContextMenu(e); + } + } + }); + + recentList.addMouseMotionListener(new MouseMotionAdapter() { + @Override + public void mouseMoved(MouseEvent e) { + int oldHoveredRemoveBtnIndex = hoveredRemoveBtnIndex; + hoveredRemoveBtnIndex = -1; + + int currentCellIndex = recentList.locationToIndex(e.getPoint()); + + if (currentCellIndex != -1) { + RecentProjectItem item = recentListModel.getElementAt(currentCellIndex); + RecentProjectListCellRenderer renderer = (RecentProjectListCellRenderer) recentList.getCellRenderer() + .getListCellRendererComponent(recentList, item, currentCellIndex, recentList.isSelectedIndex(currentCellIndex), + false); + + Rectangle cellBounds = recentList.getCellBounds(currentCellIndex, currentCellIndex); + if (cellBounds != null) { + int xInCell = e.getX() - cellBounds.x; + int yInCell = e.getY() - cellBounds.y; + + Rectangle removeIconBounds = renderer.getRemoveIconBounds(); + if (removeIconBounds != null && removeIconBounds.contains(xInCell, yInCell)) { + hoveredRemoveBtnIndex = currentCellIndex; + } + } + } + + if (oldHoveredRemoveBtnIndex != hoveredRemoveBtnIndex) { + if (oldHoveredRemoveBtnIndex != -1) { + Rectangle bounds = recentList.getCellBounds(oldHoveredRemoveBtnIndex, oldHoveredRemoveBtnIndex); + if (bounds != null) { + recentList.repaint(bounds); + } + } + if (hoveredRemoveBtnIndex != -1) { + Rectangle bounds = recentList.getCellBounds(hoveredRemoveBtnIndex, hoveredRemoveBtnIndex); + if (bounds != null) { + recentList.repaint(bounds); + } + } + } + } + }); + + JPanel recent = new JPanel(); + recent.setBorder(sectionFrame(NLS.str("start_page.recent"), baseFont)); + recent.setLayout(new BoxLayout(recent, BoxLayout.PAGE_AXIS)); + recent.add(scrollPane); + + JPanel center = new JPanel(); + center.setLayout(new BorderLayout(10, 10)); + center.add(start, BorderLayout.PAGE_START); + center.add(recent, BorderLayout.CENTER); + center.setMaximumSize(new Dimension(700, 600)); + center.setAlignmentX(CENTER_ALIGNMENT); + + setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS)); + setBorder(BorderFactory.createEmptyBorder(50, 50, 50, 50)); + add(Box.createVerticalGlue()); + add(center); + add(Box.createVerticalGlue()); + + fillRecentProjectsList(); + } + + private void fillRecentProjectsList() { + recentListModel.clear(); + List recentPaths = settings.getRecentProjects(); + for (Path path : recentPaths) { + recentListModel.addElement(new RecentProjectItem(path)); + } + recentList.revalidate(); + recentList.repaint(); + } + + private void openRecentProject(Path path) { + mainWindow.open(path); + } + + private void removeRecentProject(Path path) { + settings.removeRecentProject(path); + fillRecentProjectsList(); + if (hoveredRemoveBtnIndex != -1 && hoveredRemoveBtnIndex >= recentListModel.size()) { + hoveredRemoveBtnIndex = -1; + } + } + + private void showRecentProjectContextMenu(MouseEvent e) { + JPopupMenu popupMenu = new JPopupMenu(); + RecentProjectItem selectedItem = recentList.getSelectedValue(); + + if (selectedItem != null) { + JMenuItem openItem = new JMenuItem(NLS.str("file.open_project")); + openItem.addActionListener(actionEvent -> openRecentProject(selectedItem.getPath())); + popupMenu.add(openItem); + + JMenuItem removeItem = new JMenuItem(NLS.str("start_page.list.delete_recent_project")); + removeItem.addActionListener(actionEvent -> removeRecentProject(selectedItem.getPath())); + popupMenu.add(removeItem); + } + + popupMenu.show(e.getComponent(), e.getX(), e.getY()); + } + + private static Border sectionFrame(String title, Font font) { + TitledBorder titledBorder = BorderFactory.createTitledBorder(title); + titledBorder.setTitleFont(font.deriveFont(Font.BOLD, font.getSize() + 1)); + Border spacing = BorderFactory.createEmptyBorder(10, 10, 10, 10); + return BorderFactory.createCompoundBorder(titledBorder, spacing); + } + + @Override + public void loadSettings() { + } + + /** + * Inner class: Custom JList to override getToolTipText method. + * This allows displaying specific tooltips based on mouse position within a cell. + */ + private static class RecentProjectsJList extends JList { + private static final long serialVersionUID = 1L; + + public RecentProjectsJList(DefaultListModel model) { + super(model); + } + + @Override + public String getToolTipText(MouseEvent event) { + int index = locationToIndex(event.getPoint()); + if (index == -1) { + return null; + } + + RecentProjectItem item = getModel().getElementAt(index); + if (item == null) { + return null; + } + + RecentProjectListCellRenderer renderer = (RecentProjectListCellRenderer) getCellRenderer() + .getListCellRendererComponent(this, item, index, isSelectedIndex(index), false); + + Rectangle cellBounds = getCellBounds(index, index); + if (cellBounds != null) { + int xInCell = event.getX() - cellBounds.x; + int yInCell = event.getY() - cellBounds.y; + + Rectangle removeIconBounds = renderer.getRemoveIconBounds(); + if (removeIconBounds != null && removeIconBounds.contains(xInCell, yInCell)) { + return NLS.str("start_page.list.delete_recent_project.tooltip"); + } + } + return item.getAbsolutePath(); + } + } +} 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 d17b1a238..9d82372d6 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties @@ -56,6 +56,8 @@ file.export_node=Datei exportieren start_page.title=Startseite start_page.start=Start start_page.recent=Aktuelle Projekte +#start_page.list.delete_recent_project=Delete project +#start_page.list.delete_recent_project.tooltip=Delete project from recent list tree.inputs_title=Eingaben tree.input_files=Dateien 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 886fb093d..f72738613 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -56,6 +56,8 @@ file.export_node=Export file start_page.title=Start page start_page.start=Start start_page.recent=Recent projects +start_page.list.delete_recent_project=Delete project +start_page.list.delete_recent_project.tooltip=Delete project from recent list tree.inputs_title=Inputs tree.input_files=Files 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 c48fccb10..100b006c8 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties @@ -56,6 +56,8 @@ file.exit=Salir #start_page.title=Start page #start_page.start=Start #start_page.recent=Recent projects +#start_page.list.delete_recent_project=Delete project +#start_page.list.delete_recent_project.tooltip=Delete project from recent list #tree.inputs_title=Inputs #tree.input_files=Files 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 abc6bf54c..737f1f6fd 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties @@ -56,6 +56,8 @@ file.exit=Keluar start_page.title=Halaman Awal start_page.start=Mulai start_page.recent=Proyek Terbaru +#start_page.list.delete_recent_project=Delete project +#start_page.list.delete_recent_project.tooltip=Delete project from recent list tree.inputs_title=Input tree.input_files=Berkas 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 c42567f57..e8e2e76a2 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties @@ -56,6 +56,8 @@ file.exit=나가기 start_page.title=페이지 시작 start_page.start=시작 start_page.recent=최근 프로젝트 +#start_page.list.delete_recent_project=Delete project +#start_page.list.delete_recent_project.tooltip=Delete project from recent list #tree.inputs_title=Inputs #tree.input_files=Files 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 545408578..d2a5f9a1c 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties @@ -56,6 +56,8 @@ file.exit=Sair start_page.title=Página inicial start_page.start=Começar start_page.recent=Projetos recentes +#start_page.list.delete_recent_project=Delete project +#start_page.list.delete_recent_project.tooltip=Delete project from recent list #tree.inputs_title=Inputs #tree.input_files=Files 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 908d6d29c..3dcbcd418 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties @@ -56,6 +56,8 @@ file.exit=Выход start_page.title=Начальная страница start_page.start=Начать start_page.recent=Недавние проекты +#start_page.list.delete_recent_project=Delete project +#start_page.list.delete_recent_project.tooltip=Delete project from recent list tree.inputs_title=Входные файлы tree.input_files=Файлы 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 cecccd736..7cb3e8cfb 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties @@ -56,6 +56,8 @@ file.export_node=导出文件 start_page.title=开始页面 start_page.start=开始 start_page.recent=最近项目 +#start_page.list.delete_recent_project=Delete project +#start_page.list.delete_recent_project.tooltip=Delete project from recent list tree.inputs_title=输入 tree.input_files=文件 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 9b0e3cf71..5776e0dfe 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties @@ -56,6 +56,8 @@ file.export_node=匯出檔案 start_page.title=開始頁面 start_page.start=開始 start_page.recent=近期專案 +#start_page.list.delete_recent_project=Delete project +#start_page.list.delete_recent_project.tooltip=Delete project from recent list tree.inputs_title=輸入 tree.input_files=檔案