001 /* 002 * Copyright 2002-2004 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 005 * use this file except in compliance with the License. You may obtain a copy of 006 * the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 013 * License for the specific language governing permissions and limitations under 014 * the License. 015 */ 016 package org.springframework.richclient.util; 017 018 import org.springframework.richclient.components.MessagableTabbedPane; 019 import org.springframework.richclient.components.MayHaveMessagableTab; 020 021 import java.awt.Component; 022 import java.awt.Container; 023 import java.awt.Dimension; 024 import java.awt.LayoutManager; 025 import java.awt.Rectangle; 026 import java.awt.Point; 027 import java.awt.event.ComponentEvent; 028 import java.awt.event.ComponentListener; 029 import java.awt.event.HierarchyBoundsListener; 030 import java.awt.event.HierarchyEvent; 031 import java.awt.event.HierarchyListener; 032 import java.beans.PropertyChangeEvent; 033 import java.beans.PropertyChangeListener; 034 035 import javax.swing.JComponent; 036 import javax.swing.JLayeredPane; 037 import javax.swing.SwingConstants; 038 import javax.swing.SwingUtilities; 039 import javax.swing.JPanel; 040 import javax.swing.JViewport; 041 import javax.swing.JScrollPane; 042 import javax.swing.JRootPane; 043 044 /** 045 * A helper class that attaches one component (the overlay) on top of another 046 * component. 047 * 048 * @author oliverh 049 */ 050 public class OverlayHelper implements SwingConstants 051 { 052 private final OverlayTargetChangeHandler overlayTargetChangeHandler = new OverlayTargetChangeHandler(); 053 054 private final OverlayChangeHandler overlayChangeHandler = new OverlayChangeHandler(); 055 056 protected final JComponent overlay; 057 protected final JComponent overlayClipper; 058 protected final JComponent overlayTarget; 059 060 private final int center; 061 062 private final int xOffset; 063 064 private final int yOffset; 065 066 boolean isUpdating; 067 068 private Runnable overlayUpdater = new OverlayUpdater(); 069 070 /** 071 * Attaches an overlay to the specified component. 072 * 073 * @param overlay the overlay component 074 * @param overlayTarget the component over which <code>overlay</code> will be 075 * attached 076 * @param center position relative to <code>overlayTarget</code> that overlay 077 * should be centered. May be one of the 078 * <code>SwingConstants</code> compass positions or 079 * <code>SwingConstants.CENTER</code>. 080 * @param xOffset x offset from center 081 * @param yOffset y offset from center 082 * @see SwingConstants 083 */ 084 public static void attachOverlay(JComponent overlay, JComponent overlayTarget, int center, int xOffset, int yOffset) 085 { 086 new OverlayHelper(overlay, overlayTarget, center, xOffset, yOffset); 087 } 088 089 protected OverlayHelper(JComponent overlay, JComponent overlayTarget, int center, int xOffset, int yOffset) 090 { 091 this.overlay = overlay; 092 this.overlayTarget = overlayTarget; 093 this.center = center; 094 this.xOffset = xOffset; 095 this.yOffset = yOffset; 096 this.overlayClipper = new JPanel(); 097 this.overlayClipper.setLayout(null); 098 this.overlayClipper.add(overlay); 099 this.overlayClipper.setOpaque(false); 100 installListeners(); 101 } 102 103 final class OverlayChangeHandler implements ComponentListener, PropertyChangeListener 104 { 105 public void componentHidden(ComponentEvent e) 106 { 107 hideOverlay(); 108 } 109 110 public void componentMoved(ComponentEvent e) 111 { 112 // ignore 113 } 114 115 public void componentResized(ComponentEvent e) 116 { 117 // ignore 118 } 119 120 public void componentShown(ComponentEvent e) 121 { 122 updateOverlay(); 123 } 124 125 public void propertyChange(PropertyChangeEvent e) 126 { 127 if ("ancestor".equals(e.getPropertyName()) || "layeredContainerLayer".equals(e.getPropertyName())) 128 { 129 return; 130 } 131 updateOverlay(); 132 } 133 } 134 135 class OverlayTargetChangeHandler implements HierarchyListener, HierarchyBoundsListener, ComponentListener 136 { 137 public void hierarchyChanged(HierarchyEvent e) 138 { 139 updateOverlay(); 140 } 141 142 public void ancestorMoved(HierarchyEvent e) 143 { 144 updateOverlay(); 145 } 146 147 public void ancestorResized(HierarchyEvent e) 148 { 149 updateOverlay(); 150 } 151 152 public void componentHidden(ComponentEvent e) 153 { 154 hideOverlay(); 155 } 156 157 public void componentMoved(ComponentEvent e) 158 { 159 updateOverlay(); 160 } 161 162 public void componentResized(ComponentEvent e) 163 { 164 updateOverlay(); 165 } 166 167 public void componentShown(ComponentEvent e) 168 { 169 updateOverlay(); 170 } 171 } 172 173 private void installListeners() 174 { 175 overlayTarget.addHierarchyListener(overlayTargetChangeHandler); 176 overlayTarget.addHierarchyBoundsListener(overlayTargetChangeHandler); 177 overlayTarget.addComponentListener(overlayTargetChangeHandler); 178 overlay.addComponentListener(overlayChangeHandler); 179 overlay.addPropertyChangeListener(overlayChangeHandler); 180 } 181 182 void updateOverlay() 183 { 184 if (isUpdating) 185 { 186 return; 187 } 188 isUpdating = true; 189 // updating the overlay at the end of the event queue to avoid race conditions 190 // see RCP-126 (http://opensource.atlassian.com/projects/spring/browse/RCP-216) 191 SwingUtilities.invokeLater(overlayUpdater); 192 } 193 194 void putOverlay(final JLayeredPane layeredPane) 195 { 196 if (overlay.getParent() != overlayClipper) 197 { 198 JComponent parent = (JComponent) overlay.getParent(); 199 if (parent != null) 200 { 201 parent.remove(overlay); 202 } 203 overlayClipper.add(overlay); 204 } 205 if (overlayClipper.getParent() != layeredPane) 206 { 207 JComponent parent = (JComponent) overlayClipper.getParent(); 208 if (parent != null) 209 { 210 parent.remove(overlayClipper); 211 } 212 layeredPane.add(overlayClipper); 213 layeredPane.setLayer(overlayClipper, JLayeredPane.PALETTE_LAYER.intValue()); 214 } 215 } 216 217 void positionOverlay(JLayeredPane layeredPane) 218 { 219 int centerX = xOffset; 220 int centerY = yOffset; 221 Rectangle overlayTargetBounds = new Rectangle(0, 0, overlayTarget.getWidth(), overlayTarget.getHeight()); 222 switch (center) 223 { 224 case SwingConstants.NORTH: 225 case SwingConstants.NORTH_WEST: 226 case SwingConstants.NORTH_EAST: 227 centerY += overlayTargetBounds.y; 228 break; 229 case SwingConstants.CENTER: 230 case SwingConstants.EAST: 231 case SwingConstants.WEST: 232 centerY += overlayTargetBounds.y + (overlayTargetBounds.height / 2); 233 break; 234 case SwingConstants.SOUTH: 235 case SwingConstants.SOUTH_EAST: 236 case SwingConstants.SOUTH_WEST: 237 centerY += overlayTargetBounds.y + overlayTargetBounds.height; 238 break; 239 default: 240 throw new IllegalArgumentException("Unknown value for center [" + center + "]"); 241 } 242 switch (center) 243 { 244 case SwingConstants.WEST: 245 case SwingConstants.NORTH_WEST: 246 case SwingConstants.SOUTH_WEST: 247 centerX += overlayTargetBounds.x; 248 break; 249 case SwingConstants.CENTER: 250 case SwingConstants.NORTH: 251 case SwingConstants.SOUTH: 252 centerX += overlayTargetBounds.x + (overlayTargetBounds.width / 2); 253 break; 254 case SwingConstants.EAST: 255 case SwingConstants.NORTH_EAST: 256 case SwingConstants.SOUTH_EAST: 257 centerX += overlayTargetBounds.x + overlayTargetBounds.width; 258 break; 259 default: 260 throw new IllegalArgumentException("Unknown value for center [" + center + "]"); 261 } 262 Dimension size = overlay.getPreferredSize(); 263 Rectangle newBound = new Rectangle(centerX - (size.width / 2), centerY - (size.height / 2), size.width, 264 size.height); 265 Rectangle visibleRect = findLargestVisibleRectFor(newBound); 266 267 int offsetx = 0; 268 int offsety = 0; 269 270 if (visibleRect != null) 271 { 272 if (newBound.y < visibleRect.y) 273 { 274 offsety += visibleRect.y - newBound.y; 275 } 276 if (newBound.x < visibleRect.x) 277 { 278 offsetx += visibleRect.x - newBound.x; 279 } 280 newBound = newBound.intersection(visibleRect); 281 } 282 else 283 { 284 newBound.width = newBound.height = 0; 285 } 286 Point pt = SwingUtilities.convertPoint(overlayTarget, newBound.x, newBound.y, layeredPane); 287 newBound.x = pt.x; 288 newBound.y = pt.y; 289 setOverlayBounds(newBound, offsetx, offsety); 290 } 291 292 /** 293 * Searches up the component hierarchy to find the largest possible visible 294 * rect that can enclose the entire rectangle. 295 * 296 * @param overlayRect rectangle whose largest enclosing visible rect to find 297 * @return largest enclosing visible rect for the specified rectangle 298 */ 299 private Rectangle findLargestVisibleRectFor(final Rectangle overlayRect) 300 { 301 Rectangle visibleRect = null; 302 int curxoffset = 0; 303 int curyoffset = 0; 304 if (overlayTarget == null) 305 { 306 return null; 307 } 308 309 JComponent comp = overlayTarget; 310 do 311 { 312 313 visibleRect = comp.getVisibleRect(); 314 visibleRect.x -= curxoffset; 315 visibleRect.y -= curyoffset; 316 if (visibleRect.contains(overlayRect)) 317 { 318 return visibleRect; 319 } 320 curxoffset += comp.getX(); 321 curyoffset += comp.getY(); 322 323 comp = comp.getParent() instanceof JComponent ? (JComponent) comp.getParent() : null; 324 } 325 while (comp != null && !(comp instanceof JViewport) && !(comp instanceof JScrollPane)); 326 327 328 return visibleRect; 329 } 330 331 private void setOverlayBounds(Rectangle newBounds, int xoffset, int yoffset) 332 { 333 final Dimension preferred = overlay.getPreferredSize(); 334 final Rectangle overlayBounds = new Rectangle(-xoffset, -yoffset, preferred.width, preferred.height); 335 if (!overlayBounds.equals(overlay.getBounds())) 336 { 337 overlay.setBounds(overlayBounds); 338 } 339 if (!newBounds.equals(overlayClipper.getBounds())) 340 { 341 overlayClipper.setBounds(newBounds); 342 } 343 } 344 345 void hideOverlay() 346 { 347 setOverlayBounds(new Rectangle(0, 0, 0, 0), 0, 0); 348 } 349 350 void removeOverlay() 351 { 352 if (overlay.getParent() != overlayClipper && overlay.getParent() != null) 353 { 354 overlay.getParent().remove(overlay); 355 } 356 if (overlayClipper.getParent() != null) 357 { 358 overlayClipper.getParent().remove(overlayClipper); 359 } 360 } 361 362 private Container overlayCapableParent; 363 364 protected Container getOverlayCapableParent(JComponent component) 365 { 366 //if (overlayCapableParent != null) 367 // return overlayCapableParent; 368 Component overlayChild = component; 369 overlayCapableParent = component.getParent(); 370 if (overlay instanceof MayHaveMessagableTab) 371 { 372 MessagableTabbedPane tabbedPane; 373 while (overlayCapableParent != null && !(overlayCapableParent instanceof JRootPane)) 374 { 375 if (overlayCapableParent instanceof MessagableTabbedPane) 376 { 377 tabbedPane = (MessagableTabbedPane) overlayCapableParent; 378 int tabIndex = tabbedPane.indexOfComponent(overlayChild); 379 ((MayHaveMessagableTab) overlay).setMessagableTab(tabbedPane, tabIndex); 380 } 381 382 overlayChild = overlayCapableParent; 383 overlayCapableParent = overlayCapableParent.getParent(); 384 } 385 } 386 else 387 { 388 while (overlayCapableParent != null && !(overlayCapableParent instanceof JRootPane)) 389 { 390 overlayCapableParent = overlayCapableParent.getParent(); 391 } 392 } 393 return overlayCapableParent; 394 } 395 396 protected JLayeredPane getLayeredPane(Container overlayCapableParent) 397 { 398 if (overlayCapableParent instanceof JRootPane) 399 { 400 return ((JRootPane) overlayCapableParent).getLayeredPane(); 401 } 402 else 403 { 404 throw new IllegalArgumentException("Don't know how to handle parent of type [" 405 + overlayCapableParent.getClass().getName() + "]."); 406 } 407 } 408 409 410 public static class SingleComponentLayoutManager implements LayoutManager 411 { 412 private Component singleComponent; 413 414 public SingleComponentLayoutManager(Component singleComponent) 415 { 416 this.singleComponent = singleComponent; 417 } 418 419 public void removeLayoutComponent(Component comp) 420 { 421 } 422 423 public void addLayoutComponent(String name, Component comp) 424 { 425 } 426 427 public void layoutContainer(Container parent) 428 { 429 // Fix 5/12/06 AlD: we don't need to base this on the 430 // preferred size of the singleComponent or the extentSize 431 // of the viewport because the viewport will have already resized 432 // the JLayeredPane and taken everything else into consideration. 433 // It will have also honored the Scrollable flags, which is 434 // something the original code here did not do. 435 singleComponent.setBounds(0, 0, parent.getWidth(), parent.getHeight()); 436 } 437 438 public Dimension minimumLayoutSize(Container parent) 439 { 440 return singleComponent.getMinimumSize(); 441 } 442 443 public Dimension preferredLayoutSize(Container parent) 444 { 445 return singleComponent.getPreferredSize(); 446 } 447 } 448 449 class OverlayUpdater implements Runnable 450 { 451 452 public void run() 453 { 454 try 455 { 456 Container overlayCapableParent = getOverlayCapableParent(overlayTarget); 457 if (overlayCapableParent == null) 458 { 459 removeOverlay(); 460 } 461 else if (!overlayTarget.isShowing() || !overlay.isVisible()) 462 { 463 hideOverlay(); 464 } 465 else 466 { 467 JLayeredPane layeredPane = getLayeredPane(overlayCapableParent); 468 if (layeredPane.isVisible() && layeredPane.isShowing()) 469 { 470 putOverlay(layeredPane); 471 positionOverlay(layeredPane); 472 } 473 } 474 } 475 finally 476 { 477 isUpdating = false; 478 } 479 } 480 } 481 }