diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerProgramLocationActionContext.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerProgramLocationActionContext.java index 23d8ec3503..a4138971ec 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerProgramLocationActionContext.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerProgramLocationActionContext.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -41,4 +41,6 @@ public interface DebuggerProgramLocationActionContext extends ActionContext { Address getAddress(); CodeUnit getCodeUnit(); + + boolean isActiveProgram(); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingActionContext.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingActionContext.java index 7808243c86..9839f8a2eb 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingActionContext.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingActionContext.java @@ -55,4 +55,18 @@ public class DebuggerListingActionContext extends ListingActionContext return super.hasSelection(); } + + /** + * Overridden to signal that this navigatable's program may not be the same as the globally + * active program. This is done to signal that this navigatable can supply default context. + * + * @return false + */ + @Override + public boolean isActiveProgram() { + // The active program for the debugger listing is the on in the 'main listing'. We cannot + // use Navigatable.isConnected() here, since that always returns false for the debugger. + DebuggerListingProvider dlp = (DebuggerListingProvider) getComponentProvider(); + return dlp.isMainListing(); + } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java index b3d0000cee..f282a99c09 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java @@ -33,8 +33,7 @@ import javax.swing.event.ChangeListener; import org.apache.commons.lang3.StringUtils; import org.jdom.Element; -import docking.ActionContext; -import docking.WindowPosition; +import docking.*; import docking.action.DockingAction; import docking.action.ToggleDockingAction; import docking.action.builder.ToggleActionBuilder; @@ -173,15 +172,15 @@ public class DebuggerListingProvider extends CodeViewerProvider { } @Override - protected void specChanged(LocationTrackingSpec spec) { + protected void specChanged(LocationTrackingSpec lts) { if (isMainListing()) { - plugin.firePluginEvent(new TrackingChangedPluginEvent(getName(), spec)); + plugin.firePluginEvent(new TrackingChangedPluginEvent(getName(), lts)); } updateTitle(); trackingLabel.setText(""); trackingLabel.setToolTipText(""); trackingLabel.setForeground(Colors.FOREGROUND); - trackingSpecChangeListeners.invoke().locationTrackingSpecChanged(spec); + trackingSpecChangeListeners.invoke().locationTrackingSpecChanged(lts); } @Override @@ -348,6 +347,8 @@ public class DebuggerListingProvider extends CodeViewerProvider { private long countAddressesInIndex; + private TabContextListener contextListener; + public DebuggerListingProvider(DebuggerListingPlugin plugin, FormatManager formatManager, boolean isConnected) { super(plugin, formatManager, isConnected); @@ -379,6 +380,9 @@ public class DebuggerListingProvider extends CodeViewerProvider { if (isConnected) { traceTabs = new DebuggerTraceTabPanel(plugin); + contextListener = new TabContextListener(); + DockingWindowManager dwm = tool.getWindowManager(); + dwm.addContextListener(contextListener); } else { traceTabs = null; @@ -1049,4 +1053,44 @@ public class DebuggerListingProvider extends CodeViewerProvider { } return new DebuggerByteSource(tool, current.getView(), current.getTarget(), readsMemTrait); } + + private class TabContextListener implements DockingContextListener { + + @Override + public void contextChanged(ActionContext localContext) { + + DockingWindowManager dwm = tool.getWindowManager(); + DebuggerProgramLocationActionContext defaultContext = + (DebuggerProgramLocationActionContext) dwm + .getDefaultActionContext(DebuggerProgramLocationActionContext.class); + Trace myTrace = null; + if (defaultContext != null) { + TraceProgramView tpv = defaultContext.getProgram(); + myTrace = tpv.getTrace(); + } + + if (!(localContext instanceof DebuggerProgramLocationActionContext dlac)) { + + // Future: We would like to make the debugger be the default context in this case, + // but we need a way to have the static and dynamic views to decide who is in charge. + // For now, assume it should always be the static non-debugger listing view, which + // means making the trace tabs inactive. + traceTabs.setActive(false); + return; + } + + TraceProgramView localTraceProgramView = dlac.getProgram(); + Trace localTrace = localTraceProgramView.getTrace(); + if (myTrace != localTrace || !dlac.isActiveProgram()) { + // A different trace is in the local context; deactivate out tabs. + traceTabs.setActive(false); + return; + } + + // Signal that the trace from our default context is the active trace. + traceTabs.setActive(true); + } + + } + } diff --git a/Ghidra/Features/Base/data/base.listing.theme.properties b/Ghidra/Features/Base/data/base.listing.theme.properties index a4f8a2e929..412b78d3a1 100644 --- a/Ghidra/Features/Base/data/base.listing.theme.properties +++ b/Ghidra/Features/Base/data/base.listing.theme.properties @@ -11,15 +11,6 @@ color.bg.listing.highlighter.middle.mouse = color.palette.yellow color.bg.listing.highlighter.scoped.read = color.palette.darkkhaki color.bg.listing.highlighter.scoped.write = color.palette.lightgreen -color.bg.listing.tabs.selected = [color]system.color.bg.selected.view -color.bg.listing.tabs.unselected = [color]system.color.bg.control -color.bg.listing.tabs.highlighted = color.palette.lightcornflowerblue -color.bg.listing.tabs.list = [color]system.color.bg.tooltip -color.bg.listing.tabs.more.tabs.hover = color.bg.listing.tabs.selected -color.fg.listing.tabs.text.selected = [color]system.color.fg.selected.view -color.fg.listing.tabs.text.unselected = color.fg -color.fg.listing.tabs.list = color.fg - color.bg.listing.header.active.field = color.palette.tan color.fg.listing.header.active.field = color.fg diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/context/NavigatableActionContext.java b/Ghidra/Features/Base/src/main/java/ghidra/app/context/NavigatableActionContext.java index 26999f71a3..98885382bf 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/context/NavigatableActionContext.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/context/NavigatableActionContext.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -48,4 +48,16 @@ public class NavigatableActionContext extends ProgramLocationActionContext public Navigatable getNavigatable() { return navigatable; } + + /** + * Overridden to signal that this navigatable's program may not be the same as the globally + * active program. This is done to signal that this navigatable can supply default context. + * + * @return false + */ + @Override + public boolean isActiveProgram() { + // signal that our program may be different than the active program + return navigatable.isConnected(); + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/context/ProgramActionContext.java b/Ghidra/Features/Base/src/main/java/ghidra/app/context/ProgramActionContext.java index d94d4a328a..5fd9cb90e7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/context/ProgramActionContext.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/context/ProgramActionContext.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -49,4 +49,16 @@ public class ProgramActionContext extends DefaultActionContext { public Program getProgram() { return program; } + + /** + * Returns true if the program in this context is the globally active program in the tool. This + * is generally true for all context. Some context providers may be working with a different + * program than the active program or they may be using the active program with restricted + * address views. In this latter case, this method should return false. + * @return true if the program is the active program; false means the program may not be the + * active program + */ + public boolean isActiveProgram() { + return true; + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/MultiTabPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/MultiTabPlugin.java index 4b0a6d2236..c24ca7e094 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/MultiTabPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/MultiTabPlugin.java @@ -19,14 +19,15 @@ import java.awt.event.KeyEvent; import javax.swing.*; -import docking.ActionContext; -import docking.DockingUtils; +import docking.*; import docking.action.*; import docking.action.builder.ActionBuilder; import docking.tool.ToolConstants; import docking.widgets.tab.GTabPanel; import generic.theme.GIcon; import ghidra.app.CorePluginPackage; +import ghidra.app.context.ListingActionContext; +import ghidra.app.context.ProgramActionContext; import ghidra.app.events.*; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.services.CodeViewerService; @@ -87,6 +88,7 @@ public class MultiTabPlugin extends Plugin private DockingAction goToPreviousProgramAction; private Timer selectHighlightedProgramTimer; + private TabContextListener contextListener = new TabContextListener(); public MultiTabPlugin(PluginTool tool) { super(tool); @@ -267,6 +269,9 @@ public class MultiTabPlugin extends Plugin progService = tool.getService(ProgramManager.class); cvService = tool.getService(CodeViewerService.class); cvService.setNorthComponent(tabPanel); + + DockingWindowManager dwm = tool.getWindowManager(); + dwm.addContextListener(contextListener); } private void initOptions() { @@ -294,11 +299,7 @@ public class MultiTabPlugin extends Plugin return EMPTY8_ICON; } - boolean removeProgram(Program program) { - return progService.closeProgram(program, false); - } - - void programSelected(Program program) { + private void programSelected(Program program) { if (program != progService.getCurrentProgram()) { progService.setCurrentProgram(program); cvService.requestFocus(); @@ -405,4 +406,50 @@ public class MultiTabPlugin extends Plugin tabPanel.refreshTab((Program) domainObj); } +//================================================================================================= +// Inner Classes +//================================================================================================= + + private class TabContextListener implements DockingContextListener { + + @Override + public void contextChanged(ActionContext localContext) { + + /* + Goal: We would like to have the program tabs paint as gray when the default context + is not being driven by the active program. This should happen whenever the + focus is in a component that has a program that can be used by tool actions. + + The tool uses the active program as a fallback/default for actions when the + local context will not work. When a local context has a program different + than the active program, then we want to signal to users visually that the + focused component is the one providing the program for the current context. + */ + + DockingWindowManager dwm = tool.getWindowManager(); + Program myProgram = null; + ProgramActionContext defaultContext = + (ListingActionContext) dwm.getDefaultActionContext(ProgramActionContext.class); + if (defaultContext != null) { + myProgram = defaultContext.getProgram(); + } + + if (!(localContext instanceof ProgramActionContext pac)) { + tabPanel.setActive(true); + return; + } + + Program localProgram = pac.getProgram(); + if (myProgram != localProgram || !pac.isActiveProgram()) { + // A different program is in the local context; deactivate out tabs. + tabPanel.setActive(false); + return; + } + + // Signal that the program from our default context is the active program. + tabPanel.setActive(true); + } + + } + } diff --git a/Ghidra/Framework/Docking/data/docking.theme.properties b/Ghidra/Framework/Docking/data/docking.theme.properties index f75e208971..a883784345 100644 --- a/Ghidra/Framework/Docking/data/docking.theme.properties +++ b/Ghidra/Framework/Docking/data/docking.theme.properties @@ -48,16 +48,20 @@ color.fg.fieldpanel = color.fg color.bg.fieldpanel.selection = color.bg.selection color.bg.fieldpanel.highlight = color.bg.highlight -color.bg.widget.tabs.selected = [color]system.color.bg.selected.view -color.fg.widget.tabs.selected = [color]system.color.fg.selected.view +color.bg.widget.tabs.selected.active = [color]system.color.bg.selected.view +color.bg.widget.tabs.selected.inactive = color.palette.gray color.bg.widget.tabs.unselected = [color]system.color.bg.control -color.fg.widget.tabs.unselected = color.fg + color.bg.widget.tabs.highlighted = color.palette.lightcornflowerblue -color.bg.widget.tabs.list = [color]system.color.bg.tooltip -color.bg.widget.tabs.more.tabs.hover = color.bg.widget.tabs.selected +color.fg.widget.tabs.selected.active = [color]system.color.fg.selected.view +color.fg.widget.tabs.selected.inactive = color.fg.widget.tabs.selected.active +color.fg.widget.tabs.unselected = color.fg color.fg.widget.tabs.list = color.fg +color.bg.widget.tabs.list = [color]system.color.bg.tooltip +color.bg.widget.tabs.more.tabs.hover = color.bg.widget.tabs.selected.active + color.bg.formatted.field.error = color.palette.lightcoral color.bg.formatted.field.editing = color.bg.filterfield @@ -189,17 +193,19 @@ font.wizard.border.title = sansserif-plain-10 [Dark Defaults] +color.bg.currentline = #393D64 // gray purple -color.fg.filterfield = color.palette.darkslategray +color.bg.filechooser.shortcut = [color]system.color.bg.view + +color.fg.filterfield = color.palette.darkslategray color.bg.find.highlight = #715E41 // brownish color.bg.find.highlight.active = #BC7474 // rosybrown color.bg.highlight = #67582A // olivish -color.bg.currentline = #393D64 // gray purple +color.bg.widget.tabs.selected.inactive = #696969 // dimgray -color.bg.filechooser.shortcut = [color]system.color.bg.view [CDE/Motif] diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tab/GTab.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tab/GTab.java index 5918a1802e..02cc66da8e 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tab/GTab.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tab/GTab.java @@ -41,25 +41,28 @@ public class GTab extends JPanel { private final static Icon EMPTY16_ICON = Icons.EMPTY_ICON; private final static Icon CLOSE_ICON = new GIcon("icon.widget.tabs.close"); private final static Icon HIGHLIGHT_CLOSE_ICON = new GIcon("icon.widget.tabs.close.highlight"); - private final static Color TAB_FG_COLOR = new GColor("color.fg.widget.tabs.unselected"); - private final static Color SELECTED_TAB_FG_COLOR = new GColor("color.fg.widget.tabs.selected"); - private final static Color HIGHLIGHTED_TAB_BG_COLOR = - new GColor("color.bg.widget.tabs.highlighted"); - final static Color TAB_BG_COLOR = new GColor("color.bg.widget.tabs.unselected"); - final static Color SELECTED_TAB_BG_COLOR = new GColor("color.bg.widget.tabs.selected"); + //@formatter:off + private final static Color FG_COLOR_UNSELECTED = new GColor("color.fg.widget.tabs.unselected"); + private final static Color FG_COLOR_SELECTED_INACTIVE = new GColor("color.fg.widget.tabs.selected.inactive"); + private final static Color FG_COLOR_SELECTED_ACTIVE = new GColor("color.fg.widget.tabs.selected.active"); + + private final static Color BG_COLOR_HIGHLIGHTED = new GColor("color.bg.widget.tabs.highlighted"); + final static Color BG_COLOR_UNSELECTED = new GColor("color.bg.widget.tabs.unselected"); + final static Color BG_COLOR_SELECTED_INACTIVE = new GColor("color.bg.widget.tabs.selected.inactive"); + final static Color BG_COLOR_SELECTED_ACTIVE = new GColor("color.bg.widget.tabs.selected.active"); + //@formatter:on private GTabPanel tabPanel; private T value; - private boolean selected; private JLabel closeLabel; private JLabel nameLabel; + private boolean isSelected; GTab(GTabPanel gTabPanel, T value, boolean selected) { super(new HorizontalLayout(10)); this.tabPanel = gTabPanel; this.value = value; - this.selected = selected; setBorder(selected ? SELECTED_TAB_BORDER : TAB_BORDER); @@ -87,8 +90,12 @@ public class GTab extends JPanel { return value; } - public void setSelected(boolean selected) { - this.selected = selected; + boolean isSelected() { + return isSelected; + } + + void setSelected(boolean selected) { + this.isSelected = selected; initializeTabColors(false); setBorder(selected ? SELECTED_TAB_BORDER : TAB_BORDER); } @@ -100,8 +107,8 @@ public class GTab extends JPanel { repaint(); } - void setHighlight(boolean b) { - initializeTabColors(b); + void setHighlight(boolean isHighlighted) { + initializeTabColors(isHighlighted); } private void installMouseListener(Container c, GTabMouseListener listener) { @@ -129,18 +136,30 @@ public class GTab extends JPanel { closeLabel.setBackground(bg); } - private Color getBackgroundColor(boolean isHighlighted) { + Color getBackgroundColor(boolean isHighlighted) { if (isHighlighted) { - return HIGHLIGHTED_TAB_BG_COLOR; + return BG_COLOR_HIGHLIGHTED; } - return selected ? SELECTED_TAB_BG_COLOR : TAB_BG_COLOR; + + if (!isSelected) { + return BG_COLOR_UNSELECTED; + } + + boolean isActive = tabPanel.isActive(); + return isActive ? BG_COLOR_SELECTED_ACTIVE : BG_COLOR_SELECTED_INACTIVE; } private Color getForegroundColor(boolean isHighlighted) { - if (isHighlighted || selected) { - return SELECTED_TAB_FG_COLOR; + if (isHighlighted) { + return FG_COLOR_SELECTED_ACTIVE; } - return TAB_FG_COLOR; + + if (!isSelected) { + return FG_COLOR_UNSELECTED; + } + + boolean isActive = tabPanel.isActive(); + return isActive ? FG_COLOR_SELECTED_ACTIVE : FG_COLOR_SELECTED_INACTIVE; } private class GTabMouseListener extends MouseAdapter { @@ -151,7 +170,7 @@ public class GTab extends JPanel { @Override public void mouseExited(MouseEvent e) { - closeLabel.setIcon(selected ? CLOSE_ICON : EMPTY16_ICON); + closeLabel.setIcon(isSelected ? CLOSE_ICON : EMPTY16_ICON); } @Override @@ -169,9 +188,8 @@ public class GTab extends JPanel { tabPanel.closeTab(value); return; } - if (!selected) { - tabPanel.selectTab(value); - } + + tabPanel.selectTab(value); } @Override diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tab/GTabPanel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tab/GTabPanel.java index 81b0053efb..821d7f53ec 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tab/GTabPanel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tab/GTabPanel.java @@ -51,6 +51,7 @@ import utility.function.Dummy; */ public class GTabPanel extends JPanel { + private boolean isActive; private T selectedValue; private T highlightedValue; private boolean ignoreFocusLost; @@ -117,6 +118,7 @@ public class GTabPanel extends JPanel { @Override public void focusGained(FocusEvent e) { updateTabColors(); + repaint(); } @Override @@ -126,8 +128,9 @@ public class GTabPanel extends JPanel { } highlightedValue = null; - updateAccessibleName(); updateTabColors(); + updateAccessibleName(); + repaint(); } }); } @@ -184,6 +187,16 @@ public class GTabPanel extends JPanel { } } + public Color getSelectedTabColor() { + + if (selectedValue == null) { + return GTab.BG_COLOR_UNSELECTED; + } + + GTab tab = getTab(selectedValue); + return tab.getBackgroundColor(false); + } + /** * Returns the currently selected tab. If the panel is not empty, there will always be a * selected tab. @@ -196,43 +209,93 @@ public class GTabPanel extends JPanel { /** * Returns the currently highlighted tab if a tab is highlighted. Note: the selected tab can * never be highlighted. - * @return the currently highlighted tab or null if no tab is highligted + * @return the currently highlighted tab or null if no tab is highlighted */ public T getHighlightedTabValue() { return highlightedValue; } /** - * Makes the tab for the given value be the selected tab. + * Sets this panel to be active. When active, this panel will paint differently than when + * inactive. + * @param isActive true if active + */ + public void setActive(boolean isActive) { + this.isActive = isActive; + doUpdateSelectedTab(selectedValue); + repaint(); + } + + /** + * True if this panel is active. + * @return true if active + */ + public boolean isActive() { + return isActive; + } + + /** + * Makes the tab for the given value be the selected and active tab. If the value is null, then + * the tabs will be rebuilt and no tab will be selected. + * * @param value the value whose tab is to be selected */ public void selectTab(T value) { - if (value == selectedValue) { - return; - } if (value != null && !allValues.contains(value)) { throw new IllegalArgumentException( - "Attempted to set selected value to non added value"); + "Attempted to set selected value to non-added value"); } + + if (isAlreadySelected(value)) { + return; + } + + // This method is called for things like user clicks. Anytime we select the tab from the + // API, also make this panel active. This is easier on clients in that they do not have to + // both select and activate this panel. + isActive = true; + doUpdateSelectedTab(value); + } + + private boolean isAlreadySelected(T value) { + if (value != selectedValue) { + return false; // different values; can't ignore + } + + if (value == null) { + return true; // new value and current value are null; nothing to update + } + + GTab oldTab = getTab(selectedValue); + if (oldTab == null) { + return false; + } + + return oldTab.isSelected(); + } + + private void doUpdateSelectedTab(T newValue) { + closeTabList(); highlightedValue = null; - T oldValue = selectedValue; - selectedValue = value; + selectedValue = newValue; if (isVisibleTab(selectedValue)) { GTab oldTab = getTab(oldValue); if (oldTab != null) { oldTab.setSelected(false); } - GTab newTab = getTab(value); + GTab newTab = getTab(newValue); newTab.setSelected(true); } else { rebuildTabs(); } - selectedTabConsumer.accept(value); + if (oldValue != newValue) { + selectedTabConsumer.accept(newValue); + } } /** @@ -274,6 +337,7 @@ public class GTabPanel extends JPanel { highlightedValue = value == selectedValue ? null : value; updateTabColors(); updateAccessibleName(); + repaint(); } /** @@ -535,7 +599,7 @@ public class GTabPanel extends JPanel { if (shouldShowTabs()) { setFocusable(true); - setBorder(new GTabPanelBorder()); + setBorder(new GTabPanelBorder(this)); populateTabs(); } @@ -567,24 +631,33 @@ public class GTabPanel extends JPanel { removeAll(); // reserve space for the selected tab - GTab selectedTab = selectedValue != null ? new GTab<>(this, selectedValue, true) : null; + GTab selectedTab = null; + if (selectedValue != null) { + selectedTab = new GTab<>(this, selectedValue, true); + } + availableWidth -= getParentedComponentWidth(selectedTab); boolean selectedTabAdded = false; for (T value : allValues) { boolean isSelectedValue = value == selectedValue; - GTab nextTab = isSelectedValue ? selectedTab : new GTab<>(this, value, false); + GTab nextTab = selectedTab; + if (!isSelectedValue) { + nextTab = new GTab<>(this, value, false); + } + int tabWidth = isSelectedValue ? 0 : getParentedComponentWidth(nextTab); if (tabWidth > availableWidth) { break; } + allTabs.add(nextTab); add(nextTab); selectedTabAdded |= isSelectedValue; availableWidth -= tabWidth; } - // if we ran out of space before adding the selected tab, add it now if it fits since + // If we ran out of space before adding the selected tab, add it now if it fits since // we always want the selected tab visible and we reserved space for it (unless there // wasn't space for any tabs) if (selectedTab != null && !selectedTabAdded && availableWidth >= 0) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tab/GTabPanelBorder.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tab/GTabPanelBorder.java index 6d40ffdbd5..dfce3fa90e 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tab/GTabPanelBorder.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tab/GTabPanelBorder.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -25,9 +25,11 @@ import javax.swing.border.EmptyBorder; public class GTabPanelBorder extends EmptyBorder { public static final int MARGIN_SIZE = 2; public static final int BOTTOM_SOLID_COLOR_SIZE = 3; + private GTabPanel tabPanel; - public GTabPanelBorder() { + public GTabPanelBorder(GTabPanel tabPanel) { super(0, 0, BOTTOM_SOLID_COLOR_SIZE, 0); + this.tabPanel = tabPanel; } /** @@ -40,9 +42,10 @@ public class GTabPanelBorder extends EmptyBorder { Color oldColor = g.getColor(); g.translate(x, y); - Color highlight = GTab.TAB_BG_COLOR.brighter().brighter(); + Color highlight = GTab.BG_COLOR_UNSELECTED.brighter().brighter(); - g.setColor(GTab.SELECTED_TAB_BG_COLOR); + Color color = tabPanel.getSelectedTabColor(); + g.setColor(color); g.fillRect(insets.left, h - insets.bottom, w - insets.right - 1, insets.bottom); g.setColor(highlight);