0001: /*
0002: * Copyright 2005-2007 Sun Microsystems, Inc. All Rights Reserved.
0003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004: *
0005: * This code is free software; you can redistribute it and/or modify it
0006: * under the terms of the GNU General Public License version 2 only, as
0007: * published by the Free Software Foundation. Sun designates this
0008: * particular file as subject to the "Classpath" exception as provided
0009: * by Sun in the LICENSE file that accompanied this code.
0010: *
0011: * This code is distributed in the hope that it will be useful, but WITHOUT
0012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014: * version 2 for more details (a copy is included in the LICENSE file that
0015: * accompanied this code).
0016: *
0017: * You should have received a copy of the GNU General Public License version
0018: * 2 along with this work; if not, write to the Free Software Foundation,
0019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020: *
0021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022: * CA 95054 USA or visit www.sun.com if you need additional information or
0023: * have any questions.
0024: */
0025: package sun.awt.X11;
0026:
0027: import java.awt.*;
0028: import java.awt.peer.*;
0029: import java.awt.event.*;
0030: import java.awt.image.ColorModel;
0031:
0032: import sun.awt.*;
0033:
0034: import java.util.ArrayList;
0035: import java.util.Vector;
0036: import java.util.logging.*;
0037: import sun.java2d.SurfaceData;
0038: import sun.java2d.SunGraphics2D;
0039:
0040: /**
0041: * The abstract class XBaseMenuWindow is the superclass
0042: * of all menu windows.
0043: */
0044: abstract public class XBaseMenuWindow extends XWindow {
0045:
0046: /************************************************
0047: *
0048: * Data members
0049: *
0050: ************************************************/
0051:
0052: private static Logger log = Logger
0053: .getLogger("sun.awt.X11.XBaseMenuWindow");
0054:
0055: /*
0056: * Colors are calculated using MotifColorUtilities class
0057: * from backgroundColor and are contained in these vars.
0058: */
0059: private Color backgroundColor;
0060: private Color foregroundColor;
0061: private Color lightShadowColor;
0062: private Color darkShadowColor;
0063: private Color selectedColor;
0064: private Color disabledColor;
0065:
0066: /**
0067: * Array of items.
0068: */
0069: private ArrayList<XMenuItemPeer> items;
0070:
0071: /**
0072: * Index of selected item in array of items
0073: */
0074: private int selectedIndex = -1;
0075:
0076: /**
0077: * Specifies currently showing submenu.
0078: */
0079: private XMenuPeer showingSubmenu = null;
0080:
0081: /**
0082: * Static synchronizational object.
0083: * Following operations should be synchronized
0084: * using this object:
0085: * 1. Access to items vector
0086: * 2. Access to selection
0087: * 3. Access to showing menu window member
0088: *
0089: * This is lowest level lock,
0090: * no other locks should be taken when
0091: * thread own this lock.
0092: */
0093: static private Object menuTreeLock = new Object();
0094:
0095: /************************************************
0096: *
0097: * Event processing
0098: *
0099: ************************************************/
0100:
0101: /**
0102: * If mouse button is clicked on item showing submenu
0103: * we have to hide its submenu.
0104: * And if mouse button is pressed on such item and
0105: * dragged to another, getShowingSubmenu() is changed.
0106: * So this member saves the item that the user
0107: * presses mouse button on _only_ if it's showing submenu.
0108: */
0109: private XMenuPeer showingMousePressedSubmenu = null;
0110:
0111: /**
0112: * If the PopupMenu is invoked as a result of right button click
0113: * first mouse event after grabInput would be MouseReleased.
0114: * We need to check if the user has moved mouse after input grab.
0115: * If yes - hide the PopupMenu. If no - do nothing
0116: */
0117: protected Point grabInputPoint = null;
0118: protected boolean hasPointerMoved = false;
0119:
0120: /************************************************
0121: *
0122: * Mapping data
0123: *
0124: ************************************************/
0125:
0126: /**
0127: * Mapping data that is filled in getMappedItems function
0128: * and reset in resetSize function. It contains array of
0129: * items in order that they appear on screen and may contain
0130: * additional data defined by descendants.
0131: */
0132: private MappingData mappingData;
0133:
0134: static class MappingData implements Cloneable {
0135:
0136: /**
0137: * Array of item in order that they appear on screen
0138: */
0139: private XMenuItemPeer[] items;
0140:
0141: /**
0142: * Constructs MappingData object with list
0143: * of menu items
0144: */
0145: MappingData(XMenuItemPeer[] items) {
0146: this .items = items;
0147: }
0148:
0149: /**
0150: * Constructs MappingData without items
0151: * This constructor should be used in case of errors
0152: */
0153: MappingData() {
0154: this .items = new XMenuItemPeer[0];
0155: }
0156:
0157: public Object clone() {
0158: try {
0159: return super .clone();
0160: } catch (CloneNotSupportedException ex) {
0161: throw new InternalError();
0162: }
0163: }
0164:
0165: public XMenuItemPeer[] getItems() {
0166: return this .items;
0167: }
0168: }
0169:
0170: /************************************************
0171: *
0172: * Construction
0173: *
0174: ************************************************/
0175: XBaseMenuWindow() {
0176: super (new XCreateWindowParams(new Object[] { DELAYED,
0177: Boolean.TRUE }));
0178: }
0179:
0180: /************************************************
0181: *
0182: * Abstract methods
0183: *
0184: ************************************************/
0185:
0186: /**
0187: * Returns parent menu window (not the X-heirarchy parent window)
0188: */
0189: protected abstract XBaseMenuWindow getParentMenuWindow();
0190:
0191: /**
0192: * Performs mapping of items in window.
0193: * This function creates and fills specific
0194: * descendant of MappingData
0195: * and sets mapping coordinates of items
0196: * This function should return default menu data
0197: * if errors occur
0198: */
0199: protected abstract MappingData map();
0200:
0201: /**
0202: * Calculates placement of submenu window
0203: * given bounds of item with submenu and
0204: * size of submenu window. Returns suggested
0205: * rectangle for submenu window in global coordinates
0206: * @param itemBounds the bounding rectangle of item
0207: * in local coordinates
0208: * @param windowSize the desired size of submenu's window
0209: */
0210: protected abstract Rectangle getSubmenuBounds(Rectangle itemBounds,
0211: Dimension windowSize);
0212:
0213: /**
0214: * This function is to be called if it's likely that size
0215: * of items was changed. It can be called from any thread
0216: * in any locked state, so it should not take locks
0217: */
0218: protected abstract void updateSize();
0219:
0220: /************************************************
0221: *
0222: * Initialization
0223: *
0224: ************************************************/
0225:
0226: /**
0227: * Overrides XBaseWindow.instantPreInit
0228: */
0229: void instantPreInit(XCreateWindowParams params) {
0230: super .instantPreInit(params);
0231: items = new ArrayList();
0232: }
0233:
0234: /************************************************
0235: *
0236: * General-purpose functions
0237: *
0238: ************************************************/
0239:
0240: /**
0241: * Returns static lock used for menus
0242: */
0243: static Object getMenuTreeLock() {
0244: return menuTreeLock;
0245: }
0246:
0247: /**
0248: * This function is called to clear all saved
0249: * size data.
0250: */
0251: protected void resetMapping() {
0252: mappingData = null;
0253: }
0254:
0255: /**
0256: * Invokes repaint procedure on eventHandlerThread
0257: */
0258: void postPaintEvent() {
0259: if (isShowing()) {
0260: PaintEvent pe = new PaintEvent(target, PaintEvent.PAINT,
0261: new Rectangle(0, 0, width, height));
0262: postEvent(pe);
0263: }
0264: }
0265:
0266: /************************************************
0267: *
0268: * Utility functions for manipulating items
0269: *
0270: ************************************************/
0271:
0272: /**
0273: * Thread-safely returns item at specified index
0274: * @param index the position of the item to be returned.
0275: */
0276: XMenuItemPeer getItem(int index) {
0277: if (index >= 0) {
0278: synchronized (getMenuTreeLock()) {
0279: if (items.size() > index) {
0280: return items.get(index);
0281: }
0282: }
0283: }
0284: return null;
0285: }
0286:
0287: /**
0288: * Thread-safely creates a copy of the items vector
0289: */
0290: XMenuItemPeer[] copyItems() {
0291: synchronized (getMenuTreeLock()) {
0292: return (XMenuItemPeer[]) items
0293: .toArray(new XMenuItemPeer[] {});
0294: }
0295: }
0296:
0297: /**
0298: * Thread-safely returns selected item
0299: */
0300: XMenuItemPeer getSelectedItem() {
0301: synchronized (getMenuTreeLock()) {
0302: if (selectedIndex >= 0) {
0303: if (items.size() > selectedIndex) {
0304: return items.get(selectedIndex);
0305: }
0306: }
0307: return null;
0308: }
0309: }
0310:
0311: /**
0312: * Returns showing submenu, if any
0313: */
0314: XMenuPeer getShowingSubmenu() {
0315: synchronized (getMenuTreeLock()) {
0316: return showingSubmenu;
0317: }
0318: }
0319:
0320: /**
0321: * Adds item to end of items vector.
0322: * Note that this function does not perform
0323: * check for adding duplicate items
0324: * @param item item to add
0325: */
0326: public void addItem(MenuItem item) {
0327: XMenuItemPeer mp = (XMenuItemPeer) item.getPeer();
0328: if (mp != null) {
0329: mp.setContainer(this );
0330: synchronized (getMenuTreeLock()) {
0331: items.add(mp);
0332: }
0333: } else {
0334: if (log.isLoggable(Level.FINE)) {
0335: log
0336: .fine("WARNING: Attempt to add menu item without a peer");
0337: }
0338: }
0339: updateSize();
0340: }
0341:
0342: /**
0343: * Removes item at the specified index from items vector.
0344: * @param index the position of the item to be removed
0345: */
0346: public void delItem(int index) {
0347: synchronized (getMenuTreeLock()) {
0348: if (selectedIndex == index) {
0349: selectItem(null, false);
0350: } else if (selectedIndex > index) {
0351: selectedIndex--;
0352: }
0353: if (index < items.size()) {
0354: items.remove(index);
0355: } else {
0356: if (log.isLoggable(Level.FINE)) {
0357: log
0358: .fine("WARNING: Attempt to remove non-existing menu item, index : "
0359: + index
0360: + ", item count : "
0361: + items.size());
0362: }
0363: }
0364: }
0365: updateSize();
0366: }
0367:
0368: /**
0369: * Clears items vector and loads specified vector
0370: * @param items vector to be loaded
0371: */
0372: public void reloadItems(Vector items) {
0373: synchronized (getMenuTreeLock()) {
0374: this .items.clear();
0375: MenuItem[] itemArray = (MenuItem[]) items
0376: .toArray(new MenuItem[] {});
0377: int itemCnt = itemArray.length;
0378: for (int i = 0; i < itemCnt; i++) {
0379: addItem(itemArray[i]);
0380: }
0381: }
0382: }
0383:
0384: /**
0385: * Select specified item and shows/hides submenus if necessary
0386: * We can not select by index, so we need to select by ref.
0387: * @param item the item to be selected, null to clear selection
0388: * @param showWindowIfMenu if the item is XMenuPeer then its
0389: * window is shown/hidden according to this param.
0390: */
0391: void selectItem(XMenuItemPeer item, boolean showWindowIfMenu) {
0392: synchronized (getMenuTreeLock()) {
0393: XMenuPeer showingSubmenu = getShowingSubmenu();
0394: int newSelectedIndex = (item != null) ? items.indexOf(item)
0395: : -1;
0396: if (this .selectedIndex != newSelectedIndex) {
0397: if (log.isLoggable(Level.FINEST)) {
0398: log.finest("Selected index changed, was : "
0399: + this .selectedIndex + ", new : "
0400: + newSelectedIndex);
0401: }
0402: this .selectedIndex = newSelectedIndex;
0403: postPaintEvent();
0404: }
0405: final XMenuPeer submenuToShow = (showWindowIfMenu && (item instanceof XMenuPeer)) ? (XMenuPeer) item
0406: : null;
0407: if (submenuToShow != showingSubmenu) {
0408: XToolkit.executeOnEventHandlerThread(target,
0409: new Runnable() {
0410: public void run() {
0411: doShowSubmenu(submenuToShow);
0412: }
0413: });
0414: }
0415: }
0416: }
0417:
0418: /**
0419: * Performs hiding of currently showing submenu
0420: * and showing of submenuToShow.
0421: * This function should be executed on eventHandlerThread
0422: * @param submenuToShow submenu to be shown or null
0423: * to hide currently showing submenu
0424: */
0425: private void doShowSubmenu(XMenuPeer submenuToShow) {
0426: XMenuWindow menuWindowToShow = (submenuToShow != null) ? submenuToShow
0427: .getMenuWindow()
0428: : null;
0429: Dimension dim = null;
0430: Rectangle bounds = null;
0431: //ensureCreated can invoke XWindowPeer.init() ->
0432: //XWindowPeer.initGraphicsConfiguration() ->
0433: //Window.getGraphicsConfiguration()
0434: //that tries to obtain Component.AWTTreeLock.
0435: //So it should be called outside awtLock()
0436: if (menuWindowToShow != null) {
0437: menuWindowToShow.ensureCreated();
0438: }
0439: XToolkit.awtLock();
0440: try {
0441: synchronized (getMenuTreeLock()) {
0442: if (showingSubmenu != submenuToShow) {
0443: if (log.isLoggable(Level.FINER)) {
0444: log.finest("Changing showing submenu");
0445: }
0446: if (showingSubmenu != null) {
0447: XMenuWindow showingSubmenuWindow = showingSubmenu
0448: .getMenuWindow();
0449: if (showingSubmenuWindow != null) {
0450: showingSubmenuWindow.hide();
0451: }
0452: }
0453: if (submenuToShow != null) {
0454: dim = menuWindowToShow.getDesiredSize();
0455: bounds = menuWindowToShow.getParentMenuWindow()
0456: .getSubmenuBounds(
0457: submenuToShow.getBounds(), dim);
0458: menuWindowToShow.show(bounds);
0459: }
0460: showingSubmenu = submenuToShow;
0461: }
0462: }
0463: } finally {
0464: XToolkit.awtUnlock();
0465: }
0466: }
0467:
0468: final void setItemsFont(Font font) {
0469: XMenuItemPeer[] items = copyItems();
0470: int itemCnt = items.length;
0471: for (int i = 0; i < itemCnt; i++) {
0472: items[i].setFont(font);
0473: }
0474: }
0475:
0476: /************************************************
0477: *
0478: * Utility functions for manipulating mapped items
0479: *
0480: ************************************************/
0481:
0482: /**
0483: * Returns array of mapped items, null if error
0484: * This function has to be not synchronized
0485: * and we have to guarantee that we return
0486: * some MappingData to user. It's OK if
0487: * this.mappingData is replaced meanwhile
0488: */
0489: MappingData getMappingData() {
0490: MappingData mappingData = this .mappingData;
0491: if (mappingData == null) {
0492: mappingData = map();
0493: this .mappingData = mappingData;
0494: }
0495: return (MappingData) mappingData.clone();
0496: }
0497:
0498: /**
0499: * returns item thats mapped coordinates contain
0500: * specified point, null of none.
0501: * @param pt the point in this window's coordinate system
0502: */
0503: XMenuItemPeer getItemFromPoint(Point pt) {
0504: XMenuItemPeer[] items = getMappingData().getItems();
0505: int cnt = items.length;
0506: for (int i = 0; i < cnt; i++) {
0507: if (items[i].getBounds().contains(pt)) {
0508: return items[i];
0509: }
0510: }
0511: return null;
0512: }
0513:
0514: /**
0515: * Returns first item after currently selected
0516: * item that can be selected according to mapping array.
0517: * (no separators and no disabled items).
0518: * Currently selected item if it's only selectable,
0519: * null if no item can be selected
0520: */
0521: XMenuItemPeer getNextSelectableItem() {
0522: XMenuItemPeer[] mappedItems = getMappingData().getItems();
0523: XMenuItemPeer selectedItem = getSelectedItem();
0524: int cnt = mappedItems.length;
0525: //Find index of selected item
0526: int selIdx = -1;
0527: for (int i = 0; i < cnt; i++) {
0528: if (mappedItems[i] == selectedItem) {
0529: selIdx = i;
0530: break;
0531: }
0532: }
0533: int idx = (selIdx == cnt - 1) ? 0 : selIdx + 1;
0534: //cycle through mappedItems to find selectable item
0535: //beginning from the next item and moving to the
0536: //beginning of array when end is reached.
0537: //Cycle is finished on selected item itself
0538: for (int i = 0; i < cnt; i++) {
0539: XMenuItemPeer item = mappedItems[idx];
0540: if (!item.isSeparator() && item.isTargetItemEnabled()) {
0541: return item;
0542: }
0543: idx++;
0544: if (idx >= cnt) {
0545: idx = 0;
0546: }
0547: }
0548: //return null if no selectable item was found
0549: return null;
0550: }
0551:
0552: /**
0553: * Returns first item before currently selected
0554: * see getNextSelectableItem() for comments
0555: */
0556: XMenuItemPeer getPrevSelectableItem() {
0557: XMenuItemPeer[] mappedItems = getMappingData().getItems();
0558: XMenuItemPeer selectedItem = getSelectedItem();
0559: int cnt = mappedItems.length;
0560: //Find index of selected item
0561: int selIdx = -1;
0562: for (int i = 0; i < cnt; i++) {
0563: if (mappedItems[i] == selectedItem) {
0564: selIdx = i;
0565: break;
0566: }
0567: }
0568: int idx = (selIdx <= 0) ? cnt - 1 : selIdx - 1;
0569: //cycle through mappedItems to find selectable item
0570: for (int i = 0; i < cnt; i++) {
0571: XMenuItemPeer item = mappedItems[idx];
0572: if (!item.isSeparator() && item.isTargetItemEnabled()) {
0573: return item;
0574: }
0575: idx--;
0576: if (idx < 0) {
0577: idx = cnt - 1;
0578: }
0579: }
0580: //return null if no selectable item was found
0581: return null;
0582: }
0583:
0584: /**
0585: * Returns first selectable item
0586: * This function is intended for clearing selection
0587: */
0588: XMenuItemPeer getFirstSelectableItem() {
0589: XMenuItemPeer[] mappedItems = getMappingData().getItems();
0590: int cnt = mappedItems.length;
0591: for (int i = 0; i < cnt; i++) {
0592: XMenuItemPeer item = mappedItems[i];
0593: if (!item.isSeparator() && item.isTargetItemEnabled()) {
0594: return item;
0595: }
0596: }
0597:
0598: return null;
0599: }
0600:
0601: /************************************************
0602: *
0603: * Utility functions for manipulating
0604: * hierarchy of windows
0605: *
0606: ************************************************/
0607:
0608: /**
0609: * returns leaf menu window or
0610: * this if no children are showing
0611: */
0612: XBaseMenuWindow getShowingLeaf() {
0613: synchronized (getMenuTreeLock()) {
0614: XBaseMenuWindow leaf = this ;
0615: XMenuPeer leafchild = leaf.getShowingSubmenu();
0616: while (leafchild != null) {
0617: leaf = leafchild.getMenuWindow();
0618: leafchild = leaf.getShowingSubmenu();
0619: }
0620: return leaf;
0621: }
0622: }
0623:
0624: /**
0625: * returns root menu window
0626: * or this if this window is topmost
0627: */
0628: XBaseMenuWindow getRootMenuWindow() {
0629: synchronized (getMenuTreeLock()) {
0630: XBaseMenuWindow t = this ;
0631: XBaseMenuWindow tparent = t.getParentMenuWindow();
0632: while (tparent != null) {
0633: t = tparent;
0634: tparent = t.getParentMenuWindow();
0635: }
0636: return t;
0637: }
0638: }
0639:
0640: /**
0641: * Returns window that contains pt.
0642: * search is started from leaf window
0643: * to return first window in Z-order
0644: * @param pt point in global coordinates
0645: */
0646: XBaseMenuWindow getMenuWindowFromPoint(Point pt) {
0647: synchronized (getMenuTreeLock()) {
0648: XBaseMenuWindow t = getShowingLeaf();
0649: while (t != null) {
0650: Rectangle r = new Rectangle(
0651: t.toGlobal(new Point(0, 0)), t.getSize());
0652: if (r.contains(pt)) {
0653: return t;
0654: }
0655: t = t.getParentMenuWindow();
0656: }
0657: return null;
0658: }
0659: }
0660:
0661: /************************************************
0662: *
0663: * Primitives for getSubmenuBounds
0664: *
0665: * These functions are invoked from getSubmenuBounds
0666: * implementations in different order. They check if window
0667: * of size windowSize fits to the specified edge of
0668: * rectangle itemBounds on the screen of screenSize.
0669: * Return rectangle that occupies the window if it fits or null.
0670: *
0671: ************************************************/
0672:
0673: /**
0674: * Checks if window fits below specified item
0675: * returns rectangle that the window fits to or null.
0676: * @param itemBounds rectangle of item in global coordinates
0677: * @param windowSize size of submenu window to fit
0678: * @param screenSize size of screen
0679: */
0680: Rectangle fitWindowBelow(Rectangle itemBounds,
0681: Dimension windowSize, Dimension screenSize) {
0682: int width = windowSize.width;
0683: int height = windowSize.height;
0684: //Fix for 6267162: PIT: Popup Menu gets hidden below the screen when opened
0685: //near the periphery of the screen, XToolkit
0686: //Window should be moved if it's outside top-left screen bounds
0687: int x = (itemBounds.x > 0) ? itemBounds.x : 0;
0688: int y = (itemBounds.y + itemBounds.height > 0) ? itemBounds.y
0689: + itemBounds.height : 0;
0690: if (y + height <= screenSize.height) {
0691: //move it to the left if needed
0692: if (width > screenSize.width) {
0693: width = screenSize.width;
0694: }
0695: if (x + width > screenSize.width) {
0696: x = screenSize.width - width;
0697: }
0698: return new Rectangle(x, y, width, height);
0699: } else {
0700: return null;
0701: }
0702: }
0703:
0704: /**
0705: * Checks if window fits above specified item
0706: * returns rectangle that the window fits to or null.
0707: * @param itemBounds rectangle of item in global coordinates
0708: * @param windowSize size of submenu window to fit
0709: * @param screenSize size of screen
0710: */
0711: Rectangle fitWindowAbove(Rectangle itemBounds,
0712: Dimension windowSize, Dimension screenSize) {
0713: int width = windowSize.width;
0714: int height = windowSize.height;
0715: //Fix for 6267162: PIT: Popup Menu gets hidden below the screen when opened
0716: //near the periphery of the screen, XToolkit
0717: //Window should be moved if it's outside bottom-left screen bounds
0718: int x = (itemBounds.x > 0) ? itemBounds.x : 0;
0719: int y = (itemBounds.y > screenSize.height) ? screenSize.height
0720: - height : itemBounds.y - height;
0721: if (y >= 0) {
0722: //move it to the left if needed
0723: if (width > screenSize.width) {
0724: width = screenSize.width;
0725: }
0726: if (x + width > screenSize.width) {
0727: x = screenSize.width - width;
0728: }
0729: return new Rectangle(x, y, width, height);
0730: } else {
0731: return null;
0732: }
0733: }
0734:
0735: /**
0736: * Checks if window fits to the right specified item
0737: * returns rectangle that the window fits to or null.
0738: * @param itemBounds rectangle of item in global coordinates
0739: * @param windowSize size of submenu window to fit
0740: * @param screenSize size of screen
0741: */
0742: Rectangle fitWindowRight(Rectangle itemBounds,
0743: Dimension windowSize, Dimension screenSize) {
0744: int width = windowSize.width;
0745: int height = windowSize.height;
0746: //Fix for 6267162: PIT: Popup Menu gets hidden below the screen when opened
0747: //near the periphery of the screen, XToolkit
0748: //Window should be moved if it's outside top-left screen bounds
0749: int x = (itemBounds.x + itemBounds.width > 0) ? itemBounds.x
0750: + itemBounds.width : 0;
0751: int y = (itemBounds.y > 0) ? itemBounds.y : 0;
0752: if (x + width <= screenSize.width) {
0753: //move it to the top if needed
0754: if (height > screenSize.height) {
0755: height = screenSize.height;
0756: }
0757: if (y + height > screenSize.height) {
0758: y = screenSize.height - height;
0759: }
0760: return new Rectangle(x, y, width, height);
0761: } else {
0762: return null;
0763: }
0764: }
0765:
0766: /**
0767: * Checks if window fits to the left specified item
0768: * returns rectangle that the window fits to or null.
0769: * @param itemBounds rectangle of item in global coordinates
0770: * @param windowSize size of submenu window to fit
0771: * @param screenSize size of screen
0772: */
0773: Rectangle fitWindowLeft(Rectangle itemBounds, Dimension windowSize,
0774: Dimension screenSize) {
0775: int width = windowSize.width;
0776: int height = windowSize.height;
0777: //Fix for 6267162: PIT: Popup Menu gets hidden below the screen when opened
0778: //near the periphery of the screen, XToolkit
0779: //Window should be moved if it's outside top-right screen bounds
0780: int x = (itemBounds.x < screenSize.width) ? itemBounds.x
0781: - width : screenSize.width - width;
0782: int y = (itemBounds.y > 0) ? itemBounds.y : 0;
0783: if (x >= 0) {
0784: //move it to the top if needed
0785: if (height > screenSize.height) {
0786: height = screenSize.height;
0787: }
0788: if (y + height > screenSize.height) {
0789: y = screenSize.height - height;
0790: }
0791: return new Rectangle(x, y, width, height);
0792: } else {
0793: return null;
0794: }
0795: }
0796:
0797: /**
0798: * The last thing we can do with the window
0799: * to fit it on screen - move it to the
0800: * top-left edge and cut by screen dimensions
0801: * @param windowSize size of submenu window to fit
0802: * @param screenSize size of screen
0803: */
0804: Rectangle fitWindowToScreen(Dimension windowSize,
0805: Dimension screenSize) {
0806: int width = (windowSize.width < screenSize.width) ? windowSize.width
0807: : screenSize.width;
0808: int height = (windowSize.height < screenSize.height) ? windowSize.height
0809: : screenSize.height;
0810: return new Rectangle(0, 0, width, height);
0811: }
0812:
0813: /************************************************
0814: *
0815: * Utility functions for manipulating colors
0816: *
0817: ************************************************/
0818:
0819: /**
0820: * This function is called before every painting.
0821: * TODO:It would be better to add PropertyChangeListener
0822: * to target component
0823: * TODO:It would be better to access background color
0824: * not invoking user-overridable function
0825: */
0826: void resetColors() {
0827: replaceColors((target == null) ? SystemColor.window : target
0828: .getBackground());
0829: }
0830:
0831: /**
0832: * Calculates colors of various elements given
0833: * background color. Uses MotifColorUtilities
0834: * @param backgroundColor the color of menu window's
0835: * background.
0836: */
0837: void replaceColors(Color backgroundColor) {
0838: if (backgroundColor != this .backgroundColor) {
0839: this .backgroundColor = backgroundColor;
0840:
0841: int red = backgroundColor.getRed();
0842: int green = backgroundColor.getGreen();
0843: int blue = backgroundColor.getBlue();
0844:
0845: foregroundColor = new Color(
0846: MotifColorUtilities
0847: .calculateForegroundFromBackground(red,
0848: green, blue));
0849: lightShadowColor = new Color(MotifColorUtilities
0850: .calculateTopShadowFromBackground(red, green, blue));
0851: darkShadowColor = new Color(MotifColorUtilities
0852: .calculateBottomShadowFromBackground(red, green,
0853: blue));
0854: selectedColor = new Color(MotifColorUtilities
0855: .calculateSelectFromBackground(red, green, blue));
0856: disabledColor = (backgroundColor.equals(Color.BLACK)) ? foregroundColor
0857: .darker()
0858: : backgroundColor.darker();
0859: }
0860: }
0861:
0862: Color getBackgroundColor() {
0863: return backgroundColor;
0864: }
0865:
0866: Color getForegroundColor() {
0867: return foregroundColor;
0868: }
0869:
0870: Color getLightShadowColor() {
0871: return lightShadowColor;
0872: }
0873:
0874: Color getDarkShadowColor() {
0875: return darkShadowColor;
0876: }
0877:
0878: Color getSelectedColor() {
0879: return selectedColor;
0880: }
0881:
0882: Color getDisabledColor() {
0883: return disabledColor;
0884: }
0885:
0886: /************************************************
0887: *
0888: * Painting utility functions
0889: *
0890: ************************************************/
0891:
0892: /**
0893: * Draws raised or sunken rectangle on specified graphics
0894: * @param g the graphics on which to draw
0895: * @param x the coordinate of left edge in coordinates of graphics
0896: * @param y the coordinate of top edge in coordinates of graphics
0897: * @param width the width of rectangle
0898: * @param height the height of rectangle
0899: * @param raised true to draw raised rectangle, false to draw sunken
0900: */
0901: void draw3DRect(Graphics g, int x, int y, int width, int height,
0902: boolean raised) {
0903: if ((width <= 0) || (height <= 0)) {
0904: return;
0905: }
0906: Color c = g.getColor();
0907: g.setColor(raised ? getLightShadowColor()
0908: : getDarkShadowColor());
0909: g.drawLine(x, y, x, y + height - 1);
0910: g.drawLine(x + 1, y, x + width - 1, y);
0911: g.setColor(raised ? getDarkShadowColor()
0912: : getLightShadowColor());
0913: g
0914: .drawLine(x + 1, y + height - 1, x + width - 1, y
0915: + height - 1);
0916: g.drawLine(x + width - 1, y + 1, x + width - 1, y + height - 1);
0917: g.setColor(c);
0918: }
0919:
0920: /************************************************
0921: *
0922: * Overriden utility functions of XWindow
0923: *
0924: ************************************************/
0925:
0926: /**
0927: * Filters X events
0928: */
0929: protected boolean isEventDisabled(XEvent e) {
0930: switch (e.get_type()) {
0931: case XlibWrapper.Expose:
0932: case XlibWrapper.GraphicsExpose:
0933: case XlibWrapper.ButtonPress:
0934: case XlibWrapper.ButtonRelease:
0935: case XlibWrapper.MotionNotify:
0936: case XlibWrapper.KeyPress:
0937: case XlibWrapper.KeyRelease:
0938: case XlibWrapper.DestroyNotify:
0939: return super .isEventDisabled(e);
0940: default:
0941: return true;
0942: }
0943: }
0944:
0945: /**
0946: * Invokes disposal procedure on eventHandlerThread
0947: */
0948: public void dispose() {
0949: setDisposed(true);
0950: EventQueue.invokeLater(new Runnable() {
0951: public void run() {
0952: doDispose();
0953: }
0954: });
0955: }
0956:
0957: /**
0958: * Performs disposal of menu window.
0959: * Should be called only on eventHandlerThread
0960: */
0961: protected void doDispose() {
0962: xSetVisible(false);
0963: SurfaceData oldData = surfaceData;
0964: surfaceData = null;
0965: if (oldData != null) {
0966: oldData.invalidate();
0967: }
0968: XToolkit.targetDisposedPeer(target, this );
0969: destroy();
0970: }
0971:
0972: /**
0973: * Invokes event processing on eventHandlerThread
0974: * This function needs to be overriden since
0975: * XBaseMenuWindow has no corresponding component
0976: * so events can not be processed using standart means
0977: */
0978: void postEvent(final AWTEvent event) {
0979: EventQueue.invokeLater(new Runnable() {
0980: public void run() {
0981: handleEvent(event);
0982: }
0983: });
0984: }
0985:
0986: /**
0987: * The implementation of base window performs processing
0988: * of paint events only. This behaviour is changed in
0989: * descendants.
0990: */
0991: protected void handleEvent(AWTEvent event) {
0992: switch (event.getID()) {
0993: case PaintEvent.PAINT:
0994: doHandleJavaPaintEvent((PaintEvent) event);
0995: break;
0996: }
0997: }
0998:
0999: /**
1000: * Save location of pointer for further use
1001: * then invoke superclass
1002: */
1003: public boolean grabInput() {
1004: int rootX;
1005: int rootY;
1006: boolean res;
1007: XToolkit.awtLock();
1008: try {
1009: long root = XlibWrapper.RootWindow(XToolkit.getDisplay(),
1010: getScreenNumber());
1011: res = XlibWrapper.XQueryPointer(XToolkit.getDisplay(),
1012: root, XlibWrapper.larg1, //root
1013: XlibWrapper.larg2, //child
1014: XlibWrapper.larg3, //root_x
1015: XlibWrapper.larg4, //root_y
1016: XlibWrapper.larg5, //child_x
1017: XlibWrapper.larg6, //child_y
1018: XlibWrapper.larg7);//mask
1019: rootX = Native.getInt(XlibWrapper.larg3);
1020: rootY = Native.getInt(XlibWrapper.larg4);
1021: res &= super .grabInput();
1022: } finally {
1023: XToolkit.awtUnlock();
1024: }
1025: if (res) {
1026: //Mouse pointer is on the same display
1027: this .grabInputPoint = new Point(rootX, rootY);
1028: this .hasPointerMoved = false;
1029: } else {
1030: this .grabInputPoint = null;
1031: this .hasPointerMoved = true;
1032: }
1033: return res;
1034: }
1035:
1036: /************************************************
1037: *
1038: * Overridable event processing functions
1039: *
1040: ************************************************/
1041:
1042: /**
1043: * Performs repainting
1044: */
1045: void doHandleJavaPaintEvent(PaintEvent event) {
1046: Rectangle rect = event.getUpdateRect();
1047: repaint(rect.x, rect.y, rect.width, rect.height);
1048: }
1049:
1050: /************************************************
1051: *
1052: * User input handling utility functions
1053: *
1054: ************************************************/
1055:
1056: /**
1057: * Performs handling of java mouse event
1058: * Note that this function should be invoked
1059: * only from root of menu window's hierarchy
1060: * that grabs input focus
1061: */
1062: void doHandleJavaMouseEvent(MouseEvent mouseEvent) {
1063: if (!XToolkit.isLeftMouseButton(mouseEvent)
1064: && !XToolkit.isRightMouseButton(mouseEvent)) {
1065: return;
1066: }
1067: //Window that owns input
1068: XBaseWindow grabWindow = XAwtState.getGrabWindow();
1069: //Point of mouse event in global coordinates
1070: Point ptGlobal = toGlobal(mouseEvent.getPoint());
1071: if (!hasPointerMoved) {
1072: //Fix for 6301307: NullPointerException while dispatching mouse events, XToolkit
1073: if (grabInputPoint == null
1074: || (Math.abs(ptGlobal.x - grabInputPoint.x) > getMouseMovementSmudge())
1075: || (Math.abs(ptGlobal.y - grabInputPoint.y) > getMouseMovementSmudge())) {
1076: hasPointerMoved = true;
1077: }
1078: }
1079: //Z-order first descendant of current menu window
1080: //hierarchy that contain mouse point
1081: XBaseMenuWindow wnd = getMenuWindowFromPoint(ptGlobal);
1082: //Item in wnd that contains mouse point, if any
1083: XMenuItemPeer item = (wnd != null) ? wnd.getItemFromPoint(wnd
1084: .toLocal(ptGlobal)) : null;
1085: //Currently showing leaf window
1086: XBaseMenuWindow cwnd = getShowingLeaf();
1087: switch (mouseEvent.getID()) {
1088: case MouseEvent.MOUSE_PRESSED:
1089: //This line is to get rid of possible problems
1090: //That may occur if mouse events are lost
1091: showingMousePressedSubmenu = null;
1092: if ((grabWindow == this ) && (wnd == null)) {
1093: //Menus grab input and the user
1094: //presses mouse button outside
1095: ungrabInput();
1096: } else {
1097: //Menus grab input OR mouse is pressed on menu window
1098: grabInput();
1099: if (item != null && !item.isSeparator()
1100: && item.isTargetItemEnabled()) {
1101: //Button is pressed on enabled item
1102: if (wnd.getShowingSubmenu() == item) {
1103: //Button is pressed on item that shows
1104: //submenu. We have to hide its submenu
1105: //if user clicks on it
1106: showingMousePressedSubmenu = (XMenuPeer) item;
1107: }
1108: wnd.selectItem(item, true);
1109: } else {
1110: //Button is pressed on disabled item or empty space
1111: if (wnd != null) {
1112: wnd.selectItem(null, false);
1113: }
1114: }
1115: }
1116: break;
1117: case MouseEvent.MOUSE_RELEASED:
1118: //Note that if item is not null, wnd has to be not null
1119: if (item != null && !item.isSeparator()
1120: && item.isTargetItemEnabled()) {
1121: if (item instanceof XMenuPeer) {
1122: if (showingMousePressedSubmenu == item) {
1123: //User clicks on item that shows submenu.
1124: //Hide the submenu
1125: if (wnd instanceof XMenuBarPeer) {
1126: ungrabInput();
1127: } else {
1128: wnd.selectItem(item, false);
1129: }
1130: }
1131: } else {
1132: //Invoke action event
1133: item.action(mouseEvent.getWhen());
1134: ungrabInput();
1135: }
1136: } else {
1137: //Mouse is released outside menu items
1138: if (hasPointerMoved || (wnd instanceof XMenuBarPeer)) {
1139: ungrabInput();
1140: }
1141: }
1142: showingMousePressedSubmenu = null;
1143: break;
1144: case MouseEvent.MOUSE_DRAGGED:
1145: if (wnd != null) {
1146: //Mouse is dragged over menu window
1147: //Move selection to item under cursor
1148: if (item != null && !item.isSeparator()
1149: && item.isTargetItemEnabled()) {
1150: if (grabWindow == this ) {
1151: wnd.selectItem(item, true);
1152: }
1153: } else {
1154: wnd.selectItem(null, false);
1155: }
1156: } else {
1157: //Mouse is dragged outside menu windows
1158: //clear selection in leaf to reflect it
1159: if (cwnd != null) {
1160: cwnd.selectItem(null, false);
1161: }
1162: }
1163: break;
1164: }
1165: }
1166:
1167: /**
1168: * Performs handling of java keyboard event
1169: * Note that this function should be invoked
1170: * only from root of menu window's hierarchy
1171: * that grabs input focus
1172: */
1173: void doHandleJavaKeyEvent(KeyEvent event) {
1174: if (log.isLoggable(Level.FINER))
1175: log.finer(event.toString());
1176: if (event.getID() != KeyEvent.KEY_PRESSED) {
1177: return;
1178: }
1179: final int keyCode = event.getKeyCode();
1180: XBaseMenuWindow cwnd = getShowingLeaf();
1181: XMenuItemPeer citem = cwnd.getSelectedItem();
1182: switch (keyCode) {
1183: case KeyEvent.VK_UP:
1184: case KeyEvent.VK_KP_UP:
1185: if (!(cwnd instanceof XMenuBarPeer)) {
1186: //If active window is not menu bar,
1187: //move selection up
1188: cwnd.selectItem(cwnd.getPrevSelectableItem(), false);
1189: }
1190: break;
1191: case KeyEvent.VK_DOWN:
1192: case KeyEvent.VK_KP_DOWN:
1193: if (cwnd instanceof XMenuBarPeer) {
1194: //If active window is menu bar show current submenu
1195: selectItem(getSelectedItem(), true);
1196: } else {
1197: //move selection down
1198: cwnd.selectItem(cwnd.getNextSelectableItem(), false);
1199: }
1200: break;
1201: case KeyEvent.VK_LEFT:
1202: case KeyEvent.VK_KP_LEFT:
1203: if (cwnd instanceof XMenuBarPeer) {
1204: //leaf window is menu bar
1205: //select previous item
1206: selectItem(getPrevSelectableItem(), false);
1207: } else if (cwnd.getParentMenuWindow() instanceof XMenuBarPeer) {
1208: //leaf window is direct child of menu bar
1209: //select previous item of menu bar
1210: //and show its submenu
1211: selectItem(getPrevSelectableItem(), true);
1212: } else {
1213: //hide leaf moving focus to its parent
1214: //(equvivalent of pressing ESC)
1215: XBaseMenuWindow pwnd = cwnd.getParentMenuWindow();
1216: //Fix for 6272952: PIT: Pressing LEFT ARROW on a popup menu throws NullPointerException, XToolkit
1217: if (pwnd != null) {
1218: pwnd.selectItem(pwnd.getSelectedItem(), false);
1219: }
1220: }
1221: break;
1222: case KeyEvent.VK_RIGHT:
1223: case KeyEvent.VK_KP_RIGHT:
1224: if (cwnd instanceof XMenuBarPeer) {
1225: //leaf window is menu bar
1226: //select next item
1227: selectItem(getNextSelectableItem(), false);
1228: } else if (citem instanceof XMenuPeer) {
1229: //current item is menu, show its window
1230: //(equivalent of ENTER)
1231: cwnd.selectItem(citem, true);
1232: } else if (this instanceof XMenuBarPeer) {
1233: //if this is menu bar (not popup menu)
1234: //and the user presses RIGHT on item (not submenu)
1235: //select next top-level menu
1236: selectItem(getNextSelectableItem(), true);
1237: }
1238: break;
1239: case KeyEvent.VK_SPACE:
1240: case KeyEvent.VK_ENTER:
1241: //If the current item has submenu show it
1242: //Perform action otherwise
1243: if (citem instanceof XMenuPeer) {
1244: cwnd.selectItem(citem, true);
1245: } else if (citem != null) {
1246: citem.action(event.getWhen());
1247: ungrabInput();
1248: }
1249: break;
1250: case KeyEvent.VK_ESCAPE:
1251: //If current window is menu bar or its child - close it
1252: //If current window is popup menu - close it
1253: //go one level up otherwise
1254:
1255: //Fixed 6266513: Incorrect key handling in XAWT popup menu
1256: //Popup menu should be closed on 'ESC'
1257: if ((cwnd instanceof XMenuBarPeer)
1258: || (cwnd.getParentMenuWindow() instanceof XMenuBarPeer)) {
1259: ungrabInput();
1260: } else if (cwnd instanceof XPopupMenuPeer) {
1261: ungrabInput();
1262: } else {
1263: XBaseMenuWindow pwnd = cwnd.getParentMenuWindow();
1264: pwnd.selectItem(pwnd.getSelectedItem(), false);
1265: }
1266: break;
1267: case KeyEvent.VK_F10:
1268: //Fixed 6266513: Incorrect key handling in XAWT popup menu
1269: //All menus should be closed on 'F10'
1270: ungrabInput();
1271: break;
1272: default:
1273: break;
1274: }
1275: }
1276:
1277: } //class XBaseMenuWindow
|