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