1 /*
2  * Copyright (c) 2002, 2013, 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 final static int WINDOW_SPACING_LEFT = 2;
56     private final static int WINDOW_SPACING_RIGHT = 2;
57     private final static int WINDOW_SPACING_TOP = 2;
58     private final static int WINDOW_SPACING_BOTTOM = 2;
59     private final static int WINDOW_ITEM_INDENT = 15;
60     private final static int WINDOW_ITEM_MARGIN_LEFT = 2;
61     private final static int WINDOW_ITEM_MARGIN_RIGHT = 2;
62     private final static int WINDOW_ITEM_MARGIN_TOP = 2;
63     private final static int WINDOW_ITEM_MARGIN_BOTTOM = 2;
64     private final static 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 
MappingData(XMenuItemPeer[] items, Rectangle captionRect, Dimension desiredSize, int leftMarkWidth, int shortcutOrigin, int rightMarkOrigin)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          */
MappingData()122         MappingData() {
123             this.desiredSize = new Dimension(0, 0);
124             this.leftMarkWidth = 0;
125             this.shortcutOrigin = 0;
126             this.rightMarkOrigin = 0;
127         }
128 
getCaptionRect()129         public Rectangle getCaptionRect() {
130             return this.captionRect;
131         }
132 
getDesiredSize()133         public Dimension getDesiredSize() {
134             return this.desiredSize;
135         }
136 
getShortcutOrigin()137         public int getShortcutOrigin() {
138             return this.shortcutOrigin;
139         }
140 
getLeftMarkWidth()141         public int getLeftMarkWidth() {
142             return this.leftMarkWidth;
143         }
144 
getRightMarkOrigin()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      */
XMenuWindow(XMenuPeer menuPeer)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 targetItemVector = null;
168             targetItemVector = getMenuTargetItems();
169             reloadItems(targetItemVector);
170         }
171     }
172 
173     /************************************************
174      *
175      * Initialization
176      *
177      ************************************************/
178     /*
179      * Overriden initialization
180      */
postInit(XCreateWindowParams params)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      */
getParentMenuWindow()197     protected XBaseMenuWindow getParentMenuWindow() {
198         return (menuPeer != null) ? menuPeer.getContainer() : null;
199     }
200 
201     /**
202      * @see XBaseMenuWindow.map()
203      */
map()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 = (XMenuItemPeer)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      */
getSubmenuBounds(Rectangle itemBounds, Dimension windowSize)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      */
updateSize()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      */
getCaptionSize()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      */
paintCaption(Graphics g, Rectangle rect)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      */
getMenuPeer()351     XMenuPeer getMenuPeer() {
352         return menuPeer;
353     }
354 
355     /**
356      * Reads vector of items from target
357      * This function is overriden in XPopupMenuPeer
358      */
getMenuTargetItems()359     Vector getMenuTargetItems() {
360         return menuPeer.getTargetItems();
361     }
362 
363     /**
364      * Returns desired size calculated while mapping
365      */
getDesiredSize()366     Dimension getDesiredSize() {
367         MappingData mappingData = (MappingData)getMappingData();
368         return mappingData.getDesiredSize();
369     }
370 
371     /**
372      * Checks if menu window is created
373      */
isCreated()374     boolean isCreated() {
375         return getWindow() != 0;
376     }
377 
378     /**
379      * Performs delayed creation of menu window if necessary
380      */
ensureCreated()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      */
show(Rectangle bounds)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      */
hide()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
paintPeer(Graphics g)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 }
523