001: /*
002: * Copyright 2002-2006 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025: package sun.awt.X11;
026:
027: import java.awt.*;
028: import java.awt.peer.*;
029: import java.awt.event.*;
030:
031: import java.awt.image.BufferedImage;
032: import java.awt.geom.Point2D;
033:
034: import java.util.Vector;
035: import java.util.logging.*;
036:
037: public class XMenuWindow extends XBaseMenuWindow {
038:
039: /************************************************
040: *
041: * Data members
042: *
043: ************************************************/
044:
045: private static Logger log = Logger
046: .getLogger("sun.awt.X11.XMenuWindow");
047:
048: /*
049: * Primary members
050: */
051: private XMenuPeer menuPeer;
052:
053: /*
054: * dimension constants
055: */
056: private final static int WINDOW_SPACING_LEFT = 2;
057: private final static int WINDOW_SPACING_RIGHT = 2;
058: private final static int WINDOW_SPACING_TOP = 2;
059: private final static int WINDOW_SPACING_BOTTOM = 2;
060: private final static int WINDOW_ITEM_INDENT = 15;
061: private final static int WINDOW_ITEM_MARGIN_LEFT = 2;
062: private final static int WINDOW_ITEM_MARGIN_RIGHT = 2;
063: private final static int WINDOW_ITEM_MARGIN_TOP = 2;
064: private final static int WINDOW_ITEM_MARGIN_BOTTOM = 2;
065: private final static int WINDOW_SHORTCUT_SPACING = 10;
066:
067: /*
068: * Checkmark
069: */
070: private static final int CHECKMARK_SIZE = 128;
071: private static final int[] CHECKMARK_X = new int[] { 1, 25, 56,
072: 124, 124, 85, 64 }; // X-coords
073: private static final int[] CHECKMARK_Y = new int[] { 59, 35, 67, 0,
074: 12, 66, 123 }; // Y-coords
075:
076: /************************************************
077: *
078: * Mapping data
079: *
080: ************************************************/
081:
082: static class MappingData extends XBaseMenuWindow.MappingData {
083: /**
084: * Rectangle for the caption
085: * Necessary to fix 6267144: PIT: Popup menu label is not shown, XToolkit
086: */
087: private Rectangle captionRect;
088:
089: /**
090: * Desired size of menu window
091: */
092: private Dimension desiredSize;
093:
094: /**
095: * Width of largest checkmark
096: * At the same time the left origin
097: * of all item's text
098: */
099: private int leftMarkWidth;
100:
101: /**
102: * Left origin of all shortcut labels
103: */
104: private int shortcutOrigin;
105:
106: /**
107: * The origin of right mark
108: * (submenu's arrow)
109: */
110: private int rightMarkOrigin;
111:
112: MappingData(XMenuItemPeer[] items, Rectangle captionRect,
113: Dimension desiredSize, int leftMarkWidth,
114: int shortcutOrigin, int rightMarkOrigin) {
115: super (items);
116: this .captionRect = captionRect;
117: this .desiredSize = desiredSize;
118: this .leftMarkWidth = leftMarkWidth;
119: this .shortcutOrigin = shortcutOrigin;
120: this .rightMarkOrigin = rightMarkOrigin;
121: }
122:
123: /**
124: * Constructs MappingData without items
125: * This constructor should be used in case of errors
126: */
127: MappingData() {
128: this .desiredSize = new Dimension(0, 0);
129: this .leftMarkWidth = 0;
130: this .shortcutOrigin = 0;
131: this .rightMarkOrigin = 0;
132: }
133:
134: public Rectangle getCaptionRect() {
135: return this .captionRect;
136: }
137:
138: public Dimension getDesiredSize() {
139: return this .desiredSize;
140: }
141:
142: public int getShortcutOrigin() {
143: return this .shortcutOrigin;
144: }
145:
146: public int getLeftMarkWidth() {
147: return this .leftMarkWidth;
148: }
149:
150: public int getRightMarkOrigin() {
151: return this .rightMarkOrigin;
152: }
153:
154: }
155:
156: /************************************************
157: *
158: * Construction
159: *
160: ************************************************/
161:
162: /**
163: * Constructs XMenuWindow for specified XMenuPeer
164: * null for XPopupMenuWindow
165: */
166: XMenuWindow(XMenuPeer menuPeer) {
167: if (menuPeer != null) {
168: this .menuPeer = menuPeer;
169: this .target = menuPeer.getContainer().target;
170: // Get menus from the target.
171: Vector targetItemVector = null;
172: targetItemVector = getMenuTargetItems();
173: reloadItems(targetItemVector);
174: }
175: }
176:
177: /************************************************
178: *
179: * Initialization
180: *
181: ************************************************/
182: /*
183: * Overriden initialization
184: */
185: void postInit(XCreateWindowParams params) {
186: super .postInit(params);
187: //Fixed 6267182: PIT: Menu is not visible after
188: //showing and disposing a file dialog, XToolkit
189: //toFront() is called on every show
190: }
191:
192: /************************************************
193: *
194: * Implementation of abstract methods
195: *
196: ************************************************/
197:
198: /**
199: * @see XBaseMenuWindow.getParentMenuWindow()
200: */
201: protected XBaseMenuWindow getParentMenuWindow() {
202: return (menuPeer != null) ? menuPeer.getContainer() : null;
203: }
204:
205: /**
206: * @see XBaseMenuWindow.map()
207: */
208: protected MappingData map() {
209: //TODO:Implement popup-menu caption mapping and painting and tear-off
210: int itemCnt;
211: if (!isCreated()) {
212: MappingData mappingData = new MappingData(
213: new XMenuItemPeer[0], new Rectangle(0, 0, 0, 0),
214: new Dimension(0, 0), 0, 0, 0);
215: return mappingData;
216: }
217: XMenuItemPeer[] itemVector = copyItems();
218: itemCnt = itemVector.length;
219: //We need maximum width of components before calculating item's bounds
220: Dimension captionSize = getCaptionSize();
221: int maxWidth = (captionSize != null) ? captionSize.width : 0;
222: int maxLeftIndent = 0;
223: int maxRightIndent = 0;
224: int maxShortcutWidth = 0;
225: XMenuItemPeer.TextMetrics[] itemMetrics = new XMenuItemPeer.TextMetrics[itemCnt];
226: for (int i = 0; i < itemCnt; i++) {
227: XMenuItemPeer item = itemVector[i];
228: itemMetrics[i] = itemVector[i].getTextMetrics();
229: Dimension dim = itemMetrics[i].getTextDimension();
230: if (dim != null) {
231: if (itemVector[i] instanceof XCheckboxMenuItemPeer) {
232: maxLeftIndent = Math.max(maxLeftIndent, dim.height);
233: } else if (itemVector[i] instanceof XMenuPeer) {
234: maxRightIndent = Math.max(maxRightIndent,
235: dim.height);
236: }
237: maxWidth = Math.max(maxWidth, dim.width);
238: maxShortcutWidth = Math.max(maxShortcutWidth,
239: itemMetrics[i].getShortcutWidth());
240: }
241: }
242: //Calculate bounds
243: int nextOffset = WINDOW_SPACING_TOP;
244: int shortcutOrigin = WINDOW_SPACING_LEFT
245: + WINDOW_ITEM_MARGIN_LEFT + maxLeftIndent + maxWidth;
246: if (maxShortcutWidth > 0) {
247: shortcutOrigin = shortcutOrigin + WINDOW_SHORTCUT_SPACING;
248: }
249: int rightMarkOrigin = shortcutOrigin + maxShortcutWidth;
250: int itemWidth = rightMarkOrigin + maxRightIndent
251: + WINDOW_ITEM_MARGIN_RIGHT;
252: int width = WINDOW_SPACING_LEFT + itemWidth
253: + WINDOW_SPACING_RIGHT;
254: //Caption rectangle
255: Rectangle captionRect = null;
256: if (captionSize != null) {
257: captionRect = new Rectangle(WINDOW_SPACING_LEFT,
258: nextOffset, itemWidth, captionSize.height);
259: nextOffset += captionSize.height;
260: } else {
261: captionRect = new Rectangle(WINDOW_SPACING_LEFT,
262: nextOffset, maxWidth, 0);
263: }
264: //Item rectangles
265: for (int i = 0; i < itemCnt; i++) {
266: XMenuItemPeer item = (XMenuItemPeer) itemVector[i];
267: XMenuItemPeer.TextMetrics metrics = itemMetrics[i];
268: Dimension dim = metrics.getTextDimension();
269: if (dim != null) {
270: int itemHeight = WINDOW_ITEM_MARGIN_TOP + dim.height
271: + WINDOW_ITEM_MARGIN_BOTTOM;
272: Rectangle bounds = new Rectangle(WINDOW_SPACING_LEFT,
273: nextOffset, itemWidth, itemHeight);
274: int y = (itemHeight + dim.height) / 2
275: - metrics.getTextBaseline();
276: Point textOrigin = new Point(WINDOW_SPACING_LEFT
277: + WINDOW_ITEM_MARGIN_LEFT + maxLeftIndent,
278: nextOffset + y);
279: nextOffset += itemHeight;
280: item.map(bounds, textOrigin);
281: } else {
282: //Text metrics could not be determined because of errors
283: //Map item with empty rectangle
284: Rectangle bounds = new Rectangle(WINDOW_SPACING_LEFT,
285: nextOffset, 0, 0);
286: Point textOrigin = new Point(WINDOW_SPACING_LEFT
287: + WINDOW_ITEM_MARGIN_LEFT + maxLeftIndent,
288: nextOffset);
289: item.map(bounds, textOrigin);
290: }
291: }
292: int height = nextOffset + WINDOW_SPACING_BOTTOM;
293: MappingData mappingData = new MappingData(itemVector,
294: captionRect, new Dimension(width, height),
295: maxLeftIndent, shortcutOrigin, rightMarkOrigin);
296: return mappingData;
297: }
298:
299: /**
300: * @see XBaseMenuWindow.getSubmenuBounds()
301: */
302: protected Rectangle getSubmenuBounds(Rectangle itemBounds,
303: Dimension windowSize) {
304: Rectangle globalBounds = toGlobal(itemBounds);
305: Dimension screenSize = Toolkit.getDefaultToolkit()
306: .getScreenSize();
307: Rectangle res;
308: res = fitWindowRight(globalBounds, windowSize, screenSize);
309: if (res != null) {
310: return res;
311: }
312: res = fitWindowBelow(globalBounds, windowSize, screenSize);
313: if (res != null) {
314: return res;
315: }
316: res = fitWindowAbove(globalBounds, windowSize, screenSize);
317: if (res != null) {
318: return res;
319: }
320: res = fitWindowLeft(globalBounds, windowSize, screenSize);
321: if (res != null) {
322: return res;
323: }
324: return fitWindowToScreen(windowSize, screenSize);
325: }
326:
327: /**
328: * It's likely that size of items was changed
329: * invoke resizing of window on eventHandlerThread
330: */
331: protected void updateSize() {
332: resetMapping();
333: if (isShowing()) {
334: XToolkit.executeOnEventHandlerThread(target,
335: new Runnable() {
336: public void run() {
337: Dimension dim = getDesiredSize();
338: reshape(x, y, dim.width, dim.height);
339: }
340: });
341: }
342: }
343:
344: /************************************************
345: *
346: * Overridable caption-painting functions
347: * Necessary to fix 6267144: PIT: Popup menu label is not shown, XToolkit
348: *
349: ************************************************/
350:
351: /**
352: * Returns size of menu window's caption or null
353: * if window has no caption.
354: * Can be overriden for popup menus and tear-off menus
355: */
356: protected Dimension getCaptionSize() {
357: return null;
358: }
359:
360: /**
361: * Paints menu window's caption.
362: * Can be overriden for popup menus and tear-off menus.
363: * Default implementation does nothing
364: */
365: protected void paintCaption(Graphics g, Rectangle rect) {
366: }
367:
368: /************************************************
369: *
370: * General-purpose utility functions
371: *
372: ************************************************/
373:
374: /**
375: * Returns corresponding menu peer
376: */
377: XMenuPeer getMenuPeer() {
378: return menuPeer;
379: }
380:
381: /**
382: * Reads vector of items from target
383: * This function is overriden in XPopupMenuPeer
384: */
385: Vector getMenuTargetItems() {
386: return menuPeer.getTargetItems();
387: }
388:
389: /**
390: * Returns desired size calculated while mapping
391: */
392: Dimension getDesiredSize() {
393: MappingData mappingData = (MappingData) getMappingData();
394: return mappingData.getDesiredSize();
395: }
396:
397: /**
398: * Checks if menu window is created
399: */
400: boolean isCreated() {
401: return getWindow() != 0;
402: }
403:
404: /**
405: * Performs delayed creation of menu window if necessary
406: */
407: boolean ensureCreated() {
408: if (!isCreated()) {
409: XCreateWindowParams params = getDelayedParams();
410: params.remove(DELAYED);
411: params.add(OVERRIDE_REDIRECT, Boolean.TRUE);
412: params.add(XWindow.TARGET, target);
413: init(params);
414: }
415: return true;
416: }
417:
418: /**
419: * Init window if it's not inited yet
420: * and show it at specified coordinates
421: * @param bounds bounding rectangle of window
422: * in global coordinates
423: */
424: void show(Rectangle bounds) {
425: if (!isCreated()) {
426: return;
427: }
428: if (log.isLoggable(Level.FINER)) {
429: log.finer("showing menu window + " + getWindow() + " at "
430: + bounds);
431: }
432: XToolkit.awtLock();
433: try {
434: reshape(bounds.x, bounds.y, bounds.width, bounds.height);
435: xSetVisible(true);
436: //Fixed 6267182: PIT: Menu is not visible after
437: //showing and disposing a file dialog, XToolkit
438: toFront();
439: selectItem(getFirstSelectableItem(), false);
440: } finally {
441: XToolkit.awtUnlock();
442: }
443: }
444:
445: /**
446: * Hides menu window
447: */
448: void hide() {
449: selectItem(null, false);
450: xSetVisible(false);
451: }
452:
453: /************************************************
454: *
455: * Painting
456: *
457: ************************************************/
458:
459: /**
460: * Paints menu window
461: */
462: public void paint(Graphics g) {
463: resetColors();
464:
465: int width = getWidth();
466: int height = getHeight();
467:
468: flush();
469: //Fill background of rectangle
470: g.setColor(getBackgroundColor());
471: g.fillRect(1, 1, width - 2, height - 2);
472: draw3DRect(g, 0, 0, width, height, true);
473:
474: //Mapping data
475: MappingData mappingData = (MappingData) getMappingData();
476:
477: //Paint caption
478: paintCaption(g, mappingData.getCaptionRect());
479:
480: //Paint menus
481: XMenuItemPeer[] itemVector = mappingData.getItems();
482: Dimension windowSize = mappingData.getDesiredSize();
483: XMenuItemPeer selectedItem = getSelectedItem();
484: for (int i = 0; i < itemVector.length; i++) {
485: XMenuItemPeer item = itemVector[i];
486: XMenuItemPeer.TextMetrics metrics = item.getTextMetrics();
487: Rectangle bounds = item.getBounds();
488: if (item.isSeparator()) {
489: draw3DRect(g, bounds.x, bounds.y + bounds.height / 2,
490: bounds.width, 2, false);
491: } else {
492: //paint item
493: g.setFont(item.getTargetFont());
494: Point textOrigin = item.getTextOrigin();
495: Dimension textDim = metrics.getTextDimension();
496: if (item == selectedItem) {
497: g.setColor(getSelectedColor());
498: g.fillRect(bounds.x, bounds.y, bounds.width,
499: bounds.height);
500: draw3DRect(g, bounds.x, bounds.y, bounds.width,
501: bounds.height, false);
502: }
503: g
504: .setColor(item.isTargetItemEnabled() ? getForegroundColor()
505: : getDisabledColor());
506: g.drawString(item.getTargetLabel(), textOrigin.x,
507: textOrigin.y);
508: String shortcutText = item.getShortcutText();
509: if (shortcutText != null) {
510: g.drawString(shortcutText, mappingData
511: .getShortcutOrigin(), textOrigin.y);
512: }
513: if (item instanceof XMenuPeer) {
514: //calculate arrow coordinates
515: int markWidth = textDim.height * 4 / 5;
516: int markHeight = textDim.height * 4 / 5;
517: int markX = bounds.x + bounds.width - markWidth
518: - WINDOW_SPACING_RIGHT
519: - WINDOW_ITEM_MARGIN_RIGHT;
520: int markY = bounds.y + (bounds.height - markHeight)
521: / 2;
522: //draw arrow
523: g
524: .setColor(item.isTargetItemEnabled() ? getDarkShadowColor()
525: : getDisabledColor());
526: g.drawLine(markX, markY + markHeight, markX
527: + markWidth, markY + markHeight / 2);
528: g
529: .setColor(item.isTargetItemEnabled() ? getLightShadowColor()
530: : getDisabledColor());
531: g.drawLine(markX, markY, markX + markWidth, markY
532: + markHeight / 2);
533: g.drawLine(markX, markY, markX, markY + markHeight);
534: } else if (item instanceof XCheckboxMenuItemPeer) {
535: //calculate checkmark coordinates
536: int markWidth = textDim.height * 4 / 5;
537: int markHeight = textDim.height * 4 / 5;
538: int markX = WINDOW_SPACING_LEFT
539: + WINDOW_ITEM_MARGIN_LEFT;
540: int markY = bounds.y + (bounds.height - markHeight)
541: / 2;
542: boolean checkState = ((XCheckboxMenuItemPeer) item)
543: .getTargetState();
544: //draw checkmark
545: if (checkState) {
546: g.setColor(getSelectedColor());
547: g.fillRect(markX, markY, markWidth, markHeight);
548: draw3DRect(g, markX, markY, markWidth,
549: markHeight, false);
550: int[] px = new int[CHECKMARK_X.length];
551: int[] py = new int[CHECKMARK_X.length];
552: for (int j = 0; j < CHECKMARK_X.length; j++) {
553: px[j] = markX + CHECKMARK_X[j] * markWidth
554: / CHECKMARK_SIZE;
555: py[j] = markY + CHECKMARK_Y[j] * markHeight
556: / CHECKMARK_SIZE;
557: }
558: g
559: .setColor(item.isTargetItemEnabled() ? getForegroundColor()
560: : getDisabledColor());
561: g.fillPolygon(px, py, CHECKMARK_X.length);
562: } else {
563: g.setColor(getBackgroundColor());
564: g.fillRect(markX, markY, markWidth, markHeight);
565: draw3DRect(g, markX, markY, markWidth,
566: markHeight, true);
567: }
568: }
569: }
570: }
571: flush();
572: }
573:
574: }
|