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