1/*
2 * Copyright (c) 2002, 2017, 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 */
25package sun.awt.X11;
26
27import java.awt.*;
28import java.awt.peer.*;
29import java.awt.event.*;
30
31import java.awt.image.BufferedImage;
32import java.awt.geom.Point2D;
33
34import java.util.Vector;
35import sun.util.logging.PlatformLogger;
36
37public 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        Rectangle screenBounds = getCurrentGraphicsConfiguration().getBounds();
282        Rectangle res;
283        res = fitWindowRight(globalBounds, windowSize, screenBounds);
284        if (res != null) {
285            return res;
286        }
287        res = fitWindowBelow(globalBounds, windowSize, screenBounds);
288        if (res != null) {
289            return res;
290        }
291        res = fitWindowAbove(globalBounds, windowSize, screenBounds);
292        if (res != null) {
293            return res;
294        }
295        res = fitWindowLeft(globalBounds, windowSize, screenBounds);
296        if (res != null) {
297            return res;
298        }
299        return fitWindowToScreen(windowSize, screenBounds);
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}
523