
org.nakedobjects.nos.client.dnd.basic.PopupMenu Maven / Gradle / Ivy
package org.nakedobjects.nos.client.dnd.basic;
import java.awt.event.KeyEvent;
import java.util.Enumeration;
import java.util.Vector;
import org.apache.log4j.Logger;
import org.nakedobjects.noa.adapter.Naked;
import org.nakedobjects.noa.reflect.Consent;
import org.nakedobjects.noa.reflect.NakedObjectField;
import org.nakedobjects.noa.reflect.NakedObjectAction.Type;
import org.nakedobjects.noa.spec.NakedObjectSpecification;
import org.nakedobjects.nof.core.reflect.Veto;
import org.nakedobjects.nof.core.util.DebugString;
import org.nakedobjects.nos.client.dnd.Canvas;
import org.nakedobjects.nos.client.dnd.Click;
import org.nakedobjects.nos.client.dnd.Content;
import org.nakedobjects.nos.client.dnd.FocusManager;
import org.nakedobjects.nos.client.dnd.KeyboardAction;
import org.nakedobjects.nos.client.dnd.MenuOptions;
import org.nakedobjects.nos.client.dnd.Toolkit;
import org.nakedobjects.nos.client.dnd.UserAction;
import org.nakedobjects.nos.client.dnd.UserActionSet;
import org.nakedobjects.nos.client.dnd.View;
import org.nakedobjects.nos.client.dnd.ViewAxis;
import org.nakedobjects.nos.client.dnd.ViewSpecification;
import org.nakedobjects.nos.client.dnd.Workspace;
import org.nakedobjects.nos.client.dnd.content.AbstractContent;
import org.nakedobjects.nos.client.dnd.debug.DebugOption;
import org.nakedobjects.nos.client.dnd.drawing.Bounds;
import org.nakedobjects.nos.client.dnd.drawing.Color;
import org.nakedobjects.nos.client.dnd.drawing.Image;
import org.nakedobjects.nos.client.dnd.drawing.Location;
import org.nakedobjects.nos.client.dnd.drawing.Padding;
import org.nakedobjects.nos.client.dnd.drawing.Shape;
import org.nakedobjects.nos.client.dnd.drawing.Size;
import org.nakedobjects.nos.client.dnd.drawing.Text;
import org.nakedobjects.nos.client.dnd.focus.SubviewFocusManager;
import org.nakedobjects.nos.client.dnd.view.simple.AbstractView;
public class PopupMenu extends AbstractView {
private static class Item {
public static Item createDivider() {
Item item = new Item();
item.isBlank = true;
return item;
}
public static Item createNoOption() {
Item item = new Item();
item.name = "no options";
return item;
}
public static Item createOption(final UserAction action, final Object object, final View view, final Location location) {
Item item = new Item();
if (action == null) {
item.isBlank = true;
} else {
item.isBlank = false;
item.action = action;
item.view = view;
item.name = action.getName(view);
item.description = action.getDescription(view);
Consent disabled = action.disabled(view);
item.isDisabled = disabled.isVetoed();
item.reason = disabled.getReason();
}
return item;
}
UserAction action;
String description;
boolean isBlank;
boolean isDisabled;
String name;
String reason;
View view;
private Item() {}
public String getHelp() {
return action.getHelp(view);
}
public String toString() {
return isBlank ? "NONE" : (name + " " + (isDisabled ? "DISABLED " : " " + action));
}
}
private class PopupContent extends AbstractContent {
public PopupContent() {}
public Consent canDrop(final Content sourceContent) {
return Veto.DEFAULT;
}
public void debugDetails(final DebugString debug) {}
public Naked drop(final Content sourceContent) {
return null;
}
public String getDescription() {
int optionNo = getOption();
return items[optionNo].description;
}
public String getHelp() {
int optionNo = getOption();
return items[optionNo].getHelp();
}
public String getIconName() {
return null;
}
public Image getIconPicture(final int iconHeight) {
return null;
}
public String getId() {
return null;
}
public Naked getNaked() {
return null;
}
public boolean isOptionEnabled() {
return false;
}
public NakedObjectSpecification getSpecification() {
return null;
}
public boolean isTransient() {
return false;
}
public void parseTextEntry(final String entryText) {}
public String title() {
int optionNo = getOption();
return items[optionNo].name;
}
public Naked[] getOptions() {
return null;
}
}
private static class PopupSpecification implements ViewSpecification {
public boolean canDisplay(final Content content) {
return false;
}
public View createView(final Content content, final ViewAxis axis) {
return null;
}
public String getName() {
return "Popup Menu";
}
public boolean isAligned() {
return false;
}
public boolean isOpen() {
return true;
}
public boolean isReplaceable() {
return false;
}
public boolean isSubView() {
return false;
}
}
private static final UserAction DEBUG_OPTION = new DebugOption();
private static final Logger LOG = Logger.getLogger(PopupMenu.class);
private Color backgroundColor;
private Bounds coreSize;
private View forView;
private Item[] items = new Item[0];
private int optionIdentified;
private FocusManager simpleFocusManager;
private View submenu;
private Vector options = new Vector();
public PopupMenu() {
super(null, new PopupSpecification(), null);
setContent(new PopupContent());
simpleFocusManager = new SubviewFocusManager(this);
}
private void addItems(final View target, final UserAction[] options, final int len, final Vector list, final Type type) {
int initialSize = list.size();
for (int i = 0; i < len; i++) {
if (options[i].getType() == type) {
if (initialSize > 0 && list.size() == initialSize) {
list.addElement(Item.createDivider());
}
list.addElement(Item.createOption(options[i], null, target, getLocation()));
}
}
}
protected Color backgroundColor() {
return backgroundColor;
}
public Consent canChangeValue() {
return Veto.DEFAULT;
}
public boolean canFocus() {
return true;
}
public void debug(DebugString debug) {
super.debug(debug);
debug.appendTitle("Submenu");
debug.append(submenu);
debug.append("\n");
}
protected Color disabledColor() {
return Toolkit.getColor("menu.disabled");
}
public void dispose() {
if (getParent() == null) {
super.dispose();
getViewManager().clearOverlayView(this);
} else {
getParent().dispose();
}
}
/**
* Draws the popup menu
*
* @see java.awt.Component#paint(java.awt.Graphics)
*/
public void draw(final Canvas canvas) {
// LOG.debug("Canvas " + canvas);
int width = coreSize.getWidth();
int height = coreSize.getHeight();
canvas.drawSolidRectangle(0, 0, width, height, backgroundColor);
canvas.draw3DRectangle(0, 0, width, height, backgroundColor, true);
int itemHeight = style().getLineHeight() + VPADDING;
// int baseLine = itemHeight / 2 + style().getAscent() / 2 + getPadding().getTop();
int baseLine = style().getAscent() + getPadding().getTop() + 1;
int left = getPadding().getLeft();
for (int i = 0; i < items.length; i++) {
if (items[i].isBlank) {
int y = baseLine - (style().getAscent() / 2);
canvas.drawLine(1, y, width - 2, y, backgroundColor.brighter());
canvas.drawLine(1, y - 1, width - 2, y - 1, backgroundColor.darker());
} else {
Color color;
if (items[i].isDisabled || items[i].action == null) {
color = disabledColor();
} else if (getOption() == i) {
int top = getPadding().getTop() + i * itemHeight;
int depth = style().getLineHeight() + 2;
canvas.drawSolidRectangle(2, top, width - 4, depth, backgroundColor.darker());
canvas.draw3DRectangle(2, top, width - 4, depth + 1, backgroundColor.brighter(), false);
// canvas.drawText(items[i].name, left, baseLine, normalColor(), style());
color = reversedColor();
} else {
color = normalColor();
}
canvas.drawText(items[i].name, left, baseLine, color, style());
if (items[i].action instanceof UserActionSet) {
Shape arrow;
arrow = new Shape(0, 0);
arrow.extendsLine(4, 4);
arrow.extendsLine(-4, 4);
canvas.drawSolidShape(arrow, width - 10, baseLine - 8, color);
}
}
baseLine += itemHeight;
}
// canvas.drawRectangleAround(this, Color.DEBUG_VIEW_BOUNDS);
if (submenu != null) {
Canvas submenuCanvas = canvas.createSubcanvas(submenu.getBounds());
// LOG.debug("submenu bounds " + submenu.getBounds());
submenu.draw(submenuCanvas);
}
}
public void firstClick(final Click click) {
if (coreSize.contains(click.getLocation())) {
if (click.button1() || click.button3()) {
mouseMoved(click.getLocation());
invoke();
}
} else if (submenu != null) {
click.subtract(submenu.getLocation());
submenu.firstClick(click);
}
}
public void focusLost() {}
public void focusReceived() {}
private Size getCoreRequiredSize() {
Size size = new Size();
for (int i = 0; i < items.length; i++) {
int itemWidth = items[i].isBlank ? 0 : style().stringWidth(items[i].name);
size.ensureWidth(itemWidth);
size.extendHeight(style().getLineHeight() + VPADDING);
}
size.extend(getPadding());
size.extendWidth(HPADDING * 2);
return size;
}
public FocusManager getFocusManager() {
return simpleFocusManager;
}
public Size getMaximumSize() {
Size size = getCoreRequiredSize();
if (submenu != null) {
Size subviewSize = submenu.getMaximumSize();
size.extendWidth(subviewSize.getWidth());
size.ensureHeight(submenu.getLocation().getY() + subviewSize.getHeight());
}
return size;
}
public int getOption() {
return optionIdentified;
}
public int getOptionCount() {
return items.length;
}
public Padding getPadding() {
Padding in = super.getPadding();
in.extendTop(VPADDING);
in.extendBottom(VPADDING);
in.extendLeft(HPADDING + 5);
in.extendRight(HPADDING + 5);
return in;
}
public Workspace getWorkspace() {
return forView.getWorkspace();
}
public boolean hasFocus() {
return false;
}
private void invoke() {
int option = getOption();
Item item = items[option];
if (item.isBlank || item.action == null || item.action.disabled(forView).isVetoed()) {
return;
} else if (item.action instanceof UserActionSet) {
markDamaged();
int itemHeight = style().getLineHeight() + VPADDING;
Location menuLocation = new Location(coreSize.getWidth() - 4, itemHeight * option);
submenu = new PopupMenu();
submenu.setParent(this);
((PopupMenu) submenu).show(forView, ((UserActionSet) item.action).getMenuOptions(), backgroundColor);
submenu.setLocation(menuLocation);
invalidateLayout();
Size size = getMaximumSize();
setSize(size);
layout(size);
markDamaged();
} else {
Workspace workspace = getWorkspace();
Location location = new Location(getAbsoluteLocation());
location.subtract(workspace.getView().getAbsoluteLocation());
Padding padding = workspace.getView().getPadding();
location.move(-padding.getLeft(), -padding.getTop());
location.move(30, 0);
dispose();
LOG.debug("execute " + item.name + " on " + forView + " in " + workspace);
item.action.execute(workspace, forView, location);
}
}
public void keyPressed(final KeyboardAction key) {
if (submenu != null) {
submenu.keyPressed(key);
} else {
int keyCode = key.getKeyCode();
if (keyCode == KeyEvent.VK_ESCAPE) {
if (getParent() == null) {
dispose();
} else {
markDamaged();
((PopupMenu) getParent()).submenu = null;
}
key.consume();
} else if (keyCode == KeyEvent.VK_ENTER) {
key.consume();
invoke();
} else if (getParent() != null && keyCode == KeyEvent.VK_LEFT) {
markDamaged();
((PopupMenu) getParent()).submenu = null;
key.consume();
} else if (keyCode == KeyEvent.VK_RIGHT && items[getOption()].action instanceof UserActionSet) {
key.consume();
invoke();
} else if (keyCode == KeyEvent.VK_UP) {
key.consume();
if (optionIdentified == 0) {
optionIdentified = items.length;
}
for (int i = optionIdentified - 1; i >= 0; i--) {
if (items[i].isBlank) {
continue;
}
if (items[i].isDisabled) {
continue;
}
setOption(i);
break;
}
} else if (keyCode == KeyEvent.VK_DOWN) {
key.consume();
if (optionIdentified == items.length - 1) {
optionIdentified = -1;
}
for (int i = optionIdentified + 1; i < items.length; i++) {
if (items[i].isBlank) {
continue;
}
if (items[i].isDisabled) {
continue;
}
setOption(i);
break;
}
}
}
}
public void keyReleased(final int keyCode, final int modifiers) {}
public void keyTyped(final char keyCode) {}
public void layout(final Size maximumSize) {
if (submenu != null) {
submenu.layout(maximumSize);
}
setSize(getMaximumSize());
coreSize = new Bounds(getCoreRequiredSize());
}
public View makeView(final Naked object, final NakedObjectField field) throws CloneNotSupportedException {
throw new RuntimeException();
}
public void markDamaged() {
if (getParent() == null) {
super.markDamaged();
} else {
getParent().markDamaged();
}
// markDamaged(new Bounds(getAbsoluteLocation(), getSize())); ///getView().getBounds());
}
public void mouseMoved(final Location at) {
if (coreSize.contains(at)) {
int option = (at.getY() - getPadding().getTop()) / (style().getLineHeight() + VPADDING);
option = Math.max(option, 0);
option = Math.min(option, items.length - 1);
if (option >= 0 && optionIdentified != option) {
// LOG.debug("mouse over option " + option + " " + this);
setOption(option);
markDamaged();
}
} else if (submenu != null) {
at.subtract(submenu.getLocation());
submenu.mouseMoved(at);
}
}
protected Color normalColor() {
return Toolkit.getColor("menu.normal");
}
private String changeStatus(final View over, final boolean forView, final boolean includeExploration, final boolean includeDebug) {
StringBuffer status = new StringBuffer("Menu for ");
if (forView) {
status.append("view ");
status.append(over.getSpecification().getName());
} else {
status.append("object");
Content content = over.getContent();
if (content != null) {
status.append(" '");
status.append(content.title());
status.append("'");
}
}
if (includeDebug || includeExploration) {
status.append(" (includes ");
if (includeExploration) {
status.append("exploration");
}
if (includeDebug) {
if (includeExploration) {
status.append(" & ");
}
status.append("debug");
}
status.append(" options)");
}
return status.toString();
}
protected Color reversedColor() {
return Toolkit.getColor("menu.reversed");
}
public void setOption(final int option) {
if (option != optionIdentified) {
optionIdentified = option;
markDamaged();
updateFeedback();
}
}
private void updateFeedback() {
Item item = items[optionIdentified];
if (item.isBlank) {
getFeedbackManager().clearAction();
} else if (item.reason == "") {
getFeedbackManager().setAction(item.description == null ? "" : item.description);
} else {
getFeedbackManager().setAction(item.reason);
}
}
public void show(final View over, final boolean forView, final boolean includeDebug, boolean includeExploration) {
boolean withExploration = getViewManager().isRunningAsExploration() && includeExploration;
UserActionSet optionSet = new UserActionSet(withExploration, includeDebug, UserAction.USER);
if (forView) {
over.viewMenuOptions(optionSet);
} else {
over.contentMenuOptions(optionSet);
}
optionSet.add(DEBUG_OPTION);
Enumeration e = options.elements();
while (e.hasMoreElements()) {
MenuOptions element = (MenuOptions) e.nextElement();
element.menuOptions(optionSet);
}
show(over, optionSet.getMenuOptions(), optionSet.getColor());
getViewManager().setOverlayView(this);
String status = changeStatus(over, forView, withExploration, includeDebug);
getFeedbackManager().setViewDetail(status);
updateFeedback();
}
private void show(final View target, final UserAction[] options, final Color color) {
this.forView = target;
optionIdentified = 0;
backgroundColor = color;
int len = options.length;
if (len == 0) {
items = new Item[] { Item.createNoOption() };
} else {
Vector list = new Vector();
addItems(target, options, len, list, UserAction.USER);
addItems(target, options, len, list, UserAction.EXPLORATION);
addItems(target, options, len, list, UserAction.DEBUG);
items = new Item[list.size()];
list.copyInto(items);
}
}
protected Text style() {
return Toolkit.getText("menu");
}
public String toString() {
return "PopupMenu [location=" + getLocation() + ",item=" + optionIdentified + ",itemCount="
+ (items == null ? 0 : items.length) + "]";
}
protected boolean transparentBackground() {
return false;
}
public void addMenuOptions(MenuOptions options) {
this.options.addElement(options);
}
}
// Copyright (c) Naked Objects Group Ltd.
© 2015 - 2025 Weber Informatics LLC | Privacy Policy