mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-06 03:50:02 +02:00
GP-5646 reorder program tabs via drag-N-drop
tmp
This commit is contained in:
parent
9018e9a05a
commit
99992cf550
9 changed files with 192 additions and 28 deletions
|
@ -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.
|
||||
|
@ -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());
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue