GP-2978: Rename StateEditing to Control. Disable selection of control modes by context.

This commit is contained in:
Dan 2023-01-30 14:06:44 -05:00
parent 16d80129b8
commit f1c060d378
43 changed files with 589 additions and 555 deletions

View file

@ -15,6 +15,7 @@
*/
package docking.action;
import java.awt.Component;
import java.beans.PropertyChangeListener;
import java.util.Set;
@ -281,6 +282,21 @@ public interface DockingActionIf extends HelpDescriptor {
*/
public JMenuItem createMenuItem(boolean isPopup);
/**
* Returns a component to represent this action in the menu.
* <p>
* Typically, this is the menu item that triggers the action. However, some actions may wish to
* use components other than menu items. For example, they may produce component for helping to
* organize the menu visually.
* @param isPopup true if the action should use its Popup MenuData, else it uses the MenuBar
* MenuData.
* @return the component
* @see #createMenuItem(boolean)
*/
public default Component createMenuComponent(boolean isPopup) {
return createMenuItem(isPopup);
}
/**
* Determines whether this action should be added to a window (either the main window or a
* secondary detached window). By default, this method will return true for the main window

View file

@ -0,0 +1,173 @@
/* ###
* 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 docking.menu;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.*;
import org.apache.commons.lang3.StringUtils;
import docking.ActionContext;
import docking.DockingUtils;
import docking.action.DockingAction;
import docking.action.MenuData;
import docking.widgets.label.GDHtmlLabel;
import generic.theme.GThemeDefaults.Colors.Palette;
import ghidra.util.HTMLUtilities;
/**
* An action that can be added to a menu in order to separate menu items into groups
*/
public class HorizontalRuleAction extends DockingAction {
private static final String PADDING = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
private static int idCount = 0;
/**
* Constructor
*
* @param owner the action owner
* @param topName the name that will appear above the separator bar
* @param bottomName the name that will appear below the separator bar
*/
public HorizontalRuleAction(String owner, String topName, String bottomName) {
super("HorizontalRuleAction: " + ++idCount, owner, false);
setEnabled(false);
markHelpUnnecessary();
MenuData menuData = new MenuData(new String[] { "" });
// The menu name is both names, one over the other, in a small, light grayish font.
String topHtml = HTMLUtilities.escapeHTML(topName);
String bottomHtml = HTMLUtilities.escapeHTML(bottomName);
menuData.setMenuItemNamePlain(String.format("""
<HTML><CENTER><FONT SIZE=2 COLOR="%s">%s<BR>%s</FONT></CENTER>
""", Palette.SILVER, topHtml, bottomHtml));
setMenuBarData(menuData);
// the description is meant to be used for the tooltip and is larger
setDescription(String.format("""
<HTML><CENTER><B>%s</B><HR><B>%s</B></CENTER>
""", PADDING + topHtml + PADDING, PADDING + bottomHtml + PADDING));
}
@Override
public void actionPerformed(ActionContext context) {
// this does't actually do anything
}
@Override
public JSeparator createMenuComponent(boolean isPopup) {
String[] menuPath = getMenuBarData().getMenuPath();
String name = menuPath[menuPath.length - 1];
String description = getDescription();
return new LabeledSeparator(name, description);
}
private static class LabeledSeparator extends JSeparator {
private final int EMTPY_SEPARATOR_HEIGHT = 10;
private final int TEXT_SEPARATOR_HEIGHT = 32;
private JLabel renderer = new GDHtmlLabel();
private int separatorHeight = EMTPY_SEPARATOR_HEIGHT;
private LabeledSeparator(String name, String description) {
setBorder(BorderFactory.createEmptyBorder(20, 0, 20, 0));
renderer.setText(name);
DockingUtils.setTransparent(renderer);
renderer.setHorizontalAlignment(SwingConstants.CENTER);
renderer.setVisible(true);
if (!StringUtils.isBlank(name)) {
separatorHeight = TEXT_SEPARATOR_HEIGHT;
}
// IF WE CHOOSE TO SHOW TOOLTIPS (and below too)...
// setToolTipText( description );
}
@Override
protected void paintComponent(Graphics g) {
Dimension d = getSize();
// some edge padding, for classiness
int pad = 10;
int center = separatorHeight >> 1;
int x = 0 + pad;
int y = center;
int w = d.width - pad;
g.setColor(getForeground());
g.drawLine(x, y, w, y);
// drop-shadow
g.setColor(getBackground());
g.drawLine(x, (y + 1), w, (y + 1));
// now add our custom text
renderer.setSize(getSize());
renderer.paint(g);
}
@Override
public Dimension getPreferredSize() {
// assume horizontal
return new Dimension(0, separatorHeight);
}
@Override
public Dimension getMinimumSize() {
return new Dimension(0, separatorHeight);
}
//
// USE THE CODE BELOW IF WE WANT TOOLTIPS
//
// @Override
// public String getToolTipText( MouseEvent event ) {
// // We only want to show the tooltip when the user is over the label. Since the label
// // is not on the screen, we cannot ask it if the mouse location is within its bounds.
// Dimension labelSize = renderer.getPreferredSize();
// if ( labelSize.height == 0 && labelSize.width == 0 ) {
// return null;
// }
//
// Dimension mySize = getSize();
// int centerX = mySize.width >> 1;
//
// int labelMidPoint = labelSize.width >> 1;
// int labelStartX = centerX - labelMidPoint;
// int labelEndX = centerX + labelMidPoint;
//
// Point mousePoint = event.getPoint();
// boolean insideLabel = (mousePoint.x >= labelStartX) && (mousePoint.x <= labelEndX);
// if ( !insideLabel ) {
// return null;
// }
// return getToolTipText();
// }
//
// @Override
// public Point getToolTipLocation( MouseEvent event ) {
// Rectangle bounds = getBounds();
// bounds.x += bounds.width;
// bounds.y = 0;
// return bounds.getLocation();
// }
}
}

View file

@ -146,6 +146,16 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
actionStates.addAll(newStates);
}
/**
* Check if the given state can be selected
*
* @param state the state to check
* @return true (the default) if selectable, false to disable
*/
protected boolean isStateEnabled(ActionState<T> state) {
return true;
}
protected List<DockingActionIf> getStateActions() {
updateStates();
List<DockingActionIf> actions = new ArrayList<>(actionStates.size());
@ -154,6 +164,8 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
DockingActionIf a = useCheckboxForIcons
? new ActionStateToggleAction(actionState, isSelected)
: new ActionStateAction(actionState, isSelected);
boolean isEnabled = isStateEnabled(actionState);
a.setEnabled(isEnabled);
actions.add(a);
}
return actions;

