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.awt.image.BufferedImage;
  32 import java.awt.geom.Point2D;
  33 
  34 import java.util.Vector;
  35 import sun.util.logging.PlatformLogger;
  36 
  37 public class XMenuWindow extends XBaseMenuWindow {
  38 
  39     /************************************************
  40      *
  41      * Data members
  42      *
  43      ************************************************/
  44 
  45     private static PlatformLogger log = PlatformLogger.getLogger("sun.awt.X11.XMenuWindow");
  46 
  47     /*
  48      * Primary members
  49      */
  50     private XMenuPeer menuPeer;
  51 
  52     /*
  53      * dimension constants
  54      */
  55     private static final int WINDOW_SPACING_LEFT = 2;
  56     private static final int WINDOW_SPACING_RIGHT = 2;
  57     private static final int WINDOW_SPACING_TOP = 2;
  58     private static final int WINDOW_SPACING_BOTTOM = 2;
  59     private static final int WINDOW_ITEM_INDENT = 15;
  60     private static final int WINDOW_ITEM_MARGIN_LEFT = 2;
  61     private static final int WINDOW_ITEM_MARGIN_RIGHT = 2;
  62     private static final int WINDOW_ITEM_MARGIN_TOP = 2;
  63     private static final int WINDOW_ITEM_MARGIN_BOTTOM = 2;
  64     private static final int WINDOW_SHORTCUT_SPACING = 10;
  65 
  66     /*
  67      * Checkmark
  68      */
  69     private static final int CHECKMARK_SIZE = 128;
  70     private static final int[] CHECKMARK_X = new int[] {1, 25,56,124,124,85, 64};  // X-coords
  71     private static final int[] CHECKMARK_Y = new int[] {59,35,67,  0, 12,66,123};  // Y-coords
  72 
  73     /************************************************
  74      *
  75      * Mapping data
  76      *
  77      ************************************************/
  78 
  79     static class MappingData extends XBaseMenuWindow.MappingData {
  80         /**
  81          * Rectangle for the caption
  82          * Necessary to fix 6267144: PIT: Popup menu label is not shown, XToolkit
  83          */
  84         private Rectangle captionRect;
  85 
  86         /**
  87          * Desired size of menu window
  88          */
  89         private Dimension desiredSize;
  90 
  91         /**
  92          * Width of largest checkmark
  93          * At the same time the left origin
  94          * of all item's text
  95          */
  96         private int leftMarkWidth;
  97 
  98         /**
  99          * Left origin of all shortcut labels
 100          */
 101         private int shortcutOrigin;
 102 
 103         /**
 104          * The origin of right mark
 105          * (submenu's arrow)
 106          */
 107         private int rightMarkOrigin;
 108 
 109         MappingData(XMenuItemPeer[] items, Rectangle captionRect, Dimension desiredSize, int leftMarkWidth, int shortcutOrigin, int rightMarkOrigin) {
 110             super(items);
 111             this.captionRect = captionRect;
 112             this.desiredSize = desiredSize;
 113             this.leftMarkWidth = leftMarkWidth;
 114             this.shortcutOrigin = shortcutOrigin;
 115             this.rightMarkOrigin = rightMarkOrigin;
 116         }
 117 
 118         /**
 119          * Constructs MappingData without items
 120          * This constructor should be used in case of errors
 121          */
 122         MappingData() {
 123             this.desiredSize = new Dimension(0, 0);
 124             this.leftMarkWidth = 0;
 125             this.shortcutOrigin = 0;
 126             this.rightMarkOrigin = 0;
 127         }
 128 
 129         public Rectangle getCaptionRect() {
 130             return this.captionRect;
 131         }
 132 
 133         public Dimension getDesiredSize() {
 134             return this.desiredSize;
 135         }
 136 
 137         public int getShortcutOrigin() {
 138             return this.shortcutOrigin;
 139         }
 140 
 141         public int getLeftMarkWidth() {
 142             return this.leftMarkWidth;
 143         }
 144 
 145         public int getRightMarkOrigin() {
 146             return this.rightMarkOrigin;
 147         }
 148 
 149     }
 150 
 151 
 152     /************************************************
 153      *
 154      * Construction
 155      *
 156      ************************************************/
 157 
 158     /**
 159      * Constructs XMenuWindow for specified XMenuPeer
 160      * null for XPopupMenuWindow
 161      */
 162     XMenuWindow(XMenuPeer menuPeer) {
 163         if (menuPeer != null) {
 164             this.menuPeer = menuPeer;
 165             this.target = menuPeer.getContainer().target;
 166             // Get menus from the target.
 167             Vector<MenuItem> targetItemVector = null;
 168             targetItemVector = getMenuTargetItems();
 169             reloadItems(targetItemVector);
 170         }
 171     }
 172 
 173     /************************************************
 174      *
 175      * Initialization
 176      *
 177      ************************************************/
 178     /*
 179      * Overriden initialization
 180      */
 181     void postInit(XCreateWindowParams params) {
 182         super.postInit(params);
 183         //Fixed 6267182: PIT: Menu is not visible after
 184         //showing and disposing a file dialog, XToolkit
 185         //toFront() is called on every show
 186     }
 187 
 188     /************************************************
 189      *
 190      * Implementation of abstract methods
 191      *
 192      ************************************************/
 193 
 194     /**
 195      * @see XBaseMenuWindow#getParentMenuWindow()
 196      */
 197     protected XBaseMenuWindow getParentMenuWindow() {
 198         return (menuPeer != null) ? menuPeer.getContainer() : null;
 199     }
 200 
 201     /**
 202      * @see XBaseMenuWindow#map()
 203      */
 204     protected MappingData map() {
 205         //TODO:Implement popup-menu caption mapping and painting and tear-off
 206         int itemCnt;
 207         if (!isCreated()) {
 208             MappingData mappingData = new MappingData(new XMenuItemPeer[0], new Rectangle(0, 0, 0, 0), new Dimension(0, 0), 0, 0, 0);
 209             return mappingData;
 210         }
 211         XMenuItemPeer[] itemVector = copyItems();
 212         itemCnt = itemVector.length;
 213         //We need maximum width of components before calculating item's bounds
 214         Dimension captionSize = getCaptionSize();
 215         int maxWidth = (captionSize != null) ? captionSize.width : 0;
 216         int maxLeftIndent = 0;
 217         int maxRightIndent = 0;
 218         int maxShortcutWidth = 0;
 219         XMenuItemPeer.TextMetrics[] itemMetrics = new XMenuItemPeer.TextMetrics[itemCnt];
 220         for (int i = 0; i < itemCnt; i++) {
 221             XMenuItemPeer item = itemVector[i];
 222             itemMetrics[i] = itemVector[i].getTextMetrics();
 223             Dimension dim = itemMetrics[i].getTextDimension();
 224             if (dim != null) {
 225                 if (itemVector[i] instanceof XCheckboxMenuItemPeer) {
 226                     maxLeftIndent = Math.max(maxLeftIndent, dim.height);
 227                 } else if (itemVector[i] instanceof XMenuPeer) {
 228                     maxRightIndent = Math.max(maxRightIndent, dim.height);
 229                 }
 230                 maxWidth = Math.max(maxWidth, dim.width);
 231                 maxShortcutWidth = Math.max(maxShortcutWidth, itemMetrics[i].getShortcutWidth());
 232             }
 233         }
 234         //Calculate bounds
 235         int nextOffset = WINDOW_SPACING_TOP;
 236         int shortcutOrigin = WINDOW_SPACING_LEFT + WINDOW_ITEM_MARGIN_LEFT + maxLeftIndent + maxWidth;
 237         if (maxShortcutWidth > 0) {
 238             shortcutOrigin = shortcutOrigin + WINDOW_SHORTCUT_SPACING;
 239         }
 240         int rightMarkOrigin = shortcutOrigin + maxShortcutWidth;
 241         int itemWidth = rightMarkOrigin + maxRightIndent + WINDOW_ITEM_MARGIN_RIGHT;
 242         int width = WINDOW_SPACING_LEFT + itemWidth + WINDOW_SPACING_RIGHT;
 243         //Caption rectangle
 244         Rectangle captionRect = null;
 245         if (captionSize != null) {
 246             captionRect = new Rectangle(WINDOW_SPACING_LEFT, nextOffset, itemWidth, captionSize.height);
 247             nextOffset += captionSize.height;
 248         } else {
 249             captionRect = new Rectangle(WINDOW_SPACING_LEFT, nextOffset, maxWidth, 0);
 250         }
 251         //Item rectangles
 252         for (int i = 0; i < itemCnt; i++) {
 253             XMenuItemPeer item = itemVector[i];
 254             XMenuItemPeer.TextMetrics metrics = itemMetrics[i];
 255             Dimension dim = metrics.getTextDimension();
 256             if (dim != null) {
 257                 int itemHeight = WINDOW_ITEM_MARGIN_TOP + dim.height + WINDOW_ITEM_MARGIN_BOTTOM;
 258                 Rectangle bounds = new Rectangle(WINDOW_SPACING_LEFT, nextOffset, itemWidth, itemHeight);
 259                 int y = (itemHeight + dim.height) / 2  - metrics.getTextBaseline();
 260                 Point textOrigin = new Point(WINDOW_SPACING_LEFT + WINDOW_ITEM_MARGIN_LEFT + maxLeftIndent, nextOffset + y);
 261                 nextOffset += itemHeight;
 262                 item.map(bounds, textOrigin);
 263             } else {
 264                 //Text metrics could not be determined because of errors
 265                 //Map item with empty rectangle
 266                 Rectangle bounds = new Rectangle(WINDOW_SPACING_LEFT, nextOffset, 0, 0);
 267                 Point textOrigin = new Point(WINDOW_SPACING_LEFT + WINDOW_ITEM_MARGIN_LEFT + maxLeftIndent, nextOffset);
 268                 item.map(bounds, textOrigin);
 269             }
 270         }
 271         int height = nextOffset + WINDOW_SPACING_BOTTOM;
 272         MappingData mappingData = new MappingData(itemVector, captionRect, new Dimension(width, height), maxLeftIndent, shortcutOrigin, rightMarkOrigin);
 273         return mappingData;
 274     }
 275 
 276     /**
 277      * @see XBaseMenuWindow#getSubmenuBounds
 278      */
 279     protected Rectangle getSubmenuBounds(Rectangle itemBounds, Dimension windowSize) {
 280         Rectangle globalBounds = toGlobal(itemBounds);
 281         Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
 282         Rectangle res;
 283         res = fitWindowRight(globalBounds, windowSize, screenSize);
 284         if (res != null) {
 285             return res;
 286         }
 287         res = fitWindowBelow(globalBounds, windowSize, screenSize);
 288         if (res != null) {
 289             return res;
 290         }
 291         res = fitWindowAbove(globalBounds, windowSize, screenSize);
 292         if (res != null) {
 293             return res;
 294         }
 295         res = fitWindowLeft(globalBounds, windowSize, screenSize);
 296         if (res != null) {
 297             return res;
 298         }
 299         return fitWindowToScreen(windowSize, screenSize);
 300    }
 301 
 302     /**
 303      * It's likely that size of items was changed
 304      * invoke resizing of window on eventHandlerThread
 305      */
 306     protected void updateSize() {
 307         resetMapping();
 308         if (isShowing()) {
 309             XToolkit.executeOnEventHandlerThread(target, new Runnable() {
 310                     public void run() {
 311                         Dimension dim = getDesiredSize();
 312                         reshape(x, y, dim.width, dim.height);
 313                     }
 314                 });
 315         }
 316     }
 317 
 318     /************************************************
 319      *
 320      * Overridable caption-painting functions
 321      * Necessary to fix 6267144: PIT: Popup menu label is not shown, XToolkit
 322      *
 323      ************************************************/
 324 
 325     /**
 326      * Returns size of menu window's caption or null
 327      * if window has no caption.
 328      * Can be overriden for popup menus and tear-off menus
 329      */
 330     protected Dimension getCaptionSize() {
 331         return null;
 332     }
 333 
 334     /**
 335      * Paints menu window's caption.
 336      * Can be overriden for popup menus and tear-off menus.
 337      * Default implementation does nothing
 338      */
 339     protected void paintCaption(Graphics g, Rectangle rect) {
 340     }
 341 
 342     /************************************************
 343      *
 344      * General-purpose utility functions
 345      *
 346      ************************************************/
 347 
 348     /**
 349      * Returns corresponding menu peer
 350      */
 351     XMenuPeer getMenuPeer() {
 352         return menuPeer;
 353     }
 354 
 355     /**
 356      * Reads vector of items from target
 357      * This function is overriden in XPopupMenuPeer
 358      */
 359     Vector<MenuItem> getMenuTargetItems() {
 360         return menuPeer.getTargetItems();
 361     }
 362 
 363     /**
 364      * Returns desired size calculated while mapping
 365      */
 366     Dimension getDesiredSize() {
 367         MappingData mappingData = (MappingData)getMappingData();
 368         return mappingData.getDesiredSize();
 369     }
 370 
 371     /**
 372      * Checks if menu window is created
 373      */
 374     boolean isCreated() {
 375         return getWindow() != 0;
 376     }
 377 
 378     /**
 379      * Performs delayed creation of menu window if necessary
 380      */
 381     boolean ensureCreated() {
 382         if (!isCreated()) {
 383             XCreateWindowParams params = getDelayedParams();
 384             params.remove(DELAYED);
 385             params.add(OVERRIDE_REDIRECT, Boolean.TRUE);
 386             params.add(XWindow.TARGET, target);
 387             init(params);
 388         }
 389         return true;
 390     }
 391 
 392     /**
 393      * Init window if it's not inited yet
 394      * and show it at specified coordinates
 395      * @param bounds bounding rectangle of window
 396      * in global coordinates
 397      */
 398     void show(Rectangle bounds) {
 399         if (!isCreated()) {
 400             return;
 401         }
 402         if (log.isLoggable(PlatformLogger.Level.FINER)) {
 403             log.finer("showing menu window + " + getWindow() + " at " + bounds);
 404         }
 405         XToolkit.awtLock();
 406         try {
 407             reshape(bounds.x, bounds.y, bounds.width, bounds.height);
 408             xSetVisible(true);
 409             //Fixed 6267182: PIT: Menu is not visible after
 410             //showing and disposing a file dialog, XToolkit
 411             toFront();
 412             selectItem(getFirstSelectableItem(), false);
 413         } finally {
 414             XToolkit.awtUnlock();
 415         }
 416     }
 417 
 418     /**
 419      * Hides menu window
 420      */
 421     void hide() {
 422         selectItem(null, false);
 423         xSetVisible(false);
 424     }
 425 
 426     /************************************************
 427      *
 428      * Painting
 429      *
 430      ************************************************/
 431 
 432     /**
 433      * Paints menu window
 434      */
 435     @Override
 436     public void paintPeer(Graphics g) {
 437         resetColors();
 438         int width = getWidth();
 439         int height = getHeight();
 440 
 441         flush();
 442         //Fill background of rectangle
 443         g.setColor(getBackgroundColor());
 444         g.fillRect(1, 1, width - 2, height - 2);
 445         draw3DRect(g, 0, 0, width, height, true);
 446 
 447         //Mapping data
 448         MappingData mappingData = (MappingData)getMappingData();
 449 
 450         //Paint caption
 451         paintCaption(g, mappingData.getCaptionRect());
 452 
 453         //Paint menus
 454         XMenuItemPeer[] itemVector = mappingData.getItems();
 455         Dimension windowSize =  mappingData.getDesiredSize();
 456         XMenuItemPeer selectedItem = getSelectedItem();
 457         for (int i = 0; i < itemVector.length; i++) {
 458             XMenuItemPeer item = itemVector[i];
 459             XMenuItemPeer.TextMetrics metrics = item.getTextMetrics();
 460             Rectangle bounds = item.getBounds();
 461             if (item.isSeparator()) {
 462                 draw3DRect(g, bounds.x, bounds.y + bounds.height / 2,  bounds.width, 2, false);
 463             } else {
 464                 //paint item
 465                 g.setFont(item.getTargetFont());
 466                 Point textOrigin = item.getTextOrigin();
 467                 Dimension textDim = metrics.getTextDimension();
 468                 if (item == selectedItem) {
 469                     g.setColor(getSelectedColor());
 470                     g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
 471                     draw3DRect(g, bounds.x, bounds.y, bounds.width, bounds.height, false);
 472                 }
 473                 g.setColor(item.isTargetItemEnabled() ? getForegroundColor() : getDisabledColor());
 474                 g.drawString(item.getTargetLabel(), textOrigin.x, textOrigin.y);
 475                 String shortcutText = item.getShortcutText();
 476                 if (shortcutText != null) {
 477                     g.drawString(shortcutText, mappingData.getShortcutOrigin(), textOrigin.y);
 478                 }
 479                 if (item instanceof XMenuPeer) {
 480                     //calculate arrow coordinates
 481                     int markWidth = textDim.height * 4 / 5;
 482                     int markHeight = textDim.height * 4 / 5;
 483                     int markX = bounds.x + bounds.width - markWidth - WINDOW_SPACING_RIGHT - WINDOW_ITEM_MARGIN_RIGHT;
 484                     int markY = bounds.y + (bounds.height - markHeight) / 2;
 485                     //draw arrow
 486                     g.setColor(item.isTargetItemEnabled() ? getDarkShadowColor() : getDisabledColor());
 487                     g.drawLine(markX, markY + markHeight, markX + markWidth, markY + markHeight / 2);
 488                     g.setColor(item.isTargetItemEnabled() ? getLightShadowColor() : getDisabledColor());
 489                     g.drawLine(markX, markY, markX + markWidth, markY + markHeight / 2);
 490                     g.drawLine(markX, markY, markX, markY + markHeight);
 491                 } else if (item instanceof XCheckboxMenuItemPeer) {
 492                     //calculate checkmark coordinates
 493                     int markWidth = textDim.height * 4 / 5;
 494                     int markHeight = textDim.height * 4 / 5;
 495                     int markX = WINDOW_SPACING_LEFT + WINDOW_ITEM_MARGIN_LEFT;
 496                     int markY = bounds.y + (bounds.height - markHeight) / 2;
 497                     boolean checkState = ((XCheckboxMenuItemPeer)item).getTargetState();
 498                     //draw checkmark
 499                     if (checkState) {
 500                         g.setColor(getSelectedColor());
 501                         g.fillRect(markX, markY, markWidth, markHeight);
 502                         draw3DRect(g, markX, markY, markWidth, markHeight, false);
 503                         int[] px = new int[CHECKMARK_X.length];
 504                         int[] py = new int[CHECKMARK_X.length];
 505                         for (int j = 0; j < CHECKMARK_X.length; j++) {
 506                             px[j] = markX + CHECKMARK_X[j] * markWidth / CHECKMARK_SIZE;
 507                             py[j] = markY + CHECKMARK_Y[j] * markHeight / CHECKMARK_SIZE;
 508                         }
 509                         g.setColor(item.isTargetItemEnabled() ? getForegroundColor() : getDisabledColor());
 510                         g.fillPolygon(px, py, CHECKMARK_X.length);
 511                     } else {
 512                         g.setColor(getBackgroundColor());
 513                         g.fillRect(markX, markY, markWidth, markHeight);
 514                         draw3DRect(g, markX, markY, markWidth, markHeight, true);
 515                     }
 516                 }
 517             }
 518         }
 519         flush();
 520     }
 521 
 522 }