1 /* 2 * Copyright (c) 2005, 2014, 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-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 @SuppressWarnings("deprecation") 330 public void addItem(MenuItem item) { 331 XMenuItemPeer mp = (XMenuItemPeer)item.getPeer(); 332 if (mp != null) { 333 mp.setContainer(this); 334 synchronized(getMenuTreeLock()) { 335 items.add(mp); 336 } 337 } else { 338 if (log.isLoggable(PlatformLogger.Level.FINE)) { 339 log.fine("WARNING: Attempt to add menu item without a peer"); 340 } 341 } 342 updateSize(); 343 } 344 345 /** 346 * Removes item at the specified index from items vector. 347 * @param index the position of the item to be removed 348 */ 349 public void delItem(int index) { 350 synchronized(getMenuTreeLock()) { 351 if (selectedIndex == index) { 352 selectItem(null, false); 353 } else if (selectedIndex > index) { 354 selectedIndex--; 355 } 356 if (index < items.size()) { 357 items.remove(index); 358 } else { 359 if (log.isLoggable(PlatformLogger.Level.FINE)) { 360 log.fine("WARNING: Attempt to remove non-existing menu item, index : " + index + ", item count : " + items.size()); 361 } 362 } 363 } 364 updateSize(); 365 } 366 367 /** 368 * Clears items vector and loads specified vector 369 * @param items vector to be loaded 370 */ 371 public void reloadItems(Vector<? extends MenuItem> items) { 372 synchronized(getMenuTreeLock()) { 373 this.items.clear(); 374 MenuItem[] itemArray = items.toArray(new MenuItem[] {}); 375 int itemCnt = itemArray.length; 376 for(int i = 0; i < itemCnt; i++) { 377 addItem(itemArray[i]); 378 } 379 } 380 } 381 382 /** 383 * Select specified item and shows/hides submenus if necessary 384 * We can not select by index, so we need to select by ref. 385 * @param item the item to be selected, null to clear selection 386 * @param showWindowIfMenu if the item is XMenuPeer then its 387 * window is shown/hidden according to this param. 388 */ 389 void selectItem(XMenuItemPeer item, boolean showWindowIfMenu) { 390 synchronized(getMenuTreeLock()) { 391 XMenuPeer showingSubmenu = getShowingSubmenu(); 392 int newSelectedIndex = (item != null) ? items.indexOf(item) : -1; 393 if (this.selectedIndex != newSelectedIndex) { 394 if (log.isLoggable(PlatformLogger.Level.FINEST)) { 395 log.finest("Selected index changed, was : " + this.selectedIndex + ", new : " + newSelectedIndex); 396 } 397 this.selectedIndex = newSelectedIndex; 398 postPaintEvent(); 399 } 400 final XMenuPeer submenuToShow = (showWindowIfMenu && (item instanceof XMenuPeer)) ? (XMenuPeer)item : null; 401 if (submenuToShow != showingSubmenu) { 402 XToolkit.executeOnEventHandlerThread(target, new Runnable() { 403 public void run() { 404 doShowSubmenu(submenuToShow); 405 } 406 }); 407 } 408 } 409 } 410 411 /** 412 * Performs hiding of currently showing submenu 413 * and showing of submenuToShow. 414 * This function should be executed on eventHandlerThread 415 * @param submenuToShow submenu to be shown or null 416 * to hide currently showing submenu 417 */ 418 private void doShowSubmenu(XMenuPeer submenuToShow) { 419 XMenuWindow menuWindowToShow = (submenuToShow != null) ? submenuToShow.getMenuWindow() : null; 420 Dimension dim = null; 421 Rectangle bounds = null; 422 //ensureCreated can invoke XWindowPeer.init() -> 423 //XWindowPeer.initGraphicsConfiguration() -> 424 //Window.getGraphicsConfiguration() 425 //that tries to obtain Component.AWTTreeLock. 426 //So it should be called outside awtLock() 427 if (menuWindowToShow != null) { 428 menuWindowToShow.ensureCreated(); 429 } 430 XToolkit.awtLock(); 431 try { 432 synchronized(getMenuTreeLock()) { 433 if (showingSubmenu != submenuToShow) { 434 if (log.isLoggable(PlatformLogger.Level.FINEST)) { 435 log.finest("Changing showing submenu"); 436 } 437 if (showingSubmenu != null) { 438 XMenuWindow showingSubmenuWindow = showingSubmenu.getMenuWindow(); 439 if (showingSubmenuWindow != null) { 440 showingSubmenuWindow.hide(); 441 } 442 } 443 if (submenuToShow != null) { 444 dim = menuWindowToShow.getDesiredSize(); 445 bounds = menuWindowToShow.getParentMenuWindow().getSubmenuBounds(submenuToShow.getBounds(), dim); 446 menuWindowToShow.show(bounds); 447 } 448 showingSubmenu = submenuToShow; 449 } 450 } 451 } finally { 452 XToolkit.awtUnlock(); 453 } 454 } 455 456 final void setItemsFont( Font font ) { 457 XMenuItemPeer[] items = copyItems(); 458 int itemCnt = items.length; 459 for (int i = 0; i < itemCnt; i++) { 460 items[i].setFont(font); 461 } 462 } 463 464 /************************************************ 465 * 466 * Utility functions for manipulating mapped items 467 * 468 ************************************************/ 469 470 /** 471 * Returns array of mapped items, null if error 472 * This function has to be not synchronized 473 * and we have to guarantee that we return 474 * some MappingData to user. It's OK if 475 * this.mappingData is replaced meanwhile 476 */ 477 MappingData getMappingData() { 478 MappingData mappingData = this.mappingData; 479 if (mappingData == null) { 480 mappingData = map(); 481 this.mappingData = mappingData; 482 } 483 return (MappingData)mappingData.clone(); 484 } 485 486 /** 487 * returns item thats mapped coordinates contain 488 * specified point, null of none. 489 * @param pt the point in this window's coordinate system 490 */ 491 XMenuItemPeer getItemFromPoint(Point pt) { 492 XMenuItemPeer[] items = getMappingData().getItems(); 493 int cnt = items.length; 494 for (int i = 0; i < cnt; i++) { 495 if (items[i].getBounds().contains(pt)) { 496 return items[i]; 497 } 498 } 499 return null; 500 } 501 502 /** 503 * Returns first item after currently selected 504 * item that can be selected according to mapping array. 505 * (no separators and no disabled items). 506 * Currently selected item if it's only selectable, 507 * null if no item can be selected 508 */ 509 XMenuItemPeer getNextSelectableItem() { 510 XMenuItemPeer[] mappedItems = getMappingData().getItems(); 511 XMenuItemPeer selectedItem = getSelectedItem(); 512 int cnt = mappedItems.length; 513 //Find index of selected item 514 int selIdx = -1; 515 for (int i = 0; i < cnt; i++) { 516 if (mappedItems[i] == selectedItem) { 517 selIdx = i; 518 break; 519 } 520 } 521 int idx = (selIdx == cnt - 1) ? 0 : selIdx + 1; 522 //cycle through mappedItems to find selectable item 523 //beginning from the next item and moving to the 524 //beginning of array when end is reached. 525 //Cycle is finished on selected item itself 526 for (int i = 0; i < cnt; i++) { 527 XMenuItemPeer item = mappedItems[idx]; 528 if (!item.isSeparator() && item.isTargetItemEnabled()) { 529 return item; 530 } 531 idx++; 532 if (idx >= cnt) { 533 idx = 0; 534 } 535 } 536 //return null if no selectable item was found 537 return null; 538 } 539 540 /** 541 * Returns first item before currently selected 542 * see getNextSelectableItem() for comments 543 */ 544 XMenuItemPeer getPrevSelectableItem() { 545 XMenuItemPeer[] mappedItems = getMappingData().getItems(); 546 XMenuItemPeer selectedItem = getSelectedItem(); 547 int cnt = mappedItems.length; 548 //Find index of selected item 549 int selIdx = -1; 550 for (int i = 0; i < cnt; i++) { 551 if (mappedItems[i] == selectedItem) { 552 selIdx = i; 553 break; 554 } 555 } 556 int idx = (selIdx <= 0) ? cnt - 1 : selIdx - 1; 557 //cycle through mappedItems to find selectable item 558 for (int i = 0; i < cnt; i++) { 559 XMenuItemPeer item = mappedItems[idx]; 560 if (!item.isSeparator() && item.isTargetItemEnabled()) { 561 return item; 562 } 563 idx--; 564 if (idx < 0) { 565 idx = cnt - 1; 566 } 567 } 568 //return null if no selectable item was found 569 return null; 570 } 571 572 /** 573 * Returns first selectable item 574 * This function is intended for clearing selection 575 */ 576 XMenuItemPeer getFirstSelectableItem() { 577 XMenuItemPeer[] mappedItems = getMappingData().getItems(); 578 int cnt = mappedItems.length; 579 for (int i = 0; i < cnt; i++) { 580 XMenuItemPeer item = mappedItems[i]; 581 if (!item.isSeparator() && item.isTargetItemEnabled()) { 582 return item; 583 } 584 } 585 586 return null; 587 } 588 589 /************************************************ 590 * 591 * Utility functions for manipulating 592 * hierarchy of windows 593 * 594 ************************************************/ 595 596 /** 597 * returns leaf menu window or 598 * this if no children are showing 599 */ 600 XBaseMenuWindow getShowingLeaf() { 601 synchronized(getMenuTreeLock()) { 602 XBaseMenuWindow leaf = this; 603 XMenuPeer leafchild = leaf.getShowingSubmenu(); 604 while (leafchild != null) { 605 leaf = leafchild.getMenuWindow(); 606 leafchild = leaf.getShowingSubmenu(); 607 } 608 return leaf; 609 } 610 } 611 612 /** 613 * returns root menu window 614 * or this if this window is topmost 615 */ 616 XBaseMenuWindow getRootMenuWindow() { 617 synchronized(getMenuTreeLock()) { 618 XBaseMenuWindow t = this; 619 XBaseMenuWindow tparent = t.getParentMenuWindow(); 620 while (tparent != null) { 621 t = tparent; 622 tparent = t.getParentMenuWindow(); 623 } 624 return t; 625 } 626 } 627 628 /** 629 * Returns window that contains pt. 630 * search is started from leaf window 631 * to return first window in Z-order 632 * @param pt point in global coordinates 633 */ 634 XBaseMenuWindow getMenuWindowFromPoint(Point pt) { 635 synchronized(getMenuTreeLock()) { 636 XBaseMenuWindow t = getShowingLeaf(); 637 while (t != null) { 638 Rectangle r = new Rectangle(t.toGlobal(new Point(0, 0)), t.getSize()); 639 if (r.contains(pt)) { 640 return t; 641 } 642 t = t.getParentMenuWindow(); 643 } 644 return null; 645 } 646 } 647 648 /************************************************ 649 * 650 * Primitives for getSubmenuBounds 651 * 652 * These functions are invoked from getSubmenuBounds 653 * implementations in different order. They check if window 654 * of size windowSize fits to the specified edge of 655 * rectangle itemBounds on the screen of screenSize. 656 * Return rectangle that occupies the window if it fits or null. 657 * 658 ************************************************/ 659 660 /** 661 * Checks if window fits below specified item 662 * returns rectangle that the window fits to or null. 663 * @param itemBounds rectangle of item in global coordinates 664 * @param windowSize size of submenu window to fit 665 * @param screenSize size of screen 666 */ 667 Rectangle fitWindowBelow(Rectangle itemBounds, Dimension windowSize, Dimension screenSize) { 668 int width = windowSize.width; 669 int height = windowSize.height; 670 //Fix for 6267162: PIT: Popup Menu gets hidden below the screen when opened 671 //near the periphery of the screen, XToolkit 672 //Window should be moved if it's outside top-left screen bounds 673 int x = (itemBounds.x > 0) ? itemBounds.x : 0; 674 int y = (itemBounds.y + itemBounds.height > 0) ? itemBounds.y + itemBounds.height : 0; 675 if (y + height <= screenSize.height) { 676 //move it to the left if needed 677 if (width > screenSize.width) { 678 width = screenSize.width; 679 } 680 if (x + width > screenSize.width) { 681 x = screenSize.width - width; 682 } 683 return new Rectangle(x, y, width, height); 684 } else { 685 return null; 686 } 687 } 688 689 /** 690 * Checks if window fits above specified item 691 * returns rectangle that the window fits to or null. 692 * @param itemBounds rectangle of item in global coordinates 693 * @param windowSize size of submenu window to fit 694 * @param screenSize size of screen 695 */ 696 Rectangle fitWindowAbove(Rectangle itemBounds, Dimension windowSize, Dimension screenSize) { 697 int width = windowSize.width; 698 int height = windowSize.height; 699 //Fix for 6267162: PIT: Popup Menu gets hidden below the screen when opened 700 //near the periphery of the screen, XToolkit 701 //Window should be moved if it's outside bottom-left screen bounds 702 int x = (itemBounds.x > 0) ? itemBounds.x : 0; 703 int y = (itemBounds.y > screenSize.height) ? screenSize.height - height : itemBounds.y - height; 704 if (y >= 0) { 705 //move it to the left if needed 706 if (width > screenSize.width) { 707 width = screenSize.width; 708 } 709 if (x + width > screenSize.width) { 710 x = screenSize.width - width; 711 } 712 return new Rectangle(x, y, width, height); 713 } else { 714 return null; 715 } 716 } 717 718 /** 719 * Checks if window fits to the right specified item 720 * returns rectangle that the window fits to or null. 721 * @param itemBounds rectangle of item in global coordinates 722 * @param windowSize size of submenu window to fit 723 * @param screenSize size of screen 724 */ 725 Rectangle fitWindowRight(Rectangle itemBounds, Dimension windowSize, Dimension screenSize) { 726 int width = windowSize.width; 727 int height = windowSize.height; 728 //Fix for 6267162: PIT: Popup Menu gets hidden below the screen when opened 729 //near the periphery of the screen, XToolkit 730 //Window should be moved if it's outside top-left screen bounds 731 int x = (itemBounds.x + itemBounds.width > 0) ? itemBounds.x + itemBounds.width : 0; 732 int y = (itemBounds.y > 0) ? itemBounds.y : 0; 733 if (x + width <= screenSize.width) { 734 //move it to the top if needed 735 if (height > screenSize.height) { 736 height = screenSize.height; 737 } 738 if (y + height > screenSize.height) { 739 y = screenSize.height - height; 740 } 741 return new Rectangle(x, y, width, height); 742 } else { 743 return null; 744 } 745 } 746 747 /** 748 * Checks if window fits to the left specified item 749 * returns rectangle that the window fits to or null. 750 * @param itemBounds rectangle of item in global coordinates 751 * @param windowSize size of submenu window to fit 752 * @param screenSize size of screen 753 */ 754 Rectangle fitWindowLeft(Rectangle itemBounds, Dimension windowSize, Dimension screenSize) { 755 int width = windowSize.width; 756 int height = windowSize.height; 757 //Fix for 6267162: PIT: Popup Menu gets hidden below the screen when opened 758 //near the periphery of the screen, XToolkit 759 //Window should be moved if it's outside top-right screen bounds 760 int x = (itemBounds.x < screenSize.width) ? itemBounds.x - width : screenSize.width - width; 761 int y = (itemBounds.y > 0) ? itemBounds.y : 0; 762 if (x >= 0) { 763 //move it to the top if needed 764 if (height > screenSize.height) { 765 height = screenSize.height; 766 } 767 if (y + height > screenSize.height) { 768 y = screenSize.height - height; 769 } 770 return new Rectangle(x, y, width, height); 771 } else { 772 return null; 773 } 774 } 775 776 /** 777 * The last thing we can do with the window 778 * to fit it on screen - move it to the 779 * top-left edge and cut by screen dimensions 780 * @param windowSize size of submenu window to fit 781 * @param screenSize size of screen 782 */ 783 Rectangle fitWindowToScreen(Dimension windowSize, Dimension screenSize) { 784 int width = (windowSize.width < screenSize.width) ? windowSize.width : screenSize.width; 785 int height = (windowSize.height < screenSize.height) ? windowSize.height : screenSize.height; 786 return new Rectangle(0, 0, width, height); 787 } 788 789 790 /************************************************ 791 * 792 * Utility functions for manipulating colors 793 * 794 ************************************************/ 795 796 /** 797 * This function is called before every painting. 798 * TODO:It would be better to add PropertyChangeListener 799 * to target component 800 * TODO:It would be better to access background color 801 * not invoking user-overridable function 802 */ 803 void resetColors() { 804 replaceColors((target == null) ? SystemColor.window : target.getBackground()); 805 } 806 807 /** 808 * Calculates colors of various elements given 809 * background color. Uses MotifColorUtilities 810 * @param backgroundColor the color of menu window's 811 * background. 812 */ 813 void replaceColors(Color backgroundColor) { 814 if (backgroundColor != this.backgroundColor) { 815 this.backgroundColor = backgroundColor; 816 817 int red = backgroundColor.getRed(); 818 int green = backgroundColor.getGreen(); 819 int blue = backgroundColor.getBlue(); 820 821 foregroundColor = new Color(MotifColorUtilities.calculateForegroundFromBackground(red,green,blue)); 822 lightShadowColor = new Color(MotifColorUtilities.calculateTopShadowFromBackground(red,green,blue)); 823 darkShadowColor = new Color(MotifColorUtilities.calculateBottomShadowFromBackground(red,green,blue)); 824 selectedColor = new Color(MotifColorUtilities.calculateSelectFromBackground(red,green,blue)); 825 disabledColor = (backgroundColor.equals(Color.BLACK)) ? foregroundColor.darker() : backgroundColor.darker(); 826 } 827 } 828 829 Color getBackgroundColor() { 830 return backgroundColor; 831 } 832 833 Color getForegroundColor() { 834 return foregroundColor; 835 } 836 837 Color getLightShadowColor() { 838 return lightShadowColor; 839 } 840 841 Color getDarkShadowColor() { 842 return darkShadowColor; 843 } 844 845 Color getSelectedColor() { 846 return selectedColor; 847 } 848 849 Color getDisabledColor() { 850 return disabledColor; 851 } 852 853 /************************************************ 854 * 855 * Painting utility functions 856 * 857 ************************************************/ 858 859 /** 860 * Draws raised or sunken rectangle on specified graphics 861 * @param g the graphics on which to draw 862 * @param x the coordinate of left edge in coordinates of graphics 863 * @param y the coordinate of top edge in coordinates of graphics 864 * @param width the width of rectangle 865 * @param height the height of rectangle 866 * @param raised true to draw raised rectangle, false to draw sunken 867 */ 868 void draw3DRect(Graphics g, int x, int y, int width, int height, boolean raised) { 869 if ((width <= 0) || (height <= 0)) { 870 return; 871 } 872 Color c = g.getColor(); 873 g.setColor(raised ? getLightShadowColor() : getDarkShadowColor()); 874 g.drawLine(x, y, x, y + height - 1); 875 g.drawLine(x + 1, y, x + width - 1, y); 876 g.setColor(raised ? getDarkShadowColor() : getLightShadowColor()); 877 g.drawLine(x + 1, y + height - 1, x + width - 1, y + height - 1); 878 g.drawLine(x + width - 1, y + 1, x + width - 1, y + height - 1); 879 g.setColor(c); 880 } 881 882 /************************************************ 883 * 884 * Overriden utility functions of XWindow 885 * 886 ************************************************/ 887 888 /** 889 * Filters X events 890 */ 891 protected boolean isEventDisabled(XEvent e) { 892 switch (e.get_type()) { 893 case XConstants.Expose : 894 case XConstants.GraphicsExpose : 895 case XConstants.ButtonPress: 896 case XConstants.ButtonRelease: 897 case XConstants.MotionNotify: 898 case XConstants.KeyPress: 899 case XConstants.KeyRelease: 900 case XConstants.DestroyNotify: 901 return super.isEventDisabled(e); 902 default: 903 return true; 904 } 905 } 906 907 /** 908 * Invokes disposal procedure on eventHandlerThread 909 */ 910 public void dispose() { 911 setDisposed(true); 912 913 SunToolkit.invokeLaterOnAppContext(disposeAppContext, new Runnable() { 914 public void run() { 915 doDispose(); 916 } 917 }); 918 } 919 920 /** 921 * Performs disposal of menu window. 922 * Should be called only on eventHandlerThread 923 */ 924 protected void doDispose() { 925 xSetVisible(false); 926 SurfaceData oldData = surfaceData; 927 surfaceData = null; 928 if (oldData != null) { 929 oldData.invalidate(); 930 } 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