1 /*
   2  * Copyright (c) 2002, 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 
  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 final static int BAR_SPACING_TOP = 3;
  60     private final static int BAR_SPACING_BOTTOM = 3;
  61     private final static int BAR_SPACING_LEFT = 3;
  62     private final static int BAR_SPACING_RIGHT = 3;
  63     private final static int BAR_ITEM_SPACING = 2;
  64     private final static int BAR_ITEM_MARGIN_LEFT = 10;
  65     private final static int BAR_ITEM_MARGIN_RIGHT = 10;
  66     private final static int BAR_ITEM_MARGIN_TOP = 2;
  67     private final static 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     @SuppressWarnings("deprecation")
 167     public void addHelpMenu(Menu m) {
 168         XMenuPeer mp = (XMenuPeer)m.getPeer();
 169         synchronized(getMenuTreeLock()) {
 170             helpMenu = mp;
 171         }
 172         postPaintEvent();
 173     }
 174 
 175     /************************************************
 176      *
 177      * Initialization
 178      *
 179      ************************************************/
 180     /**
 181      * called from XFramePeer.setMenuBar
 182      */
 183     @SuppressWarnings("deprecation")
 184     public void init(Frame frame) {
 185         this.target = frame;
 186         this.framePeer = (XFramePeer)frame.getPeer();
 187         XCreateWindowParams params = getDelayedParams();
 188         params.remove(DELAYED);
 189         params.add(PARENT_WINDOW, framePeer.getShell());
 190         params.add(TARGET, frame);
 191         init(params);
 192     }
 193 
 194     /**
 195      * Overriden initialization
 196      */
 197     void postInit(XCreateWindowParams params) {
 198         super.postInit(params);
 199         // Get menus from the target.
 200         Vector<Menu> targetMenuVector = AWTAccessor.getMenuBarAccessor()
 201                                                    .getMenus(menuBarTarget);
 202         Menu targetHelpMenu = AWTAccessor.getMenuBarAccessor()
 203                                          .getHelpMenu(menuBarTarget);
 204         reloadItems(targetMenuVector);
 205         if (targetHelpMenu != null) {
 206             addHelpMenu(targetHelpMenu);
 207         }
 208         xSetVisible(true);
 209         toFront();
 210     }
 211 
 212     /************************************************
 213      *
 214      * Implementation of abstract methods
 215      *
 216      ************************************************/
 217 
 218     /**
 219      * Menu bar is always root window in menu window's
 220      * hierarchy
 221      */
 222     protected XBaseMenuWindow getParentMenuWindow() {
 223         return null;
 224     }
 225 
 226     /**
 227      * @see XBaseMenuWindow.map
 228      */
 229     protected MappingData map() {
 230         XMenuItemPeer[] itemVector = copyItems();
 231         int itemCnt = itemVector.length;
 232         XMenuItemPeer helpMenu = this.helpMenu;
 233         int helpMenuPos = -1;
 234         //find helpMenu and move it to the end of array
 235         if (helpMenu != null) {
 236             //Fixed 6270847: PIT: HELP menu is not shown at the right place when normal menus added to MB are removed, XToolkit
 237             for (int i = 0; i < itemCnt; i++) {
 238                 if (itemVector[i] == helpMenu) {
 239                     helpMenuPos = i;
 240                     break;
 241                 }
 242             }
 243             if (helpMenuPos != -1 && helpMenuPos != itemCnt - 1) {
 244                 System.arraycopy(itemVector, helpMenuPos + 1, itemVector, helpMenuPos, itemCnt - 1 - helpMenuPos);
 245                 itemVector[itemCnt - 1] = helpMenu;
 246             }
 247         }
 248         //We need maximum height before calculating item's bounds
 249         int maxHeight = 0;
 250         XMenuItemPeer.TextMetrics[] itemMetrics = new XMenuItemPeer.TextMetrics[itemCnt];
 251         for (int i = 0; i < itemCnt; i++) {
 252             itemMetrics[i] = itemVector[i].getTextMetrics();
 253             Dimension dim = itemMetrics[i].getTextDimension();
 254             if (dim != null) {
 255                 maxHeight = Math.max(maxHeight, dim.height);
 256             }
 257         }
 258         //Calculate bounds
 259         int nextOffset = 0;
 260         int itemHeight = BAR_ITEM_MARGIN_TOP + maxHeight + BAR_ITEM_MARGIN_BOTTOM;
 261         int mappedCnt = itemCnt;
 262         for (int i = 0; i < itemCnt; i++) {
 263             XMenuItemPeer item = itemVector[i];
 264             XMenuItemPeer.TextMetrics metrics = itemMetrics[i];
 265             Dimension dim = metrics.getTextDimension();
 266             if (dim != null) {
 267                 int itemWidth = BAR_ITEM_MARGIN_LEFT + dim.width + BAR_ITEM_MARGIN_RIGHT;
 268                 //Fix for 6270757: PIT: Menus and Sub-menus are shown outside the frame, XToolkit
 269                 //Cut-off items that don't fit in window
 270                 //At least one item must remain in menu
 271                 if ((nextOffset + itemWidth > this.width) && (i > 0)) {
 272                     mappedCnt = i;
 273                     break;
 274                 }
 275                 //If this item is help menu, move it to the right edge
 276                 if ((i == itemCnt - 1) && helpMenuPos != -1) {
 277                     nextOffset = Math.max(nextOffset, this.width - itemWidth - BAR_SPACING_RIGHT);
 278                 }
 279                 Rectangle bounds = new Rectangle(nextOffset, BAR_SPACING_TOP, itemWidth, itemHeight);
 280                 //text should be centered vertically in menu item's bounds
 281                 int y = (maxHeight + dim.height) / 2  - metrics.getTextBaseline();
 282                 Point textOrigin = new Point(nextOffset + BAR_ITEM_MARGIN_LEFT, BAR_SPACING_TOP + BAR_ITEM_MARGIN_TOP + y);
 283                 nextOffset += itemWidth + BAR_ITEM_SPACING;
 284                 item.map(bounds, textOrigin);
 285             } else {
 286                 Rectangle bounds = new Rectangle(nextOffset, BAR_SPACING_TOP, 0, 0);
 287                 Point textOrigin = new Point(nextOffset + BAR_ITEM_MARGIN_LEFT, BAR_SPACING_TOP + BAR_ITEM_MARGIN_TOP);
 288             }
 289         }
 290         XMenuItemPeer mappedVector[] = new XMenuItemPeer[mappedCnt];
 291         System.arraycopy(itemVector, 0, mappedVector, 0, mappedCnt);
 292         MappingData mappingData = new MappingData(mappedVector, BAR_SPACING_TOP + itemHeight + BAR_SPACING_BOTTOM);
 293         return mappingData;
 294     }
 295 
 296     /**
 297      * @see XBaseMenuWindow.getSubmenuBounds
 298      */
 299     protected Rectangle getSubmenuBounds(Rectangle itemBounds, Dimension windowSize) {
 300         Rectangle globalBounds = toGlobal(itemBounds);
 301         Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
 302         Rectangle res;
 303         res = fitWindowBelow(globalBounds, windowSize, screenSize);
 304         if (res != null) {
 305             return res;
 306         }
 307         res = fitWindowAbove(globalBounds, windowSize, screenSize);
 308         if (res != null) {
 309             return res;
 310         }
 311         res = fitWindowRight(globalBounds, windowSize, screenSize);
 312         if (res != null) {
 313             return res;
 314         }
 315         res = fitWindowLeft(globalBounds, windowSize, screenSize);
 316         if (res != null) {
 317             return res;
 318         }
 319         return fitWindowToScreen(windowSize, screenSize);
 320     }
 321 
 322     /**
 323      * This function is called when it's likely that
 324      * size of items has changed.
 325      * Invokes framePeer's updateChildrenSizes()
 326      */
 327     protected void updateSize() {
 328         resetMapping();
 329         if (framePeer != null) {
 330             framePeer.reshapeMenubarPeer();
 331         }
 332     }
 333 
 334     /************************************************
 335      *
 336      * Utility functions
 337      *
 338      ************************************************/
 339 
 340     /**
 341      * Returns desired height of menu bar
 342      */
 343     int getDesiredHeight() {
 344         MappingData mappingData = (MappingData)getMappingData();
 345         return mappingData.getDesiredHeight();
 346     }
 347 
 348     /**
 349      * Returns true if framePeer is not null and is enabled
 350      * Used to fix 6185057: Disabling a frame does not disable
 351      * the menus on the frame, on solaris/linux
 352      */
 353     boolean isFramePeerEnabled() {
 354         if (framePeer != null) {
 355             return framePeer.isEnabled();
 356         }
 357         return false;
 358     }
 359 
 360     /************************************************
 361      *
 362      * Overriden XBaseMenuWindow functions
 363      *
 364      ************************************************/
 365 
 366     /**
 367      * @see XBaseMenuWindow.doDispose()
 368      */
 369     protected void doDispose() {
 370         super.doDispose();
 371         XToolkit.targetDisposedPeer(menuBarTarget, this);
 372     }
 373 
 374     /************************************************
 375      *
 376      * Overriden XWindow general-purpose functions
 377      *
 378      ************************************************/
 379 
 380     /**
 381      * For menu bars this function is called from framePeer's
 382      * reshape(...) and updateChildrenSizes()
 383      */
 384     public void reshape(int x, int y, int width, int height) {
 385         if ((width != this.width) || (height != this.height)) {
 386             resetMapping();
 387         }
 388         super.reshape(x, y, width, height);
 389     }
 390 
 391     /**
 392      * Performs ungrabbing of input
 393      * @see XBaseWindow.ungrabInputImpl()
 394      */
 395     void ungrabInputImpl() {
 396         selectItem(null, false);
 397         super.ungrabInputImpl();
 398         postPaintEvent();
 399     }
 400 
 401     /************************************************
 402      *
 403      * Overriden XWindow painting & printing
 404      *
 405      ************************************************/
 406     public void paintPeer(Graphics g) {
 407         resetColors();
 408         /* Calculate menubar dimension. */
 409         int width = getWidth();
 410         int height = getHeight();
 411 
 412         flush();
 413         //Fill background of rectangle
 414         g.setColor(getBackgroundColor());
 415         g.fillRect(1, 1, width - 2, height - 2);
 416 
 417         draw3DRect(g, 0, 0, width, height, true);
 418 
 419         //Paint menus
 420         MappingData mappingData = (MappingData)getMappingData();
 421         XMenuItemPeer[] itemVector = mappingData.getItems();
 422         XMenuItemPeer selectedItem = getSelectedItem();
 423         for (int i = 0; i < itemVector.length; i++) {
 424             XMenuItemPeer item = itemVector[i];
 425             //paint item
 426             g.setFont(item.getTargetFont());
 427             Rectangle bounds = item.getBounds();
 428             Point textOrigin = item.getTextOrigin();
 429             if (item == selectedItem) {
 430                 g.setColor(getSelectedColor());
 431                 g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
 432                 draw3DRect(g, bounds.x, bounds.y, bounds.width, bounds.height, false);
 433             }
 434             if (isFramePeerEnabled() && item.isTargetItemEnabled()) {
 435                 g.setColor(getForegroundColor());
 436             } else {
 437                 g.setColor(getDisabledColor());
 438             }
 439             g.drawString(item.getTargetLabel(), textOrigin.x, textOrigin.y);
 440         }
 441         flush();
 442     }
 443 
 444     static final int W_DIFF = (XFramePeer.CROSSHAIR_INSET + 1) * 2;
 445     static final int H_DIFF = XFramePeer.BUTTON_Y + XFramePeer.BUTTON_H;
 446 
 447     void print(Graphics g) {
 448         //TODO:Implement
 449     }
 450 
 451     /************************************************
 452      *
 453      * Overriden XBaseMenuWindow event handling
 454      *
 455      ************************************************/
 456     protected void handleEvent(AWTEvent event) {
 457         // explicitly block all events except PaintEvent.PAINT for menus,
 458         // that are in the modal blocked window
 459         if ((framePeer != null) &&
 460             (event.getID() != PaintEvent.PAINT))
 461         {
 462             if (framePeer.isModalBlocked()) {
 463                 return;
 464             }
 465         }
 466         switch(event.getID()) {
 467         case MouseEvent.MOUSE_PRESSED:
 468         case MouseEvent.MOUSE_RELEASED:
 469         case MouseEvent.MOUSE_CLICKED:
 470         case MouseEvent.MOUSE_MOVED:
 471         case MouseEvent.MOUSE_ENTERED:
 472         case MouseEvent.MOUSE_EXITED:
 473         case MouseEvent.MOUSE_DRAGGED:
 474             //Fix for 6185057: Disabling a frame does not disable
 475             //the menus on the frame, on solaris/linux
 476             if (isFramePeerEnabled()) {
 477                 doHandleJavaMouseEvent((MouseEvent)event);
 478             }
 479             break;
 480         case KeyEvent.KEY_PRESSED:
 481         case KeyEvent.KEY_RELEASED:
 482             //Fix for 6185057: Disabling a frame does not disable
 483             //the menus on the frame, on solaris/linux
 484             if (isFramePeerEnabled()) {
 485                 doHandleJavaKeyEvent((KeyEvent)event);
 486             }
 487             break;
 488         default:
 489             super.handleEvent(event);
 490             break;
 491         }
 492     }
 493 
 494 
 495 
 496     /************************************************
 497      *
 498      * Overriden XWindow keyboard processing
 499      *
 500      ************************************************/
 501 
 502     /*
 503      * This function is called from XWindow
 504      * @see XWindow.handleF10onEDT()
 505      */
 506     void handleF10KeyPress(KeyEvent event) {
 507         int keyState = event.getModifiers();
 508         if (((keyState & InputEvent.ALT_MASK) != 0) ||
 509             ((keyState & InputEvent.SHIFT_MASK) != 0) ||
 510             ((keyState & InputEvent.CTRL_MASK) != 0)) {
 511             return;
 512         }
 513         grabInput();
 514         selectItem(getFirstSelectableItem(), true);
 515     }
 516 
 517     /*
 518      * In previous version keys were handled in handleKeyPress.
 519      * Now we override this function do disable F10 explicit
 520      * processing. All processing is done using KeyEvent.
 521      */
 522     public void handleKeyPress(XEvent xev) {
 523         XKeyEvent xkey = xev.get_xkey();
 524         if (log.isLoggable(PlatformLogger.Level.FINE)) {
 525             log.fine(xkey.toString());
 526         }
 527         if (isEventDisabled(xev)) {
 528             return;
 529         }
 530         final Component currentSource = getEventSource();
 531         //This is the only difference from XWindow.handleKeyPress
 532         //Ancestor's function can invoke handleF10KeyPress here
 533         handleKeyPress(xkey);
 534     }
 535 
 536 } //class XMenuBarPeer