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 sun.awt.AWTAccessor;
  32 
  33 public class XMenuItemPeer implements MenuItemPeer {
  34 
  35     /************************************************
  36      *
  37      * Data members
  38      *
  39      ************************************************/
  40 
  41     /*
  42      * Primary members
  43      */
  44 
  45     /**
  46      * Window that this item belongs to.
  47      */
  48     private XBaseMenuWindow container;
  49 
  50     /**
  51      * Target MenuItem. Note that 'target' member
  52      * in XWindow is required for dispatching events.
  53      * This member is only used for accessing its fields
  54      * and firing ActionEvent & ItemEvent
  55      */
  56     private MenuItem target;
  57 
  58     /*
  59      * Mapping to window
  60      */
  61 
  62     /**
  63      * Rectangle occupied by menu item in container's
  64      * coordinates. Filled by map(...) function from
  65      * XBaseMenuWindow.map()
  66      */
  67     private Rectangle bounds;
  68 
  69     /**
  70      * Point in container's coordinate system used as
  71      * origin by drawText.
  72      */
  73     private Point textOrigin;
  74 
  75     /*
  76      * Size constants
  77      */
  78     private static final int SEPARATOR_WIDTH = 20;
  79     private static final int SEPARATOR_HEIGHT = 5;
  80 
  81     /************************************************
  82      *
  83      * Text Metrics
  84      *
  85      ************************************************/
  86 
  87     /**
  88      * Text metrics are filled in calcTextMetrics function
  89      * and reset in resetTextMetrics function. Text metrics
  90      * contain calculated dimensions of various components of
  91      * menu item.
  92      */
  93     private TextMetrics textMetrics;
  94 
  95     static class TextMetrics implements Cloneable {
  96         /*
  97          * Calculated text size members
  98          */
  99         private Dimension textDimension;
 100         private int shortcutWidth;
 101         private int textBaseline;
 102 
 103         TextMetrics(Dimension textDimension, int shortcutWidth, int textBaseline) {
 104             this.textDimension = textDimension;
 105             this.shortcutWidth = shortcutWidth;
 106             this.textBaseline = textBaseline;
 107         }
 108 
 109         public Object clone() {
 110             try {
 111                 return super.clone();
 112             } catch (CloneNotSupportedException ex) {
 113                 throw new InternalError(ex);
 114             }
 115         }
 116 
 117         Dimension getTextDimension() {
 118             return this.textDimension;
 119         }
 120 
 121         int getShortcutWidth() {
 122             return this.shortcutWidth;
 123         }
 124 
 125         int getTextBaseline() {
 126             return this.textBaseline;
 127         }
 128     }
 129 
 130     /************************************************
 131      *
 132      * Construction
 133      *
 134      ************************************************/
 135     XMenuItemPeer(MenuItem target) {
 136         this.target = target;
 137     }
 138 
 139     /************************************************
 140      *
 141      * Implementaion of interface methods
 142      *
 143      ************************************************/
 144 
 145     /*
 146      * From MenuComponentPeer
 147      */
 148     public void dispose() {
 149         //Empty function
 150     }
 151 
 152     public void setFont(Font font) {
 153         resetTextMetrics();
 154         repaintIfShowing();
 155     }
 156     /*
 157      * From MenuItemPeer
 158      */
 159     public void setLabel(String label) {
 160         resetTextMetrics();
 161         repaintIfShowing();
 162     }
 163 
 164     public void setEnabled(boolean enabled) {
 165         repaintIfShowing();
 166     }
 167 
 168     /************************************************
 169      *
 170      * Access to target's fields
 171      *
 172      ************************************************/
 173 
 174     MenuItem getTarget() {
 175         return this.target;
 176     }
 177 
 178     Font getTargetFont() {
 179         if (target == null) {
 180             return XWindow.getDefaultFont();
 181         }
 182         return AWTAccessor.getMenuComponentAccessor().getFont_NoClientCode(target);
 183     }
 184 
 185     String getTargetLabel() {
 186         if (target == null) {
 187             return "";
 188         }
 189         String label = AWTAccessor.getMenuItemAccessor().getLabel(target);
 190         return (label == null) ? "" : label;
 191     }
 192 
 193     boolean isTargetEnabled() {
 194         if (target == null) {
 195             return false;
 196         }
 197         return AWTAccessor.getMenuItemAccessor().isEnabled(target);
 198     }
 199 
 200     /**
 201      * Returns true if item and all its parents are enabled
 202      * This function is used to fix
 203      * 6184485: Popup menu is not disabled on XToolkit even when calling setEnabled (false)
 204      */
 205     boolean isTargetItemEnabled() {
 206         if (target == null) {
 207             return false;
 208         }
 209         return AWTAccessor.getMenuItemAccessor().isItemEnabled(target);
 210     }
 211 
 212     String getTargetActionCommand() {
 213         if (target == null) {
 214             return "";
 215         }
 216         return AWTAccessor.getMenuItemAccessor().getActionCommandImpl(target);
 217     }
 218 
 219     MenuShortcut getTargetShortcut() {
 220         if (target == null) {
 221             return null;
 222         }
 223         return AWTAccessor.getMenuItemAccessor().getShortcut(target);
 224     }
 225 
 226     String getShortcutText() {
 227         //Fix for 6180413: shortcuts should not be displayed for any of the menuitems in a popup menu
 228         if (container == null) {
 229             return null;
 230         }
 231         if (container.getRootMenuWindow() instanceof XPopupMenuPeer) {
 232             return null;
 233         }
 234         MenuShortcut sc = getTargetShortcut();
 235         //TODO:This can potentially call user code
 236         return (sc == null) ? null : sc.toString();
 237     }
 238 
 239 
 240     /************************************************
 241      *
 242      * Basic manipulations
 243      *
 244      ************************************************/
 245 
 246     /**
 247      * This function is called when filling item vectors
 248      * in XMenuWindow & XMenuBar. We need it because peers
 249      * are created earlier than windows.
 250      * @param container the window that this item belongs to.
 251      */
 252     void setContainer(XBaseMenuWindow container) {
 253         synchronized(XBaseMenuWindow.getMenuTreeLock()) {
 254             this.container = container;
 255         }
 256     }
 257 
 258     /**
 259      * returns the window that this item belongs to
 260      */
 261     XBaseMenuWindow getContainer() {
 262         return this.container;
 263     }
 264 
 265     /************************************************
 266      *
 267      * Overridable behaviour
 268      *
 269      ************************************************/
 270 
 271     /**
 272      * This function should be overriden simply to
 273      * return false in inherited classes.
 274      */
 275     boolean isSeparator() {
 276         boolean r = (getTargetLabel().equals("-"));
 277         return r;
 278     }
 279 
 280     /************************************************
 281      *
 282      * Utility functions
 283      *
 284      ************************************************/
 285 
 286     /**
 287      * Returns true if container exists and is showing
 288      */
 289     boolean isContainerShowing() {
 290         if (container == null) {
 291             return false;
 292         }
 293         return container.isShowing();
 294     }
 295 
 296     /**
 297      * Repaints item if it is showing
 298      */
 299     void repaintIfShowing() {
 300         if (isContainerShowing()) {
 301             container.postPaintEvent();
 302         }
 303     }
 304 
 305     /**
 306      * This function is invoked when the user clicks
 307      * on menu item.
 308      * @param when the timestamp of action event
 309      */
 310     void action(long when, int modifiers) {
 311         if (!isSeparator() && isTargetItemEnabled()) {
 312             XWindow.postEventStatic(new ActionEvent(target, ActionEvent.ACTION_PERFORMED,
 313                                                     getTargetActionCommand(), when,
 314                                                     modifiers));
 315         }
 316     }
 317     /************************************************
 318      *
 319      * Text metrics
 320      *
 321      ************************************************/
 322 
 323     /**
 324      * Returns text metrics of menu item.
 325      * This function does not use any locks
 326      * and is guaranteed to return some value
 327      * (possibly actual, possibly expired)
 328      */
 329     TextMetrics getTextMetrics() {
 330         TextMetrics textMetrics = this.textMetrics;
 331         if (textMetrics == null) {
 332             textMetrics = calcTextMetrics();
 333             this.textMetrics = textMetrics;
 334         }
 335         return textMetrics;
 336     }
 337 
 338     /**
 339      * Returns dimensions of item's label.
 340      * This function does not use any locks
 341      * Returns actual or expired  value
 342      * or null if error occurs
 343      */
 344     /*Dimension getTextDimension() {
 345         TextMetrics textMetrics = this.textMetrics;
 346         if (textMetrics == null) {
 347             textMetrics = calcTextMetrics();
 348             this.textMetrics = textMetrics;
 349         }
 350         return (textMetrics != null) ? textMetrics.textDimension : null;
 351         }*/
 352 
 353     /**
 354      * Returns width of item's shortcut label,
 355      * 0 if item has no shortcut.
 356      * The height of shortcut can be deternimed
 357      * from text dimensions.
 358      * This function does not use any locks
 359      * and is guaranteed to return some value
 360      * (possibly actual, possibly expired)
 361      */
 362     /*int getShortcutWidth() {
 363         TextMetrics textMetrics = this.textMetrics;
 364         if (textMetrics == null) {
 365             textMetrics = calcTextMetrics();
 366             this.textMetrics = textMetrics;
 367         }
 368         return (textMetrics != null) ? textMetrics.shortcutWidth : 0;
 369     }
 370 
 371     int getTextBaseline() {
 372         TextMetrics textMetrics = this.textMetrics;
 373         if (textMetrics == null) {
 374             textMetrics = calcTextMetrics();
 375             this.textMetrics = textMetrics;
 376         }
 377         return (textMetrics != null) ? textMetrics.textBaseline : 0;
 378         }*/
 379 
 380     TextMetrics calcTextMetrics() {
 381         if (container == null) {
 382             return null;
 383         }
 384         if (isSeparator()) {
 385             return new TextMetrics(new Dimension(SEPARATOR_WIDTH, SEPARATOR_HEIGHT), 0, 0);
 386         }
 387         Graphics g = container.getGraphics();
 388         if (g == null) {
 389             return null;
 390         }
 391         try {
 392             g.setFont(getTargetFont());
 393             FontMetrics fm = g.getFontMetrics();
 394             String str = getTargetLabel();
 395             int width = fm.stringWidth(str);
 396             int height = fm.getHeight();
 397             Dimension textDimension = new Dimension(width, height);
 398             int textBaseline = fm.getHeight() - fm.getAscent();
 399             String sc = getShortcutText();
 400             int shortcutWidth = (sc == null) ? 0 : fm.stringWidth(sc);
 401             return new TextMetrics(textDimension, shortcutWidth, textBaseline);
 402         } finally {
 403             g.dispose();
 404         }
 405     }
 406 
 407     void resetTextMetrics() {
 408         textMetrics = null;
 409         if (container != null) {
 410             container.updateSize();
 411         }
 412     }
 413 
 414     /************************************************
 415      *
 416      * Mapping utility functions
 417      *
 418      ************************************************/
 419 
 420     /**
 421      * Sets mapping of item to window.
 422      * @param bounds bounds of item in container's coordinates
 423      * @param textOrigin point for drawString in container's coordinates
 424      * @see XBaseMenuWindow#map()
 425      */
 426     void map(Rectangle bounds, Point textOrigin) {
 427         this.bounds = bounds;
 428         this.textOrigin = textOrigin;
 429     }
 430 
 431     /**
 432      * returns bounds of item that were previously set by map() function
 433      */
 434     Rectangle getBounds() {
 435         return bounds;
 436     }
 437 
 438     /**
 439      * returns origin of item's text that was previously set by map() function
 440      */
 441     Point getTextOrigin() {
 442         return textOrigin;
 443     }
 444 
 445 }