From f03114534c2332d46fd130c3fcb1803ba234b44c Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Fri, 30 Jan 2026 09:44:44 -0500 Subject: [PATCH] Fixed issue with FlatLafs that caused intermittent icon painting in the 'more tabs' popup --- .../plugin/core/programtree/ViewPanel.java | 3 +++ .../core/decompile/DecompilerProvider.java | 7 ++++- .../plugin/core/functiongraph/FGProvider.java | 27 +++++++++++-------- .../src/main/java/docking/ComponentNode.java | 12 ++++++--- .../tabbedpane/DockingTabRenderer.java | 14 +++++++--- .../framework/main/ProjectDataPanel.java | 2 ++ 6 files changed, 46 insertions(+), 19 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/programtree/ViewPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/programtree/ViewPanel.java index c7e56cb09e..00470f4919 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/programtree/ViewPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/programtree/ViewPanel.java @@ -90,6 +90,7 @@ class ViewPanel extends JPanel implements ChangeListener { tabbedPane.insertTab(name, null, viewComponent, null, insertIndex); tabbedPane.setTabComponentAt(insertIndex, new DockingTabRenderer(tabbedPane, name, name, e -> closeView(getViewProviderForComponent(viewComponent), true))); + tabbedPane.setTitleAt(insertIndex, name); } finally { tabbedPane.addChangeListener(this); @@ -252,6 +253,7 @@ class ViewPanel extends JPanel implements ChangeListener { if (c == vps.getViewComponent()) { DockingTabRenderer renderer = (DockingTabRenderer) tabbedPane.getTabComponentAt(i); renderer.setTitle(viewName, viewName); + tabbedPane.setTitleAt(i, viewName); break; } } @@ -465,6 +467,7 @@ class ViewPanel extends JPanel implements ChangeListener { DockingTabRenderer renderer = (DockingTabRenderer) tabbedPane.getTabComponentAt(selectedIndex); renderer.setTitle(newName, newName); + tabbedPane.setTitleAt(selectedIndex, newName); map.remove(oldName); map.put(newName, vps); } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java index 65e25e3082..37cf83d3ba 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java @@ -751,16 +751,21 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter Function function = controller.getDecompileData().getFunction(); String programName = (program != null) ? program.getDomainFile().getName() : ""; String title = "Decompiler"; + String functionName = "No Function"; + String tabText = "Decompiler"; String subTitle = ""; if (function != null) { - title = "Decompile: " + function.getName(); + functionName = function.getName(); + title = "Decompile: " + functionName; subTitle = " (" + programName + ")"; } if (!isConnected()) { title = "[" + title + "]"; + tabText = "[" + functionName + "]"; } setTitle(title); setSubTitle(subTitle); + setTabText(tabText); } private void initializeDecompilerOptions() { diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGProvider.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGProvider.java index b2ae0923a0..c182a9c692 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGProvider.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGProvider.java @@ -29,7 +29,6 @@ import docking.action.*; import docking.options.OptionsService; import docking.widgets.fieldpanel.FieldPanel; import edu.uci.ics.jung.graph.Graph; -import generic.stl.Pair; import generic.theme.GIcon; import ghidra.app.context.ListingActionContext; import ghidra.app.nav.*; @@ -426,41 +425,47 @@ public class FGProvider extends VisualGraphComponentProvider result = getTitleFromGraphData("Function Graph"); - String title = result.first; - String subTitle = result.second; + GraphTitle graphTitle = getTitleFromGraphData("Function Graph"); + String title = graphTitle.title; + String subTitle = graphTitle.subTitle; + String tabText = "Function Graph"; if (!isConnected()) { title = "[" + title + "]"; + tabText = "[" + graphTitle.functionName + "]"; } setTitle(title); setSubTitle(subTitle); + setTabText(tabText); } - private Pair getTitleFromGraphData(String title) { + private GraphTitle getTitleFromGraphData(String title) { FGData graphData = controller.getFunctionGraphData(); - Pair result = new Pair<>(title, ""); if (graphData == null) { - return result; + return new GraphTitle(title, "", ""); } Function function = graphData.getFunction(); if (function == null) { - return result; + return new GraphTitle(title, "", ""); } FunctionGraph functionGraph = graphData.getFunctionGraph(); Graph graph = functionGraph; - String first = "Function Graph"; + String functionName = function.getName(); String programName = (currentProgram != null) ? currentProgram.getDomainFile().getName() : ""; - String second = function.getName() + " - " + graph.getVertexCount() + " vertices (" + + String subTitle = functionName + " - " + graph.getVertexCount() + " vertices (" + programName + ")"; - return new Pair<>(first, second); + return new GraphTitle(title, subTitle, functionName); + } + + private record GraphTitle(String title, String subTitle, String functionName) { + } void doSetProgram(Program newProgram) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/ComponentNode.java b/Ghidra/Framework/Docking/src/main/java/docking/ComponentNode.java index 8b747f2391..5ddfd6fa9b 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/ComponentNode.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/ComponentNode.java @@ -318,10 +318,11 @@ class ComponentNode extends Node { c.installDragDropTarget(tabbedPane); tabbedPane.setTabComponentAt(i, tabRenderer); + Icon icon = placeholder.getIcon(); - if (icon != null) { - tabRenderer.setIcon(icon); - } + tabbedPane.setTitleAt(i, tabText); + tabbedPane.setIconAt(i, icon); + tabRenderer.setIcon(icon); if (placeholder == top) { activeIndex = i; @@ -536,11 +537,14 @@ class ComponentNode extends Node { } DockingTabRenderer renderer = (DockingTabRenderer) pane.getTabComponentAt(index); - renderer.setIcon(placeholder.getIcon()); + Icon icon = placeholder.getIcon(); + renderer.setIcon(icon); + pane.setIconAt(index, icon); String tabText = placeholder.getTabText(); String fullTitle = placeholder.getTitle(); renderer.setTitle(tabText, fullTitle); + pane.setTitleAt(index, tabText); } public void iconChanged(ComponentPlaceholder placeholder) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tabbedpane/DockingTabRenderer.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tabbedpane/DockingTabRenderer.java index 494ad54871..051ed7c410 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tabbedpane/DockingTabRenderer.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tabbedpane/DockingTabRenderer.java @@ -146,9 +146,17 @@ public class DockingTabRenderer extends JPanel { // don't include both when the tab text is a subset of the title titleLabel.setToolTipText(fullTitle); } - else { - // both are different, include both - titleLabel.setToolTipText("" + tabText + " - [" + fullTitle + "]"); + else { // both are different, include both + + // Guilty Knowledge: we know that some providers use '[]' for the title and tab text + // of disconnected providers. + // + // We would like to use the '[]' characters to separate the tab text from the title. + // Strip off the client brackets and use ours for the title part of the tooltip. + String rawTabText = tabText.replaceAll("\\[", "").replaceAll("\\]", ""); + String rawTitle = fullTitle.replaceAll("\\[", "").replaceAll("\\]", ""); + String html = "%s - [%s]".formatted(rawTabText, rawTitle); + titleLabel.setToolTipText(html); } } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ProjectDataPanel.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ProjectDataPanel.java index 5ea27dbdd4..8108910286 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ProjectDataPanel.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ProjectDataPanel.java @@ -144,6 +144,7 @@ class ProjectDataPanel extends JSplitPane implements ProjectViewListener { int index = readOnlyTab.indexOfComponent(dtp); readOnlyTab.setTabComponentAt(index, new DockingTabRenderer(readOnlyTab, viewName, viewName, e -> viewRemoved(dtp, getProjectURL(dtp), true))); + readOnlyTab.setTitleAt(index, viewName); readOnlyViews.put(view, dtp); } catch (Exception e) { @@ -232,6 +233,7 @@ class ProjectDataPanel extends JSplitPane implements ProjectViewListener { int index = readOnlyTab.indexOfComponent(newPanel); readOnlyTab.setTabComponentAt(index, new DockingTabRenderer(readOnlyTab, viewName, viewName, e -> viewRemoved(newPanel, getProjectURL(newPanel), true))); + readOnlyTab.setTitleAt(index, viewName); readOnlyTab.setSelectedIndex(0); readOnlyViews.put(projectData.getProjectLocator(), newPanel); setViewsVisible(true);