View file

@ -24,12 +24,9 @@ import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.event.*;
import org.apache.commons.lang3.StringUtils;
import docking.*;
import docking.action.*;
import docking.widgets.EmptyBorderButton;
import docking.widgets.label.GDHtmlLabel;
import generic.theme.GThemeDefaults.Colors;
import ghidra.util.Swing;
import resources.ResourceManager;
@ -150,8 +147,9 @@ public class MultipleActionDockingToolbarButton extends EmptyBorderButton {
return manager.getActiveComponentProvider();
}
/**
/**
* Show a popup containing all the actions below the button
*
* @return the popup menu that was shown
*/
JPopupMenu showPopup() {
@ -181,19 +179,13 @@ public class MultipleActionDockingToolbarButton extends EmptyBorderButton {
List<DockingActionIf> actionList = multipleAction.getActionList(getActionContext());
for (DockingActionIf dockingAction : actionList) {
String[] menuPath = dockingAction.getMenuBarData().getMenuPath();
String name = menuPath[menuPath.length - 1];
// this is a special signal to say we should insert a separator and not a real menu item
if (!dockingAction.isEnabled()) {
String description = dockingAction.getDescription();
JSeparator separator = new ProgramNameSeparator(name, description);
menu.add(separator);
Component component = dockingAction.createMenuComponent(false);
if (!(component instanceof JMenuItem item)) {
// not an actual item, e.g., a separator as in HorizontalRuleAction
menu.add(component);
continue;
}
JMenuItem item = dockingAction.createMenuItem(false);
// a custom Ghidra UI that handles alignment issues and allows for tabulating presentation
item.setUI((DockingMenuItemUI) DockingMenuItemUI.createUI(item));
final DockingActionIf delegateAction = dockingAction;
@ -415,95 +407,4 @@ public class MultipleActionDockingToolbarButton extends EmptyBorderButton {
}
private static class ProgramNameSeparator extends JSeparator {
private final int EMTPY_SEPARATOR_HEIGHT = 10;
private final int TEXT_SEPARATOR_HEIGHT = 32;
private JLabel renderer = new GDHtmlLabel();
private int separatorHeight = EMTPY_SEPARATOR_HEIGHT;
private ProgramNameSeparator(String name, String description) {
setBorder(BorderFactory.createEmptyBorder(20, 0, 20, 0));
renderer.setText(name);
DockingUtils.setTransparent(renderer);
renderer.setHorizontalAlignment(SwingConstants.CENTER);
renderer.setVisible(true);
if (!StringUtils.isBlank(name)) {
separatorHeight = TEXT_SEPARATOR_HEIGHT;
}
// IF WE CHOOSE TO SHOW TOOLTIPS (and below too)...
// setToolTipText( description );
}
@Override
protected void paintComponent(Graphics g) {
Dimension d = getSize();
// some edge padding, for classiness
int pad = 10;
int center = separatorHeight >> 1;
int x = 0 + pad;
int y = center;
int w = d.width - pad;
g.setColor(getForeground());
g.drawLine(x, y, w, y);
// drop-shadow
g.setColor(getBackground());
g.drawLine(x, (y + 1), w, (y + 1));
// now add our custom text
renderer.setSize(getSize());
renderer.paint(g);
}
@Override
public Dimension getPreferredSize() {
// assume horizontal
return new Dimension(0, separatorHeight);
}
@Override
public Dimension getMinimumSize() {
return new Dimension(0, separatorHeight);
}
//
// USE THE CODE BELOW IF WE WANT TOOLTIPS
//
// @Override
// public String getToolTipText( MouseEvent event ) {
// // We only want to show the tooltip when the user is over the label. Since the label
// // is not on the screen, we cannot ask it if the mouse location is within its bounds.
// Dimension labelSize = renderer.getPreferredSize();
// if ( labelSize.height == 0 && labelSize.width == 0 ) {
// return null;
// }
//
// Dimension mySize = getSize();
// int centerX = mySize.width >> 1;
//
// int labelMidPoint = labelSize.width >> 1;
// int labelStartX = centerX - labelMidPoint;
// int labelEndX = centerX + labelMidPoint;
//
// Point mousePoint = event.getPoint();
// boolean insideLabel = (mousePoint.x >= labelStartX) && (mousePoint.x <= labelEndX);
// if ( !insideLabel ) {
// return null;
// }
// return getToolTipText();
// }
//
// @Override
// public Point getToolTipLocation( MouseEvent event ) {
// Rectangle bounds = getBounds();
// bounds.x += bounds.width;
// bounds.y = 0;
// return bounds.getLocation();
// }
}
}