001/* 002 * $Id: JXPanel.java 4158 2012-02-03 18:29:40Z kschaefe $ 003 * 004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle, 005 * Santa Clara, California 95054, U.S.A. All rights reserved. 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this library; if not, write to the Free Software 019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 020 */ 021 022package org.jdesktop.swingx; 023 024import java.awt.AlphaComposite; 025import java.awt.Color; 026import java.awt.Component; 027import java.awt.Composite; 028import java.awt.Dimension; 029import java.awt.Graphics; 030import java.awt.Graphics2D; 031import java.awt.LayoutManager; 032import java.awt.Rectangle; 033import java.awt.image.BufferedImage; 034import java.beans.PropertyChangeEvent; 035import java.beans.PropertyChangeListener; 036 037import javax.swing.JPanel; 038import javax.swing.RepaintManager; 039import javax.swing.Scrollable; 040import javax.swing.SwingConstants; 041 042import org.jdesktop.beans.JavaBean; 043import org.jdesktop.swingx.painter.AbstractPainter; 044import org.jdesktop.swingx.painter.Painter; 045import org.jdesktop.swingx.util.GraphicsUtilities; 046 047/** 048 * <p> 049 * An extended {@code JPanel} that provides additional features. First, the 050 * component is {@code Scrollable}, using reasonable defaults. Second, the 051 * component is alpha-channel enabled. This means that the {@code JXPanel} can 052 * be made fully or partially transparent. Finally, {@code JXPanel} has support 053 * for {@linkplain Painter painters}. 054 * </p> 055 * <p> 056 * A transparency example, this following code will show the black background of 057 * the parent: 058 * 059 * <pre> 060 * JXPanel panel = new JXPanel(); 061 * panel.add(new JButton("Push Me")); 062 * panel.setAlpha(.5f); 063 * 064 * container.setBackground(Color.BLACK); 065 * container.add(panel); 066 * </pre> 067 * 068 * </p> 069 * <p> 070 * A painter example, this following code will show how to add a simple painter: 071 * 072 * <pre> 073 * JXPanel panel = new JXPanel(); 074 * panel.setBackgroundPainter(new PinstripePainter()); 075 * </pre> 076 * 077 * </p> 078 * 079 * @author rbair 080 * @see Scrollable 081 * @see Painter 082 */ 083@JavaBean 084@SuppressWarnings("nls") 085public class JXPanel extends JPanel implements AlphaPaintable, BackgroundPaintable, Scrollable { 086// private boolean scrollableTracksViewportHeight = true; 087// private boolean scrollableTracksViewportWidth = true; 088 089 private ScrollableSizeHint scrollableWidthHint = ScrollableSizeHint.FIT; 090 private ScrollableSizeHint scrollableHeightHint = ScrollableSizeHint.FIT; 091 092 /** 093 * The alpha level for this component. 094 */ 095 private float alpha = 1.0f; 096 /** 097 * If the old alpha value was 1.0, I keep track of the opaque setting because 098 * a translucent component is not opaque, but I want to be able to restore 099 * opacity to its default setting if the alpha is 1.0. Honestly, I don't know 100 * if this is necessary or not, but it sounded good on paper :) 101 * <p>TODO: Check whether this variable is necessary or not</p> 102 */ 103 private boolean oldOpaque; 104 /** 105 * Indicates whether this component should inherit its parent alpha value 106 */ 107 private boolean inheritAlpha = true; 108 /** 109 * Specifies the Painter to use for painting the background of this panel. 110 * If no painter is specified, the normal painting routine for JPanel 111 * is called. Old behavior is also honored for the time being if no 112 * backgroundPainter is specified 113 */ 114 @SuppressWarnings("rawtypes") 115 private Painter backgroundPainter; 116 117 private boolean paintBorderInsets = true; 118 119 /** 120 * The listener installed on the current backgroundPainter, if any. 121 */ 122 private PropertyChangeListener painterChangeListener; 123 124 /** 125 * Creates a new <code>JXPanel</code> with a double buffer 126 * and a flow layout. 127 */ 128 public JXPanel() { 129 } 130 131 /** 132 * Creates a new <code>JXPanel</code> with <code>FlowLayout</code> 133 * and the specified buffering strategy. 134 * If <code>isDoubleBuffered</code> is true, the <code>JXPanel</code> 135 * will use a double buffer. 136 * 137 * @param isDoubleBuffered a boolean, true for double-buffering, which 138 * uses additional memory space to achieve fast, flicker-free 139 * updates 140 */ 141 public JXPanel(boolean isDoubleBuffered) { 142 super(isDoubleBuffered); 143 } 144 145 /** 146 * Create a new buffered JXPanel with the specified layout manager 147 * 148 * @param layout the LayoutManager to use 149 */ 150 public JXPanel(LayoutManager layout) { 151 super(layout); 152 } 153 154 /** 155 * Creates a new JXPanel with the specified layout manager and buffering 156 * strategy. 157 * 158 * @param layout the LayoutManager to use 159 * @param isDoubleBuffered a boolean, true for double-buffering, which 160 * uses additional memory space to achieve fast, flicker-free 161 * updates 162 */ 163 public JXPanel(LayoutManager layout, boolean isDoubleBuffered) { 164 super(layout, isDoubleBuffered); 165 } 166 167 /** 168 * {@inheritDoc} 169 */ 170 @Override 171 public float getAlpha() { 172 return alpha; 173 } 174 175 /** 176 * {@inheritDoc} 177 */ 178 @Override 179 public void setAlpha(float alpha) { 180 if (alpha < 0f || alpha > 1f) { 181 throw new IllegalArgumentException("invalid alpha value " + alpha); 182 } 183 184 float oldValue = getAlpha(); 185 this.alpha = alpha; 186 187 if (getAlpha() < 1f) { 188 if (oldValue == 1) { 189 //it used to be 1, but now is not. Save the oldOpaque 190 oldOpaque = isOpaque(); 191 setOpaque(false); 192 } 193 194 installRepaintManager(); 195 } else { 196 uninstallRepaintManager(); 197 198 //restore the oldOpaque if it was true (since opaque is false now) 199 if (oldOpaque) { 200 setOpaque(true); 201 } 202 } 203 204 firePropertyChange("alpha", oldValue, getAlpha()); 205 repaint(); 206 } 207 208 void installRepaintManager() { 209 RepaintManager manager = RepaintManager.currentManager(this); 210 RepaintManager trm = SwingXUtilities.getTranslucentRepaintManager(manager); 211 RepaintManager.setCurrentManager(trm); 212 } 213 214 void uninstallRepaintManager() { 215 //TODO uninstall TranslucentRepaintManager when no more non-opaque JXPanel's exist 216 } 217 218 /** 219 * {@inheritDoc} 220 */ 221 @Override 222 public float getEffectiveAlpha() { 223 float a = getAlpha(); 224 225 if (isInheritAlpha()) { 226 for (Component c = getParent(); c != null; c = c.getParent()) { 227 if (c instanceof AlphaPaintable) { 228 a = Math.min(((AlphaPaintable) c).getEffectiveAlpha(), a); 229 break; 230 } 231 } 232 } 233 234 return a; 235 } 236 237 /** 238 * {@inheritDoc} 239 */ 240 @Override 241 public boolean isInheritAlpha() { 242 return inheritAlpha; 243 } 244 245 /** 246 * {@inheritDoc} 247 */ 248 @Override 249 public void setInheritAlpha(boolean val) { 250 boolean oldValue = isInheritAlpha(); 251 inheritAlpha = val; 252 firePropertyChange("inheritAlpha", oldValue, isInheritAlpha()); 253 } 254 255 /** 256 * Sets the horizontal sizing hint. The hint is used by the Scrollable implementation 257 * to service the getScrollableTracksWidth. 258 * 259 * @param hint the horizontal sizing hint, must not be null 260 * and must be vertical. 261 * 262 * @throws IllegalArgumentException if track not horizontal 263 * @throws NullPointerException if null 264 * 265 * @see #setScrollableHeightHint(ScrollableSizeHint) 266 * @see ScrollableSizeHint 267 */ 268 public final void setScrollableWidthHint(ScrollableSizeHint hint) { 269 if (!hint.isHorizontalCompatible()) throw 270 new IllegalArgumentException("track must be horizontal, but was " + hint); 271 ScrollableSizeHint oldValue = getScrollableWidthHint(); 272 if (oldValue == hint) return; 273 this.scrollableWidthHint = hint; 274 revalidate(); 275 firePropertyChange("scrollableWidthHint", oldValue, getScrollableWidthHint()); 276 } 277 278 279 /** 280 * Sets the vertical sizing hint. The hint is used by the Scrollable implementation 281 * to service the getScrollableTracksHeight. 282 * 283 * @param hint the vertical sizing hint, must not be null 284 * and must be vertical. 285 * 286 * @throws IllegalArgumentException if track not vertical 287 * @throws NullPointerException if null 288 * 289 * @see #setScrollableWidthHint(ScrollableSizeHint) 290 * @see ScrollableSizeHint 291 */ 292 public final void setScrollableHeightHint(ScrollableSizeHint hint) { 293 if (!hint.isVerticalCompatible()) throw 294 new IllegalArgumentException("track must be vertical, but was " + hint); 295 ScrollableSizeHint oldValue = getScrollableHeightHint(); 296 if (oldValue == hint) return; 297 this.scrollableHeightHint = hint; 298 revalidate(); 299 firePropertyChange("scrollableHeightHint", oldValue, getScrollableHeightHint()); 300 } 301 302 protected ScrollableSizeHint getScrollableWidthHint() { 303 return scrollableWidthHint; 304 } 305 306 protected ScrollableSizeHint getScrollableHeightHint() { 307 return scrollableHeightHint; 308 309 } 310 311 312 /** 313 * {@inheritDoc} 314 */ 315 @Override 316 public boolean getScrollableTracksViewportHeight() { 317 return scrollableHeightHint.getTracksParentSize(this, SwingConstants.VERTICAL); 318 } 319 320 /** 321 * {@inheritDoc} 322 */ 323 @Override 324 public boolean getScrollableTracksViewportWidth() { 325 return scrollableWidthHint.getTracksParentSize(this, SwingConstants.HORIZONTAL); 326 } 327 328 /** 329 * {@inheritDoc} 330 */ 331 @Override 332 public Dimension getPreferredScrollableViewportSize() { 333 return getPreferredSize(); 334 } 335 336 /** 337 * {@inheritDoc} 338 */ 339 @Override 340 public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { 341 if (orientation == SwingConstants.VERTICAL) { 342 return visibleRect.height; 343 } else if (orientation == SwingConstants.HORIZONTAL) { 344 return visibleRect.width; 345 } else { 346 throw new IllegalArgumentException("invalid orientation"); //$NON-NLS-1$ 347 } 348 } 349 350 /** 351 * {@inheritDoc} 352 */ 353 @Override 354 public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { 355 return getScrollableBlockIncrement(visibleRect, orientation, direction) / 10; 356 } 357 358 /** 359 * 360 * Sets the vertical size tracking to either ScrollableSizeTrack.FIT or NONE, if the 361 * boolean parameter is true or false, respectively.<p> 362 * 363 * <b>NOTE</b>: this method is kept for backward compatibility only, for full 364 * control use setScrollableHeightHint. 365 * 366 * @param scrollableTracksViewportHeight The scrollableTracksViewportHeight to set. 367 * 368 * @see #setScrollableHeightHint(ScrollableSizeHint) 369 */ 370 public void setScrollableTracksViewportHeight(boolean scrollableTracksViewportHeight) { 371 setScrollableHeightHint(scrollableTracksViewportHeight ? 372 ScrollableSizeHint.FIT : ScrollableSizeHint.NONE); 373 } 374 /** 375 * Sets the horizontal size tracking to either ScrollableSizeTrack.FIT or NONE, if the 376 * boolean parameter is true or false, respectively.<p> 377 * 378 * <b>NOTE</b>: this method is kept for backward compatibility only, for full 379 * control use setScrollableWidthHint. 380 * 381 * 382 * @param scrollableTracksViewportWidth The scrollableTracksViewportWidth to set. 383 * 384 * @see #setScrollableWidthHint(ScrollableSizeHint) 385 */ 386 public void setScrollableTracksViewportWidth(boolean scrollableTracksViewportWidth) { 387 setScrollableWidthHint(scrollableTracksViewportWidth ? 388 ScrollableSizeHint.FIT : ScrollableSizeHint.NONE); 389 } 390 391 /** 392 * Sets the background color for this component by 393 * 394 * @param bg 395 * the desired background <code>Color</code> 396 * @see javax.swing.JComponent#getBackground() 397 * @see #setOpaque(boolean) 398 * 399 * @beaninfo 400 * preferred: true 401 * bound: true 402 * attribute: visualUpdate true 403 * description: The background color of the component. 404 */ 405 @Override 406 public void setBackground(Color bg) { 407 super.setBackground(bg); 408 409 SwingXUtilities.installBackground(this, bg); 410 } 411 412 /** 413 * Sets a Painter to use to paint the background of this JXPanel. 414 * 415 * @param p the new painter 416 * @see #getBackgroundPainter() 417 */ 418 @Override 419 public void setBackgroundPainter(Painter p) { 420 Painter old = getBackgroundPainter(); 421 if (old instanceof AbstractPainter) { 422 ((AbstractPainter<?>) old).removePropertyChangeListener(painterChangeListener); 423 } 424 backgroundPainter = p; 425 if (backgroundPainter instanceof AbstractPainter) { 426 ((AbstractPainter<?>) backgroundPainter).addPropertyChangeListener(getPainterChangeListener()); 427 } 428 firePropertyChange("backgroundPainter", old, getBackgroundPainter()); 429 repaint(); 430 } 431 432 /** 433 * @return a listener for painter change events 434 */ 435 protected PropertyChangeListener getPainterChangeListener() { 436 if (painterChangeListener == null) { 437 painterChangeListener = new PropertyChangeListener() { 438 439 @Override 440 public void propertyChange(PropertyChangeEvent evt) { 441 repaint(); 442 } 443 }; 444 } 445 return painterChangeListener; 446 } 447 448 /** 449 * Returns the current background painter. The default value of this property 450 * is a painter which draws the normal JPanel background according to the current look and feel. 451 * @return the current painter 452 * @see #setBackgroundPainter(Painter) 453 * @see #isPaintBorderInsets() 454 */ 455 @Override 456 public Painter getBackgroundPainter() { 457 return backgroundPainter; 458 } 459 460 /** 461 * Returns true if the background painter should paint where the border is 462 * or false if it should only paint inside the border. This property is 463 * true by default. This property affects the width, height, 464 * and initial transform passed to the background painter. 465 */ 466 @Override 467 public boolean isPaintBorderInsets() { 468 return paintBorderInsets; 469 } 470 471 /** 472 * Sets the paintBorderInsets property. 473 * Set to true if the background painter should paint where the border is 474 * or false if it should only paint inside the border. This property is true by default. 475 * This property affects the width, height, 476 * and initial transform passed to the background painter. 477 * 478 * This is a bound property. 479 */ 480 @Override 481 public void setPaintBorderInsets(boolean paintBorderInsets) { 482 boolean old = this.isPaintBorderInsets(); 483 this.paintBorderInsets = paintBorderInsets; 484 firePropertyChange("paintBorderInsets", old, isPaintBorderInsets()); 485 } 486 487 /** 488 * Overridden paint method to take into account the alpha setting. 489 * 490 * @param g 491 * the <code>Graphics</code> context in which to paint 492 */ 493 @Override 494 public void paint(Graphics g) { 495 //short circuit painting if no transparency 496 if (getAlpha() == 1f) { 497 super.paint(g); 498 } else { 499 //the component is translucent, so we need to render to 500 //an intermediate image before painting 501 // TODO should we cache this image? repaint to same image unless size changes? 502 BufferedImage img = GraphicsUtilities.createCompatibleTranslucentImage(getWidth(), getHeight()); 503 Graphics2D gfx = img.createGraphics(); 504 505 try { 506 super.paint(gfx); 507 } finally { 508 gfx.dispose(); 509 } 510 511 Graphics2D g2d = (Graphics2D) g; 512 Composite oldComp = g2d.getComposite(); 513 514 try { 515 Composite alphaComp = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, getEffectiveAlpha()); 516 g2d.setComposite(alphaComp); 517 //TODO should we cache the image? 518 g2d.drawImage(img, null, 0, 0); 519 } finally { 520 g2d.setComposite(oldComp); 521 } 522 } 523 } 524 525 /** 526 * Overridden to provide Painter support. It will call backgroundPainter.paint() 527 * if it is not null, else it will call super.paintComponent(). 528 * 529 * @param g 530 * the <code>Graphics</code> context in which to paint 531 */ 532 @Override 533 protected void paintComponent(Graphics g) { 534 if (backgroundPainter == null) { 535 super.paintComponent(g); 536 } else { 537 if (isOpaque()) { 538 super.paintComponent(g); 539 } 540 541 Graphics2D g2 = (Graphics2D) g.create(); 542 543 try { 544 SwingXUtilities.paintBackground(this, g2); 545 } finally { 546 g2.dispose(); 547 } 548 549 getUI().paint(g, this); 550 } 551 } 552}