mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-25 02:26:13 +08:00
Merge remote-tracking branch 'origin/GP-6323-dragonmacher-program-tabs-focus--SQUASHED'
This commit is contained in:
+4
-2
@@ -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();
|
||||
}
|
||||
|
||||
+14
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
+49
-5
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+14
-2
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+54
-7
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -41,25 +41,28 @@ public class GTab<T> 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<T> tabPanel;
|
||||
private T value;
|
||||
private boolean selected;
|
||||
private JLabel closeLabel;
|
||||
private JLabel nameLabel;
|
||||
private boolean isSelected;
|
||||
|
||||
GTab(GTabPanel<T> 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<T> 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<T> 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<T> 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<T> 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<T> extends JPanel {
|
||||
tabPanel.closeTab(value);
|
||||
return;
|
||||
}
|
||||
if (!selected) {
|
||||
tabPanel.selectTab(value);
|
||||
}
|
||||
|
||||
tabPanel.selectTab(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -51,6 +51,7 @@ import utility.function.Dummy;
|
||||
*/
|
||||
public class GTabPanel<T> extends JPanel {
|
||||
|
||||
private boolean isActive;
|
||||
private T selectedValue;
|
||||
private T highlightedValue;
|
||||
private boolean ignoreFocusLost;
|
||||
@@ -117,6 +118,7 @@ public class GTabPanel<T> extends JPanel {
|
||||
@Override
|
||||
public void focusGained(FocusEvent e) {
|
||||
updateTabColors();
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -126,8 +128,9 @@ public class GTabPanel<T> extends JPanel {
|
||||
}
|
||||
|
||||
highlightedValue = null;
|
||||
updateAccessibleName();
|
||||
updateTabColors();
|
||||
updateAccessibleName();
|
||||
repaint();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -184,6 +187,16 @@ public class GTabPanel<T> extends JPanel {
|
||||
}
|
||||
}
|
||||
|
||||
public Color getSelectedTabColor() {
|
||||
|
||||
if (selectedValue == null) {
|
||||
return GTab.BG_COLOR_UNSELECTED;
|
||||
}
|
||||
|
||||
GTab<T> 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<T> 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<T> 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<T> oldTab = getTab(oldValue);
|
||||
if (oldTab != null) {
|
||||
oldTab.setSelected(false);
|
||||
}
|
||||
GTab<T> newTab = getTab(value);
|
||||
GTab<T> newTab = getTab(newValue);
|
||||
newTab.setSelected(true);
|
||||
}
|
||||
else {
|
||||
rebuildTabs();
|
||||
}
|
||||
|
||||
selectedTabConsumer.accept(value);
|
||||
if (oldValue != newValue) {
|
||||
selectedTabConsumer.accept(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -274,6 +337,7 @@ public class GTabPanel<T> extends JPanel {
|
||||
highlightedValue = value == selectedValue ? null : value;
|
||||
updateTabColors();
|
||||
updateAccessibleName();
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -535,7 +599,7 @@ public class GTabPanel<T> extends JPanel {
|
||||
|
||||
if (shouldShowTabs()) {
|
||||
setFocusable(true);
|
||||
setBorder(new GTabPanelBorder());
|
||||
setBorder(new GTabPanelBorder(this));
|
||||
populateTabs();
|
||||
}
|
||||
|
||||
@@ -567,24 +631,33 @@ public class GTabPanel<T> extends JPanel {
|
||||
removeAll();
|
||||
|
||||
// reserve space for the selected tab
|
||||
GTab<T> selectedTab = selectedValue != null ? new GTab<>(this, selectedValue, true) : null;
|
||||
GTab<T> 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<T> nextTab = isSelectedValue ? selectedTab : new GTab<>(this, value, false);
|
||||
GTab<T> 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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user