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