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