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