1 /* 2 * Copyright (c) 2002, 2014, 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 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 Dimension screenSize = graphicsConfig.getBounds().getSize(); 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<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 }