mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-06 03:50:02 +02:00
Merge remote-tracking branch
'origin/GP-5646_ghidragon_drag_n_drop_program_tabs--SQUASHED' (Closes #8099)
This commit is contained in:
commit
f3d4ccbf2f
9 changed files with 192 additions and 28 deletions
|
@ -47,7 +47,13 @@
|
|||
window.</P>
|
||||
|
||||
<P>Those programs listed in bold are those that are hidden.</P>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<BLOCKQUOTE>
|
||||
<P><IMG src="help/shared/tip.png" alt="" border="0">The order of the tabs can be changed
|
||||
using drag-n-drop. Drag the tab that you wish to move and drop it on the tab where you
|
||||
like it to appear. </P>
|
||||
</BLOCKQUOTE>
|
||||
</BLOCKQUOTE>
|
||||
<H2>Navigation Actions</H2>
|
||||
<!-- Next and Previous -->
|
||||
|
||||
|
@ -118,7 +124,8 @@
|
|||
<P>To execute this action, from the Tool menu, select <B>Navigation<IMG src=
|
||||
"help/shared/arrow.gif" border="0">Go To Last Active Program</B>.</P>
|
||||
</BLOCKQUOTE>
|
||||
</BLOCKQUOTE>
|
||||
</BLOCKQUOTE>>
|
||||
|
||||
|
||||
<P align="left" class="providedbyplugin">Provided by: <I>Program Manager</I> Plugin</P>
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ import ghidra.framework.plugintool.util.PluginStatus;
|
|||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.bean.opteditor.OptionsVetoException;
|
||||
import help.Help;
|
||||
|
||||
/**
|
||||
* Plugin to show a "tab" for each open program; the selected tab is the activated program.
|
||||
|
@ -262,6 +263,8 @@ public class MultiTabPlugin extends Plugin implements DomainObjectListener, Opti
|
|||
tabPanel.setToolTipFunction(p -> getToolTip(p));
|
||||
tabPanel.setSelectedTabConsumer(p -> programSelected(p));
|
||||
tabPanel.setCloseTabConsumer(p -> progService.closeProgram(p, false));
|
||||
Help.getHelpService()
|
||||
.registerHelp(tabPanel, new HelpLocation("ProgramManagerPlugin", "Navigate_File"));
|
||||
|
||||
initOptions();
|
||||
|
||||
|
|
|
@ -553,7 +553,7 @@ public class MultiTabPluginTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
}
|
||||
|
||||
private void selectTab(Program p) {
|
||||
JPanel tab = runSwing(() -> panel.getTab(p));
|
||||
GTab<Program> tab = runSwing(() -> panel.getTab(p));
|
||||
Point point = runSwing(() -> tab.getLocationOnScreen());
|
||||
clickMouse(tab, MouseEvent.BUTTON1, point.x + 1, point.y + 1, 1, 0);
|
||||
assertEquals(p, getSelectedTabValue());
|
||||
|
|
|
@ -95,6 +95,7 @@ src/main/resources/images/mail-folder-outbox.png||Oxygen Icons - LGPL 3.0|||Oxyg
|
|||
src/main/resources/images/mail-receive.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
|
||||
src/main/resources/images/media-playback-start.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
|
||||
src/main/resources/images/menu16.gif||GHIDRA||reviewed||END|
|
||||
src/main/resources/images/move.png||GHIDRA||||END|
|
||||
src/main/resources/images/oxygen-edit-redo.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
|
||||
src/main/resources/images/page_code.png||FAMFAMFAM Icons - CC 2.5||||END|
|
||||
src/main/resources/images/page_excel.png||FAMFAMFAM Icons - CC 2.5||||END|
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
package docking.widgets.tab;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.Border;
|
||||
|
@ -32,7 +33,7 @@ import resources.Icons;
|
|||
*
|
||||
* @param <T> the type of the tab values
|
||||
*/
|
||||
class GTab<T> extends JPanel {
|
||||
public class GTab<T> extends JPanel {
|
||||
private final static Border TAB_BORDER = new GTabBorder(false);
|
||||
private final static Border SELECTED_TAB_BORDER = new GTabBorder(true);
|
||||
private static final String SELECTED_FONT_TABS_ID = "font.widget.tabs.selected";
|
||||
|
@ -67,6 +68,7 @@ class GTab<T> extends JPanel {
|
|||
nameLabel.setText(tabPanel.getDisplayName(value));
|
||||
nameLabel.setIcon(tabPanel.getValueIcon(value));
|
||||
nameLabel.setToolTipText(tabPanel.getValueToolTip(value));
|
||||
|
||||
Gui.registerFont(nameLabel, selected ? SELECTED_FONT_TABS_ID : FONT_TABS_ID);
|
||||
add(nameLabel, BorderLayout.WEST);
|
||||
|
||||
|
@ -76,8 +78,8 @@ class GTab<T> extends JPanel {
|
|||
closeLabel.setOpaque(true);
|
||||
add(closeLabel, BorderLayout.EAST);
|
||||
|
||||
installMouseListener(this, new GTabMouseListener());
|
||||
|
||||
GTabMouseListener listener = new GTabMouseListener();
|
||||
installMouseListener(this, listener);
|
||||
initializeTabColors(false);
|
||||
}
|
||||
|
||||
|
@ -85,6 +87,11 @@ class GTab<T> extends JPanel {
|
|||
return value;
|
||||
}
|
||||
|
||||
public void setSelected(boolean selected) {
|
||||
this.selected = selected;
|
||||
initializeTabColors(false);
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
nameLabel.setText(tabPanel.getDisplayName(value));
|
||||
nameLabel.setIcon(tabPanel.getValueIcon(value));
|
||||
|
@ -96,9 +103,10 @@ class GTab<T> extends JPanel {
|
|||
initializeTabColors(b);
|
||||
}
|
||||
|
||||
private void installMouseListener(Container c, MouseListener listener) {
|
||||
private void installMouseListener(Container c, GTabMouseListener listener) {
|
||||
|
||||
c.addMouseListener(listener);
|
||||
c.addMouseMotionListener(listener);
|
||||
Component[] children = c.getComponents();
|
||||
for (Component element : children) {
|
||||
if (element instanceof Container) {
|
||||
|
@ -106,6 +114,7 @@ class GTab<T> extends JPanel {
|
|||
}
|
||||
else {
|
||||
element.addMouseListener(listener);
|
||||
element.addMouseMotionListener(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -163,6 +172,16 @@ class GTab<T> extends JPanel {
|
|||
tabPanel.selectTab(value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
tabPanel.mouseReleased(GTab.this, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseDragged(MouseEvent e) {
|
||||
tabPanel.mouseDragged(GTab.this, e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,10 +15,10 @@
|
|||
*/
|
||||
package docking.widgets.tab;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Container;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -26,6 +26,7 @@ import java.util.stream.Collectors;
|
|||
import javax.swing.*;
|
||||
|
||||
import ghidra.util.layout.HorizontalLayout;
|
||||
import resources.ResourceManager;
|
||||
import utility.function.Dummy;
|
||||
|
||||
/**
|
||||
|
@ -66,6 +67,9 @@ public class GTabPanel<T> extends JPanel {
|
|||
private Consumer<T> closeTabConsumer = t -> removeTab(t);
|
||||
private boolean showTabsAlways = true;
|
||||
|
||||
private Cursor moveCursor = createMoveCursor();
|
||||
private boolean isDragging;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param tabTypeName the name of the type of values in the tab panel. This will be used to
|
||||
|
@ -129,8 +133,9 @@ public class GTabPanel<T> extends JPanel {
|
|||
* @param value the value for the new tab
|
||||
*/
|
||||
public void addTab(T value) {
|
||||
doAddValue(value);
|
||||
rebuildTabs();
|
||||
if (doAddValue(value)) {
|
||||
rebuildTabs();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -198,14 +203,31 @@ public class GTabPanel<T> extends JPanel {
|
|||
* @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");
|
||||
}
|
||||
closeTabList();
|
||||
highlightedValue = null;
|
||||
|
||||
T oldValue = selectedValue;
|
||||
selectedValue = value;
|
||||
rebuildTabs();
|
||||
|
||||
if (isVisibleTab(selectedValue)) {
|
||||
GTab<T> oldTab = getTab(oldValue);
|
||||
if (oldTab != null) {
|
||||
oldTab.setSelected(false);
|
||||
}
|
||||
GTab<T> newTab = getTab(value);
|
||||
newTab.setSelected(true);
|
||||
}
|
||||
else {
|
||||
rebuildTabs();
|
||||
}
|
||||
|
||||
selectedTabConsumer.accept(value);
|
||||
}
|
||||
|
||||
|
@ -401,6 +423,15 @@ public class GTabPanel<T> extends JPanel {
|
|||
return null;
|
||||
}
|
||||
|
||||
public GTab<T> getTab(T value) {
|
||||
for (GTab<T> tab : allTabs) {
|
||||
if (tab.getValue().equals(value)) {
|
||||
return tab;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void showTabList() {
|
||||
if (tabList != null) {
|
||||
return;
|
||||
|
@ -482,9 +513,13 @@ public class GTabPanel<T> extends JPanel {
|
|||
return false;
|
||||
}
|
||||
|
||||
private void doAddValue(T value) {
|
||||
private boolean doAddValue(T value) {
|
||||
Objects.requireNonNull(value);
|
||||
allValues.add(value);
|
||||
if (!allValues.contains(value)) {
|
||||
allValues.add(value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void rebuildTabs() {
|
||||
|
@ -648,13 +683,91 @@ public class GTabPanel<T> extends JPanel {
|
|||
this.ignoreFocusLost = ignoreFocusLost;
|
||||
}
|
||||
|
||||
/*testing*/public JPanel getTab(T value) {
|
||||
for (GTab<T> tab : allTabs) {
|
||||
if (tab.getValue().equals(value)) {
|
||||
return tab;
|
||||
void mouseDragged(GTab<T> draggedTab, MouseEvent e) {
|
||||
isDragging = true;
|
||||
clearAllHighlights();
|
||||
GTab<T> targetTab = getTab(e);
|
||||
if (targetTab == null) {
|
||||
// if the mouse is not currently over a valid target tab, put the cursor back to the
|
||||
// default cursor to indicate this is not a valid drop location. (Couldn't find a
|
||||
// decent "nope" icon that looked good when converted to a cursor)
|
||||
setCursor(Cursor.getDefaultCursor());
|
||||
return;
|
||||
}
|
||||
|
||||
setCursor(moveCursor);
|
||||
if (targetTab != draggedTab) {
|
||||
// we highlight the tab we are hovering over to indicate it is a valid drop target
|
||||
targetTab.setHighlight(true);
|
||||
}
|
||||
}
|
||||
|
||||
void mouseReleased(GTab<T> draggedTab, MouseEvent e) {
|
||||
if (!isDragging) {
|
||||
return;
|
||||
}
|
||||
isDragging = false;
|
||||
setCursor(Cursor.getDefaultCursor());
|
||||
|
||||
int targetTabIndex = getTabIndex(e);
|
||||
if (targetTabIndex >= 0) {
|
||||
int draggedTabIndex = allTabs.indexOf(draggedTab);
|
||||
if (draggedTabIndex == targetTabIndex) {
|
||||
return;
|
||||
}
|
||||
moveTab(draggedTab.getValue(), targetTabIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private GTab<T> getTab(MouseEvent e) {
|
||||
int index = getTabIndex(e);
|
||||
if (index < 0) {
|
||||
return null;
|
||||
}
|
||||
return allTabs.get(index);
|
||||
}
|
||||
|
||||
private int getTabIndex(MouseEvent e) {
|
||||
// this e is from a GTab component, so we need to convert to GTablePanel point
|
||||
Point gTabPoint = e.getPoint();
|
||||
Point p = SwingUtilities.convertPoint(e.getComponent(), gTabPoint, this);
|
||||
Dimension size = getSize();
|
||||
|
||||
// if the point is outside of the the tab panel, not a valid drop target
|
||||
if (p.x < 0 || p.y < 0 || p.x >= size.width || p.y >= size.height) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// find the tab the mouse is over
|
||||
for (int i = 0; i < allTabs.size(); i++) {
|
||||
GTab<T> tab = allTabs.get(i);
|
||||
Rectangle tabBounds = tab.getBounds();
|
||||
if (tabBounds.contains(p)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
// we are in the area past the last tab, just return the last tab index
|
||||
return allTabs.size() - 1;
|
||||
}
|
||||
|
||||
public void moveTab(T value, int newIndex) {
|
||||
List<T> newValues = new ArrayList<>(allValues);
|
||||
newValues.remove(value);
|
||||
newValues.add(newIndex, value);
|
||||
allValues.clear();
|
||||
allValues.addAll(newValues);
|
||||
rebuildTabs();
|
||||
}
|
||||
|
||||
private static Cursor createMoveCursor() {
|
||||
Icon icon = ResourceManager.loadIcon("move.png");
|
||||
Image image = ResourceManager.getImageIcon(icon).getImage();
|
||||
return Toolkit.getDefaultToolkit().createCustomCursor(image, new Point(8, 8), "nope");
|
||||
}
|
||||
|
||||
private void clearAllHighlights() {
|
||||
allTabs.forEach(t -> t.setHighlight(false));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
BIN
Ghidra/Framework/Docking/src/main/resources/images/move.png
Normal file
BIN
Ghidra/Framework/Docking/src/main/resources/images/move.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 194 B |
|
@ -127,7 +127,7 @@ public class GTabPanelTest extends AbstractDockingTest {
|
|||
setSelectedValue("ABCDEFGHIJK");
|
||||
assertTrue(isVisibleTab("ABCDEFGHIJK"));
|
||||
setSelectedValue("One");
|
||||
assertFalse(isVisibleTab("ABCDEFGHIJK"));
|
||||
assertTrue(isVisibleTab("ABCDEFGHIJK"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -233,6 +233,21 @@ public class GTabPanelTest extends AbstractDockingTest {
|
|||
gTabPanel.getAccessibleName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMoveTab() {
|
||||
assertEquals("One", getValue(0));
|
||||
assertEquals("Two", getValue(1));
|
||||
assertEquals("Three Three Three", getValue(2));
|
||||
moveTab("One", 2);
|
||||
assertEquals("Two", getValue(0));
|
||||
assertEquals("Three Three Three", getValue(1));
|
||||
assertEquals("One", getValue(2));
|
||||
}
|
||||
|
||||
private void moveTab(String value, int newIndex) {
|
||||
runSwing(() -> gTabPanel.moveTab(value, newIndex));
|
||||
}
|
||||
|
||||
private List<String> getHiddenTabs() {
|
||||
return runSwing(() -> gTabPanel.getHiddenTabs());
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import javax.swing.*;
|
|||
import generic.theme.GThemeDefaults.Colors;
|
||||
import ghidra.util.MathUtilities;
|
||||
import ghidra.util.Msg;
|
||||
import resources.ResourceManager;
|
||||
|
||||
public class ImageUtils {
|
||||
|
||||
|
@ -310,7 +311,12 @@ public class ImageUtils {
|
|||
icon.paintIcon(null, g, 0, 0);
|
||||
g.dispose();
|
||||
|
||||
return new ImageIcon(newImage);
|
||||
ImageIcon imageIcon = new ImageIcon(newImage);
|
||||
String iconName = ResourceManager.getIconName(icon);
|
||||
if (iconName != null) {
|
||||
imageIcon.setDescription(iconName);
|
||||
}
|
||||
return imageIcon;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue