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          */
MappingData(XMenuItemPeer[] items)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          */
MappingData()152         MappingData() {
153             this.items = new XMenuItemPeer[0];
154         }
155 
clone()156         public Object clone() {
157             try {
158                 return super.clone();
159             } catch (CloneNotSupportedException ex) {
160                 throw new InternalError(ex);
161             }
162         }
163 
getItems()164         public XMenuItemPeer[] getItems() {
165             return this.items;
166         }
167     }
168 
169     /************************************************
170      *
171      * Construction
172      *
173      ************************************************/
XBaseMenuWindow()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      */
getParentMenuWindow()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      */
map()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      */
getSubmenuBounds(Rectangle itemBounds, Dimension windowSize)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      */
updateSize()219     protected abstract void updateSize();
220 
221     /************************************************
222      *
223      * Initialization
224      *
225      ************************************************/
226 
227     /**
228      * Overrides XBaseWindow.instantPreInit
229      */
instantPreInit(XCreateWindowParams params)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      */
getMenuTreeLock()244     static Object getMenuTreeLock() {
245         return menuTreeLock;
246     }
247 
248     /**
249      * This function is called to clear all saved
250      * size data.
251      */
resetMapping()252     protected void resetMapping() {
253         mappingData = null;
254     }
255 
256     /**
257      * Invokes repaint procedure on eventHandlerThread
258      */
postPaintEvent()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      */
getItem(int index)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      */
copyItems()291     XMenuItemPeer[] copyItems() {
292         synchronized(getMenuTreeLock()) {
293             return items.toArray(new XMenuItemPeer[] {});
294         }
295     }
296 
297 
298     /**
299      * Thread-safely returns selected item
300      */
getSelectedItem()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      */
getShowingSubmenu()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      */
addItem(MenuItem item)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      */
delItem(int index)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      */
reloadItems(Vector<? extends MenuItem> items)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      */
selectItem(XMenuItemPeer item, boolean showWindowIfMenu)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      */
doShowSubmenu(XMenuPeer submenuToShow)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 
setItemsFont( Font font )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      */
getMappingData()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      */
getItemFromPoint(Point pt)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      */
getNextSelectableItem()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      */
getPrevSelectableItem()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      */
getFirstSelectableItem()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      */
getShowingLeaf()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      */
getRootMenuWindow()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      */
getMenuWindowFromPoint(Point pt)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 
getCurrentGraphicsConfiguration()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      */
fitWindowBelow(Rectangle itemBounds, Dimension windowSize, Rectangle screenBounds)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      */
fitWindowAbove(Rectangle itemBounds, Dimension windowSize, Rectangle screenBounds)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      */
fitWindowRight(Rectangle itemBounds, Dimension windowSize, Rectangle screenBounds)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      */
fitWindowLeft(Rectangle itemBounds, Dimension windowSize, Rectangle screenBounds)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      */
fitWindowToScreen(Dimension windowSize, Rectangle screenBounds)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      */
resetColors()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      */
replaceColors(Color backgroundColor)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 
getBackgroundColor()835     Color getBackgroundColor() {
836         return backgroundColor;
837     }
838 
getForegroundColor()839     Color getForegroundColor() {
840         return foregroundColor;
841     }
842 
getLightShadowColor()843     Color getLightShadowColor() {
844         return lightShadowColor;
845     }
846 
getDarkShadowColor()847     Color getDarkShadowColor() {
848         return darkShadowColor;
849     }
850 
getSelectedColor()851     Color getSelectedColor() {
852         return selectedColor;
853     }
854 
getDisabledColor()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      */
draw3DRect(Graphics g, int x, int y, int width, int height, boolean raised)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      */
isEventDisabled(XEvent e)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      */
dispose()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      */
doDispose()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      */
postEvent(final AWTEvent event)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      */
handleEvent(AWTEvent event)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      */
grabInput()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      */
doHandleJavaPaintEvent(PaintEvent event)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      */
doHandleJavaMouseEvent( MouseEvent mouseEvent )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      */
doHandleJavaKeyEvent(KeyEvent event)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
1245