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