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