1 /*
   2  * Copyright (c) 2002, 2018, 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             if (itemMetrics[i] != null) {
 252                 Dimension dim = itemMetrics[i].getTextDimension();
 253                 if (dim != null) {
 254                     maxHeight = Math.max(maxHeight, dim.height);
 255                 }
 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             if (metrics == null) {
 266                 continue;
 267             }
 268             Dimension dim = metrics.getTextDimension();
 269             if (dim != null) {
 270                 int itemWidth = BAR_ITEM_MARGIN_LEFT + dim.width + BAR_ITEM_MARGIN_RIGHT;
 271                 //Fix for 6270757: PIT: Menus and Sub-menus are shown outside the frame, XToolkit
 272                 //Cut-off items that don't fit in window
 273                 //At least one item must remain in menu
 274                 if ((nextOffset + itemWidth > this.width) && (i > 0)) {
 275                     mappedCnt = i;
 276                     break;
 277                 }
 278                 //If this item is help menu, move it to the right edge
 279                 if ((i == itemCnt - 1) && helpMenuPos != -1) {
 280                     nextOffset = Math.max(nextOffset, this.width - itemWidth - BAR_SPACING_RIGHT);
 281                 }
 282                 Rectangle bounds = new Rectangle(nextOffset, BAR_SPACING_TOP, itemWidth, itemHeight);
 283                 //text should be centered vertically in menu item's bounds
 284                 int y = (maxHeight + dim.height) / 2  - metrics.getTextBaseline();
 285                 Point textOrigin = new Point(nextOffset + BAR_ITEM_MARGIN_LEFT, BAR_SPACING_TOP + BAR_ITEM_MARGIN_TOP + y);
 286                 nextOffset += itemWidth + BAR_ITEM_SPACING;
 287                 item.map(bounds, textOrigin);
 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         Rectangle screenBounds = getCurrentGraphicsConfiguration().getBounds();
 302         Rectangle res;
 303         res = fitWindowBelow(globalBounds, windowSize, screenBounds);
 304         if (res != null) {
 305             return res;
 306         }
 307         res = fitWindowAbove(globalBounds, windowSize, screenBounds);
 308         if (res != null) {
 309             return res;
 310         }
 311         res = fitWindowRight(globalBounds, windowSize, screenBounds);
 312         if (res != null) {
 313             return res;
 314         }
 315         res = fitWindowLeft(globalBounds, windowSize, screenBounds);
 316         if (res != null) {
 317             return res;
 318         }
 319         return fitWindowToScreen(windowSize, screenBounds);
 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     @SuppressWarnings("deprecation")
 507     void handleF10KeyPress(KeyEvent event) {
 508         int keyState = event.getModifiers();
 509         if (((keyState & InputEvent.ALT_MASK) != 0) ||
 510             ((keyState & InputEvent.SHIFT_MASK) != 0) ||
 511             ((keyState & InputEvent.CTRL_MASK) != 0)) {
 512             return;
 513         }
 514         grabInput();
 515         selectItem(getFirstSelectableItem(), true);
 516     }
 517 
 518     /*
 519      * In previous version keys were handled in handleKeyPress.
 520      * Now we override this function do disable F10 explicit
 521      * processing. All processing is done using KeyEvent.
 522      */
 523     public void handleKeyPress(XEvent xev) {
 524         XKeyEvent xkey = xev.get_xkey();
 525         if (log.isLoggable(PlatformLogger.Level.FINE)) {
 526             log.fine(xkey.toString());
 527         }
 528         if (isEventDisabled(xev)) {
 529             return;
 530         }
 531         final Component currentSource = getEventSource();
 532         //This is the only difference from XWindow.handleKeyPress
 533         //Ancestor's function can invoke handleF10KeyPress here
 534         handleKeyPress(xkey);
 535     }
 536 
 537 } //class XMenuBarPeer