From 07b3e5a7c068142e2ff75f351793c65d57cee112 Mon Sep 17 00:00:00 2001 From: Skylot <118523+skylot@users.noreply.github.com> Date: Mon, 2 Feb 2026 21:09:15 +0000 Subject: [PATCH] fix(gui): limit tabs title length, fix tooltips (#2771) --- .../gui/ui/codearea/CodeContentPanel.java | 15 ----------- .../java/jadx/gui/ui/panel/ContentPanel.java | 17 ------------ .../java/jadx/gui/ui/tab/TabComponent.java | 25 ++++++++++++----- .../main/java/jadx/gui/ui/tab/TabbedPane.java | 16 +++++++++++ .../src/main/java/jadx/gui/utils/UiUtils.java | 27 +++++++++++++++++++ 5 files changed, 62 insertions(+), 38 deletions(-) diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeContentPanel.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeContentPanel.java index 0ddab125a..217de2d1b 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeContentPanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeContentPanel.java @@ -46,21 +46,6 @@ public final class CodeContentPanel extends AbstractCodeContentPanel implements return getCodeArea(); } - @Override - public String getTabTooltip() { - String s = node.getName(); - JNode n = (JNode) node.getParent(); - while (n != null) { - String name = n.getName(); - if (name == null) { - break; - } - s = name + '/' + s; - n = (JNode) n.getParent(); - } - return '/' + s; - } - @Override public void saveEditorViewState(EditorViewState viewState) { int caretPos = codePanel.getCodeArea().getCaretPosition(); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/panel/ContentPanel.java b/jadx-gui/src/main/java/jadx/gui/ui/panel/ContentPanel.java index 0d1673ba1..fcb14017b 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/panel/ContentPanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/panel/ContentPanel.java @@ -2,12 +2,10 @@ package jadx.gui.ui.panel; import javax.swing.JPanel; -import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import jadx.gui.settings.JadxSettings; -import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JNode; import jadx.gui.ui.MainWindow; import jadx.gui.ui.tab.TabbedPane; @@ -47,21 +45,6 @@ public abstract class ContentPanel extends JPanel { LOG.warn("ContentPanel.scrollToPos method not implemented, class: {}", getClass().getSimpleName()); } - /** - * Allows to show a tool tip on the tab e.g. for displaying a long path of the - * selected entry inside the APK file. - *

- * If null is returned no tool tip will be displayed. - */ - @Nullable - public String getTabTooltip() { - JClass jClass = getNode().getRootClass(); - if (jClass != null) { - return jClass.getFullName(); - } - return getNode().getName(); - } - public JadxSettings getSettings() { return tabbedPane.getMainWindow().getSettings(); } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/tab/TabComponent.java b/jadx-gui/src/main/java/jadx/gui/ui/tab/TabComponent.java index 8f9407f76..3db7e430d 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/tab/TabComponent.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/tab/TabComponent.java @@ -39,6 +39,8 @@ import jadx.gui.utils.ui.NodeLabel; public class TabComponent extends JPanel { private static final long serialVersionUID = -8147035487543610321L; + private static final int TAB_TITLE_MAX_LENGTH = 30; + private final TabbedPane tabbedPane; private final TabsController tabsController; private final ContentPanel contentPanel; @@ -81,7 +83,7 @@ public class TabComponent extends JPanel { icon = new OverlayIcon(node.getIcon()); label = new NodeLabel(buildTabTitle(node), node.disableHtml()); - String toolTip = contentPanel.getTabTooltip(); + String toolTip = contentPanel.getNode().getTooltip(); if (toolTip != null) { setToolTipText(toolTip); } @@ -208,11 +210,18 @@ public class TabComponent extends JPanel { } private String buildTabTitle(JNode node) { - String tabTitle; - if (node.getRootClass() != null) { - tabTitle = node.getRootClass().getName(); - } else { - tabTitle = node.makeLongStringHtml(); + String tabTitle = node.makeStringHtml(); + if (tabbedPane.tabWithTitleExists(tabTitle)) { + tabTitle = node.makeLongString(); + } + String newTabTitle = UiUtils.limitStringLength(tabTitle, TAB_TITLE_MAX_LENGTH); + if (!newTabTitle.equals(tabTitle)) { + if (tabbedPane.tabWithTitleExists(newTabTitle)) { + // shorter version also exist => make longer version (last try) + tabTitle = UiUtils.limitStringLength(tabTitle, (int) (TAB_TITLE_MAX_LENGTH * 1.2)); + } else { + tabTitle = newTabTitle; + } } if (node instanceof JEditableNode) { if (((JEditableNode) node).isChanged()) { @@ -361,4 +370,8 @@ public class TabComponent extends JPanel { public JNode getNode() { return contentPanel.getNode(); } + + public String getTabTitle() { + return label.getText(); + } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/tab/TabbedPane.java b/jadx-gui/src/main/java/jadx/gui/ui/tab/TabbedPane.java index ef5b1404e..6dff674b2 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/tab/TabbedPane.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/tab/TabbedPane.java @@ -315,6 +315,22 @@ public class TabbedPane extends JTabbedPane implements ITabStatesListener { return (TabComponent) component; } + public boolean tabWithTitleExists(String tabTitle) { + try { + for (int i = 0; i < getTabCount(); i++) { + Component component = getTabComponentAt(i); + if (component instanceof TabComponent) { + if (((TabComponent) component).getTabTitle().equals(tabTitle)) { + return true; + } + } + } + } catch (Exception e) { + LOG.warn("Failed to check tabs titles", e); + } + return false; + } + public void refresh(JNode node) { ContentPanel panel = getTabByNode(node); if (panel != null) { 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 69e618a73..5ff3bc831 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/UiUtils.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/UiUtils.java @@ -15,6 +15,7 @@ import java.awt.event.ActionEvent; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; +import java.io.File; import java.net.URL; import java.util.ArrayList; import java.util.List; @@ -158,6 +159,32 @@ public class UiUtils { return str.replace("<", "<").replace(">", ">"); } + private static final String CUT_STR_REPLACE = "..."; + + public static String limitStringLength(String str, int maxLength) { + if (str.length() <= maxLength) { + return str; + } + char fileSepChar = File.separatorChar; + int firstFileSep = str.indexOf(fileSepChar); + if (firstFileSep != -1) { + // remove path parts + int lastFileSep = str.lastIndexOf(fileSepChar); + if (firstFileSep == lastFileSep) { + // single path char => cut before + str = CUT_STR_REPLACE + str.substring(lastFileSep - 1); + } else { + // cut middle + str = str.substring(0, firstFileSep + 1) + CUT_STR_REPLACE + str.substring(lastFileSep); + } + if (str.length() < maxLength) { + return str; + } + } + // cut end by default + return str.substring(0, maxLength - CUT_STR_REPLACE.length()) + CUT_STR_REPLACE; + } + public static String typeStr(ArgType type) { if (type == null) { return "null";