mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-06 03:50:02 +02:00
Merge remote-tracking branch 'origin/GP-4475_ghidragon_improvements_to_GTabPanel--SQUASHED'
This commit is contained in:
commit
37c798604a
10 changed files with 375 additions and 166 deletions
|
@ -718,7 +718,6 @@ src/main/resources/images/ThunkFunction.gif||GHIDRA||||END|
|
|||
src/main/resources/images/U.gif||GHIDRA||||END|
|
||||
src/main/resources/images/Unpackage.gif||GHIDRA||||END|
|
||||
src/main/resources/images/V.png||GHIDRA||||END|
|
||||
src/main/resources/images/VCRFastForward.gif||GHIDRA||||END|
|
||||
src/main/resources/images/akregator.png||Oxygen Icons - LGPL 3.0||||END|
|
||||
src/main/resources/images/application_double.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
|
||||
src/main/resources/images/applications-development.png||Oxygen Icons - LGPL 3.0||||END|
|
||||
|
@ -876,7 +875,6 @@ src/main/resources/images/page_white.png||FAMFAMFAM Icons - CC 2.5|||famfamfam s
|
|||
src/main/resources/images/page_white_c.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
|
||||
src/main/resources/images/pencil16.png||GHIDRA||||END|
|
||||
src/main/resources/images/pin.png||GHIDRA||||END|
|
||||
src/main/resources/images/pinkX.gif||GHIDRA||||END|
|
||||
src/main/resources/images/play_again.png||GHIDRA||||END|
|
||||
src/main/resources/images/preferences-system.png||Tango Icons - Public Domain|||tango|END|
|
||||
src/main/resources/images/question_zero.png||GHIDRA||||END|
|
||||
|
@ -916,7 +914,6 @@ src/main/resources/images/view-sort-descending.png||Oxygen Icons - LGPL 3.0|||Ox
|
|||
src/main/resources/images/window.png||GHIDRA||||END|
|
||||
src/main/resources/images/wizard.png||Nuvola Icons - LGPL 2.1|||nuvola|END|
|
||||
src/main/resources/images/x-office-document-template.png||Tango Icons - Public Domain|||tango icon set|END|
|
||||
src/main/resources/images/x.gif||GHIDRA||||END|
|
||||
src/main/resources/images/xor.png||GHIDRA||||END|
|
||||
src/main/resources/pcodetest/chunk1.hinc||GHIDRA||||END|
|
||||
src/main/resources/pcodetest/chunk2.hinc||GHIDRA||||END|
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
<H2>To Close a Program File</H2>
|
||||
|
||||
<OL type="1">
|
||||
<LI>From the Tool menu, select <B>File<IMG src="help/shared/arrow.gif" border="0">
|
||||
<LI>From the Tool menu, select <B>File<IMG alt="" src="help/shared/arrow.gif" border="0">
|
||||
Close</B></LI>
|
||||
|
||||
<LI>If changes were made to the program and they haven't been saved yet, the <I>Save
|
||||
|
@ -35,7 +35,7 @@
|
|||
</OL>
|
||||
|
||||
<P align="center"><BR>
|
||||
<IMG src="images/SaveProgram.png" border="0"></P>
|
||||
<IMG alt="" src="images/SaveProgram.png" border="0"></P>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>The buttons in the <I>Save Program?</I> dialog perform the following functions.</P>
|
||||
|
@ -49,39 +49,50 @@
|
|||
<LI><B>Cancel</B> - Leaves the program open in the current tool without any changes being
|
||||
saved.</LI>
|
||||
</UL>
|
||||
</BLOCKQUOTE>
|
||||
</BLOCKQUOTE><A name="Close_Program"></A>
|
||||
|
||||
<P align="left"><IMG border="0" src="help/shared/tip.png"> If the listing
|
||||
window is open and multiple programs are open, the program names are displayed on tabs across
|
||||
the top of the listing window. Programs can be closed by selecting the appropriate tab
|
||||
and pressing the corresponding "x" button.</P>
|
||||
<P align="left"><IMG alt="" border="0" src="help/shared/tip.png"> If the
|
||||
listing window is open and multiple programs are open, the program names are displayed on tabs
|
||||
across the top of the listing window. Programs can be closed by selecting the appropriate
|
||||
tab and pressing the corresponding "x" button or right-clicking on the tab and choosing the
|
||||
<B>close</B> menu option.</P>
|
||||
|
||||
<P align="center"><IMG border="0" src="images/ClosedTab.png"></P>
|
||||
<P align="center"><IMG alt="" border="0" src="images/ClosedTab.png"></P>
|
||||
|
||||
<H2><A name="Close_All"></A>To Close All Programs</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<OL type="1">
|
||||
<LI>From the Tool menu, select <B>File<IMG src="help/shared/arrow.gif" border="0"> Close
|
||||
All</B></LI>
|
||||
<LI>From the Tool menu, select <B>File<IMG alt="" src="help/shared/arrow.gif" border="0">
|
||||
Close All</B></LI>
|
||||
|
||||
<LI>For each program that was changed, the <I>Save Program?</I> dialog appears.</LI>
|
||||
</OL>
|
||||
|
||||
<P> </P>
|
||||
|
||||
<P align="left"><IMG alt="" border="0" src="help/shared/tip.png"> If the
|
||||
listing window is open and multiple programs are open, you can also close all programs by
|
||||
right-clicking on any tab and choosing the <B>Close All</B> menu option.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2><A name="Close_Others"></A>To Close All Programs Other Than The Current Program</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<OL type="1">
|
||||
<LI>From the Tool menu, select <B>File<IMG src="help/shared/arrow.gif" border="0"> Close
|
||||
Others</B></LI>
|
||||
<LI>From the Tool menu, select <B>File<IMG alt="" src="help/shared/arrow.gif" border="0">
|
||||
Close Others</B></LI>
|
||||
|
||||
<LI>For each of the other programs that was changed, the <I>Save Program?</I> dialog appears.</LI>
|
||||
<LI>For each of the other programs that was changed, the <I>Save Program?</I> dialog
|
||||
appears.</LI>
|
||||
</OL>
|
||||
|
||||
<P> </P>
|
||||
|
||||
<P align="left"><IMG alt="" border="0" src="help/shared/tip.png"> If the
|
||||
listing window is open and multiple programs are open, you can also close other programs by
|
||||
right-clicking on the tab of the program to keep and choosing the <B>Close Others</B> menu
|
||||
option.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<P align="left" class="providedbyplugin">Provided by: <I>Program Manager</I> Plugin</P>
|
||||
|
|
|
@ -293,6 +293,13 @@
|
|||
it loses focus. Use the Windows menu to make the undocked window visible.</TD>
|
||||
</TR>
|
||||
|
||||
<TR valign="middle">
|
||||
<TD valign="top" width="200" align="left">Goto Dialog Memory</TD>
|
||||
|
||||
<TD valign="top" align="left">Selected means that the last goto query will
|
||||
remain in the dialog the next time the dialog is invoked.</TD>
|
||||
</TR>
|
||||
|
||||
<TR valign="middle">
|
||||
<TD valign="top" width="200" align="left">Max Goto Entries</TD>
|
||||
|
||||
|
@ -301,11 +308,24 @@
|
|||
Label</A> dialog</TD>
|
||||
</TR>
|
||||
|
||||
<TR valign="middle">
|
||||
<TD valign="top" width="200" align="left">Max Navigation History Size</TD>
|
||||
|
||||
<TD valign="top" align="left">Max number of items to retain in the navigation
|
||||
history.</A> dialog</TD>
|
||||
</TR>
|
||||
|
||||
<TR valign="middle">
|
||||
<TD valign="top" width="200" align="left">Show Program Tabs Always</TD>
|
||||
|
||||
<TD valign="top" align="left">If selected, a program tab will be displayed even
|
||||
there is only one program open.</TD>
|
||||
</TR>
|
||||
<TR valign="middle">
|
||||
<TD valign="top" width="200" align="left">Subroutine Model</TD>
|
||||
|
||||
<TD valign="top" align="left">
|
||||
<P>Sets the default subroutine model. This setting is mainly used when creating
|
||||
Sets the default subroutine model. This setting is mainly used when creating
|
||||
call graphs. See <A href=
|
||||
"help/topics/BlockModel/Block_Model.htm#BlockModelDefinition">Block Models</A>
|
||||
for a description of the valid Models.</P>
|
||||
|
@ -370,16 +390,6 @@
|
|||
|
||||
<TH valign="top" align="left"><B>Description</B></TH>
|
||||
</TR>
|
||||
|
||||
<TR>
|
||||
<TD valign="top" width="200" align="left">Swing Look and Feel</TD>
|
||||
|
||||
<TD valign="top" align="left"><A name="Look_And_Feel"></A> This controls the
|
||||
appearance of the UI widgets for things such as colors and fonts. Each operating
|
||||
system provides a different default Look and Feel. Some of these work better than
|
||||
others.</TD>
|
||||
</TR>
|
||||
|
||||
<TR>
|
||||
<TD valign="top" width="200" align="left">Automatically Save Tools</TD>
|
||||
|
||||
|
@ -387,6 +397,24 @@
|
|||
when the tool is closed.</TD>
|
||||
</TR>
|
||||
|
||||
<TR>
|
||||
<TD valign="top" width="200" align="left">Default Tool Launch Mode</TD>
|
||||
|
||||
<TD valign="top" align="left">This controls if a new or already running tool should
|
||||
be used during default launch. Tool "reuse" mode will open selected file within a
|
||||
suitable running tool if one can be identified, otherwise a new tool will be
|
||||
launched.</TD>
|
||||
</TR>
|
||||
|
||||
<TR valign="middle">
|
||||
<TD valign="top" width="200" align="left">Docking Windows On Top</TD>
|
||||
|
||||
<TD valign="top" align="left">Selected means to show each undocked window on top of
|
||||
its parent tool window; the undocked window will not get "lost" behind its parent
|
||||
window. Unselected means that the undocked window may go behind other windows once
|
||||
it loses focus. Use the Windows menu to make the undocked window visible.</TD>
|
||||
</TR>
|
||||
|
||||
<TR>
|
||||
<TD valign="top" width="200" align="left">Restore Previous Project</TD>
|
||||
|
||||
|
@ -394,37 +422,26 @@
|
|||
opens the previously loaded project on startup.</TD>
|
||||
</TR>
|
||||
|
||||
<TR>
|
||||
<TD valign="top" width="200" align="left">Default Tool Launch Mode</TD>
|
||||
<TR>
|
||||
<TD valign="top" width="200" align="left">Show Tooltips</TD>
|
||||
|
||||
<TD valign="top" align="left">This controls if a new or already running tool should
|
||||
be used during default launch. Tool "reuse" mode will open selected file within a
|
||||
suitable running tool if one can be identified, otherwise a new tool will be
|
||||
launched.</TD>
|
||||
<TD valign="top" align="left">This controls whether or not Ghidra will show
|
||||
tooltips.</TD>
|
||||
</TR>
|
||||
<TR>
|
||||
<TD valign="top" width="200" align="left">Use DataBuffer Output Compression</TD>
|
||||
|
||||
<TD valign="top" align="left">This controls whether or not Ghidra will compress
|
||||
data being sent to the server.</TD>
|
||||
</TR>
|
||||
<TR>
|
||||
<TD valign="top" width="200" align="left">Use Notification Animation</TD>
|
||||
|
||||
<TD valign="top" align="left">This controls whether or not Ghidra will use
|
||||
automations to provide visual feedback that something is happening, such as
|
||||
launching a tool.</TD>
|
||||
</TR>
|
||||
|
||||
<TR>
|
||||
<TD valign="top" width="200" align="left"><A name="Use_Inverted_Colors"></A>Use
|
||||
Inverted Colors</TD>
|
||||
|
||||
<TD valign="top" align="left">
|
||||
<P>This is a <B>prototype</B> feature that allows the user to invert each color
|
||||
of the UI. Doing this effectively creates a Dark Theme, which some users find
|
||||
less visually straining.</P>
|
||||
|
||||
<BLOCKQUOTE style="background-color: #FFF0E0; color: black;">
|
||||
<P><IMG alt="" border="0" src="help/shared/warning.png"> As a prototype
|
||||
feature, this feature has many known issues, including:</P>
|
||||
|
||||
<UL>
|
||||
<LI>Pre-generated content, such as images, icons and help files will have
|
||||
inverted colors,</LI>
|
||||
|
||||
<LI>Some color combinations will be difficult to read</LI>
|
||||
</UL>
|
||||
</BLOCKQUOTE>
|
||||
</TD>
|
||||
</TR>
|
||||
</TBODY>
|
||||
</TABLE>
|
||||
</CENTER>
|
||||
|
|
|
@ -100,4 +100,8 @@ public class ListingPanelContainer extends JPanel {
|
|||
add(northComponent, BorderLayout.NORTH);
|
||||
}
|
||||
}
|
||||
|
||||
public JComponent getNorthPanel() {
|
||||
return northComponent;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ import docking.widgets.fieldpanel.FieldPanel;
|
|||
import docking.widgets.fieldpanel.HoverHandler;
|
||||
import docking.widgets.fieldpanel.internal.FieldPanelCoordinator;
|
||||
import docking.widgets.fieldpanel.support.*;
|
||||
import docking.widgets.tab.GTabPanel;
|
||||
import generic.theme.GIcon;
|
||||
import ghidra.app.context.ListingActionContext;
|
||||
import ghidra.app.nav.ListingPanelContainer;
|
||||
|
@ -45,6 +46,7 @@ import ghidra.app.nav.LocationMemento;
|
|||
import ghidra.app.plugin.core.clipboard.CodeBrowserClipboardProvider;
|
||||
import ghidra.app.plugin.core.codebrowser.actions.*;
|
||||
import ghidra.app.plugin.core.codebrowser.hover.ListingHoverService;
|
||||
import ghidra.app.plugin.core.progmgr.ProgramTabActionContext;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.app.util.*;
|
||||
import ghidra.app.util.viewer.field.ListingField;
|
||||
|
@ -299,6 +301,17 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
|
|||
}
|
||||
return new OtherPanelContext(this, program);
|
||||
}
|
||||
|
||||
JComponent northPanel = decorationPanel.getNorthPanel();
|
||||
if (northPanel != null && northPanel.isAncestorOf((Component) source)) {
|
||||
if (northPanel instanceof GTabPanel tabPanel) {
|
||||
Program tabValue = (Program) tabPanel.getValueFor(event);
|
||||
if (tabValue != null) {
|
||||
return new ProgramTabActionContext(this, tabValue, tabPanel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return createContext(getContextForMarginPanels(listingPanel, event));
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import javax.swing.*;
|
|||
import docking.ActionContext;
|
||||
import docking.DockingUtils;
|
||||
import docking.action.*;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.tool.ToolConstants;
|
||||
import docking.widgets.tab.GTabPanel;
|
||||
import generic.theme.GIcon;
|
||||
|
@ -32,10 +33,13 @@ import ghidra.app.plugin.PluginCategoryNames;
|
|||
import ghidra.app.services.CodeViewerService;
|
||||
import ghidra.app.services.ProgramManager;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.options.OptionsChangeListener;
|
||||
import ghidra.framework.options.ToolOptions;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.bean.opteditor.OptionsVetoException;
|
||||
|
||||
/**
|
||||
* Plugin to show a "tab" for each open program; the selected tab is the activated program.
|
||||
|
@ -53,9 +57,10 @@ import ghidra.util.HelpLocation;
|
|||
eventsConsumed = { ProgramOpenedPluginEvent.class, ProgramClosedPluginEvent.class, ProgramActivatedPluginEvent.class, ProgramVisibilityChangePluginEvent.class }
|
||||
)
|
||||
//@formatter:on
|
||||
public class MultiTabPlugin extends Plugin implements DomainObjectListener {
|
||||
public class MultiTabPlugin extends Plugin implements DomainObjectListener, OptionsChangeListener {
|
||||
private final static Icon TRANSIENT_ICON = new GIcon("icon.plugin.programmanager.transient");
|
||||
private final static Icon EMPTY8_ICON = new GIcon("icon.plugin.programmanager.empty.small");
|
||||
private static final String SHOW_TABS_ALWAYS = "Show Program Tabs Always";
|
||||
|
||||
//
|
||||
// Unusual Code Alert!: We can't initialize these fields below because calling
|
||||
|
@ -82,12 +87,32 @@ public class MultiTabPlugin extends Plugin implements DomainObjectListener {
|
|||
|
||||
public MultiTabPlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
|
||||
createActions();
|
||||
}
|
||||
|
||||
private void createActions() {
|
||||
|
||||
new ActionBuilder("Close Program", getName())
|
||||
.popupMenuPath("Close")
|
||||
.helpLocation(new HelpLocation("ProgramManagerPlugin", "Close_Program"))
|
||||
.withContext(ProgramTabActionContext.class)
|
||||
.onAction(c -> progService.closeProgram(c.getProgram(), false))
|
||||
.buildAndInstall(tool);
|
||||
|
||||
new ActionBuilder("Close Other Programs", getName())
|
||||
.popupMenuPath("Close Others")
|
||||
.helpLocation(new HelpLocation("ProgramManagerPlugin", "Close_Others"))
|
||||
.withContext(ProgramTabActionContext.class)
|
||||
.onAction(c -> closeOtherPrograms(c.getProgram()))
|
||||
.buildAndInstall(tool);
|
||||
|
||||
new ActionBuilder("Close All Programs", getName())
|
||||
.popupMenuPath("Close All")
|
||||
.helpLocation(new HelpLocation("ProgramManagerPlugin", "Close_All"))
|
||||
.withContext(ProgramTabActionContext.class)
|
||||
.onAction(c -> progService.closeAllPrograms(false))
|
||||
.buildAndInstall(tool);
|
||||
|
||||
String firstGroup = "1";
|
||||
String secondGroup = "2";
|
||||
|
||||
|
@ -113,7 +138,7 @@ public class MultiTabPlugin extends Plugin implements DomainObjectListener {
|
|||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
// highlight the next tab
|
||||
nextProgramPressed();
|
||||
cycleNextProgram(true);
|
||||
}
|
||||
};
|
||||
goToNextProgramAction.setEnabled(false);
|
||||
|
@ -127,7 +152,7 @@ public class MultiTabPlugin extends Plugin implements DomainObjectListener {
|
|||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
// highlight the previous tab
|
||||
previousProgramPressed();
|
||||
cycleNextProgram(false);
|
||||
}
|
||||
};
|
||||
goToPreviousProgramAction.setEnabled(false);
|
||||
|
@ -164,6 +189,11 @@ public class MultiTabPlugin extends Plugin implements DomainObjectListener {
|
|||
tool.addAction(goToPreviousProgramAction);
|
||||
}
|
||||
|
||||
private void closeOtherPrograms(Program keepProgram) {
|
||||
progService.setCurrentProgram(keepProgram);
|
||||
progService.closeOtherPrograms(false);
|
||||
}
|
||||
|
||||
private void updateActionEnablement() {
|
||||
// the next/previous actions should not be enabled if no tabs are hidden
|
||||
boolean enable = (tabPanel.getTabCount() > 1);
|
||||
|
@ -185,12 +215,11 @@ public class MultiTabPlugin extends Plugin implements DomainObjectListener {
|
|||
tabPanel.showTabList(!tabPanel.isShowingTabList());
|
||||
}
|
||||
|
||||
private void highlightNextProgram(boolean forwardDirection) {
|
||||
tabPanel.highlightNextTab(forwardDirection);
|
||||
}
|
||||
|
||||
private void selectHighlightedProgram() {
|
||||
tabPanel.selectTab(tabPanel.getHighlightedTabValue());
|
||||
Program highlightedTabValue = tabPanel.getHighlightedTabValue();
|
||||
if (highlightedTabValue != null) {
|
||||
tabPanel.selectTab(highlightedTabValue);
|
||||
}
|
||||
}
|
||||
|
||||
String getStringUsedInList(Program program) {
|
||||
|
@ -244,20 +273,15 @@ public class MultiTabPlugin extends Plugin implements DomainObjectListener {
|
|||
|
||||
KeyStroke stroke = KeyStroke.getKeyStrokeForEvent(e);
|
||||
if (stroke.equals(NEXT_TAB_KEYSTROKE)) {
|
||||
nextProgramPressed();
|
||||
cycleNextProgram(true);
|
||||
}
|
||||
else if (stroke.equals(PREVIOUS_TAB_KEYSTROKE)) {
|
||||
previousProgramPressed();
|
||||
cycleNextProgram(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void nextProgramPressed() {
|
||||
highlightNextProgram(true);
|
||||
selectHighlightedProgramTimer.restart();
|
||||
}
|
||||
|
||||
private void previousProgramPressed() {
|
||||
highlightNextProgram(false);
|
||||
private void cycleNextProgram(boolean forward) {
|
||||
tabPanel.highlightNextPreviousTab(forward);
|
||||
selectHighlightedProgramTimer.restart();
|
||||
}
|
||||
|
||||
|
@ -276,13 +300,32 @@ public class MultiTabPlugin extends Plugin implements DomainObjectListener {
|
|||
tabPanel.setIconFunction(p -> getIcon(p));
|
||||
tabPanel.setToolTipFunction(p -> getToolTip(p));
|
||||
tabPanel.setSelectedTabConsumer(p -> programSelected(p));
|
||||
tabPanel.setRemoveTabActionPredicate(p -> progService.closeProgram(p, false));
|
||||
tabPanel.setCloseTabConsumer(p -> progService.closeProgram(p, false));
|
||||
|
||||
initOptions();
|
||||
|
||||
progService = tool.getService(ProgramManager.class);
|
||||
cvService = tool.getService(CodeViewerService.class);
|
||||
cvService.setNorthComponent(tabPanel);
|
||||
}
|
||||
|
||||
private void initOptions() {
|
||||
ToolOptions options = tool.getOptions(ToolConstants.TOOL_OPTIONS);
|
||||
options.registerOption(SHOW_TABS_ALWAYS, false, null,
|
||||
"If true, program tabs will be displayed even if only one");
|
||||
|
||||
tabPanel.setShowTabsAlways(options.getBoolean(SHOW_TABS_ALWAYS, false));
|
||||
options.addOptionsChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
|
||||
Object newValue) throws OptionsVetoException {
|
||||
if (optionName.equals(SHOW_TABS_ALWAYS)) {
|
||||
tabPanel.setShowTabsAlways((Boolean) newValue);
|
||||
}
|
||||
}
|
||||
|
||||
private Icon getIcon(Program program) {
|
||||
ProjectLocator projectLocator = program.getDomainFile().getProjectLocator();
|
||||
if (projectLocator != null && projectLocator.isTransient()) {
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.progmgr;
|
||||
|
||||
import java.awt.Component;
|
||||
|
||||
import docking.ComponentProvider;
|
||||
import docking.DefaultActionContext;
|
||||
import ghidra.program.model.listing.Program;
|
||||
|
||||
/**
|
||||
* Action context for program tabs
|
||||
*/
|
||||
public class ProgramTabActionContext extends DefaultActionContext {
|
||||
public ProgramTabActionContext(ComponentProvider provider, Program program, Component source) {
|
||||
super(provider, program, source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the program for the tab that was clicked on.
|
||||
* @return the program for the tab that was clicked on
|
||||
*/
|
||||
public Program getProgram() {
|
||||
return (Program) getContextObject();
|
||||
}
|
||||
}
|
|
@ -279,7 +279,6 @@ public class MultiTabPluginTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
builder.createMemory("test", "0x0", 100);
|
||||
Program p = doOpenProgram(builder.getProgram(), true);
|
||||
p.setTemporary(false); // we need to be notified of changes
|
||||
|
||||
// select notepad
|
||||
panel.selectTab(p);
|
||||
int transactionID = p.startTransaction("test");
|
||||
|
@ -622,6 +621,7 @@ public class MultiTabPluginTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
|
||||
// don't let focus issues hide the popup list
|
||||
panel.setIgnoreFocus(true);
|
||||
panel.setShowTabsAlways(true);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
|
|
@ -15,9 +15,11 @@
|
|||
*/
|
||||
package docking.widgets.tab;
|
||||
|
||||
import java.awt.Container;
|
||||
import java.awt.event.*;
|
||||
import java.util.*;
|
||||
import java.util.function.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.*;
|
||||
|
@ -59,9 +61,9 @@ public class GTabPanel<T> extends JPanel {
|
|||
private Function<T, String> nameFunction = v -> v.toString();
|
||||
private Function<T, Icon> iconFunction = Dummy.function();
|
||||
private Function<T, String> toolTipFunction = Dummy.function();
|
||||
private Predicate<T> removeTabPredicate = Dummy.predicate();
|
||||
private Consumer<T> selectedTabConsumer = Dummy.consumer();
|
||||
private Consumer<T> removedTabConsumer = Dummy.consumer();
|
||||
private Consumer<T> closeTabConsumer = t -> removeTab(t);
|
||||
private boolean showTabsAlways = true;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
@ -93,12 +95,15 @@ public class GTabPanel<T> extends JPanel {
|
|||
case KeyEvent.VK_SPACE:
|
||||
case KeyEvent.VK_ENTER:
|
||||
selectHighlightedValue();
|
||||
e.consume();
|
||||
break;
|
||||
case KeyEvent.VK_LEFT:
|
||||
highlightNextTab(false);
|
||||
highlightNextPreviousTab(false);
|
||||
e.consume();
|
||||
break;
|
||||
case KeyEvent.VK_RIGHT:
|
||||
highlightNextTab(true);
|
||||
highlightNextPreviousTab(true);
|
||||
e.consume();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -148,11 +153,11 @@ public class GTabPanel<T> extends JPanel {
|
|||
highlightedValue = null;
|
||||
// ensure there is a valid selected value
|
||||
if (value == selectedValue) {
|
||||
selectedValue = allValues.isEmpty() ? null : allValues.iterator().next();
|
||||
selectTab(null);
|
||||
}
|
||||
else {
|
||||
rebuildTabs();
|
||||
}
|
||||
|
||||
rebuildTabs();
|
||||
removedTabConsumer.accept(value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -162,12 +167,12 @@ public class GTabPanel<T> extends JPanel {
|
|||
public void removeTabs(Collection<T> values) {
|
||||
allValues.removeAll(values);
|
||||
|
||||
// ensure there is a valid selected value
|
||||
if (!allValues.contains(selectedValue)) {
|
||||
selectedValue = allValues.isEmpty() ? null : allValues.iterator().next();
|
||||
selectTab(null);
|
||||
}
|
||||
else {
|
||||
rebuildTabs();
|
||||
}
|
||||
|
||||
rebuildTabs();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -193,10 +198,7 @@ public class GTabPanel<T> extends JPanel {
|
|||
* @param value the value whose tab is to be selected
|
||||
*/
|
||||
public void selectTab(T value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
if (!allValues.contains(value)) {
|
||||
if (value != null && !allValues.contains(value)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Attempted to set selected value to non added value");
|
||||
}
|
||||
|
@ -288,12 +290,12 @@ public class GTabPanel<T> extends JPanel {
|
|||
}
|
||||
|
||||
/**
|
||||
* Moves the highlight the next or previous tab from the current highlight. If there is no
|
||||
* Moves the highlight to the next or previous tab from the current highlight. If there is no
|
||||
* current highlight, it will highlight the next or previous tab from the selected tab.
|
||||
* @param forward true moves the highlight to the right; otherwise move the highlight to the
|
||||
* left
|
||||
*/
|
||||
public void highlightNextTab(boolean forward) {
|
||||
public void highlightNextPreviousTab(boolean forward) {
|
||||
if (allValues.size() < 2) {
|
||||
return;
|
||||
}
|
||||
|
@ -347,20 +349,13 @@ public class GTabPanel<T> extends JPanel {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets the predicate that will be called before removing a tab via the gui close control. If
|
||||
* the predicate returns true, the tab will be removed, otherwise the remove will be cancelled.
|
||||
* @param removeTabPredicate the predicate called to decide if a tab value can be removed
|
||||
* Sets the predicate that will be called before removing a tab via the gui close control. Note
|
||||
* that that tab panel's default action is to remove the tab value, but if you set your own
|
||||
* consumer, you have the responsibility to remove the value.
|
||||
* @param closeTabConsumer the consumer called when the close gui control is clicked.
|
||||
*/
|
||||
public void setRemoveTabActionPredicate(Predicate<T> removeTabPredicate) {
|
||||
this.removeTabPredicate = removeTabPredicate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the consumer to be notified if a tab value is removed.
|
||||
* @param removedTabConsumer the consumer to be notified when tab values are removed
|
||||
*/
|
||||
public void setRemovedTabConsumer(Consumer<T> removedTabConsumer) {
|
||||
this.removedTabConsumer = removedTabConsumer;
|
||||
public void setCloseTabConsumer(Consumer<T> closeTabConsumer) {
|
||||
this.closeTabConsumer = closeTabConsumer;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -379,6 +374,33 @@ public class GTabPanel<T> extends JPanel {
|
|||
return tabList != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not tabs should be display when there is only one tab.
|
||||
* @param b true to show one tab; false collapses tab panel when only one tab exists
|
||||
*/
|
||||
public void setShowTabsAlways(boolean b) {
|
||||
showTabsAlways = b;
|
||||
rebuildTabs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the tab that generated the given mouse event. If the mouse event
|
||||
* is not from one of the tabs, then null is returned.
|
||||
* @param event the MouseEvent to get a value for
|
||||
* @return the value of the tab that generated the mouse event
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public T getValueFor(MouseEvent event) {
|
||||
Object source = event.getSource();
|
||||
if (source instanceof JLabel label) {
|
||||
Container parent = label.getParent();
|
||||
if (parent instanceof GTab gTab) {
|
||||
return (T) gTab.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void showTabList() {
|
||||
if (tabList != null) {
|
||||
return;
|
||||
|
@ -389,9 +411,7 @@ public class GTabPanel<T> extends JPanel {
|
|||
}
|
||||
|
||||
void closeTab(T value) {
|
||||
if (removeTabPredicate.test(value)) {
|
||||
removeTab(value);
|
||||
}
|
||||
closeTabConsumer.accept(value);
|
||||
}
|
||||
|
||||
private void selectHighlightedValue() {
|
||||
|
@ -465,30 +485,28 @@ public class GTabPanel<T> extends JPanel {
|
|||
private void doAddValue(T value) {
|
||||
Objects.requireNonNull(value);
|
||||
allValues.add(value);
|
||||
|
||||
// make the first added value selected, non-empty panels must always have a selected value
|
||||
if (allValues.size() == 1) {
|
||||
selectedValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
private void rebuildTabs() {
|
||||
allTabs.clear();
|
||||
removeAll();
|
||||
closeTabList();
|
||||
if (allValues.isEmpty()) {
|
||||
validate();
|
||||
if (!shouldShowTabs()) {
|
||||
revalidate();
|
||||
repaint();
|
||||
return;
|
||||
}
|
||||
|
||||
GTab<T> selectedTab = new GTab<T>(this, selectedValue, true);
|
||||
int availableWidth = getPanelWidth() - getTabWidth(selectedTab);
|
||||
|
||||
GTab<T> selectedTab = null;
|
||||
int availableWidth = getPanelWidth();
|
||||
if (selectedValue != null) {
|
||||
selectedTab = new GTab<T>(this, selectedValue, true);
|
||||
availableWidth -= getTabWidth(selectedTab);
|
||||
}
|
||||
createNonSelectedTabsForWidth(availableWidth);
|
||||
|
||||
// a negative available width means there wasn't even enough room for the selected value tab
|
||||
if (availableWidth >= 0) {
|
||||
if (selectedValue != null && availableWidth >= 0) {
|
||||
allTabs.add(getIndexToInsertSelectedValue(allTabs.size()), selectedTab);
|
||||
}
|
||||
|
||||
|
@ -504,24 +522,44 @@ public class GTabPanel<T> extends JPanel {
|
|||
}
|
||||
updateTabColors();
|
||||
updateAccessibleName();
|
||||
validate();
|
||||
revalidate();
|
||||
repaint();
|
||||
}
|
||||
|
||||
private boolean shouldShowTabs() {
|
||||
if (allValues.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (allValues.size() == 1 && !showTabsAlways) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateAccessibleName() {
|
||||
getAccessibleContext().setAccessibleName(getAccessibleName());
|
||||
}
|
||||
|
||||
private String getAccessibleName() {
|
||||
String panelName = tabTypeName + "Tab Panel";
|
||||
String getAccessibleName() {
|
||||
StringBuilder builder = new StringBuilder(tabTypeName);
|
||||
builder.append(" Tab Panel: ");
|
||||
if (allValues.isEmpty()) {
|
||||
return panelName + ": No Tabs";
|
||||
builder.append("No Tabs");
|
||||
return builder.toString();
|
||||
}
|
||||
if (selectedValue != null) {
|
||||
builder.append(getDisplayName(selectedValue));
|
||||
builder.append(" selected");
|
||||
}
|
||||
else {
|
||||
builder.append("No Selected Tab");
|
||||
}
|
||||
String accessibleName = panelName + ": " + getDisplayName(selectedValue) + "Selected";
|
||||
if (highlightedValue != null) {
|
||||
accessibleName += ": " + getDisplayName(highlightedValue) + " highlighted";
|
||||
builder.append(": ");
|
||||
builder.append(getDisplayName(highlightedValue));
|
||||
builder.append(" highlighted");
|
||||
}
|
||||
return accessibleName;
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private int getIndexToInsertSelectedValue(int maxIndex) {
|
||||
|
|
|
@ -21,7 +21,6 @@ import java.awt.BorderLayout;
|
|||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
|
@ -64,17 +63,29 @@ public class GTabPanelTest extends AbstractDockingTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testFirstTabIsSelectedByDefault() {
|
||||
assertEquals("One", getSelectedValue());
|
||||
public void testNoTabIsSelectedByDefault() {
|
||||
assertEquals(null, getSelectedValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSettingNoTabSelected() {
|
||||
|
||||
AtomicReference<String> selectedValue = new AtomicReference<String>();
|
||||
Consumer<String> c = s -> selectedValue.set(s);
|
||||
runSwing(() -> gTabPanel.setSelectedTabConsumer(c));
|
||||
setSelectedValue("One");
|
||||
assertEquals("One", selectedValue.get());
|
||||
setSelectedValue(null);
|
||||
assertEquals(null, selectedValue.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddValue() {
|
||||
assertEquals(3, getTabCount());
|
||||
assertEquals("One", getSelectedValue());
|
||||
assertEquals(null, getSelectedValue());
|
||||
addValue("Four");
|
||||
assertEquals(4, getTabCount());
|
||||
assertEquals("One", getSelectedValue());
|
||||
assertEquals(null, getSelectedValue());
|
||||
assertEquals("Four", getValue(3));
|
||||
}
|
||||
|
||||
|
@ -86,6 +97,7 @@ public class GTabPanelTest extends AbstractDockingTest {
|
|||
|
||||
@Test
|
||||
public void testSwitchToInvalidValue() {
|
||||
setSelectedValue("One");
|
||||
try {
|
||||
gTabPanel.selectTab("Four");
|
||||
fail("expected exception");
|
||||
|
@ -99,11 +111,12 @@ public class GTabPanelTest extends AbstractDockingTest {
|
|||
|
||||
@Test
|
||||
public void testCloseSelected() {
|
||||
setSelectedValue("One");
|
||||
assertEquals(3, getTabCount());
|
||||
assertEquals("One", getSelectedValue());
|
||||
removeTab("One");
|
||||
assertEquals(2, getTabCount());
|
||||
assertEquals("Two", getSelectedValue());
|
||||
assertNull(getSelectedValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -148,50 +161,76 @@ public class GTabPanelTest extends AbstractDockingTest {
|
|||
|
||||
@Test
|
||||
public void testRemovedConsumer() {
|
||||
AtomicReference<String> removedValue = new AtomicReference<String>();
|
||||
Consumer<String> c = s -> removedValue.set(s);
|
||||
runSwing(() -> gTabPanel.setRemovedTabConsumer(c));
|
||||
AtomicReference<String> closedValue = new AtomicReference<String>();
|
||||
Consumer<String> c = s -> closedValue.set(s);
|
||||
runSwing(() -> gTabPanel.setCloseTabConsumer(c));
|
||||
runSwing(() -> gTabPanel.closeTab("Two"));
|
||||
assertEquals("Two", removedValue.get());
|
||||
assertEquals("Two", closedValue.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetRemoveTabPredicateAcceptsRemove() {
|
||||
AtomicReference<String> removePredicateCallValue = new AtomicReference<String>();
|
||||
Predicate<String> p = s -> {
|
||||
removePredicateCallValue.set(s);
|
||||
return true;
|
||||
public void testSetRemoveTabConsumer() {
|
||||
AtomicReference<String> closedValueReference = new AtomicReference<String>();
|
||||
Consumer<String> c = s -> {
|
||||
closedValueReference.set(s);
|
||||
gTabPanel.removeTab(s);
|
||||
};
|
||||
runSwing(() -> gTabPanel.setRemoveTabActionPredicate(p));
|
||||
runSwing(() -> gTabPanel.setCloseTabConsumer(c));
|
||||
runSwing(() -> gTabPanel.closeTab("Two"));
|
||||
assertEquals("Two", removePredicateCallValue.get());
|
||||
assertEquals("Two", closedValueReference.get());
|
||||
assertEquals(2, getTabCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetRemoveTabPredicateRejectsRemove() {
|
||||
AtomicReference<String> removePredicateCallValue = new AtomicReference<String>();
|
||||
Predicate<String> p = s -> {
|
||||
removePredicateCallValue.set(s);
|
||||
return false;
|
||||
};
|
||||
runSwing(() -> gTabPanel.setRemoveTabActionPredicate(p));
|
||||
runSwing(() -> gTabPanel.closeTab("Two"));
|
||||
assertEquals("Two", removePredicateCallValue.get());
|
||||
assertEquals(3, getTabCount());
|
||||
public void testHighlightNext() {
|
||||
assertNull(getHighlightedValue());
|
||||
highlightNextTab(true);
|
||||
assertEquals("One", getHighlightedValue());
|
||||
highlightNextTab(true);
|
||||
assertEquals("Two", getHighlightedValue());
|
||||
highlightNextTab(false);
|
||||
assertEquals("One", getHighlightedValue());
|
||||
highlightNextTab(false);
|
||||
assertEquals("Three Three Three", getHighlightedValue());
|
||||
setSelectedValue("One");
|
||||
highlightNextTab(true);
|
||||
assertEquals("Two", getHighlightedValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHighlightNext() {
|
||||
assertNull(gTabPanel.getHighlightedTabValue());
|
||||
runSwing(() -> gTabPanel.highlightNextTab(true));
|
||||
assertEquals("Two", gTabPanel.getHighlightedTabValue());
|
||||
runSwing(() -> gTabPanel.highlightNextTab(true));
|
||||
assertEquals("Three Three Three", gTabPanel.getHighlightedTabValue());
|
||||
runSwing(() -> gTabPanel.highlightNextTab(false));
|
||||
assertEquals("Two", gTabPanel.getHighlightedTabValue());
|
||||
runSwing(() -> gTabPanel.highlightNextTab(false));
|
||||
assertNull(gTabPanel.getHighlightedTabValue());
|
||||
public void testGetAccessibleNameNoTabs() {
|
||||
removeTab("One");
|
||||
removeTab("Two");
|
||||
removeTab("Three Three Three");
|
||||
assertEquals("Test Tab Panel: No Tabs", gTabPanel.getAccessibleName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAccessibleNameNoTabSelected() {
|
||||
setSelectedValue(null);
|
||||
assertEquals("Test Tab Panel: No Selected Tab", gTabPanel.getAccessibleName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAccessiblNameTabSelected() {
|
||||
setSelectedValue("Two");
|
||||
assertEquals("Test Tab Panel: Two selected", gTabPanel.getAccessibleName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAccessiblNameNoTabSelectedAndTabHighighted() {
|
||||
setSelectedValue(null);
|
||||
highlightNextTab(true);
|
||||
assertEquals("Test Tab Panel: No Selected Tab: One highlighted",
|
||||
gTabPanel.getAccessibleName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAccessiblNameTabSelectedAndTabHighighted() {
|
||||
setSelectedValue("One");
|
||||
highlightNextTab(true);
|
||||
assertEquals("Test Tab Panel: One selected: Two highlighted",
|
||||
gTabPanel.getAccessibleName());
|
||||
}
|
||||
|
||||
private List<String> getHiddenTabs() {
|
||||
|
@ -210,6 +249,10 @@ public class GTabPanelTest extends AbstractDockingTest {
|
|||
runSwing(() -> gTabPanel.selectTab(value));
|
||||
}
|
||||
|
||||
private void highlightNextTab(boolean b) {
|
||||
runSwing(() -> gTabPanel.highlightNextPreviousTab(b));
|
||||
}
|
||||
|
||||
private void removeTab(String value) {
|
||||
runSwing(() -> gTabPanel.removeTab(value));
|
||||
}
|
||||
|
@ -222,6 +265,10 @@ public class GTabPanelTest extends AbstractDockingTest {
|
|||
return runSwing(() -> gTabPanel.getSelectedTabValue());
|
||||
}
|
||||
|
||||
private String getHighlightedValue() {
|
||||
return runSwing(() -> gTabPanel.getHighlightedTabValue());
|
||||
}
|
||||
|
||||
private String getValue(int i) {
|
||||
return runSwing(() -> gTabPanel.getTabValues().get(i));
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue