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