blob: 8a722b133555edc8e438b58fdce0ec34f8c03895 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2002-2006 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any 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 java.util.logging.*;
36
37public class XMenuWindow extends XBaseMenuWindow {
38
39 /************************************************
40 *
41 * Data members
42 *
43 ************************************************/
44
45 private static Logger log = Logger.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
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 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 = (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 */
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 */
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 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(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 public void paint(Graphics g) {
436 resetColors();
437
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}