1 /*
   2  * Copyright (c) 2002, 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  */
  25 package sun.awt.X11;
  26 
  27 import java.awt.*;
  28 import java.awt.peer.*;
  29 import java.awt.event.*;
  30 
  31 import java.util.Vector;
  32 import sun.util.logging.PlatformLogger;
  33 import sun.awt.AWTAccessor;
  34 
  35 public class XMenuBarPeer extends XBaseMenuWindow implements MenuBarPeer {
  36 
  37     /************************************************
  38      *
  39      * Data members
  40      *
  41      ************************************************/
  42 
  43     private static PlatformLogger log = PlatformLogger.getLogger("sun.awt.X11.XMenuBarPeer");
  44 
  45     /*
  46      * Primary members
  47      */
  48     private XFramePeer framePeer;
  49     private MenuBar menuBarTarget;
  50 
  51     /*
  52      * Index of help menu
  53      */
  54     private XMenuPeer helpMenu = null;
  55 
  56     /*
  57      * dimension constants
  58      */
  59     private static final int BAR_SPACING_TOP = 3;
  60     private static final int BAR_SPACING_BOTTOM = 3;
  61     private static final int BAR_SPACING_LEFT = 3;
  62     private static final int BAR_SPACING_RIGHT = 3;
  63     private static final int BAR_ITEM_SPACING = 2;
  64     private static final int BAR_ITEM_MARGIN_LEFT = 10;
  65     private static final int BAR_ITEM_MARGIN_RIGHT = 10;
  66     private static final int BAR_ITEM_MARGIN_TOP = 2;
  67     private static final int BAR_ITEM_MARGIN_BOTTOM = 2;
  68 
  69     /************************************************
  70      *
  71      * Mapping data
  72      *
  73      ************************************************/
  74 
  75     /**
  76      * XBaseMenuWindow's mappingData is extended with
  77      * desired height of menu bar
  78      */
  79     static class MappingData extends XBaseMenuWindow.MappingData {
  80         int desiredHeight;
  81 
  82         MappingData(XMenuItemPeer[] items, int desiredHeight) {
  83             super(items);
  84             this.desiredHeight = desiredHeight;
  85         }
  86 
  87         /**
  88          * Constructs MappingData without items
  89          * This constructor should be used in case of errors
  90          */
  91         MappingData() {
  92             this.desiredHeight = 0;
  93         }
  94 
  95         public int getDesiredHeight() {
  96             return this.desiredHeight;
  97         }
  98     }
  99 
 100     /************************************************
 101      *
 102      * Construction
 103      *
 104      ************************************************/
 105     XMenuBarPeer(MenuBar menuBarTarget) {
 106         this.menuBarTarget = menuBarTarget;
 107     }
 108 
 109     /************************************************
 110      *
 111      * Implementaion of interface methods
 112      *
 113      ************************************************/
 114 
 115     /*
 116      * From MenuComponentPeer
 117      */
 118     public void setFont(Font f) {
 119         resetMapping();
 120         setItemsFont(f);
 121         postPaintEvent();
 122     }
 123 
 124     /*
 125      * From MenuBarPeer
 126      */
 127 
 128     /*
 129      * Functions addMenu, delMenu, addHelpMenu
 130      * need to have somewhat strange behaivour
 131      * deduced from java.awt.MenuBar.
 132      * We can not get index of particular item in
 133      * MenuBar.menus array, because MenuBar firstly
 134      * performs array operations and then calls peer.
 135      * So we need to synchronize indicies in 'items'
 136      * array with MenuBar.menus. We have to follow
 137      * these rules:
 138      * 1. Menus are always added to the end of array,
 139      * even when helpMenu is present
 140      * 2. Removal of any menu item acts as casual
 141      * remove from array
 142      * 3. MenuBar.setHelpMenu _firstly_ removes
 143      * previous helpMenu by calling delMenu() if
 144      * necessary, then it performs addMenu(),
 145      * and then - addHelpMenu().
 146      *
 147      * Note that these functions don't perform
 148      * type checks and checks for nulls or duplicates
 149      */
 150     public void addMenu(Menu m) {
 151         addItem(m);
 152         postPaintEvent();
 153     }
 154 
 155     public void delMenu(int index) {
 156         synchronized(getMenuTreeLock()) {
 157             XMenuItemPeer item = getItem(index);
 158             if (item != null && item == helpMenu) {
 159                 helpMenu = null;
 160             }
 161             delItem(index);
 162         }
 163         postPaintEvent();
 164     }
 165 
 166     public void addHelpMenu(Menu m) {
 167         XMenuPeer mp = AWTAccessor.getMenuComponentAccessor().getPeer(m);
 168         synchronized(getMenuTreeLock()) {
 169             helpMenu = mp;
 170         }
 171         postPaintEvent();
 172     }
 173 
 174     /************************************************
 175      *
 176      * Initialization
 177      *
 178      ************************************************/
 179     /**
 180      * called from XFramePeer.setMenuBar
 181      */
 182     public void init(Frame frame) {
 183         this.target = frame;
 184         this.framePeer = AWTAccessor.getComponentAccessor().getPeer(frame);
 185         XCreateWindowParams params = getDelayedParams();
 186         params.remove(DELAYED);
 187         params.add(PARENT_WINDOW, framePeer.getShell());
 188         params.add(TARGET, frame);
 189         init(params);
 190     }
 191 
 192     /**
 193      * Overriden initialization
 194      */
 195     void postInit(XCreateWindowParams params) {
 196         super.postInit(params);
 197         // Get menus from the target.
 198         Vector<Menu> targetMenuVector = AWTAccessor.getMenuBarAccessor()
 199                                                    .getMenus(menuBarTarget);
 200         Menu targetHelpMenu = AWTAccessor.getMenuBarAccessor()
 201                                          .getHelpMenu(menuBarTarget);
 202         reloadItems(targetMenuVector);
 203         if (targetHelpMenu != null) {
 204             addHelpMenu(targetHelpMenu);
 205         }
 206         xSetVisible(true);
 207         toFront();
 208     }
 209 
 210     /************************************************
 211      *
 212      * Implementation of abstract methods
 213      *
 214      ************************************************/
 215 
 216     /**
 217      * Menu bar is always root window in menu window's
 218      * hierarchy
 219      */
 220     protected XBaseMenuWindow getParentMenuWindow() {
 221         return null;
 222     }
 223 
 224     /**
 225      * @see XBaseMenuWindow#map
 226      */
 227     protected MappingData map() {
 228         XMenuItemPeer[] itemVector = copyItems();
 229         int itemCnt = itemVector.length;
 230         XMenuItemPeer helpMenu = this.helpMenu;
 231         int helpMenuPos = -1;
 232         //find helpMenu and move it to the end of array
 233         if (helpMenu != null) {
 234             //Fixed 6270847: PIT: HELP menu is not shown at the right place when normal menus added to MB are removed, XToolkit
 235             for (int i = 0; i < itemCnt; i++) {
 236                 if (itemVector[i] == helpMenu) {
 237                     helpMenuPos = i;
 238                     break;
 239                 }
 240             }
 241             if (helpMenuPos != -1 && helpMenuPos != itemCnt - 1) {
 242                 System.arraycopy(itemVector, helpMenuPos + 1, itemVector, helpMenuPos, itemCnt - 1 - helpMenuPos);
 243                 itemVector[itemCnt - 1] = helpMenu;
 244             }
 245         }
 246         //We need maximum height before calculating item's bounds
 247         int maxHeight = 0;
 248         XMenuItemPeer.TextMetrics[] itemMetrics = new XMenuItemPeer.TextMetrics[itemCnt];
 249         for (int i = 0; i < itemCnt; i++) {
 250             itemMetrics[i] = itemVector[i].getTextMetrics();
 251             Dimension dim = itemMetrics[i].getTextDimension();
 252             if (dim != null) {
 253                 maxHeight = Math.max(maxHeight, dim.height);
 254             }
 255         }
 256         //Calculate bounds
 257         int nextOffset = 0;
 258         int itemHeight = BAR_ITEM_MARGIN_TOP + maxHeight + BAR_ITEM_MARGIN_BOTTOM;
 259         int mappedCnt = itemCnt;
 260         for (int i = 0; i < itemCnt; i++) {
 261             XMenuItemPeer item = itemVector[i];
 262             XMenuItemPeer.TextMetrics metrics = itemMetrics[i];
 263             Dimension dim = metrics.getTextDimension();
 264             if (dim != null) {
 265                 int itemWidth = BAR_ITEM_MARGIN_LEFT + dim.width + BAR_ITEM_MARGIN_RIGHT;
 266                 //Fix for 6270757: PIT: Menus and Sub-menus are shown outside the frame, XToolkit
 267                 //Cut-off items that don't fit in window
 268                 //At least one item must remain in menu
 269                 if ((nextOffset + itemWidth > this.width) && (i > 0)) {
 270                     mappedCnt = i;
 271                     break;
 272                 }
 273                 //If this item is help menu, move it to the right edge
 274                 if ((i == itemCnt - 1) && helpMenuPos != -1) {
 275                     nextOffset = Math.max(nextOffset, this.width - itemWidth - BAR_SPACING_RIGHT);
 276                 }
 277                 Rectangle bounds = new Rectangle(nextOffset, BAR_SPACING_TOP, itemWidth, itemHeight);
 278                 //text should be centered vertically in menu item's bounds
 279                 int y = (maxHeight + dim.height) / 2  - metrics.getTextBaseline();
 280                 Point textOrigin = new Point(nextOffset + BAR_ITEM_MARGIN_LEFT, BAR_SPACING_TOP + BAR_ITEM_MARGIN_TOP + y);
 281                 nextOffset += itemWidth + BAR_ITEM_SPACING;
 282                 item.map(bounds, textOrigin);
 283             } else {
 284                 Rectangle bounds = new Rectangle(nextOffset, BAR_SPACING_TOP, 0, 0);
 285                 Point textOrigin = new Point(nextOffset + BAR_ITEM_MARGIN_LEFT, BAR_SPACING_TOP + BAR_ITEM_MARGIN_TOP);
 286             }
 287         }
 288         XMenuItemPeer mappedVector[] = new XMenuItemPeer[mappedCnt];
 289         System.arraycopy(itemVector, 0, mappedVector, 0, mappedCnt);
 290         MappingData mappingData = new MappingData(mappedVector, BAR_SPACING_TOP + itemHeight + BAR_SPACING_BOTTOM);
 291         return mappingData;
 292     }
 293 
 294     /**
 295      * @see XBaseMenuWindow#getSubmenuBounds
 296      */
 297     protected Rectangle getSubmenuBounds(Rectangle itemBounds, Dimension windowSize) {
 298         Rectangle globalBounds = toGlobal(itemBounds);
 299         Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
 300         Rectangle res;
 301         res = fitWindowBelow(globalBounds, windowSize, screenSize);
 302         if (res != null) {
 303             return res;
 304         }
 305         res = fitWindowAbove(globalBounds, windowSize, screenSize);
 306         if (res != null) {
 307             return res;
 308         }
 309         res = fitWindowRight(globalBounds, windowSize, screenSize);
 310         if (res != null) {
 311             return res;
 312         }
 313         res = fitWindowLeft(globalBounds, windowSize, screenSize);
 314         if (res != null) {
 315             return res;
 316         }
 317         return fitWindowToScreen(windowSize, screenSize);
 318     }
 319 
 320     /**
 321      * This function is called when it's likely that
 322      * size of items has changed.
 323      * Invokes framePeer's updateChildrenSizes()
 324      */
 325     protected void updateSize() {
 326         resetMapping();
 327         if (framePeer != null) {
 328             framePeer.reshapeMenubarPeer();
 329         }
 330     }
 331 
 332     /************************************************
 333      *
 334      * Utility functions
 335      *
 336      ************************************************/
 337 
 338     /**
 339      * Returns desired height of menu bar
 340      */
 341     int getDesiredHeight() {
 342         MappingData mappingData = (MappingData)getMappingData();
 343         return mappingData.getDesiredHeight();
 344     }
 345 
 346     /**
 347      * Returns true if framePeer is not null and is enabled
 348      * Used to fix 6185057: Disabling a frame does not disable
 349      * the menus on the frame, on solaris/linux
 350      */
 351     boolean isFramePeerEnabled() {
 352         if (framePeer != null) {
 353             return framePeer.isEnabled();
 354         }
 355         return false;
 356     }
 357 
 358     /************************************************
 359      *
 360      * Overriden XBaseMenuWindow functions
 361      *
 362      ************************************************/
 363 
 364     /**
 365      * @see XBaseMenuWindow#doDispose()
 366      */
 367     protected void doDispose() {
 368         super.doDispose();
 369         XToolkit.targetDisposedPeer(menuBarTarget, this);
 370     }
 371 
 372     /************************************************
 373      *
 374      * Overriden XWindow general-purpose functions
 375      *
 376      ************************************************/
 377 
 378     /**
 379      * For menu bars this function is called from framePeer's
 380      * reshape(...) and updateChildrenSizes()
 381      */
 382     public void reshape(int x, int y, int width, int height) {
 383         if ((width != this.width) || (height != this.height)) {
 384             resetMapping();
 385         }
 386         super.reshape(x, y, width, height);
 387     }
 388 
 389     /**
 390      * Performs ungrabbing of input
 391      * @see XBaseWindow#ungrabInputImpl()
 392      */
 393     void ungrabInputImpl() {
 394         selectItem(null, false);
 395         super.ungrabInputImpl();
 396         postPaintEvent();
 397     }
 398 
 399     /************************************************
 400      *
 401      * Overriden XWindow painting & printing
 402      *
 403      ************************************************/
 404     public void paintPeer(Graphics g) {
 405         resetColors();
 406         /* Calculate menubar dimension. */
 407         int width = getWidth();
 408         int height = getHeight();
 409 
 410         flush();
 411         //Fill background of rectangle
 412         g.setColor(getBackgroundColor());
 413         g.fillRect(1, 1, width - 2, height - 2);
 414 
 415         draw3DRect(g, 0, 0, width, height, true);
 416 
 417         //Paint menus
 418         MappingData mappingData = (MappingData)getMappingData();
 419         XMenuItemPeer[] itemVector = mappingData.getItems();
 420         XMenuItemPeer selectedItem = getSelectedItem();
 421         for (int i = 0; i < itemVector.length; i++) {
 422             XMenuItemPeer item = itemVector[i];
 423             //paint item
 424             g.setFont(item.getTargetFont());
 425             Rectangle bounds = item.getBounds();
 426             Point textOrigin = item.getTextOrigin();
 427             if (item == selectedItem) {
 428                 g.setColor(getSelectedColor());
 429                 g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
 430                 draw3DRect(g, bounds.x, bounds.y, bounds.width, bounds.height, false);
 431             }
 432             if (isFramePeerEnabled() && item.isTargetItemEnabled()) {
 433                 g.setColor(getForegroundColor());
 434             } else {
 435                 g.setColor(getDisabledColor());
 436             }
 437             g.drawString(item.getTargetLabel(), textOrigin.x, textOrigin.y);
 438         }
 439         flush();
 440     }
 441 
 442     static final int W_DIFF = (XFramePeer.CROSSHAIR_INSET + 1) * 2;
 443     static final int H_DIFF = XFramePeer.BUTTON_Y + XFramePeer.BUTTON_H;
 444 
 445     void print(Graphics g) {
 446         //TODO:Implement
 447     }
 448 
 449     /************************************************
 450      *
 451      * Overriden XBaseMenuWindow event handling
 452      *
 453      ************************************************/
 454     protected void handleEvent(AWTEvent event) {
 455         // explicitly block all events except PaintEvent.PAINT for menus,
 456         // that are in the modal blocked window
 457         if ((framePeer != null) &&
 458             (event.getID() != PaintEvent.PAINT))
 459         {
 460             if (framePeer.isModalBlocked()) {
 461                 return;
 462             }
 463         }
 464         switch(event.getID()) {
 465         case MouseEvent.MOUSE_PRESSED:
 466         case MouseEvent.MOUSE_RELEASED:
 467         case MouseEvent.MOUSE_CLICKED:
 468         case MouseEvent.MOUSE_MOVED:
 469         case MouseEvent.MOUSE_ENTERED:
 470         case MouseEvent.MOUSE_EXITED:
 471         case MouseEvent.MOUSE_DRAGGED:
 472             //Fix for 6185057: Disabling a frame does not disable
 473             //the menus on the frame, on solaris/linux
 474             if (isFramePeerEnabled()) {
 475                 doHandleJavaMouseEvent((MouseEvent)event);
 476             }
 477             break;
 478         case KeyEvent.KEY_PRESSED:
 479         case KeyEvent.KEY_RELEASED:
 480             //Fix for 6185057: Disabling a frame does not disable
 481             //the menus on the frame, on solaris/linux
 482             if (isFramePeerEnabled()) {
 483                 doHandleJavaKeyEvent((KeyEvent)event);
 484             }
 485             break;
 486         default:
 487             super.handleEvent(event);
 488             break;
 489         }
 490     }
 491 
 492 
 493 
 494     /************************************************
 495      *
 496      * Overriden XWindow keyboard processing
 497      *
 498      ************************************************/
 499 
 500     /*
 501      * This function is called from XWindow
 502      * @see XWindow.handleF10onEDT()
 503      */
 504     void handleF10KeyPress(KeyEvent event) {
 505         int keyState = event.getModifiers();
 506         if (((keyState & InputEvent.ALT_MASK) != 0) ||
 507             ((keyState & InputEvent.SHIFT_MASK) != 0) ||
 508             ((keyState & InputEvent.CTRL_MASK) != 0)) {
 509             return;
 510         }
 511         grabInput();
 512         selectItem(getFirstSelectableItem(), true);
 513     }
 514 
 515     /*
 516      * In previous version keys were handled in handleKeyPress.
 517      * Now we override this function do disable F10 explicit
 518      * processing. All processing is done using KeyEvent.
 519      */
 520     public void handleKeyPress(XEvent xev) {
 521         XKeyEvent xkey = xev.get_xkey();
 522         if (log.isLoggable(PlatformLogger.Level.FINE)) {
 523             log.fine(xkey.toString());
 524         }
 525         if (isEventDisabled(xev)) {
 526             return;
 527         }
 528         final Component currentSource = getEventSource();
 529         //This is the only difference from XWindow.handleKeyPress
 530         //Ancestor's function can invoke handleF10KeyPress here
 531         handleKeyPress(xkey);
 532     }
 533 
 534 } //class XMenuBarPeer