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