0001: /*
0002: * Javu WingS - Lightweight Java Component Set
0003: * Copyright (c) 2005-2007 Krzysztof A. Sadlocha
0004: * e-mail: ksadlocha@programics.com
0005: *
0006: * This library is free software; you can redistribute it and/or
0007: * modify it under the terms of the GNU Lesser General Public
0008: * License as published by the Free Software Foundation; either
0009: * version 2.1 of the License, or (at your option) any later version.
0010: *
0011: * This library is distributed in the hope that it will be useful,
0012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0014: * Lesser General Public License for more details.
0015: *
0016: * You should have received a copy of the GNU Lesser General Public
0017: * License along with this library; if not, write to the Free Software
0018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0019: */
0020:
0021: package com.javujavu.javux.wings;
0022:
0023: import java.awt.AWTEvent;
0024: import java.awt.Container;
0025: import java.awt.Dimension;
0026: import java.awt.Graphics;
0027: import java.awt.IllegalComponentStateException;
0028: import java.awt.Insets;
0029: import java.awt.Point;
0030: import java.awt.Rectangle;
0031: import java.awt.Window;
0032: import java.awt.event.ActionEvent;
0033: import java.awt.event.AdjustmentEvent;
0034: import java.awt.event.AdjustmentListener;
0035: import java.awt.event.FocusEvent;
0036: import java.awt.event.KeyEvent;
0037: import java.awt.event.MouseEvent;
0038: import java.util.Vector;
0039: import com.javujavu.javux.wings.item.ItemRenderer;
0040:
0041: /** <code>WingMenu</code> is a component
0042: * that can be used as a menu, menu bar and popup menu.
0043: * It can be also used as typical component and added to
0044: * container like buttons and other dialog fields.
0045: * Menu is a list containing <code>WingMenuItem</code>s and another <code>WingMenu</code>s.
0046: * <br>
0047: * <br>
0048: * <b>This class is thread safe.</b>
0049: **/
0050: public class WingMenu extends WingComponent implements
0051: AdjustmentListener {
0052: private Style stItemNormal;
0053: private Style stItemSelected;
0054: private Style stItemPressed;
0055: private Style stItemDisabled;
0056: private Style stSeparator;
0057: private Style stLineGap;
0058: private Style stTail;
0059: private Style stShortcutNormal;
0060: private Style stShortcutDisabled;
0061: private WingImage[] imgIcon;
0062: private int separatorWidth;
0063: private int lineGapWidth;
0064: private boolean heavy;
0065:
0066: protected/*final*/WingMenuItem face;
0067: protected/*final*/int orientation;
0068: protected WingTimer expander = new WingTimer(200, false, this );
0069: protected WingTimer loser = new WingTimer(200, false, this );
0070:
0071: protected Vector items;
0072: private int selected = -1;
0073: private int hover = -1;
0074: private boolean drag;
0075: private boolean pressed;
0076: private boolean expanded;
0077: private boolean focused;
0078: private WingMenuItem openItem;
0079: private Container popupHandle;
0080:
0081: private Object lastSize;
0082: private boolean resetLayout;
0083: private int extraLeft, extraRight;
0084: private WingScroll scroll;
0085:
0086: public WingMenu(int orientation) {
0087: this (null, KeyEvent.VK_UNDEFINED, 0, orientation);
0088: }
0089:
0090: public WingMenu(Object label) {
0091: this (label, KeyEvent.VK_UNDEFINED, 0, VERTICAL);
0092: }
0093:
0094: public WingMenu(Object label, int shortcutCode,
0095: int shortcutModifiers) {
0096: this (label, shortcutCode, shortcutModifiers, VERTICAL);
0097: }
0098:
0099: public WingMenu(Object label, int shortcutCode,
0100: int shortcutModifiers, int orientation) {
0101: items = new Vector();
0102: face = new WingMenuItem(label, MENU, shortcutCode,
0103: shortcutModifiers, this );
0104: this .orientation = orientation;
0105: setWingFocusable(true);
0106: enableEvents(AWTEvent.MOUSE_EVENT_MASK
0107: | AWTEvent.MOUSE_MOTION_EVENT_MASK
0108: | AWTEvent.FOCUS_EVENT_MASK | AWTEvent.KEY_EVENT_MASK);
0109: }
0110:
0111: public void loadSkin() {
0112: String idt = WingSkin.catKeys(styleId,
0113: (orientation == HORIZONTAL) ? "h.menu" : "v.menu");
0114: String idt2 = WingSkin.catKeys(styleId, WingSkin.catKeys(
0115: (heavy) ? "heavy" : "light",
0116: (orientation == HORIZONTAL) ? "h.menu" : "v.menu"));
0117:
0118: stNormal = WingSkin.getStyle(idt2, null, NORMAL, null);
0119: stDisabled = WingSkin.getStyle(idt2, null, DISABLED, stNormal);
0120:
0121: stItemNormal = WingSkin
0122: .getStyle(idt, null, ITEM | NORMAL, null);
0123: stItemSelected = WingSkin.getStyle(idt, null, ITEM | SELECTED,
0124: stItemNormal);
0125: stItemPressed = WingSkin.getStyle(idt, null, ITEM | PRESSED,
0126: stItemSelected);
0127: stItemDisabled = WingSkin.getStyle(idt, null, ITEM | DISABLED,
0128: stItemNormal);
0129: stSeparator = WingSkin.getStyle(idt, "separator", null);
0130: stLineGap = WingSkin.getStyle(idt, "linegap", null);
0131: stTail = WingSkin.getStyle(idt, "tail", stItemNormal);
0132: stShortcutNormal = WingSkin.getStyle(idt, "shortcut", NORMAL,
0133: null);
0134: stShortcutDisabled = WingSkin.getStyle(idt, "shortcut",
0135: DISABLED, stShortcutNormal);
0136: separatorWidth = WingSkin.getInteger(idt, "separator.width", 0);
0137: lineGapWidth = WingSkin.getInteger(idt, "linegap.width", 0);
0138:
0139: imgIcon = new WingImage[15];
0140: // 0 check off normal
0141: // 1 radio off normal
0142: // 2 check on normal
0143: // 3 radio on normal
0144: // 4 check off hover
0145: // 5 radio off hover
0146: // 6 check on hover
0147: // 7 radio on hover
0148: // 8 check off disabled
0149: // 9 radio off disabled
0150: // 10 check on disabled
0151: // 11 radio on disabled
0152: // 12 expand normal
0153: // 13 expand hover
0154: // 14 expand disabled
0155:
0156: for (int i = 0; i < 12; i++) {
0157: String id = ((i & 1) == 0) ? "check" : "radio";
0158: if ((i & 2) != 0)
0159: id = WingSkin.catKeys(id, "on");
0160: id = WingSkin.catKeys(id, (i < 4) ? "normal"
0161: : (i < 8) ? "hover" : "disabled");
0162: if (i < 4)
0163: imgIcon[i] = WingSkin.getImage(idt, id);
0164: else
0165: imgIcon[i] = WingSkin.getImage(idt, id, imgIcon[i % 4]);
0166: }
0167: imgIcon[12] = WingSkin.getImage(idt, WingSkin.catKeys("expand",
0168: "normal"), null);
0169: imgIcon[13] = WingSkin.getImage(idt, WingSkin.catKeys("expand",
0170: "hover"), imgIcon[12]);
0171: imgIcon[14] = WingSkin.getImage(idt, WingSkin.catKeys("expand",
0172: "disabled"), imgIcon[12]);
0173:
0174: resetLayout = true;
0175: }
0176:
0177: public WingMenuItem getFaceItem() {
0178: return face;
0179: }
0180:
0181: public void setEnabled(boolean enabled) {
0182: face.setEnabled(enabled);
0183: super .setEnabled(enabled);
0184: }
0185:
0186: public void setTooltip(Object tooltip) {
0187: face.setTooltip(tooltip);
0188: super .setTooltip(tooltip);
0189: }
0190:
0191: public Object getTooltipAt(int x, int y) {
0192: WingMenuItem mi = getItem(itemAt(x, y));
0193: return ((mi != null && mi.tooltip != null) ? mi.tooltip
0194: : this .tooltip);
0195: }
0196:
0197: public void revalidateAndRepaint() {
0198: resetLayout = true;
0199: super .revalidateAndRepaint();
0200: }
0201:
0202: public WingMenuItem add(Object label) {
0203: WingMenuItem mi;
0204: add(mi = new WingMenuItem(label));
0205: return mi;
0206: }
0207:
0208: public WingMenuItem addSeparator() {
0209: WingMenuItem mi;
0210: add(mi = new WingMenuItem(null, SEPARATOR));
0211: return mi;
0212: }
0213:
0214: public WingMenuItem add(WingMenu m) {
0215: add(m.face);
0216: return m.face;
0217: }
0218:
0219: public void add(WingMenuItem mi) {
0220: insert(mi, -1);
0221: }
0222:
0223: public synchronized void insert(WingMenuItem mi, int index) {
0224: if (index >= items.size())
0225: index = -1;
0226: if (index == -1)
0227: index = items.size();
0228: items.insertElementAt(mi, index);
0229: mi.owner = this ;
0230: revalidateAndRepaint();
0231: }
0232:
0233: public synchronized void removeItem(WingMenuItem mi) {
0234: removeItem(items.indexOf(mi));
0235: }
0236:
0237: public synchronized void removeAllItems() {
0238: while (items.size() > 0)
0239: removeItem(items.size() - 1);
0240: }
0241:
0242: public synchronized void removeItem(int index) {
0243: if (index < 0 || index >= items.size())
0244: return;
0245:
0246: item(index).owner = null;
0247: items.removeElementAt(index);
0248:
0249: if (selected >= index)
0250: selected--;
0251: if (hover >= index)
0252: hover = -1;
0253:
0254: revalidateAndRepaint();
0255: }
0256:
0257: //////////////////////////////////////////////////////////
0258: // layout
0259: public void adjustmentValueChanged(AdjustmentEvent e) {
0260: repaint();
0261: }
0262:
0263: /**
0264: * <br><br><strong>This method acquire TreeLock</strong>
0265: */
0266: public Dimension getPreferredSize() {
0267: Dimension size = getSize();
0268: Dimension prefSize = wingPrefSize;
0269: if (prefSize == null
0270: || (!wingPrefSizeSet && (lastSize == null || !lastSize
0271: .equals(size)))) {
0272: lastSize = size;
0273: wingPrefSize = prefSize = layoutMenu(false);
0274: }
0275: return prefSize;
0276: }
0277:
0278: /**
0279: * <br><br><strong>This method acquire TreeLock</strong>
0280: */
0281: public void doLayout() {
0282: layoutMenu(true);
0283: }
0284:
0285: /**
0286: * <br><br><strong>This method acquire TreeLock</strong>
0287: */
0288: private Dimension layoutMenu(boolean doLayout) {
0289: boolean check = false, radio = false, expand = false;
0290: int extraLeft = 0, extraRight = 0, extraHeight = 0, shortcutWidth = 0, shortcutHeight = 0, menuWidth = 0, menuHeight = 0;
0291: boolean relayout = resetLayout;
0292: resetLayout = false;
0293: ItemRenderer renderer = getRenderer();
0294:
0295: synchronized (getTreeLock()) {
0296: synchronized (this ) {
0297:
0298: if (relayout && orientation == VERTICAL) {
0299: for (int i = 0; i < items.size(); i++) {
0300: WingMenuItem mi = (WingMenuItem) items
0301: .elementAt(i);
0302: if (mi.kind == CHECKBOX)
0303: check = true;
0304: if (mi.kind == RADIO)
0305: radio = true;
0306: if (mi.kind == MENU)
0307: expand = true;
0308:
0309: String s = mi.getShortcutText();
0310: if (s != null) {
0311: Dimension d = renderer.getItemSize(s, this ,
0312: stShortcutNormal, mi);
0313: if (d.width > shortcutWidth)
0314: shortcutWidth = d.width;
0315: if (d.height > shortcutHeight)
0316: shortcutHeight = d.height;
0317: }
0318: }
0319: if (check) {
0320: extraLeft = imgIcon[2].getWidth();
0321: extraHeight = imgIcon[2].getHeight();
0322: }
0323: if (radio) {
0324: if (imgIcon[3].getWidth() > extraLeft)
0325: extraLeft = imgIcon[3].getWidth();
0326: if (imgIcon[3].getHeight() > extraHeight)
0327: extraHeight = imgIcon[3].getHeight();
0328: }
0329: if (expand && imgIcon[12] != null) {
0330: extraRight = imgIcon[12].getWidth();
0331: if (imgIcon[12].getHeight() > extraHeight)
0332: extraHeight = imgIcon[12].getHeight();
0333: }
0334: if (extraLeft > 0)
0335: extraLeft += stItemNormal.gap;
0336: if (extraRight > 0)
0337: extraRight += stItemNormal.gap;
0338: if (shortcutHeight > extraHeight)
0339: extraHeight = shortcutHeight;
0340: extraRight += shortcutWidth;
0341: this .extraLeft = extraLeft;
0342: this .extraRight = extraRight;
0343: }
0344: int width = 0, height = 0;
0345:
0346: for (int i = 0; i < items.size(); i++) {
0347: WingMenuItem mi = (WingMenuItem) items.elementAt(i);
0348: Rectangle bounds = mi.bounds;
0349: if (relayout) {
0350: bounds = new Rectangle(renderer.getItemSize(
0351: mi.label, this , stItemNormal, mi));
0352: if (mi.kind == SEPARATOR) {
0353: if (orientation == HORIZONTAL)
0354: bounds.width = separatorWidth;
0355: else
0356: bounds.height = separatorWidth;
0357: } else if (orientation == VERTICAL) {
0358: bounds.width += extraLeft + extraRight;
0359: if (extraHeight > bounds.height)
0360: bounds.height = extraHeight;
0361: } else {
0362: WingImage img = (mi.kind == CHECKBOX) ? imgIcon[2]
0363: : (mi.kind == RADIO) ? imgIcon[3]
0364: : (mi.kind == MENU) ? imgIcon[12]
0365: : null;
0366: if (img != null) {
0367: bounds.width += img.getWidth()
0368: + stItemNormal.gap;
0369: if (img.getHeight() > bounds.height)
0370: bounds.height = img.getHeight();
0371: }
0372: }
0373: mi.bounds = bounds;
0374: }
0375: if (orientation == HORIZONTAL) {
0376: if (bounds.height > height)
0377: height = bounds.height;
0378: } else {
0379: if (bounds.width > width)
0380: width = bounds.width;
0381: }
0382: }
0383: Dimension size = new Dimension(getSize());
0384:
0385: if (size.width <= 0 || size.height <= 0) {
0386: size.width = size.height = 5000;
0387: }
0388: size.width -= stNormal.margin.right;
0389: size.height -= stNormal.margin.bottom;
0390:
0391: for (int f = 0; f < 2; f++) {
0392: int left = stNormal.margin.left, top = stNormal.margin.top;
0393: int x = left, y = top, w, h;
0394: menuWidth = menuHeight = 0;
0395:
0396: for (int i = 0; i < items.size(); i++) {
0397: WingMenuItem mi = (WingMenuItem) items
0398: .elementAt(i);
0399: w = mi.bounds.width;
0400: h = mi.bounds.height;
0401: if (orientation == HORIZONTAL) {
0402: if (x + w > size.width && x > left) {
0403: if (mi.kind == SEPARATOR) {
0404: if (doLayout) {
0405: mi.bounds.x = size.width;
0406: mi.bounds.y = y;
0407: }
0408: continue;
0409: }
0410: if (x > menuWidth)
0411: menuWidth = x;
0412: y += height + lineGapWidth;
0413: x = left;
0414: }
0415: if (doLayout) {
0416: mi.bounds.setBounds(x, y, w, height);
0417: }
0418: x += w;
0419: } else {
0420: if (y + h > size.height && y > top) {
0421: if (mi.kind == SEPARATOR) {
0422: if (doLayout) {
0423: mi.bounds.x = x;
0424: mi.bounds.y = size.height;
0425: }
0426: continue;
0427: }
0428: if (y > menuHeight)
0429: menuHeight = y;
0430: x += width + lineGapWidth;
0431: y = top;
0432: }
0433: if (doLayout) {
0434: mi.bounds.setBounds(x, y, width, h);
0435: }
0436: y += h;
0437: }
0438: }
0439: if (orientation == HORIZONTAL) {
0440: if (x > menuWidth)
0441: menuWidth = x;
0442: menuHeight = y + height;
0443: } else {
0444: if (y > menuHeight)
0445: menuHeight = y;
0446: menuWidth = x + width;
0447: }
0448:
0449: if (!doLayout
0450: || (orientation == HORIZONTAL && menuHeight <= size.height)
0451: || (orientation == VERTICAL && menuWidth <= size.width)) {
0452: if (doLayout && scroll != null) {
0453: scroll.setVisible(false);
0454: scroll.setValue(0);
0455: }
0456: break;
0457: } else {
0458: if (scroll == null) {
0459: scroll = new WingScroll(orientation
0460: ^ VERTICAL);
0461: scroll.addAdjustmentListener(this );
0462: WingToolkit.the().addWheelListener(this ,
0463: scroll);
0464: super .add(scroll);
0465: } else
0466: scroll.setVisible(true);
0467: Dimension scrollPrefSize = scroll
0468: .getPreferredSize();
0469: if (f == 0) {
0470: if (orientation == HORIZONTAL)
0471: size.width -= scrollPrefSize.width;
0472: else
0473: size.height -= scrollPrefSize.height;
0474: } else {
0475: int maximum, extent, line;
0476: if (orientation == HORIZONTAL) {
0477: scroll.setBounds(size.width,
0478: stNormal.margin.top,
0479: scrollPrefSize.width,
0480: extent = size.height
0481: - stNormal.margin.top);
0482: maximum = menuHeight
0483: - stNormal.margin.top;
0484: line = height;
0485: } else {
0486: scroll.setBounds(stNormal.margin.left,
0487: size.height,
0488: extent = size.width
0489: - stNormal.margin.left,
0490: scrollPrefSize.height);
0491: maximum = menuWidth
0492: - stNormal.margin.top;
0493: line = width;
0494: }
0495: scroll.setMaximum(maximum);
0496: scroll.setVisibleAmount(extent);
0497: scroll.setUnitIncrement(line / 2);
0498: }
0499: }
0500: }
0501: }
0502: }
0503: return new Dimension(menuWidth + stNormal.margin.right,
0504: menuHeight + stNormal.margin.bottom);
0505: }
0506:
0507: public synchronized void wingPaint(Graphics g) {
0508: ItemRenderer renderer = getRenderer();
0509: Style st;
0510: Insets margin;
0511: Dimension size = getSize();
0512: WingMenuItem mi = null;
0513: Rectangle prevBounds = null;
0514: int dx = 0, dy = 0;
0515: Rectangle oldClip = g.getClipBounds();
0516: Rectangle newClip = new Rectangle(stNormal.margin.left,
0517: stNormal.margin.top, size.width - stNormal.margin.left
0518: - stNormal.margin.right, size.height
0519: - stNormal.margin.top - stNormal.margin.bottom);
0520: if (oldClip != null)
0521: newClip = newClip.intersection(oldClip);
0522: g.setClip(newClip);
0523:
0524: if (scroll != null) {
0525: if (orientation == HORIZONTAL)
0526: dy = -scroll.getValue();
0527: else
0528: dx = -scroll.getValue();
0529: }
0530: for (int i = 0; i < items.size(); i++) {
0531: mi = (WingMenuItem) items.elementAt(i);
0532:
0533: st = (mi.kind == SEPARATOR) ? stSeparator
0534: : (!mi.enabled) ? stItemDisabled
0535: : (selected == i && pressed || (mi == openItem)) ? stItemPressed
0536: : (hover == i || (hover == -1 && selected == i)) ? stItemSelected
0537: : stItemNormal;
0538:
0539: int imgX = mi.bounds.x + st.margin.left;
0540: WingImage img = null;
0541: if (mi.kind == MENU && imgIcon[12] != null) {
0542: img = imgIcon[(!mi.enabled) ? 14 : (mi.selected) ? 13
0543: : 12];
0544: imgX = mi.bounds.x + mi.bounds.width - st.margin.right
0545: - imgIcon[12].getWidth();
0546: } else {
0547: int ii = -12;
0548: if (mi.kind == CHECKBOX)
0549: ii = 0;
0550: if (mi.kind == RADIO)
0551: ii = 1;
0552: if (mi.selected)
0553: ii += 2;
0554: if (!mi.enabled)
0555: ii += 8;
0556: else if (selected == i)
0557: ii += 4;
0558: if (ii >= 0)
0559: img = imgIcon[ii];
0560: }
0561:
0562: margin = (Insets) st.margin.clone();
0563: int expandWidth = 0;
0564: if (mi.kind != SEPARATOR) {
0565: if (orientation == VERTICAL) {
0566: margin.left += extraLeft;
0567: margin.right += extraRight;
0568: } else if (mi.kind == RADIO)
0569: margin.left += imgIcon[3].getWidth() + st.gap;
0570: else if (mi.kind == CHECKBOX)
0571: margin.left += imgIcon[2].getWidth() + st.gap;
0572: else if (mi.kind == MENU && imgIcon[12] != null)
0573: margin.right += expandWidth = imgIcon[12]
0574: .getWidth()
0575: + st.gap;
0576: }
0577: renderer.drawItem(g, dx + mi.bounds.x, dy + mi.bounds.y,
0578: mi.bounds.width, mi.bounds.height, mi.label, this ,
0579: st, margin, LEFT, RIGHT, mi);
0580:
0581: if (img != null) {
0582: img.drawImage(g, dx + imgX, dy + mi.bounds.y
0583: + (mi.bounds.height - img.getHeight()) / 2,
0584: this );
0585: }
0586: if (orientation == VERTICAL) {
0587: String s = mi.getShortcutText();
0588: if (s != null) {
0589: Style sts = mi.enabled ? stShortcutNormal
0590: : stShortcutDisabled;
0591: renderer.drawItem(g, dx + mi.bounds.x
0592: + mi.bounds.width - st.margin.right
0593: - extraRight, dy + mi.bounds.y, extraRight
0594: - expandWidth, mi.bounds.height, s, this ,
0595: sts, sts.margin, LEFT, RIGHT, mi);
0596: }
0597: }
0598: //fill empty spaces
0599: if (prevBounds != null) {
0600: if (orientation == HORIZONTAL) {
0601: if (prevBounds.y != mi.bounds.y) {
0602: if (prevBounds.x + prevBounds.width < size.width
0603: - stNormal.margin.right)
0604: renderer.drawItem(g, dx + prevBounds.x
0605: + prevBounds.width, dy
0606: + prevBounds.y, size.width
0607: - stNormal.margin.right
0608: - prevBounds.x - prevBounds.width,
0609: prevBounds.height, null, this ,
0610: stTail, stTail.margin, LEFT, RIGHT,
0611: null);
0612: if (lineGapWidth > 0)
0613: renderer.drawItem(g, dx
0614: + stNormal.margin.left, dy
0615: + prevBounds.y + prevBounds.height,
0616: size.width - stNormal.margin.left
0617: - stNormal.margin.right,
0618: lineGapWidth, null, this ,
0619: stLineGap, stLineGap.margin, LEFT,
0620: RIGHT, null);
0621: }
0622: } else {
0623: if (prevBounds.x != mi.bounds.x) {
0624: if (prevBounds.y + prevBounds.height < size.height
0625: - stNormal.margin.bottom)
0626: renderer.drawItem(g, dx + prevBounds.x, dy
0627: + prevBounds.y + prevBounds.height,
0628: prevBounds.width, size.height
0629: - stNormal.margin.bottom
0630: - prevBounds.y
0631: - prevBounds.height, null,
0632: this , stTail, stTail.margin, LEFT,
0633: RIGHT, null);
0634: if (lineGapWidth > 0)
0635: renderer
0636: .drawItem(
0637: g,
0638: dx + prevBounds.x
0639: + prevBounds.width,
0640: dy + stNormal.margin.top,
0641: lineGapWidth,
0642: size.height
0643: - stNormal.margin.top
0644: - stNormal.margin.bottom,
0645: null, this , stLineGap,
0646: stLineGap.margin, LEFT,
0647: RIGHT, null);
0648: }
0649: }
0650: }
0651: prevBounds = mi.bounds;
0652: }
0653: if (prevBounds != null) {
0654: if (orientation == HORIZONTAL) {
0655: if (prevBounds.x + prevBounds.width < size.width
0656: - stNormal.margin.right)
0657: renderer.drawItem(g, dx + prevBounds.x
0658: + prevBounds.width, prevBounds.y, dy
0659: + size.width - stNormal.margin.right
0660: - prevBounds.x - prevBounds.width,
0661: prevBounds.height, null, this , stTail,
0662: stTail.margin, LEFT, RIGHT, null);
0663: } else {
0664: if (prevBounds.y + prevBounds.height < size.height
0665: - stNormal.margin.bottom)
0666: renderer.drawItem(g, dx + prevBounds.x, dy
0667: + prevBounds.y + prevBounds.height,
0668: prevBounds.width, size.height
0669: - stNormal.margin.bottom
0670: - prevBounds.y - prevBounds.height,
0671: null, this , stTail, stTail.margin, LEFT,
0672: RIGHT, null);
0673: }
0674: }
0675: g.setClip(oldClip);
0676: }
0677:
0678: private synchronized void repaintItem(int i) {
0679: if (i >= 0 && i < items.size()) {
0680: Rectangle b = ((WingMenuItem) items.elementAt(i)).bounds;
0681: if (b != null) {
0682:
0683: int dx = 0, dy = 0;
0684: if (scroll != null) {
0685: if (orientation == HORIZONTAL)
0686: dy = -scroll.getValue();
0687: else
0688: dx = -scroll.getValue();
0689: }
0690:
0691: repaint(dx + b.x, dy + b.y, b.width, b.height);
0692: }
0693: }
0694: }
0695:
0696: protected void updateShortcuts(WingComponent target, boolean add) {
0697: if (target == null)
0698: target = this ;
0699: for (int i = 0; true; i++) {
0700: WingMenuItem mi;
0701: synchronized (this ) {
0702: if (i >= items.size())
0703: break;
0704: mi = item(i);
0705: }
0706: if (add)
0707: target.addShortcut(mi);
0708: else
0709: target.removeShortcut(mi);
0710: if (mi.menu != null)
0711: mi.menu.updateShortcuts(target, add);
0712: }
0713: }
0714:
0715: ////////////////////////////////////////////////////////
0716: //actions
0717:
0718: protected void wingProcessMouseEvent(MouseEvent e) {
0719: int id = e.getID();
0720: Point p = e.getPoint();
0721: if (id == MouseEvent.MOUSE_ENTERED) {
0722: mouseSelect(p.x, p.y, id);
0723: } else if (id == MouseEvent.MOUSE_EXITED) {
0724: expander.stop();
0725: mouseSelect(p.x, p.y, id);
0726: } else if (id == MouseEvent.MOUSE_PRESSED) {
0727: pressed = true;
0728: drag = true;
0729: if (!rootMenu().isOneFocused()) {
0730: if (isHeavy() && rootMenu() == this ) {
0731: //for Solaris
0732: ((Window) popupHandle).toFront();
0733: } else {
0734: wingRequestFocusInWindow();
0735: }
0736: }
0737: mouseSelect(p.x, p.y, id);
0738: } else if (id == MouseEvent.MOUSE_RELEASED) {
0739: pressed = false;
0740: repaintItem(selected);
0741: if (drag) {
0742: mouseDragged(p.x, p.y, id, true);
0743: }
0744: } else if (id == MouseEvent.MOUSE_MOVED) {
0745: mouseSelect(p.x, p.y, id);
0746: } else if (id == MouseEvent.MOUSE_DRAGGED) {
0747: mouseDragged(p.x, p.y, id, true);
0748: }
0749: }
0750:
0751: protected void wingProcessKeyEvent(KeyEvent e,
0752: WingComponent redirecting) {
0753: if (redirecting == this )
0754: return;
0755:
0756: WingMenu focusedSubmenu = topMenu();
0757: if (focusedSubmenu != this ) {
0758: focusedSubmenu.wingProcessKeyEvent(e, this );
0759: if (e.isConsumed())
0760: return;
0761: }
0762:
0763: int id = e.getID();
0764: if (id == KeyEvent.KEY_PRESSED && e.getModifiers() == 0) {
0765: int key = e.getKeyCode();
0766: boolean consume = true;
0767: if (orientation == HORIZONTAL) {
0768: if (key == KeyEvent.VK_LEFT)
0769: shiftAndFire(-1, true, true);
0770: else if (key == KeyEvent.VK_RIGHT)
0771: shiftAndFire(+1, true, true);
0772: else if (key == KeyEvent.VK_DOWN && canExpand(true))
0773: fire(true, false, true);
0774: else
0775: consume = false;
0776: } else {
0777: if (key == KeyEvent.VK_UP)
0778: shiftAndFire(-1, true, true);
0779: else if (key == KeyEvent.VK_DOWN)
0780: shiftAndFire(+1, true, true);
0781: else if (key == KeyEvent.VK_RIGHT && canExpand(true))
0782: fire(true, false, true);
0783: else if (key == KeyEvent.VK_LEFT && canExpand(false))
0784: fire(true, true, true);
0785: else
0786: consume = false;
0787: }
0788: if (key == KeyEvent.VK_ENTER || key == KeyEvent.VK_SPACE) {
0789: if (!pressed) {
0790: pressed = true;
0791: fire(false, !isOpenPopup(), true);
0792: }
0793: consume = true;
0794: } else if (key == KeyEvent.VK_ESCAPE) {
0795: closeMenu(false);
0796: consume = true;
0797: } else {
0798: int index = indexOfKey(key);
0799: if (index != -1) {
0800: if (!pressed) {
0801: pressed = true;
0802: select(index);
0803: fire(false, !isOpenPopup(), true);
0804: }
0805: consume = true;
0806: }
0807: }
0808: if (consume)
0809: e.consume();
0810: } else if (id == KeyEvent.KEY_RELEASED) {
0811: pressed = false;
0812: repaintItem(selected);
0813: }
0814: super .wingProcessKeyEvent(e, redirecting);
0815: }
0816:
0817: /**
0818: * <br><br><strong>This method acquire TreeLock</strong>
0819: */
0820: protected void itemShortcut(WingMenuItem mi, KeyEvent e) {
0821: int i = items.indexOf(mi);
0822: if (i == -1 || (mi.menu != null && !isShowing())
0823: || !rootMenu().isShowing()) {
0824: return;
0825: }
0826: e.consume();
0827: select(i);
0828: int id = e.getID();
0829: if (id == KeyEvent.KEY_PRESSED) {
0830: if (!pressed) {
0831: pressed = true;
0832: wingRequestFocusInWindow();
0833: fire(false, !isOpenPopup(), true);
0834: }
0835: } else if (id == KeyEvent.KEY_RELEASED) {
0836: pressed = false;
0837: repaintItem(selected);
0838: }
0839: }
0840:
0841: protected void processFocusEvent(FocusEvent e) {
0842: int id = e.getID();
0843: WingMenu rootMenu = rootMenu();
0844: if (id == FocusEvent.FOCUS_GAINED) {
0845: rootMenu.loser.stop();
0846: if (selected == -1)
0847: shiftAndFire(+1, false, true);
0848: } else if (id == FocusEvent.FOCUS_LOST) {
0849: pressed = false;
0850: repaintItem(selected);
0851: if (popupHandle != null || rootMenu == this )
0852: rootMenu.loser.start();
0853: }
0854: focused = (id == FocusEvent.FOCUS_GAINED);
0855:
0856: super .processFocusEvent(e);
0857: }
0858:
0859: protected void wingProcessActionEvent(ActionEvent e) {
0860: Object src = e.getSource();
0861: if (src == expander) {
0862: synchronized (this ) {
0863: WingMenuItem mi = getItem(hover);
0864: if (mi == null || (mi.kind != MENU && openItem == null)) {
0865: return;
0866: }
0867: select(hover);
0868: }
0869: fire(true, false, false);
0870: } else if (src == loser) {
0871: if (!rootMenu().isOneFocused())
0872: closeMenu(true);
0873: } else {
0874: if (src != tipStartTimer && src != tipStopTimer) {
0875: closeMenu(false);
0876: }
0877: super .wingProcessActionEvent(e);
0878: }
0879: }
0880:
0881: /**
0882: * <br><br><strong>This method acquire TreeLock</strong>
0883: */
0884: protected void cancelPopup(WingComponent src) {
0885: WingMenuItem sub = rootMenu().face;
0886: WingMenu o;
0887: while ((o = sub.owner) != null)
0888: sub = o.face;
0889: while (sub != null) {
0890: Container c = sub.menu;
0891: if (c == src || c.isAncestorOf(src))
0892: return;
0893: sub = sub.menu.openItem;
0894: }
0895: closeMenu(true);
0896: }
0897:
0898: static int ln = 1;
0899:
0900: ////////////////////////////////////////////////////////
0901: // reactions
0902:
0903: public synchronized WingMenuItem getItem(int index) {
0904: if (index < 0 || index >= items.size())
0905: return null;
0906: return item(index);
0907: }
0908:
0909: public int getItemCount() {
0910: return items.size();
0911: }
0912:
0913: private WingMenuItem item(int index) {
0914: return (WingMenuItem) items.elementAt(index);
0915: }
0916:
0917: private boolean canExpand(boolean expand) {
0918: WingMenuItem mi = (expand) ? getItem(selected) : openItem;
0919: if (mi == null || mi.kind != MENU)
0920: return false;
0921: return (openItem == mi) ^ expand;
0922: }
0923:
0924: /**
0925: * <br><br><strong>This method acquire TreeLock</strong>
0926: */
0927: private void shiftAndFire(int delta, boolean fire,
0928: boolean fromKeyboard) {
0929: synchronized (this ) {
0930: int next = (hover != -1) ? hover : selected;
0931: for (int i = 0, is = items.size(); i < is; i++) {
0932: next = (next + is + is + delta) % is;
0933:
0934: WingMenuItem mi = item(next);
0935: if (mi.enabled && mi.kind != SEPARATOR) {
0936: if (next == selected)
0937: return;
0938: select(next);
0939: break;
0940: }
0941: }
0942: }
0943: if (expanded && fire)
0944: fire(true, false, fromKeyboard);
0945: }
0946:
0947: private synchronized int indexOfKey(int key) {
0948: for (int i = 0; i < items.size(); i++) {
0949: if (item(i).getShortcutCode() == key)
0950: return i;
0951: }
0952: return -1;
0953: }
0954:
0955: private synchronized int itemAt(int x, int y) {
0956: int dx = 0, dy = 0;
0957: if (scroll != null) {
0958: if (orientation == HORIZONTAL)
0959: dy = -scroll.getValue();
0960: else
0961: dx = -scroll.getValue();
0962: }
0963:
0964: for (int i = 0; i < items.size(); i++) {
0965: if (item(i).bounds.contains(-dx + x, -dy + y)) {
0966: return i;
0967: }
0968: }
0969: return -1;
0970: }
0971:
0972: /**
0973: * <br><br><strong>This method acquire TreeLock</strong>
0974: */
0975: private void mouseDragged(int x, int y, int eventId, boolean stage1) {
0976: Point ps = null;
0977: try {
0978: ps = getLocationOnScreen();
0979: } catch (IllegalComponentStateException e) {
0980: }
0981:
0982: if (stage1) {
0983:
0984: WingMenu m = topMenu();
0985: if (ps != null)
0986: m.mouseDragged(ps.x + x, ps.y + y, eventId, false);
0987: } else {
0988: Dimension size;
0989: if (ps != null && x >= ps.x && y >= ps.y
0990: && x < ps.x + (size = getSize()).width
0991: && y < ps.y + size.height) {
0992: mouseSelect(x - ps.x, y - ps.y, eventId);
0993: } else {
0994: WingMenu owner = face.owner;
0995: if (owner != null)
0996: owner.mouseDragged(x, y, eventId, false);
0997: }
0998: }
0999: }
1000:
1001: /**
1002: * <br><br><strong>This method acquire TreeLock</strong>
1003: */
1004: private void mouseSelect(int x, int y, int eventId) {
1005: synchronized (this ) {
1006: int old = hover;
1007: hover = (eventId != MouseEvent.MOUSE_EXITED) ? itemAt(x, y)
1008: : -1;
1009: if (hover == old
1010: && (eventId == MouseEvent.MOUSE_MOVED || eventId == MouseEvent.MOUSE_DRAGGED)) {
1011: return;
1012: }
1013:
1014: repaintItem(old);
1015: if (((eventId == MouseEvent.MOUSE_MOVED || eventId == MouseEvent.MOUSE_ENTERED) && (isOpenPopup() || !expanded))
1016: || eventId == MouseEvent.MOUSE_EXITED) {
1017: repaintItem(hover);
1018: repaintItem(selected);
1019: scrollTo(hover);
1020: } else {
1021: select(hover);
1022: }
1023:
1024: if (hover == -1 && selected == -1)
1025: return;
1026: }
1027: if (eventId == MouseEvent.MOUSE_PRESSED) {
1028: if (selected == hover)
1029: fire(false, !isOpenPopup(), false);
1030: } else if (eventId == MouseEvent.MOUSE_RELEASED) {
1031: if (selected == hover)
1032: fire(false, false, false);
1033: } else if (eventId == MouseEvent.MOUSE_DRAGGED) {
1034: fire(true, false, false);
1035: } else if (eventId == MouseEvent.MOUSE_MOVED
1036: || eventId == MouseEvent.MOUSE_ENTERED) {
1037: if (isOpenPopup()) {
1038: expander.start();
1039: } else if (expanded)
1040: fire(true, false, false);
1041: }
1042: }
1043:
1044: private synchronized void select(int index) {
1045: if (index >= items.size())
1046: index = -1;
1047: if (index == selected || (index == -1 && (expanded || pressed)))
1048: return;
1049: if (index != -1) {
1050: WingMenuItem mi = item(index);
1051: if (mi.kind == SEPARATOR || !mi.enabled)
1052: return;
1053: }
1054:
1055: repaintItem(selected);
1056: selected = index;
1057: if (hover != -1) {
1058: repaintItem(hover);
1059: hover = index;
1060: }
1061: repaintItem(index);
1062: scrollTo(index);
1063: }
1064:
1065: private synchronized void scrollTo(int index) {
1066: if (scroll != null && index != -1) {
1067: WingMenuItem mi = item(index);
1068: if (mi.kind == SEPARATOR)
1069: return;
1070: Rectangle b = mi.bounds;
1071: int d, min, max, s, e;
1072: d = -scroll.getValue();
1073: if (orientation == HORIZONTAL) {
1074: min = stNormal.margin.top;
1075: max = getSize().height - stNormal.margin.bottom;
1076: s = b.y;
1077: e = b.y + b.height;
1078: } else {
1079: min = stNormal.margin.left;
1080: max = getSize().width - stNormal.margin.right;
1081: s = b.x;
1082: e = b.x + b.width;
1083: }
1084: if (d + s < min) {
1085: scroll.setValue(s - min);
1086: repaint();
1087: } else if (d + e > max) {
1088: scroll.setValue(e - max);
1089: repaint();
1090: }
1091: }
1092: }
1093:
1094: /**
1095: * <br><br><strong>This method acquire TreeLock</strong>
1096: */
1097: private void fire(boolean menuOnly, boolean toggle,
1098: boolean fromKeyboard) {
1099: WingMenuItem mi, toClose = null;
1100: int toCloseIndex = -1;
1101: synchronized (this ) {
1102: mi = getItem(selected);
1103: if (mi == null || (mi == openItem && !toggle))
1104: return;
1105: if (openItem != null && (mi != openItem || toggle)) {
1106: if (toggle && mi == openItem)
1107: drag = false;
1108: toClose = openItem;
1109: toCloseIndex = this .items.indexOf(toClose);
1110: openItem = null;
1111: }
1112: }
1113:
1114: if (!mi.enabled || mi.kind == SEPARATOR) {
1115: // do nothing
1116: } else if ((mi.kind == NORMAL || mi.kind == CHECKBOX || mi.kind == RADIO)
1117: && !menuOnly) {
1118: expanded = false;
1119: drag = false;
1120: mi.fireAction();
1121: } else if (mi.kind == MENU) {
1122: if (toClose == mi)
1123: mi = null;
1124: synchronized (this ) {
1125: openItem = mi;
1126: }
1127: if (mi != null) {
1128: expanded = true;
1129:
1130: int dx = 0, dy = 0;
1131: if (scroll != null) {
1132: if (orientation == HORIZONTAL)
1133: dy = -scroll.getValue();
1134: else
1135: dx = -scroll.getValue();
1136: }
1137: mi.menu.showPopup(this , dx + mi.bounds.x, dy
1138: + mi.bounds.y, mi.bounds.width,
1139: mi.bounds.height, orientation, fromKeyboard);
1140: } else {
1141: expanded = false;
1142: }
1143: repaintItem(selected);
1144: }
1145: if (toClose != null) {
1146: repaintItem(toCloseIndex);
1147: boolean rf = toClose.menu.isHeavy()
1148: && toClose.menu.isOneFocused();
1149: toClose.menu.hidePopup();
1150: if (rf) {
1151: rootMenu().requestFocus();
1152:
1153: for (WingMenuItem mi2 = rootMenu().face; mi2 != null; mi2 = mi2.menu.openItem) {
1154: if (mi2.menu.isHeavy()) {
1155: Container handle = mi2.menu.popupHandle;
1156: if (handle instanceof Window) {
1157: ((Window) handle).toFront();
1158: }
1159: }
1160: }
1161: }
1162: }
1163: }
1164:
1165: private boolean isHeavy() {
1166: return popupHandle != null && popupHandle != this ;
1167: }
1168:
1169: private boolean isOneFocused() {
1170: WingMenuItem mi = face;
1171: boolean r = false;
1172: while (mi != null) {
1173: if (mi.menu.focused) {
1174: r = true;
1175: }
1176: mi = mi.menu.openItem;
1177: }
1178: if (r)
1179: return true;
1180: return false;
1181: }
1182:
1183: private WingMenu rootMenu() {
1184: WingMenu m = this , next;
1185: while ((next = m.face.owner) != null)
1186: m = next;
1187: return m;
1188: }
1189:
1190: private WingMenu topMenu() {
1191: WingMenuItem mi = this .face, next;
1192: while ((next = mi.menu.openItem) != null)
1193: mi = next;
1194: return mi.menu;
1195: }
1196:
1197: /**
1198: * <br><br><strong>This method acquire TreeLock</strong>
1199: */
1200: private void closeMenu(boolean lostFocus) {
1201: expanded = false;
1202: drag = false;
1203: focused = false;
1204: int oldSel = selected;
1205: if (lostFocus) {
1206: select(-1);
1207: }
1208: repaintItem(oldSel);
1209: WingMenu owner = face.owner;
1210: if (owner != null)
1211: owner.closeMenu(lostFocus);
1212: else if (isOpenPopup() || openItem != null) {
1213: hidePopup();
1214:
1215: WingMenuItem mi;
1216: synchronized (this ) {
1217: mi = openItem;
1218: openItem = null;
1219: }
1220: if (mi != null)
1221: mi.menu.hidePopup();
1222:
1223: if (!lostFocus) {
1224: WingComponent po = popupOwner;
1225: if (po != null) {
1226: if (po.wingFocusable)
1227: po.requestFocus();
1228: else
1229: po.transferFocus();
1230: } else if (!focused) {
1231: this .requestFocus();
1232: }
1233: }
1234: }
1235: }
1236:
1237: /**
1238: * invoking on event thread is recommended<br>
1239: * <br><br><strong>This method acquire TreeLock</strong>
1240: */
1241: public void showPopup(WingComponent origin, int pivotX, int pivotY,
1242: int pivotWidth, int pivotHeight, int direction) {
1243: showPopup(origin, pivotX, pivotY, pivotWidth, pivotHeight,
1244: direction, false);
1245: }
1246:
1247: private void showPopup(WingComponent origin, int pivotX,
1248: int pivotY, int pivotWidth, int pivotHeight, int direction,
1249: boolean select) {
1250: if (isOpenPopup())
1251: return;
1252:
1253: WingMenu content = this ;
1254:
1255: setSize(0, 0);
1256:
1257: boolean isRoot = rootMenu() == this ;
1258:
1259: if (heavy) {
1260: heavy = false;
1261: WingComponent.updateSkin(this );
1262: }
1263: Container handle = WingToolkit.showPopup(origin, pivotX,
1264: pivotY, pivotWidth, pivotHeight, direction, 16, 16,
1265: content, null, lightPopups, false, !heavyPopups, false);
1266: if (handle == null) {
1267: heavy = true;
1268: WingComponent.updateSkin(this );
1269: handle = WingToolkit.showPopup(origin, pivotX, pivotY,
1270: pivotWidth, pivotHeight, direction, 16, 16,
1271: content, null, false, heavyPopups, false, isRoot);
1272: }
1273:
1274: if (handle != null) {
1275: regCancelPopup();
1276: if (scroll != null)
1277: scroll.setValue(0);
1278: }
1279: synchronized (this ) {
1280: popupHandle = handle;
1281: popupOwner = origin;
1282: }
1283: if (handle != null) {
1284: if (isRoot) {
1285: requestFocus();
1286: } else if (isHeavy()) {
1287: wingRequestFocusInWindow();
1288: }
1289: if (select && selected == -1)
1290: shiftAndFire(+1, false, false);
1291: }
1292:
1293: }
1294:
1295: /**
1296: * invoking on event thread is recommended<br>
1297: * <br><br><strong>This method acquire TreeLock</strong>
1298: */
1299: public void hidePopup() {
1300: if (!isOpenPopup())
1301: return;
1302:
1303: Container popupHandle;
1304: WingMenuItem openItem;
1305: synchronized (this ) {
1306: popupHandle = this .popupHandle;
1307: this .popupHandle = null;
1308: openItem = this .openItem;
1309: this .openItem = null;
1310: }
1311: clrPopup(this );
1312: if (openItem != null)
1313: openItem.menu.hidePopup();
1314:
1315: expanded = false;
1316: pressed = false;
1317: select(-1);
1318: WingToolkit.hidePopup(popupHandle);
1319: }
1320:
1321: private boolean isOpenPopup() {
1322: return popupHandle != null;
1323: }
1324:
1325: /**
1326: * <br><br><strong>This method acquire TreeLock</strong>
1327: */
1328: private void regCancelPopup() {
1329: WingMenu o = face.owner;
1330: if (o != null && o.isOpenPopup())
1331: o.regCancelPopup();
1332: else
1333: setPopup(this);
1334: }
1335: }
|