1 /* 2 * Copyright (c) 2005, 2007, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package sun.awt.X11; 26 27 import java.awt.*; 28 import java.awt.peer.*; 29 import java.awt.event.*; 30 import java.awt.image.ColorModel; 31 32 import sun.awt.*; 33 34 import java.util.ArrayList; 35 import java.util.Vector; 36 import java.util.logging.*; 37 import sun.java2d.SurfaceData; 38 import sun.java2d.SunGraphics2D; 39 40 /** 41 * The abstract class XBaseMenuWindow is the superclass 42 * of all menu windows. 43 */ 44 abstract public class XBaseMenuWindow extends XWindow { 45 46 /************************************************ 47 * 48 * Data members 49 * 50 ************************************************/ 51 52 private static 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