1 /*
   2  * Copyright (c) 2005, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package sun.awt.X11;
  26 
  27 import java.awt.*;
  28 import java.awt.peer.*;
  29 import java.awt.event.*;
  30 import java.awt.image.ColorModel;
  31 
  32 import sun.awt.*;
  33 
  34 import java.util.ArrayList;
  35 import java.util.Vector;
  36 import sun.util.logging.PlatformLogger;
  37 import sun.java2d.SurfaceData;
  38 import sun.java2d.SunGraphics2D;
  39 
  40 /**
  41  * The abstract class XBaseMenuWindow is the superclass
  42  * of all menu windows.
  43  */
  44 abstract public class XBaseMenuWindow extends XWindow {
  45 
  46     /************************************************
  47      *
  48      * Data members
  49      *
  50      ************************************************/
  51 
  52     private static PlatformLogger log = PlatformLogger.getLogger("sun.awt.X11.XBaseMenuWindow");
  53 
  54     /*
  55      * Colors are calculated using MotifColorUtilities class
  56      * from backgroundColor and are contained in these vars.
  57      */
  58     private Color backgroundColor;
  59     private Color foregroundColor;
  60     private Color lightShadowColor;
  61     private Color darkShadowColor;
  62     private Color selectedColor;
  63     private Color disabledColor;
  64 
  65     /**
  66      * Array of items.
  67      */
  68     private ArrayList<XMenuItemPeer> items;
  69 
  70     /**
  71      * Index of selected item in array of items
  72      */
  73     private int selectedIndex = -1;
  74 
  75     /**
  76      * Specifies currently showing submenu.
  77      */
  78     private XMenuPeer showingSubmenu = null;
  79 
  80     /**
  81      * Static synchronizational object.
  82      * Following operations should be synchronized
  83      * using this object:
  84      * 1. Access to items vector
  85      * 2. Access to selection
  86      * 3. Access to showing menu window member
  87      *
  88      * This is lowest level lock,
  89      * no other locks should be taken when
  90      * thread own this lock.
  91      */
  92     static private Object menuTreeLock = new Object();
  93 
  94     /************************************************
  95      *
  96      * Event processing
  97      *
  98      ************************************************/
  99 
 100     /**
 101      * If mouse button is clicked on item showing submenu
 102      * we have to hide its submenu.
 103      * And if mouse button is pressed on such item and
 104      * dragged to another, getShowingSubmenu() is changed.
 105      * So this member saves the item that the user
 106      * presses mouse button on _only_ if it's showing submenu.
 107      */
 108     private XMenuPeer showingMousePressedSubmenu = null;
 109 
 110     /**
 111      * If the PopupMenu is invoked as a result of right button click
 112      * first mouse event after grabInput would be MouseReleased.
 113      * We need to check if the user has moved mouse after input grab.
 114      * If yes - hide the PopupMenu. If no - do nothing
 115      */
 116     protected Point grabInputPoint = null;
 117     protected boolean hasPointerMoved = false;
 118 
 119     private AppContext disposeAppContext;
 120 
 121     /************************************************
 122      *
 123      * Mapping data
 124      *
 125      ************************************************/
 126 
 127     /**
 128      * Mapping data that is filled in getMappedItems function
 129      * and reset in resetSize function. It contains array of
 130      * items in order that they appear on screen and may contain
 131      * additional data defined by descendants.
 132      */
 133     private MappingData mappingData;
 134 
 135     static class MappingData implements Cloneable {
 136 
 137         /**
 138          * Array of item in order that they appear on screen
 139          */
 140         private XMenuItemPeer[] items;
 141 
 142         /**
 143          * Constructs MappingData object with list
 144          * of menu items
 145          */
 146         MappingData(XMenuItemPeer[] items) {
 147             this.items = items;
 148         }
 149 
 150         /**
 151          * Constructs MappingData without items
 152          * This constructor should be used in case of errors
 153          */
 154         MappingData() {
 155             this.items = new XMenuItemPeer[0];
 156         }
 157 
 158         public Object clone() {
 159             try {
 160                 return super.clone();
 161             } catch (CloneNotSupportedException ex) {
 162                 throw new InternalError(ex);
 163             }
 164         }
 165 
 166         public XMenuItemPeer[] getItems() {
 167             return this.items;
 168         }
 169     }
 170 
 171     /************************************************
 172      *
 173      * Construction
 174      *
 175      ************************************************/
 176     XBaseMenuWindow() {
 177         super(new XCreateWindowParams(new Object[] {
 178             DELAYED, Boolean.TRUE}));
 179 
 180         disposeAppContext = AppContext.getAppContext();
 181     }
 182 
 183     /************************************************
 184      *
 185      * Abstract methods
 186      *
 187      ************************************************/
 188 
 189     /**
 190      * Returns parent menu window (not the X-hierarchy parent window)
 191      */
 192     protected abstract XBaseMenuWindow getParentMenuWindow();
 193 
 194     /**
 195      * Performs mapping of items in window.
 196      * This function creates and fills specific
 197      * descendant of MappingData
 198      * and sets mapping coordinates of items
 199      * This function should return default menu data
 200      * if errors occur
 201      */
 202     protected abstract MappingData map();
 203 
 204     /**
 205      * Calculates placement of submenu window
 206      * given bounds of item with submenu and
 207      * size of submenu window. Returns suggested
 208      * rectangle for submenu window in global coordinates
 209      * @param itemBounds the bounding rectangle of item
 210      * in local coordinates
 211      * @param windowSize the desired size of submenu's window
 212      */
 213     protected abstract Rectangle getSubmenuBounds(Rectangle itemBounds, Dimension windowSize);
 214 
 215 
 216     /**
 217      * This function is to be called if it's likely that size
 218      * of items was changed. It can be called from any thread
 219      * in any locked state, so it should not take locks
 220      */
 221     protected abstract void updateSize();
 222 
 223     /************************************************
 224      *
 225      * Initialization
 226      *
 227      ************************************************/
 228 
 229     /**
 230      * Overrides XBaseWindow.instantPreInit
 231      */
 232     void instantPreInit(XCreateWindowParams params) {
 233         super.instantPreInit(params);
 234         items = new ArrayList<>();
 235     }
 236 
 237     /************************************************
 238      *
 239      * General-purpose functions
 240      *
 241      ************************************************/
 242 
 243     /**
 244      * Returns static lock used for menus
 245      */
 246     static Object getMenuTreeLock() {
 247         return menuTreeLock;
 248     }
 249 
 250     /**
 251      * This function is called to clear all saved
 252      * size data.
 253      */
 254     protected void resetMapping() {
 255         mappingData = null;
 256     }
 257 
 258     /**
 259      * Invokes repaint procedure on eventHandlerThread
 260      */
 261     void postPaintEvent() {
 262         if (isShowing()) {
 263             PaintEvent pe = new PaintEvent(target, PaintEvent.PAINT,
 264                                            new Rectangle(0, 0, width, height));
 265             postEvent(pe);
 266         }
 267     }
 268 
 269     /************************************************
 270      *
 271      * Utility functions for manipulating items
 272      *
 273      ************************************************/
 274 
 275     /**
 276      * Thread-safely returns item at specified index
 277      * @param index the position of the item to be returned.
 278      */
 279     XMenuItemPeer getItem(int index) {
 280         if (index >= 0) {
 281             synchronized(getMenuTreeLock()) {
 282                 if (items.size() > index) {
 283                     return items.get(index);
 284                 }
 285             }
 286         }
 287         return null;
 288     }
 289 
 290     /**
 291      * Thread-safely creates a copy of the items vector
 292      */
 293     XMenuItemPeer[] copyItems() {
 294         synchronized(getMenuTreeLock()) {
 295             return items.toArray(new XMenuItemPeer[] {});
 296         }
 297     }
 298 
 299 
 300     /**
 301      * Thread-safely returns selected item
 302      */
 303     XMenuItemPeer getSelectedItem() {
 304         synchronized(getMenuTreeLock()) {
 305             if (selectedIndex >= 0) {
 306                 if (items.size() > selectedIndex) {
 307                     return items.get(selectedIndex);
 308                 }
 309             }
 310             return null;
 311         }
 312     }
 313 
 314     /**
 315      * Returns showing submenu, if any
 316      */
 317     XMenuPeer getShowingSubmenu() {
 318         synchronized(getMenuTreeLock()) {
 319             return showingSubmenu;
 320         }
 321     }
 322 
 323     /**
 324      * Adds item to end of items vector.
 325      * Note that this function does not perform
 326      * check for adding duplicate items
 327      * @param item item to add
 328      */
 329     @SuppressWarnings("deprecation")
 330     public void addItem(MenuItem item) {
 331         XMenuItemPeer mp = (XMenuItemPeer)item.getPeer();
 332         if (mp != null) {
 333             mp.setContainer(this);
 334             synchronized(getMenuTreeLock()) {
 335                 items.add(mp);
 336             }
 337         } else {
 338             if (log.isLoggable(PlatformLogger.Level.FINE)) {
 339                 log.fine("WARNING: Attempt to add menu item without a peer");
 340             }
 341         }
 342         updateSize();
 343     }
 344 
 345     /**
 346      * Removes item at the specified index from items vector.
 347      * @param index the position of the item to be removed
 348      */
 349     public void delItem(int index) {
 350         synchronized(getMenuTreeLock()) {
 351             if (selectedIndex == index) {
 352                 selectItem(null, false);
 353             } else if (selectedIndex > index) {
 354                 selectedIndex--;
 355             }
 356             if (index < items.size()) {
 357                 items.remove(index);
 358             } else {
 359                 if (log.isLoggable(PlatformLogger.Level.FINE)) {
 360                     log.fine("WARNING: Attempt to remove non-existing menu item, index : " + index + ", item count : " + items.size());
 361                 }
 362             }
 363         }
 364         updateSize();
 365     }
 366 
 367     /**
 368      * Clears items vector and loads specified vector
 369      * @param items vector to be loaded
 370      */
 371     public void reloadItems(Vector<? extends MenuItem> items) {
 372         synchronized(getMenuTreeLock()) {
 373             this.items.clear();
 374             MenuItem[] itemArray = items.toArray(new MenuItem[] {});
 375             int itemCnt = itemArray.length;
 376             for(int i = 0; i < itemCnt; i++) {
 377                 addItem(itemArray[i]);
 378             }
 379         }
 380     }
 381 
 382     /**
 383      * Select specified item and shows/hides submenus if necessary
 384      * We can not select by index, so we need to select by ref.
 385      * @param item the item to be selected, null to clear selection
 386      * @param showWindowIfMenu if the item is XMenuPeer then its
 387      * window is shown/hidden according to this param.
 388      */
 389     void selectItem(XMenuItemPeer item, boolean showWindowIfMenu) {
 390         synchronized(getMenuTreeLock()) {
 391             XMenuPeer showingSubmenu = getShowingSubmenu();
 392             int newSelectedIndex = (item != null) ? items.indexOf(item) : -1;
 393             if (this.selectedIndex != newSelectedIndex) {
 394                 if (log.isLoggable(PlatformLogger.Level.FINEST)) {
 395                     log.finest("Selected index changed, was : " + this.selectedIndex + ", new : " + newSelectedIndex);
 396                 }
 397                 this.selectedIndex = newSelectedIndex;
 398                 postPaintEvent();
 399             }
 400             final XMenuPeer submenuToShow = (showWindowIfMenu && (item instanceof XMenuPeer)) ? (XMenuPeer)item : null;
 401             if (submenuToShow != showingSubmenu) {
 402                 XToolkit.executeOnEventHandlerThread(target, new Runnable() {
 403                         public void run() {
 404                             doShowSubmenu(submenuToShow);
 405                         }
 406                     });
 407             }
 408         }
 409     }
 410 
 411     /**
 412      * Performs hiding of currently showing submenu
 413      * and showing of submenuToShow.
 414      * This function should be executed on eventHandlerThread
 415      * @param submenuToShow submenu to be shown or null
 416      * to hide currently showing submenu
 417      */
 418     private void doShowSubmenu(XMenuPeer submenuToShow) {
 419         XMenuWindow menuWindowToShow = (submenuToShow != null) ? submenuToShow.getMenuWindow() : null;
 420         Dimension dim = null;
 421         Rectangle bounds = null;
 422         //ensureCreated can invoke XWindowPeer.init() ->
 423         //XWindowPeer.initGraphicsConfiguration() ->
 424         //Window.getGraphicsConfiguration()
 425         //that tries to obtain Component.AWTTreeLock.
 426         //So it should be called outside awtLock()
 427         if (menuWindowToShow != null) {
 428             menuWindowToShow.ensureCreated();
 429         }
 430         XToolkit.awtLock();
 431         try {
 432             synchronized(getMenuTreeLock()) {
 433                 if (showingSubmenu != submenuToShow) {
 434                     if (log.isLoggable(PlatformLogger.Level.FINEST)) {
 435                         log.finest("Changing showing submenu");
 436                     }
 437                     if (showingSubmenu != null) {
 438                         XMenuWindow showingSubmenuWindow = showingSubmenu.getMenuWindow();
 439                         if (showingSubmenuWindow != null) {
 440                             showingSubmenuWindow.hide();
 441                         }
 442                     }
 443                     if (submenuToShow != null) {
 444                         dim = menuWindowToShow.getDesiredSize();
 445                         bounds = menuWindowToShow.getParentMenuWindow().getSubmenuBounds(submenuToShow.getBounds(), dim);
 446                         menuWindowToShow.show(bounds);
 447                     }
 448                     showingSubmenu = submenuToShow;
 449                 }
 450             }
 451         } finally {
 452             XToolkit.awtUnlock();
 453         }
 454     }
 455 
 456     final void setItemsFont( Font font ) {
 457         XMenuItemPeer[] items = copyItems();
 458         int itemCnt = items.length;
 459         for (int i = 0; i < itemCnt; i++) {
 460             items[i].setFont(font);
 461         }
 462     }
 463 
 464     /************************************************
 465      *
 466      * Utility functions for manipulating mapped items
 467      *
 468      ************************************************/
 469 
 470     /**
 471      * Returns array of mapped items, null if error
 472      * This function has to be not synchronized
 473      * and we have to guarantee that we return
 474      * some MappingData to user. It's OK if
 475      * this.mappingData is replaced meanwhile
 476      */
 477     MappingData getMappingData() {
 478         MappingData mappingData = this.mappingData;
 479         if (mappingData == null) {
 480             mappingData = map();
 481             this.mappingData = mappingData;
 482         }
 483         return (MappingData)mappingData.clone();
 484     }
 485 
 486     /**
 487      * returns item thats mapped coordinates contain
 488      * specified point, null of none.
 489      * @param pt the point in this window's coordinate system
 490      */
 491     XMenuItemPeer getItemFromPoint(Point pt) {
 492         XMenuItemPeer[] items = getMappingData().getItems();
 493         int cnt = items.length;
 494         for (int i = 0; i < cnt; i++) {
 495             if (items[i].getBounds().contains(pt)) {
 496                 return items[i];
 497             }
 498         }
 499         return null;
 500     }
 501 
 502     /**
 503      * Returns first item after currently selected
 504      * item that can be selected according to mapping array.
 505      * (no separators and no disabled items).
 506      * Currently selected item if it's only selectable,
 507      * null if no item can be selected
 508      */
 509     XMenuItemPeer getNextSelectableItem() {
 510         XMenuItemPeer[] mappedItems = getMappingData().getItems();
 511         XMenuItemPeer selectedItem = getSelectedItem();
 512         int cnt = mappedItems.length;
 513         //Find index of selected item
 514         int selIdx = -1;
 515         for (int i = 0; i < cnt; i++) {
 516             if (mappedItems[i] == selectedItem) {
 517                 selIdx = i;
 518                 break;
 519             }
 520         }
 521         int idx = (selIdx == cnt - 1) ? 0 : selIdx + 1;
 522         //cycle through mappedItems to find selectable item
 523         //beginning from the next item and moving to the
 524         //beginning of array when end is reached.
 525         //Cycle is finished on selected item itself
 526         for (int i = 0; i < cnt; i++) {
 527             XMenuItemPeer item = mappedItems[idx];
 528             if (!item.isSeparator() && item.isTargetItemEnabled()) {
 529                 return item;
 530             }
 531             idx++;
 532             if (idx >= cnt) {
 533                 idx = 0;
 534             }
 535         }
 536         //return null if no selectable item was found
 537         return null;
 538     }
 539 
 540     /**
 541      * Returns first item before currently selected
 542      * see getNextSelectableItem() for comments
 543      */
 544     XMenuItemPeer getPrevSelectableItem() {
 545         XMenuItemPeer[] mappedItems = getMappingData().getItems();
 546         XMenuItemPeer selectedItem = getSelectedItem();
 547         int cnt = mappedItems.length;
 548         //Find index of selected item
 549         int selIdx = -1;
 550         for (int i = 0; i < cnt; i++) {
 551             if (mappedItems[i] == selectedItem) {
 552                 selIdx = i;
 553                 break;
 554             }
 555         }
 556         int idx = (selIdx <= 0) ? cnt - 1 : selIdx - 1;
 557         //cycle through mappedItems to find selectable item
 558         for (int i = 0; i < cnt; i++) {
 559             XMenuItemPeer item = mappedItems[idx];
 560             if (!item.isSeparator() && item.isTargetItemEnabled()) {
 561                 return item;
 562             }
 563             idx--;
 564             if (idx < 0) {
 565                 idx = cnt - 1;
 566             }
 567         }
 568         //return null if no selectable item was found
 569         return null;
 570     }
 571 
 572     /**
 573      * Returns first selectable item
 574      * This function is intended for clearing selection
 575      */
 576     XMenuItemPeer getFirstSelectableItem() {
 577         XMenuItemPeer[] mappedItems = getMappingData().getItems();
 578         int cnt = mappedItems.length;
 579         for (int i = 0; i < cnt; i++) {
 580             XMenuItemPeer item = mappedItems[i];
 581             if (!item.isSeparator() && item.isTargetItemEnabled()) {
 582                 return item;
 583             }
 584         }
 585 
 586         return null;
 587     }
 588 
 589     /************************************************
 590      *
 591      * Utility functions for manipulating
 592      * hierarchy of windows
 593      *
 594      ************************************************/
 595 
 596     /**
 597      * returns leaf menu window or
 598      * this if no children are showing
 599      */
 600     XBaseMenuWindow getShowingLeaf() {
 601         synchronized(getMenuTreeLock()) {
 602             XBaseMenuWindow leaf = this;
 603             XMenuPeer leafchild = leaf.getShowingSubmenu();
 604             while (leafchild != null) {
 605                 leaf = leafchild.getMenuWindow();
 606                 leafchild = leaf.getShowingSubmenu();
 607             }
 608             return leaf;
 609         }
 610     }
 611 
 612     /**
 613      * returns root menu window
 614      * or this if this window is topmost
 615      */
 616     XBaseMenuWindow getRootMenuWindow() {
 617         synchronized(getMenuTreeLock()) {
 618             XBaseMenuWindow t = this;
 619             XBaseMenuWindow tparent = t.getParentMenuWindow();
 620             while (tparent != null) {
 621                 t = tparent;
 622                 tparent = t.getParentMenuWindow();
 623             }
 624             return t;
 625         }
 626     }
 627 
 628     /**
 629      * Returns window that contains pt.
 630      * search is started from leaf window
 631      * to return first window in Z-order
 632      * @param pt point in global coordinates
 633      */
 634     XBaseMenuWindow getMenuWindowFromPoint(Point pt) {
 635         synchronized(getMenuTreeLock()) {
 636             XBaseMenuWindow t = getShowingLeaf();
 637             while (t != null) {
 638                 Rectangle r = new Rectangle(t.toGlobal(new Point(0, 0)), t.getSize());
 639                 if (r.contains(pt)) {
 640                     return t;
 641                 }
 642                 t = t.getParentMenuWindow();
 643             }
 644             return null;
 645         }
 646     }
 647 
 648     /************************************************
 649      *
 650      * Primitives for getSubmenuBounds
 651      *
 652      * These functions are invoked from getSubmenuBounds
 653      * implementations in different order. They check if window
 654      * of size windowSize fits to the specified edge of
 655      * rectangle itemBounds on the screen of screenSize.
 656      * Return rectangle that occupies the window if it fits or null.
 657      *
 658      ************************************************/
 659 
 660     /**
 661      * Checks if window fits below specified item
 662      * returns rectangle that the window fits to or null.
 663      * @param itemBounds rectangle of item in global coordinates
 664      * @param windowSize size of submenu window to fit
 665      * @param screenSize size of screen
 666      */
 667     Rectangle fitWindowBelow(Rectangle itemBounds, Dimension windowSize, Dimension screenSize) {
 668         int width = windowSize.width;
 669         int height = windowSize.height;
 670         //Fix for 6267162: PIT: Popup Menu gets hidden below the screen when opened
 671         //near the periphery of the screen, XToolkit
 672         //Window should be moved if it's outside top-left screen bounds
 673         int x = (itemBounds.x > 0) ? itemBounds.x : 0;
 674         int y = (itemBounds.y + itemBounds.height > 0) ? itemBounds.y + itemBounds.height : 0;
 675         if (y + height <= screenSize.height) {
 676             //move it to the left if needed
 677             if (width > screenSize.width) {
 678                 width = screenSize.width;
 679             }
 680             if (x + width > screenSize.width) {
 681                 x = screenSize.width - width;
 682             }
 683             return new Rectangle(x, y, width, height);
 684         } else {
 685             return null;
 686         }
 687     }
 688 
 689     /**
 690      * Checks if window fits above specified item
 691      * returns rectangle that the window fits to or null.
 692      * @param itemBounds rectangle of item in global coordinates
 693      * @param windowSize size of submenu window to fit
 694      * @param screenSize size of screen
 695      */
 696     Rectangle fitWindowAbove(Rectangle itemBounds, Dimension windowSize, Dimension screenSize) {
 697         int width = windowSize.width;
 698         int height = windowSize.height;
 699         //Fix for 6267162: PIT: Popup Menu gets hidden below the screen when opened
 700         //near the periphery of the screen, XToolkit
 701         //Window should be moved if it's outside bottom-left screen bounds
 702         int x = (itemBounds.x > 0) ? itemBounds.x : 0;
 703         int y = (itemBounds.y > screenSize.height) ? screenSize.height - height : itemBounds.y - height;
 704         if (y >= 0) {
 705             //move it to the left if needed
 706             if (width > screenSize.width) {
 707                 width = screenSize.width;
 708             }
 709             if (x + width > screenSize.width) {
 710                 x = screenSize.width - width;
 711             }
 712             return new Rectangle(x, y, width, height);
 713         } else {
 714             return null;
 715         }
 716     }
 717 
 718     /**
 719      * Checks if window fits to the right specified item
 720      * returns rectangle that the window fits to or null.
 721      * @param itemBounds rectangle of item in global coordinates
 722      * @param windowSize size of submenu window to fit
 723      * @param screenSize size of screen
 724      */
 725     Rectangle fitWindowRight(Rectangle itemBounds, Dimension windowSize, Dimension screenSize) {
 726         int width = windowSize.width;
 727         int height = windowSize.height;
 728         //Fix for 6267162: PIT: Popup Menu gets hidden below the screen when opened
 729         //near the periphery of the screen, XToolkit
 730         //Window should be moved if it's outside top-left screen bounds
 731         int x = (itemBounds.x + itemBounds.width > 0) ? itemBounds.x + itemBounds.width : 0;
 732         int y = (itemBounds.y > 0) ? itemBounds.y : 0;
 733         if (x + width <= screenSize.width) {
 734             //move it to the top if needed
 735             if (height > screenSize.height) {
 736                 height = screenSize.height;
 737             }
 738             if (y + height > screenSize.height) {
 739                 y = screenSize.height - height;
 740             }
 741             return new Rectangle(x, y, width, height);
 742         } else {
 743             return null;
 744         }
 745     }
 746 
 747     /**
 748      * Checks if window fits to the left specified item
 749      * returns rectangle that the window fits to or null.
 750      * @param itemBounds rectangle of item in global coordinates
 751      * @param windowSize size of submenu window to fit
 752      * @param screenSize size of screen
 753      */
 754     Rectangle fitWindowLeft(Rectangle itemBounds, Dimension windowSize, Dimension screenSize) {
 755         int width = windowSize.width;
 756         int height = windowSize.height;
 757         //Fix for 6267162: PIT: Popup Menu gets hidden below the screen when opened
 758         //near the periphery of the screen, XToolkit
 759         //Window should be moved if it's outside top-right screen bounds
 760         int x = (itemBounds.x < screenSize.width) ? itemBounds.x - width : screenSize.width - width;
 761         int y = (itemBounds.y > 0) ? itemBounds.y : 0;
 762         if (x >= 0) {
 763             //move it to the top if needed
 764             if (height > screenSize.height) {
 765                 height = screenSize.height;
 766             }
 767             if (y + height > screenSize.height) {
 768                 y = screenSize.height - height;
 769             }
 770             return new Rectangle(x, y, width, height);
 771         } else {
 772             return null;
 773         }
 774     }
 775 
 776     /**
 777      * The last thing we can do with the window
 778      * to fit it on screen - move it to the
 779      * top-left edge and cut by screen dimensions
 780      * @param windowSize size of submenu window to fit
 781      * @param screenSize size of screen
 782      */
 783     Rectangle fitWindowToScreen(Dimension windowSize, Dimension screenSize) {
 784         int width = (windowSize.width < screenSize.width) ? windowSize.width : screenSize.width;
 785         int height = (windowSize.height < screenSize.height) ? windowSize.height : screenSize.height;
 786         return new Rectangle(0, 0, width, height);
 787     }
 788 
 789 
 790     /************************************************
 791      *
 792      * Utility functions for manipulating colors
 793      *
 794      ************************************************/
 795 
 796     /**
 797      * This function is called before every painting.
 798      * TODO:It would be better to add PropertyChangeListener
 799      * to target component
 800      * TODO:It would be better to access background color
 801      * not invoking user-overridable function
 802      */
 803     void resetColors() {
 804         replaceColors((target == null) ? SystemColor.window : target.getBackground());
 805     }
 806 
 807     /**
 808      * Calculates colors of various elements given
 809      * background color. Uses MotifColorUtilities
 810      * @param backgroundColor the color of menu window's
 811      * background.
 812      */
 813     void replaceColors(Color backgroundColor) {
 814         if (backgroundColor != this.backgroundColor) {
 815             this.backgroundColor = backgroundColor;
 816 
 817             int red = backgroundColor.getRed();
 818             int green = backgroundColor.getGreen();
 819             int blue = backgroundColor.getBlue();
 820 
 821             foregroundColor = new Color(MotifColorUtilities.calculateForegroundFromBackground(red,green,blue));
 822             lightShadowColor = new Color(MotifColorUtilities.calculateTopShadowFromBackground(red,green,blue));
 823             darkShadowColor = new Color(MotifColorUtilities.calculateBottomShadowFromBackground(red,green,blue));
 824             selectedColor = new Color(MotifColorUtilities.calculateSelectFromBackground(red,green,blue));
 825             disabledColor = (backgroundColor.equals(Color.BLACK)) ? foregroundColor.darker() : backgroundColor.darker();
 826         }
 827     }
 828 
 829     Color getBackgroundColor() {
 830         return backgroundColor;
 831     }
 832 
 833     Color getForegroundColor() {
 834         return foregroundColor;
 835     }
 836 
 837     Color getLightShadowColor() {
 838         return lightShadowColor;
 839     }
 840 
 841     Color getDarkShadowColor() {
 842         return darkShadowColor;
 843     }
 844 
 845     Color getSelectedColor() {
 846         return selectedColor;
 847     }
 848 
 849     Color getDisabledColor() {
 850         return disabledColor;
 851     }
 852 
 853     /************************************************
 854      *
 855      * Painting utility functions
 856      *
 857      ************************************************/
 858 
 859     /**
 860      * Draws raised or sunken rectangle on specified graphics
 861      * @param g the graphics on which to draw
 862      * @param x the coordinate of left edge in coordinates of graphics
 863      * @param y the coordinate of top edge in coordinates of graphics
 864      * @param width the width of rectangle
 865      * @param height the height of rectangle
 866      * @param raised true to draw raised rectangle, false to draw sunken
 867      */
 868     void draw3DRect(Graphics g, int x, int y, int width, int height, boolean raised) {
 869         if ((width <= 0) || (height <= 0)) {
 870             return;
 871         }
 872         Color c = g.getColor();
 873         g.setColor(raised ? getLightShadowColor() : getDarkShadowColor());
 874         g.drawLine(x, y, x, y + height - 1);
 875         g.drawLine(x + 1, y, x + width - 1, y);
 876         g.setColor(raised ? getDarkShadowColor() : getLightShadowColor());
 877         g.drawLine(x + 1, y + height - 1, x + width - 1, y + height - 1);
 878         g.drawLine(x + width - 1, y + 1, x + width - 1, y + height - 1);
 879         g.setColor(c);
 880     }
 881 
 882     /************************************************
 883      *
 884      * Overriden utility functions of XWindow
 885      *
 886      ************************************************/
 887 
 888     /**
 889      * Filters X events
 890      */
 891      protected boolean isEventDisabled(XEvent e) {
 892         switch (e.get_type()) {
 893           case XConstants.Expose :
 894           case XConstants.GraphicsExpose :
 895           case XConstants.ButtonPress:
 896           case XConstants.ButtonRelease:
 897           case XConstants.MotionNotify:
 898           case XConstants.KeyPress:
 899           case XConstants.KeyRelease:
 900           case XConstants.DestroyNotify:
 901               return super.isEventDisabled(e);
 902           default:
 903               return true;
 904         }
 905     }
 906 
 907     /**
 908      * Invokes disposal procedure on eventHandlerThread
 909      */
 910     public void dispose() {
 911         setDisposed(true);
 912 
 913         SunToolkit.invokeLaterOnAppContext(disposeAppContext, new Runnable()  {
 914             public void run() {
 915                 doDispose();
 916             }
 917         });
 918     }
 919 
 920     /**
 921      * Performs disposal of menu window.
 922      * Should be called only on eventHandlerThread
 923      */
 924     protected void doDispose() {
 925         xSetVisible(false);
 926         SurfaceData oldData = surfaceData;
 927         surfaceData = null;
 928         if (oldData != null) {
 929             oldData.invalidate();
 930         }
 931         destroy();
 932     }
 933 
 934     /**
 935      * Invokes event processing on eventHandlerThread
 936      * This function needs to be overriden since
 937      * XBaseMenuWindow has no corresponding component
 938      * so events can not be processed using standart means
 939      */
 940     void postEvent(final AWTEvent event) {
 941         InvocationEvent ev = new InvocationEvent(event.getSource(), new Runnable() {
 942             public void run() {
 943                 handleEvent(event);
 944             }
 945         });
 946         super.postEvent(ev);
 947     }
 948 
 949     /**
 950      * The implementation of base window performs processing
 951      * of paint events only. This behaviour is changed in
 952      * descendants.
 953      */
 954     protected void handleEvent(AWTEvent event) {
 955         switch(event.getID()) {
 956         case PaintEvent.PAINT:
 957             doHandleJavaPaintEvent((PaintEvent)event);
 958             break;
 959         }
 960     }
 961 
 962     /**
 963      * Save location of pointer for further use
 964      * then invoke superclass
 965      */
 966     public boolean grabInput() {
 967         int rootX;
 968         int rootY;
 969         boolean res;
 970         XToolkit.awtLock();
 971         try {
 972             long root = XlibWrapper.RootWindow(XToolkit.getDisplay(),
 973                     getScreenNumber());
 974             res = XlibWrapper.XQueryPointer(XToolkit.getDisplay(), root,
 975                                             XlibWrapper.larg1, //root
 976                                             XlibWrapper.larg2, //child
 977                                             XlibWrapper.larg3, //root_x
 978                                             XlibWrapper.larg4, //root_y
 979                                             XlibWrapper.larg5, //child_x
 980                                             XlibWrapper.larg6, //child_y
 981                                             XlibWrapper.larg7);//mask
 982             rootX = Native.getInt(XlibWrapper.larg3);
 983             rootY = Native.getInt(XlibWrapper.larg4);
 984             res &= super.grabInput();
 985         } finally {
 986             XToolkit.awtUnlock();
 987         }
 988         if (res) {
 989             //Mouse pointer is on the same display
 990             this.grabInputPoint = new Point(rootX, rootY);
 991             this.hasPointerMoved = false;
 992         } else {
 993             this.grabInputPoint = null;
 994             this.hasPointerMoved = true;
 995         }
 996         return res;
 997     }
 998     /************************************************
 999      *
1000      * Overridable event processing functions
1001      *
1002      ************************************************/
1003 
1004     /**
1005      * Performs repainting
1006      */
1007     void doHandleJavaPaintEvent(PaintEvent event) {
1008         Rectangle rect = event.getUpdateRect();
1009         repaint(rect.x, rect.y, rect.width, rect.height);
1010     }
1011 
1012     /************************************************
1013      *
1014      * User input handling utility functions
1015      *
1016      ************************************************/
1017 
1018     /**
1019      * Performs handling of java mouse event
1020      * Note that this function should be invoked
1021      * only from root of menu window's hierarchy
1022      * that grabs input focus
1023      */
1024     void doHandleJavaMouseEvent( MouseEvent mouseEvent ) {
1025         if (!XToolkit.isLeftMouseButton(mouseEvent) && !XToolkit.isRightMouseButton(mouseEvent)) {
1026             return;
1027         }
1028         //Window that owns input
1029         XBaseWindow grabWindow = XAwtState.getGrabWindow();
1030         //Point of mouse event in global coordinates
1031         Point ptGlobal = mouseEvent.getLocationOnScreen();
1032         if (!hasPointerMoved) {
1033             //Fix for 6301307: NullPointerException while dispatching mouse events, XToolkit
1034             if (grabInputPoint == null ||
1035                 (Math.abs(ptGlobal.x - grabInputPoint.x) > getMouseMovementSmudge()) ||
1036                 (Math.abs(ptGlobal.y - grabInputPoint.y) > getMouseMovementSmudge())) {
1037                 hasPointerMoved = true;
1038             }
1039         }
1040         //Z-order first descendant of current menu window
1041         //hierarchy that contain mouse point
1042         XBaseMenuWindow wnd = getMenuWindowFromPoint(ptGlobal);
1043         //Item in wnd that contains mouse point, if any
1044         XMenuItemPeer item = (wnd != null) ? wnd.getItemFromPoint(wnd.toLocal(ptGlobal)) : null;
1045         //Currently showing leaf window
1046         XBaseMenuWindow cwnd = getShowingLeaf();
1047         switch (mouseEvent.getID()) {
1048           case MouseEvent.MOUSE_PRESSED:
1049               //This line is to get rid of possible problems
1050               //That may occur if mouse events are lost
1051               showingMousePressedSubmenu = null;
1052               if ((grabWindow == this) && (wnd == null)) {
1053                   //Menus grab input and the user
1054                   //presses mouse button outside
1055                   ungrabInput();
1056               } else {
1057                   //Menus grab input OR mouse is pressed on menu window
1058                   grabInput();
1059                   if (item != null && !item.isSeparator() && item.isTargetItemEnabled()) {
1060                       //Button is pressed on enabled item
1061                       if (wnd.getShowingSubmenu() == item) {
1062                           //Button is pressed on item that shows
1063                           //submenu. We have to hide its submenu
1064                           //if user clicks on it
1065                           showingMousePressedSubmenu = (XMenuPeer)item;
1066                       }
1067                       wnd.selectItem(item, true);
1068                   } else {
1069                       //Button is pressed on disabled item or empty space
1070                       if (wnd != null) {
1071                           wnd.selectItem(null, false);
1072                       }
1073                   }
1074               }
1075               break;
1076           case MouseEvent.MOUSE_RELEASED:
1077               //Note that if item is not null, wnd has to be not null
1078               if (item != null && !item.isSeparator() && item.isTargetItemEnabled()) {
1079                   if  (item instanceof XMenuPeer) {
1080                       if (showingMousePressedSubmenu == item) {
1081                           //User clicks on item that shows submenu.
1082                           //Hide the submenu
1083                           if (wnd instanceof XMenuBarPeer) {
1084                               ungrabInput();
1085                           } else {
1086                               wnd.selectItem(item, false);
1087                           }
1088                       }
1089                   } else {
1090                       //Invoke action event
1091                       item.action(mouseEvent.getWhen());
1092                       ungrabInput();
1093                   }
1094               } else {
1095                   //Mouse is released outside menu items
1096                   if (hasPointerMoved || (wnd instanceof XMenuBarPeer)) {
1097                       ungrabInput();
1098                   }
1099               }
1100               showingMousePressedSubmenu = null;
1101               break;
1102           case MouseEvent.MOUSE_DRAGGED:
1103               if (wnd != null) {
1104                   //Mouse is dragged over menu window
1105                   //Move selection to item under cursor
1106                   if (item != null && !item.isSeparator() && item.isTargetItemEnabled()) {
1107                       if (grabWindow == this){
1108                           wnd.selectItem(item, true);
1109                       }
1110                   } else {
1111                       wnd.selectItem(null, false);
1112                   }
1113               } else {
1114                   //Mouse is dragged outside menu windows
1115                   //clear selection in leaf to reflect it
1116                   if (cwnd != null) {
1117                       cwnd.selectItem(null, false);
1118                   }
1119               }
1120               break;
1121         }
1122     }
1123 
1124     /**
1125      * Performs handling of java keyboard event
1126      * Note that this function should be invoked
1127      * only from root of menu window's hierarchy
1128      * that grabs input focus
1129      */
1130     void doHandleJavaKeyEvent(KeyEvent event) {
1131         if (log.isLoggable(PlatformLogger.Level.FINER)) {
1132             log.finer(event.toString());
1133         }
1134         if (event.getID() != KeyEvent.KEY_PRESSED) {
1135             return;
1136         }
1137         final int keyCode = event.getKeyCode();
1138         XBaseMenuWindow cwnd = getShowingLeaf();
1139         XMenuItemPeer citem = cwnd.getSelectedItem();
1140         switch(keyCode) {
1141           case KeyEvent.VK_UP:
1142           case KeyEvent.VK_KP_UP:
1143               if (!(cwnd instanceof XMenuBarPeer)) {
1144                   //If active window is not menu bar,
1145                   //move selection up
1146                   cwnd.selectItem(cwnd.getPrevSelectableItem(), false);
1147               }
1148               break;
1149           case KeyEvent.VK_DOWN:
1150           case KeyEvent.VK_KP_DOWN:
1151               if (cwnd instanceof XMenuBarPeer) {
1152                   //If active window is menu bar show current submenu
1153                   selectItem(getSelectedItem(), true);
1154               } else {
1155                   //move selection down
1156                   cwnd.selectItem(cwnd.getNextSelectableItem(), false);
1157               }
1158               break;
1159           case KeyEvent.VK_LEFT:
1160           case KeyEvent.VK_KP_LEFT:
1161               if (cwnd instanceof XMenuBarPeer) {
1162                   //leaf window is menu bar
1163                   //select previous item
1164                   selectItem(getPrevSelectableItem(), false);
1165               } else if (cwnd.getParentMenuWindow() instanceof XMenuBarPeer) {
1166                   //leaf window is direct child of menu bar
1167                   //select previous item of menu bar
1168                   //and show its submenu
1169                   selectItem(getPrevSelectableItem(), true);
1170               } else {
1171                   //hide leaf moving focus to its parent
1172                   //(equvivalent of pressing ESC)
1173                   XBaseMenuWindow pwnd = cwnd.getParentMenuWindow();
1174                   //Fix for 6272952: PIT: Pressing LEFT ARROW on a popup menu throws NullPointerException, XToolkit
1175                   if (pwnd != null) {
1176                       pwnd.selectItem(pwnd.getSelectedItem(), false);
1177                   }
1178               }
1179               break;
1180           case KeyEvent.VK_RIGHT:
1181           case KeyEvent.VK_KP_RIGHT:
1182               if (cwnd instanceof XMenuBarPeer) {
1183                   //leaf window is menu bar
1184                   //select next item
1185                   selectItem(getNextSelectableItem(), false);
1186               } else if (citem instanceof XMenuPeer) {
1187                   //current item is menu, show its window
1188                   //(equivalent of ENTER)
1189                   cwnd.selectItem(citem, true);
1190               } else if (this instanceof XMenuBarPeer) {
1191                   //if this is menu bar (not popup menu)
1192                   //and the user presses RIGHT on item (not submenu)
1193                   //select next top-level menu
1194                   selectItem(getNextSelectableItem(), true);
1195               }
1196               break;
1197           case KeyEvent.VK_SPACE:
1198           case KeyEvent.VK_ENTER:
1199               //If the current item has submenu show it
1200               //Perform action otherwise
1201               if (citem instanceof XMenuPeer) {
1202                   cwnd.selectItem(citem, true);
1203               } else if (citem != null) {
1204                   citem.action(event.getWhen());
1205                   ungrabInput();
1206               }
1207               break;
1208           case KeyEvent.VK_ESCAPE:
1209               //If current window is menu bar or its child - close it
1210               //If current window is popup menu - close it
1211               //go one level up otherwise
1212 
1213               //Fixed 6266513: Incorrect key handling in XAWT popup menu
1214               //Popup menu should be closed on 'ESC'
1215               if ((cwnd instanceof XMenuBarPeer) || (cwnd.getParentMenuWindow() instanceof XMenuBarPeer)) {
1216                   ungrabInput();
1217               } else if (cwnd instanceof XPopupMenuPeer) {
1218                   ungrabInput();
1219               } else {
1220                   XBaseMenuWindow pwnd = cwnd.getParentMenuWindow();
1221                   pwnd.selectItem(pwnd.getSelectedItem(), false);
1222               }
1223               break;
1224           case KeyEvent.VK_F10:
1225               //Fixed 6266513: Incorrect key handling in XAWT popup menu
1226               //All menus should be closed on 'F10'
1227               ungrabInput();
1228               break;
1229           default:
1230               break;
1231         }
1232     }
1233 
1234 } //class XBaseMenuWindow