1 /* 2 * Copyright 2002-2009 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 */ 25 package sun.awt.X11; 26 27 import java.awt.*; 28 29 import java.awt.event.ComponentEvent; 30 import java.awt.event.InvocationEvent; 31 import java.awt.event.WindowEvent; 32 33 import java.util.logging.Level; 34 import java.util.logging.Logger; 35 36 import sun.awt.AWTAccessor; 37 import sun.awt.ComponentAccessor; 38 import sun.awt.SunToolkit; 39 40 abstract class XDecoratedPeer extends XWindowPeer { 41 private static final Logger log = Logger.getLogger("sun.awt.X11.XDecoratedPeer"); 42 private static final Logger insLog = Logger.getLogger("sun.awt.X11.insets.XDecoratedPeer"); 43 private static final Logger focusLog = Logger.getLogger("sun.awt.X11.focus.XDecoratedPeer"); 44 private static final Logger iconLog = Logger.getLogger("sun.awt.X11.icon.XDecoratedPeer"); 45 46 XIconWindow iconWindow; 47 48 /** 49 * The dimensions of the window. 50 * 51 * The entity encapsulates information about the bounds and the insets of 52 * the window. The value is initialized in the preInit() method with the 53 * bounds of the window as known on the shared level. Further updates 54 * should be performed with the reportReshape() method only. 55 */ 56 protected WindowDimensions dimensions; 57 58 XContentWindow content; 59 XFocusProxyWindow focusProxy; 60 61 XDecoratedPeer(Window target) { 62 super(target); 63 } 64 65 XDecoratedPeer(XCreateWindowParams params) { 66 super(params); 67 } 68 69 public long getShell() { 70 return window; 71 } 72 73 public long getContentWindow() { 74 return (content == null) ? window : content.getWindow(); 75 } 76 77 void preInit(XCreateWindowParams params) { 78 super.preInit(params); 79 winAttr.initialFocus = true; 80 81 Rectangle bounds = (Rectangle)params.get(BOUNDS); 82 dimensions = new WindowDimensions(bounds, getNativeInsets(), false); 83 params.put(BOUNDS, dimensions.getClientRect()); 84 insLog.log(Level.FINE, "Initial dimensions {0}", new Object[] { dimensions }); 85 86 // Deny default processing of these events on the shell - proxy will take care of 87 // them instead 88 Long eventMask = (Long)params.get(EVENT_MASK); 89 params.add(EVENT_MASK, Long.valueOf(eventMask.longValue() & ~(XConstants.FocusChangeMask | XConstants.KeyPressMask | XConstants.KeyReleaseMask))); 90 } 91 92 void postInit(XCreateWindowParams params) { 93 super.postInit(params); 94 // The lines that follow need to be in a postInit, so they 95 // happen after the X window is created. 96 initResizability(); 97 updateSizeHints(dimensions); 98 99 content = XContentWindow.createContent(this); 100 101 if (warningWindow != null) { 102 warningWindow.toFront(); 103 } 104 focusProxy = createFocusProxy(); 105 } 106 107 void setIconHints(java.util.List<XIconInfo> icons) { 108 if (!XWM.getWM().setNetWMIcon(this, icons)) { 109 if (icons.size() > 0) { 110 if (iconWindow == null) { 111 iconWindow = new XIconWindow(this); 112 } 113 iconWindow.setIconImages(icons); 114 } 115 } 116 } 117 118 public void updateMinimumSize() { 119 super.updateMinimumSize(); 120 updateMinSizeHints(getNativeInsets()); 121 } 122 123 private void updateMinSizeHints(Insets insets) { 124 if (isResizable()) { 125 Dimension minimumSize = getTargetMinimumSize(); 126 if (minimumSize != null) { 127 int minWidth = minimumSize.width - insets.left - insets.right; 128 int minHeight = minimumSize.height - insets.top - insets.bottom; 129 if (minWidth < 0) minWidth = 0; 130 if (minHeight < 0) minHeight = 0; 131 setSizeHints(XUtilConstants.PMinSize | (isLocationByPlatform()?0:(XUtilConstants.PPosition | XUtilConstants.USPosition)), 132 getX(), getY(), minWidth, minHeight); 133 if (isVisible()) { 134 Rectangle bounds = getShellBounds(); 135 int nw = (bounds.width < minWidth) ? minWidth : bounds.width; 136 int nh = (bounds.height < minHeight) ? minHeight : bounds.height; 137 if (nw != bounds.width || nh != bounds.height) { 138 setShellSize(new Rectangle(0, 0, nw, nh)); 139 } 140 } 141 } else { 142 boolean isMinSizeSet = isMinSizeSet(); 143 XWM.removeSizeHints(this, XUtilConstants.PMinSize); 144 /* Some WMs need remap to redecorate the window */ 145 if (isMinSizeSet && isShowing() && XWM.needRemap(this)) { 146 /* 147 * Do the re/mapping at the Xlib level. Since we essentially 148 * work around a WM bug we don't want this hack to be exposed 149 * to Intrinsics (i.e. don't mess with grabs, callbacks etc). 150 */ 151 xSetVisible(false); 152 XToolkit.XSync(); 153 xSetVisible(true); 154 } 155 } 156 } 157 } 158 159 XFocusProxyWindow createFocusProxy() { 160 return new XFocusProxyWindow(this); 161 } 162 163 protected XAtomList getWMProtocols() { 164 XAtomList protocols = super.getWMProtocols(); 165 protocols.add(wm_delete_window); 166 protocols.add(wm_take_focus); 167 return protocols; 168 } 169 170 public Graphics getGraphics() { 171 return getGraphics(content.surfaceData, 172 ComponentAccessor.getForeground(target), 173 ComponentAccessor.getBackground(target), 174 ComponentAccessor.getFont_NoClientCode(target)); 175 } 176 177 public void setTitle(String title) { 178 if (log.isLoggable(Level.FINE)) log.fine("Title is " + title); 179 winAttr.title = title; 180 updateWMName(); 181 } 182 183 protected String getWMName() { 184 if (winAttr.title == null || winAttr.title.trim().equals("")) { 185 return " "; 186 } else { 187 return winAttr.title; 188 } 189 } 190 191 void updateWMName() { 192 super.updateWMName(); 193 String name = getWMName(); 194 XToolkit.awtLock(); 195 try { 196 if (name == null || name.trim().equals("")) { 197 name = "Java"; 198 } 199 XAtom iconNameAtom = XAtom.get(XAtom.XA_WM_ICON_NAME); 200 iconNameAtom.setProperty(getWindow(), name); 201 XAtom netIconNameAtom = XAtom.get("_NET_WM_ICON_NAME"); 202 netIconNameAtom.setPropertyUTF8(getWindow(), name); 203 } finally { 204 XToolkit.awtUnlock(); 205 } 206 } 207 208 // NOTE: This method may be called by privileged threads. 209 // DO NOT INVOKE CLIENT CODE ON THIS THREAD! 210 public void handleIconify() { 211 postEvent(new WindowEvent((Window)target, WindowEvent.WINDOW_ICONIFIED)); 212 } 213 214 // NOTE: This method may be called by privileged threads. 215 // DO NOT INVOKE CLIENT CODE ON THIS THREAD! 216 public void handleDeiconify() { 217 postEvent(new WindowEvent((Window)target, WindowEvent.WINDOW_DEICONIFIED)); 218 } 219 220 public void handleFocusEvent(XEvent xev) { 221 super.handleFocusEvent(xev); 222 XFocusChangeEvent xfe = xev.get_xfocus(); 223 224 // If we somehow received focus events forward it instead to proxy 225 // FIXME: Shouldn't we instead check for inferrior? 226 focusLog.finer("Received focus event on shell: " + xfe); 227 // focusProxy.xRequestFocus(); 228 } 229 230 /*************************************************************************************** 231 * I N S E T S C O D E 232 **************************************************************************************/ 233 234 235 /** 236 * Current native insets of the window on the desktop. 237 * When it's noticed the insets might have been changed (like when we 238 * receive a ReparentNotify), the value must be reset to null by the 239 * setNativeInsets(null) call. 240 * 241 * Synchronization: the awtLock is used. This should have been the state 242 * lock instead, but the retrieveNativeInsets() uses the awtLock, and the 243 * later must be always taken before the state lock. 244 * 245 * The field MUST NOT be used directly. Use get/setNativeInsets() instead. 246 */ 247 private Insets nativeInsets = null; 248 249 /** 250 * Gets the current native insets. 251 * This method may only return null if the fallBackToDefault is false. 252 */ 253 private Insets getNativeInsets(boolean retrieve, boolean fallBackToDefault) { 254 if (isTargetUndecorated() || isEmbedded() || !XWM.isRunning()) 255 { 256 return (Insets)ZERO_INSETS.clone(); 257 } 258 if (getWindow() == XConstants.None) { 259 return getDefaultInsets(); 260 } 261 XToolkit.awtLock(); 262 try { 263 if (nativeInsets == null && retrieve) { 264 retrieveNativeInsets(); 265 } 266 return nativeInsets == null ? 267 (fallBackToDefault ? getDefaultInsets() : null) : 268 (Insets)nativeInsets.clone(); 269 } finally { 270 XToolkit.awtUnlock(); 271 } 272 } 273 274 /** 275 * Gets the current native insets. 276 */ 277 public Insets getNativeInsets() { 278 return getNativeInsets(true, true); 279 } 280 281 @Override 282 public Insets getInsets() { 283 Insets insets = getNativeInsets(); 284 insets.top += getMenuBarHeight(); 285 if (insLog.isLoggable(Level.FINEST)) { 286 insLog.log(Level.FINEST, "Get insets returns {0}", 287 new Object[] {insets}); 288 } 289 return insets; 290 } 291 292 /** 293 * Returns the insets that the window probably will get. 294 */ 295 private Insets getDefaultInsets() { 296 Insets insets = XWM.getWM().getDefaultInsets(); 297 return (Insets)(insets == null ? DEFAULT_INSETS : insets).clone(); 298 } 299 300 /** 301 * Get rid of insane insets. 302 * The operation is performed in place - the given object gets modified. 303 */ 304 private static Insets sanitize(Insets insets) { 305 //XXX: Perhaps using the marginal values instead of the default would 306 // make more sense? 307 if (insets.top > 64 || insets.top < 0) { 308 insets.top = DEFAULT_INSETS.top; 309 } 310 if (insets.left > 32 || insets.left < 0) { 311 insets.left = DEFAULT_INSETS.left; 312 } 313 if (insets.right > 32 || insets.right < 0) { 314 insets.right = DEFAULT_INSETS.right; 315 } 316 if (insets.bottom > 32 || insets.bottom < 0) { 317 insets.bottom = DEFAULT_INSETS.bottom; 318 } 319 return insets; 320 } 321 322 private void setNativeInsets(Insets insets) { 323 XToolkit.awtLock(); 324 try { 325 nativeInsets = insets == null ? null : 326 sanitize((Insets)insets.clone()); 327 } finally { 328 XToolkit.awtUnlock(); 329 } 330 } 331 332 public static final Insets DEFAULT_INSETS = new Insets(25, 5, 5, 5); 333 public static final Insets ZERO_INSETS = new Insets(0, 0, 0, 0); 334 335 /** 336 * Retrieve the current insets of the window. 337 * 338 * This method must only be called by the getNativeInsets() method. DO NOT 339 * call this method directly. If the insets need to be updated, nullify 340 * them using the setNativeInsets(null) method. 341 */ 342 private void retrieveNativeInsets() { 343 long window = getWindow(); 344 345 if (XWM.requestWMExtents(window)) { 346 XWM.waitForExtentsUpdateEvent(); 347 } 348 349 // Some WMs may provide the extents via a property, but do not require 350 // a request to update them. Hence try getting them unconditionally. 351 // We could use the XEvent returned by the waitForExtentsUpdateEvent() 352 // above, though that doesn't seem to make much sense. 353 Insets insets = XWM.getInsetsFromExtents(window); 354 if (insets != null) { 355 setNativeInsets(insets); 356 return; 357 } 358 359 // The window manager is unable to report any extents, so we need to 360 // calculate them ourselves. 361 Rectangle winRect = XlibUtil.getWindowGeometry( 362 window, XlibWrapper.larg1); 363 if (winRect == null) { 364 // Some error occured 365 return; 366 } 367 // The root window must be got immediately after the previous 368 // getWindowGeometry() call. 369 long root = Native.getWindow(XlibWrapper.larg1); 370 371 long parent = getNativeParent(); 372 if (parent == XConstants.None || parent == root) { 373 // Non-reparenting WM. Assume the insets are zero. 374 setNativeInsets(ZERO_INSETS); 375 return; 376 } 377 378 Rectangle parentRect = XlibUtil.getWindowGeometry(parent); 379 if (parentRect == null) { 380 // Some error again 381 return; 382 } 383 384 long grand_parent = XlibUtil.getParentWindow(parent); 385 if (grand_parent == XConstants.None || grand_parent == root || 386 (winRect.x != 0 || winRect.y != 0 || 387 winRect.width != parentRect.width || 388 winRect.height != parentRect.height)) 389 { 390 // Single-reparenting WM 391 // Either there's no a valid grand-parent, or the direct parent 392 // has greater bounds than the window itself. 393 setNativeInsets(calculateInsets(winRect, parentRect)); 394 } else { 395 // Double-reparenting WM 396 // There's a valid grand-parent. The bounds of the direct 397 // parent are equal to the bounds of the window itself. 398 Rectangle grandParentRect = XlibUtil.getWindowGeometry( 399 grand_parent); 400 if (grandParentRect == null) { 401 // One more error condition. This time we fall back to the 402 // single-reparenting case, though this will produce 403 // ZERO_INSETS actually... 404 setNativeInsets(calculateInsets(winRect, parentRect)); 405 } else { 406 setNativeInsets(calculateInsets(parentRect, grandParentRect)); 407 } 408 } 409 } 410 411 /** 412 * Calculates the insets for the given interior and exterior rectangles. 413 * Only positive insets are allowed. If the interior rectangle is bigger 414 * than the exterior one, the negative inset values will be ignored, 415 * and zero value used instead. 416 */ 417 private static Insets calculateInsets(Rectangle interior, Rectangle exterior) { 418 return new Insets( 419 Math.max(interior.y, 0), 420 Math.max(interior.x, 0), 421 Math.max(exterior.height - interior.height - interior.y, 0), 422 Math.max(exterior.width - interior.width - interior.x, 0)); 423 } 424 425 /** 426 * Applies the new insets. 427 */ 428 private void setInsets(Insets insets) { 429 setNativeInsets(insets); 430 431 WindowDimensions dims = new WindowDimensions(dimensions); 432 dims.setInsets(insets); 433 reportOrAdjust(dims, false); 434 } 435 436 @Override 437 public void handlePropertyNotify(XEvent xev) { 438 super.handlePropertyNotify(xev); 439 440 XPropertyEvent ev = xev.get_xproperty(); 441 if (XWM.isExtentsPropertyAtom(ev.get_atom())) { 442 Insets insets = XWM.getInsetsFromProp(getWindow(), 443 XAtom.get(ev.get_atom())); 444 if (insLog.isLoggable(Level.FINE)) { 445 insLog.fine("" + insets); 446 } 447 if (insets != null) { 448 setInsets(insets); 449 } 450 } 451 } 452 453 // The serial of the last received ReparentNotify event 454 private long reparentNotifySerial = 0; 455 456 // The number of processed ConfigureNotify events with the same serial 457 // as reparentNotifySerial. 458 private int numOfConfigureNotifyJustAfterReparentNotify = 0; 459 460 @Override 461 public void handleReparentNotifyEvent(XEvent xev) { 462 super.handleReparentNotifyEvent(xev); 463 464 XReparentEvent xe = xev.get_xreparent(); 465 if (insLog.isLoggable(Level.FINE)) { 466 insLog.fine(xe.toString()); 467 } 468 469 if (xe.get_window() != getWindow()) { 470 return; 471 } 472 473 if (!isParented()) { 474 if (isVisible()) { 475 // Either the WM or the embedder exited 476 XWM.getWM().unshadeKludge(this); 477 if (!isEmbedded()) { 478 XWM.reset(); 479 } 480 setInsets(null); 481 } //else: The window just got hidden 482 } else { 483 // We just got parented: prepare stuff to recalculate the insets, 484 // and to readjust the bounds if needed 485 reparentNotifySerial = xe.get_serial(); 486 numOfConfigureNotifyJustAfterReparentNotify = 0; 487 488 setNativeInsets(null); 489 needToAdjustBounds(); 490 } 491 } 492 493 protected boolean isInitialReshape() { 494 return false; 495 } 496 497 public void handleMoved(Point loc) { 498 ComponentAccessor.setX((Component)target, loc.x); 499 ComponentAccessor.setY((Component)target, loc.y); 500 postEvent(new ComponentEvent(target, ComponentEvent.COMPONENT_MOVED)); 501 } 502 503 504 public void revalidate() { 505 XToolkit.executeOnEventHandlerThread(target, new Runnable() { 506 public void run() { 507 target.invalidate(); 508 target.validate(); 509 } 510 }); 511 } 512 513 boolean gravityBug() { 514 return XWM.configureGravityBuggy(); 515 } 516 517 // The height of area used to display current active input method 518 int getInputMethodHeight() { 519 return 0; 520 } 521 522 void updateSizeHints(WindowDimensions dims) { 523 Rectangle rec = dims.getClientRect(); 524 checkShellRect(rec); 525 updateSizeHints(rec.x, rec.y, rec.width, rec.height); 526 } 527 528 void updateSizeHints() { 529 updateSizeHints(dimensions); 530 } 531 532 // Coordinates are that of the target 533 // Called only on Toolkit thread 534 private void reshape(WindowDimensions newDimensions, int op) 535 { 536 if (insLog.isLoggable(Level.FINE)) { 537 insLog.fine("Reshaping " + this + " to " + newDimensions + 538 "; op " + operationToString(op)); 539 } 540 XToolkit.awtLock(); 541 try { 542 if (!isVisible()) { 543 Rectangle client = newDimensions.getClientRect(); 544 checkShellRect(client); 545 setShellBounds(client); 546 547 reportReshape(newDimensions); 548 } else { 549 requestReshape(newDimensions, op); 550 } 551 } finally { 552 XToolkit.awtUnlock(); 553 } 554 } 555 556 /** 557 * Sets window dimensions and propagates the changes to the target sending 558 * corresponding events if needed. 559 * 560 * MUST be invoked under the AWTLock. 561 */ 562 private void reportReshape(WindowDimensions newDims) { 563 if (insLog.isLoggable(Level.FINE)) { 564 insLog.fine("" + newDims); 565 } 566 567 final Insets insets = newDims.getInsets(); 568 if (!insets.equals(dimensions.getInsets())) { 569 // Recalculate the minimum size of the client area 570 updateMinSizeHints(insets); 571 } 572 573 // If the client area size changes, we need to revalidate the target. 574 // This may happen in the XContentWindow.setContentBounds(). If this 575 // does not happen there, we need to dispatch the operation here. 576 final boolean needRevalidate = 577 !dimensions.getClientSize().equals(newDims.getClientSize()); 578 579 checkIfOnNewScreen(newDims.getBounds()); 580 581 Point oldLocation = getLocation(); 582 dimensions = newDims; 583 if (!getLocation().equals(oldLocation)) { 584 handleMoved(getLocation()); 585 } 586 587 if (!reconfigureContentWindow(dimensions) && needRevalidate) { 588 revalidate(); 589 } 590 591 updateChildrenSizes(); 592 repositionSecurityWarning(); 593 } 594 595 private void reportOrAdjust(WindowDimensions newDims, 596 boolean handlingConfigureNotify) 597 { 598 if (dimensions.equals(newDims) && !areBoundsAdjusting()) { 599 // If nothing has changed and we're already adjusted, return 600 if (insLog.isLoggable(Level.FINE)) { 601 insLog.fine("Ignored: nothing changed: " + newDims); 602 } 603 return; 604 } 605 if (adjustBounds(newDims, handlingConfigureNotify)) { 606 // We expect another ConfigureNotify 607 return; 608 } 609 610 reportReshape(newDims); 611 } 612 613 /** 614 * Requests the system to reshape the window. 615 * 616 * MUST be invoked under the AWTLock. 617 */ 618 private void requestReshape(WindowDimensions newDimensions, int op) { 619 if (insLog.isLoggable(Level.FINE)) { 620 insLog.fine("Request reshape: " + newDimensions + "; op: " + 621 operationToString(op)); 622 } 623 624 Rectangle shellRect = newDimensions.getClientRect(); 625 626 if (gravityBug()) { 627 Insets in = newDimensions.getInsets(); 628 shellRect.translate(in.left, in.top); 629 } 630 631 if ((op & NO_EMBEDDED_CHECK) == 0 && isEmbedded()) { 632 shellRect.setLocation(0, 0); 633 } 634 635 checkShellRectSize(shellRect); 636 if (!isEmbedded()) { 637 checkShellRectPos(shellRect); 638 } 639 640 op = op & ~NO_EMBEDDED_CHECK; 641 642 if (op == SET_LOCATION) { 643 setShellPosition(shellRect); 644 } else if (isResizable()) { 645 if (op == SET_BOUNDS) { 646 setShellBounds(shellRect); 647 } else { 648 setShellSize(shellRect); 649 } 650 } else { 651 XWM.setShellNotResizable(this, newDimensions, shellRect, true); 652 if (op == SET_BOUNDS) { 653 setShellPosition(shellRect); 654 } 655 } 656 } 657 658 // This method gets overriden in XFramePeer & XDialogPeer. 659 abstract boolean isTargetUndecorated(); 660 661 /** 662 * @see java.awt.peer.ComponentPeer#setBounds 663 */ 664 public void setBounds(int x, int y, int width, int height, int operation) { 665 WindowDimensions dims = new WindowDimensions(dimensions); 666 switch (operation & (~NO_EMBEDDED_CHECK)) { 667 case SET_LOCATION: 668 // Set location always sets bounds location. However, until the window is mapped we 669 // should use client coordinates 670 dims.setLocation(x, y); 671 break; 672 case SET_SIZE: 673 // Set size sets bounds size. However, until the window is mapped we 674 // should use client coordinates 675 dims.setSize(width, height); 676 break; 677 case SET_CLIENT_SIZE: { 678 // Sets client rect size. Width and height contain insets. 679 // Also update the insets to the latest known value to decrease 680 // (or even eliminate) the bounds adjustment after showing 681 // the window. 682 Insets in = getNativeInsets(); 683 width -= in.left+in.right; 684 height -= in.top+in.bottom; 685 dims.setClientSize(width, height); 686 dims.setInsets(in); 687 break; 688 } 689 case SET_BOUNDS: 690 default: 691 dims.setLocation(x, y); 692 dims.setSize(width, height); 693 break; 694 } 695 reshape(dims, operation); 696 validateSurface(); 697 } 698 699 /** 700 * Sets the content window bounds. 701 * 702 * @return whether a COMPONENT_RESIZED has been sent 703 */ 704 boolean reconfigureContentWindow(WindowDimensions dims) { 705 if (content == null) { 706 insLog.fine("WARNING: Content window is null"); 707 return false; 708 } 709 return content.setContentBounds(dims); 710 } 711 712 /** 713 * Indicates if the adjustBounds() needs to be invoked. 714 * Synchronization: state lock. 715 */ 716 private boolean areBoundsAdjusted = false; 717 718 /** 719 * Forces the system to re-adjust the bounds of the window after it has 720 * been finally adopted by the windowing system. 721 */ 722 private void needToAdjustBounds() { 723 synchronized (getStateLock()) { 724 areBoundsAdjusted = false; 725 } 726 } 727 728 /** 729 * Indicates if the window bounds has not yet been finally configured by 730 * the X server/window manager. 731 */ 732 public boolean areBoundsAdjusting() { 733 synchronized (getStateLock()) { 734 return !areBoundsAdjusted; 735 } 736 } 737 738 // XXX: Perhaps the following two might be replaced with Window.isSizeSet, 739 // isLocationSet? Just like we have Window.isPacked... 740 /** 741 * Indicates if the size hints of the window specify a position requested 742 * by the user's code. 743 */ 744 private boolean isUserSpecifiedPositionSet() { 745 return (getHints().get_flags() & 746 (XUtilConstants.USPosition | XUtilConstants.PPosition)) != 0; 747 } 748 749 /** 750 * Indicates if the size hints of the window specify a position requested 751 * by the user's code. 752 */ 753 private boolean isUserSpecifiedSizeSet() { 754 return (getHints().get_flags() & 755 (XUtilConstants.USSize | XUtilConstants.PSize)) != 0; 756 } 757 758 /** 759 * Adjust the bounds of the window. 760 * 761 * @return true if an adjustment has taken place. false if the new bounds 762 * are OK. 763 */ 764 private boolean adjustBounds(WindowDimensions newDims, 765 boolean handlingConfigureNotify) 766 { 767 synchronized (getStateLock()) { 768 if (areBoundsAdjusted) { 769 return false; 770 } 771 // We have to adjust bounds upon showing once. 772 // We also may adjust them several times before showing - e.g. 773 // when we receive the PropertyNotify event with the extents. 774 if (handlingConfigureNotify && isVisible()) { 775 insLog.log(Level.FINE, "Final adjustment"); 776 areBoundsAdjusted = true; 777 } 778 } 779 780 if (isMaximized()) { 781 return false; 782 } 783 784 int operation = 0; 785 boolean isPacked = AWTAccessor.getWindowAccessor(). 786 isPacked((Window)target); 787 788 if (!newDims.getClientSize().equals(dimensions.getClientSize()) 789 && isPacked) 790 { 791 // The client area size calculated via pack() needs to be preserved 792 operation = SET_SIZE; 793 } else 794 if (!newDims.getSize().equals(dimensions.getSize()) 795 && isUserSpecifiedSizeSet()) 796 { 797 // The size set via setSize()/setBounds() needs to be preserved 798 operation = SET_SIZE; 799 } 800 801 if (isUserSpecifiedPositionSet() && 802 !newDims.getLocation().equals(dimensions.getLocation())) 803 { 804 // The user also requested a specific position 805 if (operation == 0) { 806 operation = SET_LOCATION; 807 } else { 808 operation = SET_BOUNDS; 809 } 810 } 811 812 if (operation == 0) { 813 insLog.fine("No adjustment needed"); 814 return false; 815 } 816 817 WindowDimensions dims = new WindowDimensions( 818 dimensions.getLocation(), 819 isPacked ? dimensions.getClientSize() : dimensions.getSize(), 820 newDims.getInsets(), 821 isPacked); 822 823 if (insLog.isLoggable(Level.FINE)) { 824 insLog.fine("Request: " + dims + "; operation=" + 825 operationToString(operation)); 826 } 827 requestReshape(dims, operation); 828 return true; 829 } 830 831 @Override 832 public void handleConfigureNotifyEvent(XEvent xev) { 833 // Note: we don't call super because the XWindowPeer immediately calls 834 // the checkIfOnNewScreen() assuming the bounds are correct. This 835 // is not always correct for decorated peers. 836 837 XConfigureEvent xe = xev.get_xconfigure(); 838 839 // Due to the SubstructureNotifyMask we should process only those 840 // events that belong to us. 841 if (xe.get_window() != getWindow()) { 842 return; 843 } 844 845 if (insLog.isLoggable(Level.FINE)) { 846 insLog.fine("" + xe); 847 insLog.fine("XWM.isRunning: " + XWM.isRunning()); 848 } 849 850 // If there's a WM we have to ignore some events 851 if (XWM.isRunning()) { 852 // Ignore any events until after we become visible 853 if (!isVisible()) { 854 insLog.fine("Ignored: Not visible yet"); 855 needToAdjustBounds(); 856 return; 857 } 858 859 // We have not yet been parented by the WM 860 if (mayBeReparented() && !isTargetUndecorated()) { 861 insLog.fine("Ignored: Not parented yet"); 862 needToAdjustBounds(); 863 return; 864 } 865 866 // Just after reparenting we can receive a synthetic and/or real 867 // event(s). Since sometimes we need to adjust the bounds of the 868 // frame, we need to process only one of the events. Justification: 869 // 1. Both events carry the same information: one from the X 870 // server, the other from the WM. 871 // 2. The adjustment operation is performed only once upon 872 // processing the first ConfigureNotify event that is not 873 // ignored. 874 // 3. If the first event causes the adjustment to happen, the 875 // second event will be considered as a normal, not ignored 876 // event that may generate Java events and set incorrect 877 // (not-yet-adjusted) bounds to the frame. 878 // So generally, if the adjustment is needed, the first 879 // ConfigureNotify that we should really process is the one caused 880 // by the adjustment operation. So we process only the first 881 // ConfigureNotify event having the same serial that the preceeding 882 // ReparentNotify. 883 // 884 // There's one exception however: we do not ignore the events for 885 // maximized frames. The real ConfigureNotify that we would like to 886 // act upon usually is the third one (it carries the correct 887 // maximized bounds). However, it has the same serial (which is 888 // obvious), and what's worse - we can't reliably determine if the 889 // window manager/X server send us exactly two events before that, 890 // or maybe one of them might be not sent at all - and in this case 891 // we would need to process the second event, not the third. So we 892 // just process everything for a maximized frame. This does produce 893 // absolutely meaningless bounds dancing (with events sent to the 894 // user space), but at least this is backward-compatible and quite 895 // reliable. Should we find a way to detect and process only the 896 // very last of ConfigureNotify events sent with the same serial, 897 // we would like to use this approach instead of the current 898 // processing of the very first event only. 899 if (xe.get_serial() == reparentNotifySerial && !isMaximized()) 900 { 901 if (++numOfConfigureNotifyJustAfterReparentNotify > 1) { 902 insLog.fine("Ignored: serial matches the ReparentNotify"); 903 return; 904 } 905 } 906 } 907 908 // At this point we can calculate some reliable insets 909 Insets insets = getNativeInsets(); 910 911 // Note that in some cases this kind of event may be caused by the 912 // changed insets (like if the theme changes, or something). However 913 // it's difficult if at all possible to identify this situation, and 914 // not confuse it with a regular reconfiguration w/o introducing some 915 // serious performance degradation. So we only support changing the 916 // insets: 917 // a. when a reparenting WM exits/gets replaced. 918 // b. when the window gets hidden/shown. 919 // c. if the WM is smart enough to send the PropertyNotify 920 // events with the updated extents. 921 922 // x, y, width, height hold the coordinates of the whole frame 923 // including the decorations. 924 int x; 925 int y; 926 927 if (xe.get_send_event() || !isParented()) { 928 // If the event is synthetic or we're not parented, the coordinates 929 // are relative to the root window. 930 x = xe.get_x() - insets.left; 931 y = xe.get_y() - insets.top; 932 } else { 933 // The event is real and we're parented - the coordinates are 934 // relative to the parent 935 Point loc = null; 936 if (XWM.isNoSyntheticConfigureNotifyOnLeftTopResize()) { 937 // there's a bug in the WM, so ask the X server 938 loc = queryXLocation(); 939 } 940 if (loc == null) { 941 // consider the current location unchanged 942 // TODO: this may not be true... perhaps querying X in all cases is worthwhile? 943 // after all: we already do this for Metacity which is quite common, 944 // so we should not worry much about the performance. 945 loc = getLocation(); 946 } 947 948 x = loc.x; 949 y = loc.y; 950 } 951 952 int width = xe.get_width() + insets.left + insets.right; 953 int height = xe.get_height() + insets.top + insets.bottom; 954 955 // Now apply the new bounds 956 WindowDimensions d = new WindowDimensions( 957 new Rectangle(x, y, width, height), insets, false); 958 reportOrAdjust(d, true); 959 } 960 961 private void checkShellRectSize(Rectangle shellRect) { 962 if (shellRect.width < 0) { 963 shellRect.width = 1; 964 } 965 if (shellRect.height < 0) { 966 shellRect.height = 1; 967 } 968 } 969 970 private void checkShellRectPos(Rectangle shellRect) { 971 int wm = XWM.getWMID(); 972 if (wm == XWM.MOTIF_WM || wm == XWM.CDE_WM) { 973 if (shellRect.x == 0 && shellRect.y == 0) { 974 shellRect.x = shellRect.y = 1; 975 } 976 } 977 } 978 979 private void checkShellRect(Rectangle shellRect) { 980 checkShellRectSize(shellRect); 981 checkShellRectPos(shellRect); 982 } 983 984 public void setShellBounds(Rectangle rec) { 985 if (insLog.isLoggable(Level.FINE)) insLog.fine("Setting shell bounds on " + 986 this + " to " + rec); 987 XToolkit.awtLock(); 988 try { 989 updateSizeHints(rec.x, rec.y, rec.width, rec.height); 990 XlibWrapper.XResizeWindow(XToolkit.getDisplay(), getShell(), rec.width, rec.height); 991 XlibWrapper.XMoveWindow(XToolkit.getDisplay(), getShell(), rec.x, rec.y); 992 } 993 finally { 994 XToolkit.awtUnlock(); 995 } 996 } 997 public void setShellSize(Rectangle rec) { 998 if (insLog.isLoggable(Level.FINE)) insLog.fine("Setting shell size on " + 999 this + " to " + rec); 1000 XToolkit.awtLock(); 1001 try { 1002 updateSizeHints(rec.x, rec.y, rec.width, rec.height); 1003 XlibWrapper.XResizeWindow(XToolkit.getDisplay(), getShell(), rec.width, rec.height); 1004 } 1005 finally { 1006 XToolkit.awtUnlock(); 1007 } 1008 } 1009 public void setShellPosition(Rectangle rec) { 1010 if (insLog.isLoggable(Level.FINE)) insLog.fine("Setting shell position on " + 1011 this + " to " + rec); 1012 XToolkit.awtLock(); 1013 try { 1014 updateSizeHints(rec.x, rec.y, rec.width, rec.height); 1015 XlibWrapper.XMoveWindow(XToolkit.getDisplay(), getShell(), rec.x, rec.y); 1016 } 1017 finally { 1018 XToolkit.awtUnlock(); 1019 } 1020 } 1021 1022 void initResizability() { 1023 setResizable(winAttr.initialResizability); 1024 } 1025 public void setResizable(boolean resizable) { 1026 int fs = winAttr.functions; 1027 if (!isResizable() && resizable) { 1028 setNativeInsets(null); 1029 winAttr.isResizable = resizable; 1030 if ((fs & MWMConstants.MWM_FUNC_ALL) != 0) { 1031 fs &= ~(MWMConstants.MWM_FUNC_RESIZE | MWMConstants.MWM_FUNC_MAXIMIZE); 1032 } else { 1033 fs |= (MWMConstants.MWM_FUNC_RESIZE | MWMConstants.MWM_FUNC_MAXIMIZE); 1034 } 1035 winAttr.functions = fs; 1036 XWM.setShellResizable(this); 1037 } else if (isResizable() && !resizable) { 1038 setNativeInsets(null); 1039 winAttr.isResizable = resizable; 1040 if ((fs & MWMConstants.MWM_FUNC_ALL) != 0) { 1041 fs |= (MWMConstants.MWM_FUNC_RESIZE | MWMConstants.MWM_FUNC_MAXIMIZE); 1042 } else { 1043 fs &= ~(MWMConstants.MWM_FUNC_RESIZE | MWMConstants.MWM_FUNC_MAXIMIZE); 1044 } 1045 winAttr.functions = fs; 1046 XWM.setShellNotResizable(this, dimensions, dimensions.getBounds(), false); 1047 } 1048 } 1049 1050 Rectangle getShellBounds() { 1051 return dimensions.getClientRect(); 1052 } 1053 1054 public Rectangle getBounds() { 1055 return dimensions.getBounds(); 1056 } 1057 1058 public Dimension getSize() { 1059 return dimensions.getSize(); 1060 } 1061 1062 public int getX() { 1063 return dimensions.getLocation().x; 1064 } 1065 1066 public int getY() { 1067 return dimensions.getLocation().y; 1068 } 1069 1070 public Point getLocation() { 1071 return dimensions.getLocation(); 1072 } 1073 1074 public int getAbsoluteX() { 1075 // NOTE: returning this peer's location which is shell location 1076 return dimensions.getScreenBounds().x; 1077 } 1078 1079 public int getAbsoluteY() { 1080 // NOTE: returning this peer's location which is shell location 1081 return dimensions.getScreenBounds().y; 1082 } 1083 1084 public int getWidth() { 1085 return getSize().width; 1086 } 1087 1088 public int getHeight() { 1089 return getSize().height; 1090 } 1091 1092 final public WindowDimensions getDimensions() { 1093 return dimensions; 1094 } 1095 1096 public Point getLocationOnScreen() { 1097 XToolkit.awtLock(); 1098 try { 1099 if (!areBoundsAdjusting()) { 1100 return toGlobal(0,0); 1101 } else { 1102 Point location = target.getLocation(); 1103 if (insLog.isLoggable(Level.FINE)) { 1104 insLog.log(Level.FINE, "getLocationOnScreen {0} not reparented: {1} ", 1105 new Object[] {this, location}); 1106 } 1107 return location; 1108 } 1109 } finally { 1110 XToolkit.awtUnlock(); 1111 } 1112 } 1113 1114 1115 /*************************************************************************************** 1116 * END OF I N S E T S C O D E 1117 **************************************************************************************/ 1118 1119 protected boolean isEventDisabled(XEvent e) { 1120 switch (e.get_type()) { 1121 // Do not generate MOVED/RESIZED events since we generate them by ourselves 1122 case XConstants.ConfigureNotify: 1123 return true; 1124 case XConstants.EnterNotify: 1125 case XConstants.LeaveNotify: 1126 // Disable crossing event on outer borders of Frame so 1127 // we receive only one set of cross notifications(first set is from content window) 1128 return true; 1129 default: 1130 return super.isEventDisabled(e); 1131 } 1132 } 1133 1134 int getDecorations() { 1135 return winAttr.decorations; 1136 } 1137 1138 int getFunctions() { 1139 return winAttr.functions; 1140 } 1141 1142 public void setVisible(boolean vis) { 1143 log.log(Level.FINER, "Setting {0} to visible {1}", new Object[] {this, Boolean.valueOf(vis)}); 1144 if (vis && !isVisible()) { 1145 XWM.setShellDecor(this); 1146 super.setVisible(vis); 1147 if (winAttr.isResizable) { 1148 //Fix for 4320050: Minimum size for java.awt.Frame is not being enforced. 1149 //We need to update frame's minimum size, not to reset it 1150 XWM.removeSizeHints(this, XUtilConstants.PMaxSize); 1151 updateMinimumSize(); 1152 } 1153 } else { 1154 super.setVisible(vis); 1155 } 1156 } 1157 1158 protected void suppressWmTakeFocus(boolean doSuppress) { 1159 XAtomList protocols = getWMProtocols(); 1160 if (doSuppress) { 1161 protocols.remove(wm_take_focus); 1162 } else { 1163 protocols.add(wm_take_focus); 1164 } 1165 wm_protocols.setAtomListProperty(this, protocols); 1166 } 1167 1168 public void dispose() { 1169 if (content != null) { 1170 content.destroy(); 1171 } 1172 focusProxy.destroy(); 1173 1174 if (iconWindow != null) { 1175 iconWindow.destroy(); 1176 } 1177 1178 super.dispose(); 1179 } 1180 1181 public void handleClientMessage(XEvent xev) { 1182 super.handleClientMessage(xev); 1183 XClientMessageEvent cl = xev.get_xclient(); 1184 if ((wm_protocols != null) && (cl.get_message_type() == wm_protocols.getAtom())) { 1185 if (cl.get_data(0) == wm_delete_window.getAtom()) { 1186 handleQuit(); 1187 } else if (cl.get_data(0) == wm_take_focus.getAtom()) { 1188 handleWmTakeFocus(cl); 1189 } 1190 } 1191 } 1192 1193 private void handleWmTakeFocus(XClientMessageEvent cl) { 1194 focusLog.log(Level.FINE, "WM_TAKE_FOCUS on {0}", new Object[]{this}); 1195 requestWindowFocus(cl.get_data(1), true); 1196 } 1197 1198 /** 1199 * Requests focus to this decorated top-level by requesting X input focus 1200 * to the shell window. 1201 */ 1202 protected void requestXFocus(long time, boolean timeProvided) { 1203 // We have proxied focus mechanism - instead of shell the focus is held 1204 // by "proxy" - invisible mapped window. When we want to set X input focus to 1205 // toplevel set it on proxy instead. 1206 if (focusProxy == null) { 1207 if (focusLog.isLoggable(Level.FINE)) focusLog.warning("Focus proxy is null for " + this); 1208 } else { 1209 if (focusLog.isLoggable(Level.FINE)) focusLog.fine("Requesting focus to proxy: " + focusProxy); 1210 if (timeProvided) { 1211 focusProxy.xRequestFocus(time); 1212 } else { 1213 focusProxy.xRequestFocus(); 1214 } 1215 } 1216 } 1217 1218 XFocusProxyWindow getFocusProxy() { 1219 return focusProxy; 1220 } 1221 1222 public void handleQuit() { 1223 postEvent(new WindowEvent((Window)target, WindowEvent.WINDOW_CLOSING)); 1224 } 1225 1226 final void dumpMe() { 1227 System.err.println(">>> Peer: " + x + ", " + y + ", " + width + ", " + height); 1228 } 1229 1230 final void dumpTarget() { 1231 int getWidth = ComponentAccessor.getWidth((Component)target); 1232 int getHeight = ComponentAccessor.getHeight((Component)target); 1233 int getTargetX = ComponentAccessor.getX((Component)target); 1234 int getTargetY = ComponentAccessor.getY((Component)target); 1235 System.err.println(">>> Target: " + getTargetX + ", " + getTargetY + ", " + getWidth + ", " + getHeight); 1236 } 1237 1238 final void dumpShell() { 1239 dumpWindow("Shell", getShell()); 1240 } 1241 final void dumpContent() { 1242 dumpWindow("Content", getContentWindow()); 1243 } 1244 final void dumpParent() { 1245 long parent = XlibUtil.getParentWindow(getShell()); 1246 if (parent != XConstants.None) 1247 { 1248 dumpWindow("Parent", parent); 1249 } 1250 else 1251 { 1252 System.err.println(">>> NO PARENT"); 1253 } 1254 } 1255 1256 final void dumpWindow(String id, long window) { 1257 XWindowAttributes pattr = new XWindowAttributes(); 1258 try { 1259 XToolkit.awtLock(); 1260 try { 1261 int status = 1262 XlibWrapper.XGetWindowAttributes(XToolkit.getDisplay(), 1263 window, pattr.pData); 1264 } 1265 finally { 1266 XToolkit.awtUnlock(); 1267 } 1268 System.err.println(">>>> " + id + ": " + pattr.get_x() 1269 + ", " + pattr.get_y() + ", " + pattr.get_width() 1270 + ", " + pattr.get_height()); 1271 } finally { 1272 pattr.dispose(); 1273 } 1274 } 1275 1276 final void dumpAll() { 1277 dumpTarget(); 1278 dumpMe(); 1279 dumpParent(); 1280 dumpShell(); 1281 dumpContent(); 1282 } 1283 1284 boolean isMaximized() { 1285 return false; 1286 } 1287 1288 boolean isOverrideRedirect() { 1289 return ((XToolkit)Toolkit.getDefaultToolkit()).isOverrideRedirect((Window)target); 1290 } 1291 1292 public boolean requestWindowFocus(long time, boolean timeProvided) { 1293 focusLog.fine("Request for decorated window focus"); 1294 // If this is Frame or Dialog we can't assure focus request success - but we still can try 1295 // If this is Window and its owner Frame is active we can be sure request succedded. 1296 Window focusedWindow = XKeyboardFocusManagerPeer.getCurrentNativeFocusedWindow(); 1297 Window activeWindow = XWindowPeer.getDecoratedOwner(focusedWindow); 1298 1299 focusLog.log(Level.FINER, "Current window is: active={0}, focused={1}", 1300 new Object[]{ Boolean.valueOf(target == activeWindow), 1301 Boolean.valueOf(target == focusedWindow)}); 1302 1303 XWindowPeer toFocus = this; 1304 while (toFocus.nextTransientFor != null) { 1305 toFocus = toFocus.nextTransientFor; 1306 } 1307 if (toFocus == null || !toFocus.focusAllowedFor()) { 1308 // This might change when WM will have property to determine focus policy. 1309 // Right now, because policy is unknown we can't be sure we succedded 1310 return false; 1311 } 1312 if (this == toFocus) { 1313 if (isWMStateNetHidden()) { 1314 focusLog.fine("The window is unmapped, so rejecting the request"); 1315 return false; 1316 } 1317 if (target == activeWindow && target != focusedWindow) { 1318 // Happens when an owned window is currently focused 1319 focusLog.fine("Focus is on child window - transfering it back to the owner"); 1320 handleWindowFocusInSync(-1); 1321 return true; 1322 } 1323 Window realNativeFocusedWindow = XWindowPeer.getNativeFocusedWindow(); 1324 focusLog.finest("Real native focused window: " + realNativeFocusedWindow + 1325 "\nKFM's focused window: " + focusedWindow); 1326 1327 // See 6522725, 6613426. 1328 if (target == realNativeFocusedWindow) { 1329 focusLog.fine("The window is already natively focused."); 1330 return true; 1331 } 1332 } 1333 focusLog.fine("Requesting focus to " + (this == toFocus ? "this window" : toFocus)); 1334 1335 if (timeProvided) { 1336 toFocus.requestXFocus(time); 1337 } else { 1338 toFocus.requestXFocus(); 1339 } 1340 return (this == toFocus); 1341 } 1342 1343 XWindowPeer actualFocusedWindow = null; 1344 void setActualFocusedWindow(XWindowPeer actualFocusedWindow) { 1345 synchronized(getStateLock()) { 1346 this.actualFocusedWindow = actualFocusedWindow; 1347 } 1348 } 1349 1350 boolean requestWindowFocus(XWindowPeer actualFocusedWindow, 1351 long time, boolean timeProvided) 1352 { 1353 setActualFocusedWindow(actualFocusedWindow); 1354 return requestWindowFocus(time, timeProvided); 1355 } 1356 public void handleWindowFocusIn(long serial) { 1357 if (null == actualFocusedWindow) { 1358 super.handleWindowFocusIn(serial); 1359 } else { 1360 /* 1361 * Fix for 6314575. 1362 * If this is a result of clicking on one of the Frame's component 1363 * then 'actualFocusedWindow' shouldn't be focused. A decision of focusing 1364 * it or not should be made after the appropriate Java mouse event (if any) 1365 * is handled by the component where 'actualFocusedWindow' value may be reset. 1366 * 1367 * The fix is based on the empiric fact consisting in that the component 1368 * receives native mouse event nearly at the same time the Frame receives 1369 * WM_TAKE_FOCUS (when FocusIn is generated via XSetInputFocus call) but 1370 * definetely before the Frame gets FocusIn event (when this method is called). 1371 */ 1372 postEvent(new InvocationEvent(target, new Runnable() { 1373 public void run() { 1374 XWindowPeer fw = null; 1375 synchronized (getStateLock()) { 1376 fw = actualFocusedWindow; 1377 actualFocusedWindow = null; 1378 if (null == fw || !fw.isVisible() || !fw.isFocusableWindow()) { 1379 fw = XDecoratedPeer.this; 1380 } 1381 } 1382 fw.handleWindowFocusIn_Dispatch(); 1383 } 1384 })); 1385 } 1386 } 1387 1388 public void handleWindowFocusOut(Window oppositeWindow, long serial) { 1389 Window actualFocusedWindow = XKeyboardFocusManagerPeer.getCurrentNativeFocusedWindow(); 1390 1391 // If the actual focused window is not this decorated window then retain it. 1392 if (actualFocusedWindow != null && actualFocusedWindow != target) { 1393 Window owner = XWindowPeer.getDecoratedOwner(actualFocusedWindow); 1394 1395 if (owner != null && owner == target) { 1396 setActualFocusedWindow((XWindowPeer) ComponentAccessor.getPeer(actualFocusedWindow)); 1397 } 1398 } 1399 super.handleWindowFocusOut(oppositeWindow, serial); 1400 } 1401 1402 private Point queryXLocation() 1403 { 1404 return XlibUtil.translateCoordinates( 1405 getContentWindow(), 1406 XlibWrapper.RootWindow(XToolkit.getDisplay(), getScreenNumber()), 1407 new Point(0, 0)); 1408 } 1409 }