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