1 /* 2 * Copyright (c) 2005, 2013, 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 sun.util.logging.PlatformLogger; 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 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 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 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-heirarchy 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 (XMenuItemPeer[])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 = (XMenuItemPeer)item.getPeer(); 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 items) { 371 synchronized(getMenuTreeLock()) { 372 this.items.clear(); 373 MenuItem[] itemArray = (MenuItem[])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 XToolkit.targetDisposedPeer(target, this); 931 destroy(); 932 } 933 934 /** 935 * Invokes event processing on eventHandlerThread 936 * This function needs to be overriden since 937 * XBaseMenuWindow has no corresponding component 938 * so events can not be processed using standart means 939 */ 940 void postEvent(final AWTEvent event) { 941 InvocationEvent ev = new InvocationEvent(event.getSource(), new Runnable() { 942 public void run() { 943 handleEvent(event); 944 } 945 }); 946 super.postEvent(ev); 947 } 948 949 /** 950 * The implementation of base window performs processing 951 * of paint events only. This behaviour is changed in 952 * descendants. 953 */ 954 protected void handleEvent(AWTEvent event) { 955 switch(event.getID()) { 956 case PaintEvent.PAINT: 957 doHandleJavaPaintEvent((PaintEvent)event); 958 break; 959 } 960 } 961 962 /** 963 * Save location of pointer for further use 964 * then invoke superclass 965 */ 966 public boolean grabInput() { 967 int rootX; 968 int rootY; 969 boolean res; 970 XToolkit.awtLock(); 971 try { 972 long root = XlibWrapper.RootWindow(XToolkit.getDisplay(), 973 getScreenNumber()); 974 res = XlibWrapper.XQueryPointer(XToolkit.getDisplay(), root, 975 XlibWrapper.larg1, //root 976 XlibWrapper.larg2, //child 977 XlibWrapper.larg3, //root_x 978 XlibWrapper.larg4, //root_y 979 XlibWrapper.larg5, //child_x 980 XlibWrapper.larg6, //child_y 981 XlibWrapper.larg7);//mask 982 rootX = Native.getInt(XlibWrapper.larg3); 983 rootY = Native.getInt(XlibWrapper.larg4); 984 res &= super.grabInput(); 985 } finally { 986 XToolkit.awtUnlock(); 987 } 988 if (res) { 989 //Mouse pointer is on the same display 990 this.grabInputPoint = new Point(rootX, rootY); 991 this.hasPointerMoved = false; 992 } else { 993 this.grabInputPoint = null; 994 this.hasPointerMoved = true; 995 } 996 return res; 997 } 998 /************************************************ 999 * 1000 * Overridable event processing functions 1001 * 1002 ************************************************/ 1003 1004 /** 1005 * Performs repainting 1006 */ 1007 void doHandleJavaPaintEvent(PaintEvent event) { 1008 Rectangle rect = event.getUpdateRect(); 1009 repaint(rect.x, rect.y, rect.width, rect.height); 1010 } 1011 1012 /************************************************ 1013 * 1014 * User input handling utility functions 1015 * 1016 ************************************************/ 1017 1018 /** 1019 * Performs handling of java mouse event 1020 * Note that this function should be invoked 1021 * only from root of menu window's hierarchy 1022 * that grabs input focus 1023 */ 1024 void doHandleJavaMouseEvent( MouseEvent mouseEvent ) { 1025 if (!XToolkit.isLeftMouseButton(mouseEvent) && !XToolkit.isRightMouseButton(mouseEvent)) { 1026 return; 1027 } 1028 //Window that owns input 1029 XBaseWindow grabWindow = XAwtState.getGrabWindow(); 1030 //Point of mouse event in global coordinates 1031 Point ptGlobal = mouseEvent.getLocationOnScreen(); 1032 if (!hasPointerMoved) { 1033 //Fix for 6301307: NullPointerException while dispatching mouse events, XToolkit 1034 if (grabInputPoint == null || 1035 (Math.abs(ptGlobal.x - grabInputPoint.x) > getMouseMovementSmudge()) || 1036 (Math.abs(ptGlobal.y - grabInputPoint.y) > getMouseMovementSmudge())) { 1037 hasPointerMoved = true; 1038 } 1039 } 1040 //Z-order first descendant of current menu window 1041 //hierarchy that contain mouse point 1042 XBaseMenuWindow wnd = getMenuWindowFromPoint(ptGlobal); 1043 //Item in wnd that contains mouse point, if any 1044 XMenuItemPeer item = (wnd != null) ? wnd.getItemFromPoint(wnd.toLocal(ptGlobal)) : null; 1045 //Currently showing leaf window 1046 XBaseMenuWindow cwnd = getShowingLeaf(); 1047 switch (mouseEvent.getID()) { 1048 case MouseEvent.MOUSE_PRESSED: 1049 //This line is to get rid of possible problems 1050 //That may occur if mouse events are lost 1051 showingMousePressedSubmenu = null; 1052 if ((grabWindow == this) && (wnd == null)) { 1053 //Menus grab input and the user 1054 //presses mouse button outside 1055 ungrabInput(); 1056 } else { 1057 //Menus grab input OR mouse is pressed on menu window 1058 grabInput(); 1059 if (item != null && !item.isSeparator() && item.isTargetItemEnabled()) { 1060 //Button is pressed on enabled item 1061 if (wnd.getShowingSubmenu() == item) { 1062 //Button is pressed on item that shows 1063 //submenu. We have to hide its submenu 1064 //if user clicks on it 1065 showingMousePressedSubmenu = (XMenuPeer)item; 1066 } 1067 wnd.selectItem(item, true); 1068 } else { 1069 //Button is pressed on disabled item or empty space 1070 if (wnd != null) { 1071 wnd.selectItem(null, false); 1072 } 1073 } 1074 } 1075 break; 1076 case MouseEvent.MOUSE_RELEASED: 1077 //Note that if item is not null, wnd has to be not null 1078 if (item != null && !item.isSeparator() && item.isTargetItemEnabled()) { 1079 if (item instanceof XMenuPeer) { 1080 if (showingMousePressedSubmenu == item) { 1081 //User clicks on item that shows submenu. 1082 //Hide the submenu 1083 if (wnd instanceof XMenuBarPeer) { 1084 ungrabInput(); 1085 } else { 1086 wnd.selectItem(item, false); 1087 } 1088 } 1089 } else { 1090 //Invoke action event 1091 item.action(mouseEvent.getWhen()); 1092 ungrabInput(); 1093 } 1094 } else { 1095 //Mouse is released outside menu items 1096 if (hasPointerMoved || (wnd instanceof XMenuBarPeer)) { 1097 ungrabInput(); 1098 } 1099 } 1100 showingMousePressedSubmenu = null; 1101 break; 1102 case MouseEvent.MOUSE_DRAGGED: 1103 if (wnd != null) { 1104 //Mouse is dragged over menu window 1105 //Move selection to item under cursor 1106 if (item != null && !item.isSeparator() && item.isTargetItemEnabled()) { 1107 if (grabWindow == this){ 1108 wnd.selectItem(item, true); 1109 } 1110 } else { 1111 wnd.selectItem(null, false); 1112 } 1113 } else { 1114 //Mouse is dragged outside menu windows 1115 //clear selection in leaf to reflect it 1116 if (cwnd != null) { 1117 cwnd.selectItem(null, false); 1118 } 1119 } 1120 break; 1121 } 1122 } 1123 1124 /** 1125 * Performs handling of java keyboard event 1126 * Note that this function should be invoked 1127 * only from root of menu window's hierarchy 1128 * that grabs input focus 1129 */ 1130 void doHandleJavaKeyEvent(KeyEvent event) { 1131 if (log.isLoggable(PlatformLogger.Level.FINER)) { 1132 log.finer(event.toString()); 1133 } 1134 if (event.getID() != KeyEvent.KEY_PRESSED) { 1135 return; 1136 } 1137 final int keyCode = event.getKeyCode(); 1138 XBaseMenuWindow cwnd = getShowingLeaf(); 1139 XMenuItemPeer citem = cwnd.getSelectedItem(); 1140 switch(keyCode) { 1141 case KeyEvent.VK_UP: 1142 case KeyEvent.VK_KP_UP: 1143 if (!(cwnd instanceof XMenuBarPeer)) { 1144 //If active window is not menu bar, 1145 //move selection up 1146 cwnd.selectItem(cwnd.getPrevSelectableItem(), false); 1147 } 1148 break; 1149 case KeyEvent.VK_DOWN: 1150 case KeyEvent.VK_KP_DOWN: 1151 if (cwnd instanceof XMenuBarPeer) { 1152 //If active window is menu bar show current submenu 1153 selectItem(getSelectedItem(), true); 1154 } else { 1155 //move selection down 1156 cwnd.selectItem(cwnd.getNextSelectableItem(), false); 1157 } 1158 break; 1159 case KeyEvent.VK_LEFT: 1160 case KeyEvent.VK_KP_LEFT: 1161 if (cwnd instanceof XMenuBarPeer) { 1162 //leaf window is menu bar 1163 //select previous item 1164 selectItem(getPrevSelectableItem(), false); 1165 } else if (cwnd.getParentMenuWindow() instanceof XMenuBarPeer) { 1166 //leaf window is direct child of menu bar 1167 //select previous item of menu bar 1168 //and show its submenu 1169 selectItem(getPrevSelectableItem(), true); 1170 } else { 1171 //hide leaf moving focus to its parent 1172 //(equvivalent of pressing ESC) 1173 XBaseMenuWindow pwnd = cwnd.getParentMenuWindow(); 1174 //Fix for 6272952: PIT: Pressing LEFT ARROW on a popup menu throws NullPointerException, XToolkit 1175 if (pwnd != null) { 1176 pwnd.selectItem(pwnd.getSelectedItem(), false); 1177 } 1178 } 1179 break; 1180 case KeyEvent.VK_RIGHT: 1181 case KeyEvent.VK_KP_RIGHT: 1182 if (cwnd instanceof XMenuBarPeer) { 1183 //leaf window is menu bar 1184 //select next item 1185 selectItem(getNextSelectableItem(), false); 1186 } else if (citem instanceof XMenuPeer) { 1187 //current item is menu, show its window 1188 //(equivalent of ENTER) 1189 cwnd.selectItem(citem, true); 1190 } else if (this instanceof XMenuBarPeer) { 1191 //if this is menu bar (not popup menu) 1192 //and the user presses RIGHT on item (not submenu) 1193 //select next top-level menu 1194 selectItem(getNextSelectableItem(), true); 1195 } 1196 break; 1197 case KeyEvent.VK_SPACE: 1198 case KeyEvent.VK_ENTER: 1199 //If the current item has submenu show it 1200 //Perform action otherwise 1201 if (citem instanceof XMenuPeer) { 1202 cwnd.selectItem(citem, true); 1203 } else if (citem != null) { 1204 citem.action(event.getWhen()); 1205 ungrabInput(); 1206 } 1207 break; 1208 case KeyEvent.VK_ESCAPE: 1209 //If current window is menu bar or its child - close it 1210 //If current window is popup menu - close it 1211 //go one level up otherwise 1212 1213 //Fixed 6266513: Incorrect key handling in XAWT popup menu 1214 //Popup menu should be closed on 'ESC' 1215 if ((cwnd instanceof XMenuBarPeer) || (cwnd.getParentMenuWindow() instanceof XMenuBarPeer)) { 1216 ungrabInput(); 1217 } else if (cwnd instanceof XPopupMenuPeer) { 1218 ungrabInput(); 1219 } else { 1220 XBaseMenuWindow pwnd = cwnd.getParentMenuWindow(); 1221 pwnd.selectItem(pwnd.getSelectedItem(), false); 1222 } 1223 break; 1224 case KeyEvent.VK_F10: 1225 //Fixed 6266513: Incorrect key handling in XAWT popup menu 1226 //All menus should be closed on 'F10' 1227 ungrabInput(); 1228 break; 1229 default: 1230 break; 1231 } 1232 } 1233 1234 } //class XBaseMenuWindow