001 /* 002 * $Id: BasicHeaderUI.java 3166 2009-01-02 13:27:18Z rah003 $ 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 022 package org.jdesktop.swingx.plaf.basic; 023 024 import java.awt.Color; 025 import java.awt.Container; 026 import java.awt.Font; 027 import java.awt.GradientPaint; 028 import java.awt.Graphics; 029 import java.awt.Graphics2D; 030 import java.awt.GridBagConstraints; 031 import java.awt.GridBagLayout; 032 import java.awt.Insets; 033 import java.awt.RenderingHints; 034 import java.awt.event.HierarchyBoundsAdapter; 035 import java.awt.event.HierarchyBoundsListener; 036 import java.awt.event.HierarchyEvent; 037 import java.beans.PropertyChangeEvent; 038 import java.beans.PropertyChangeListener; 039 import java.util.logging.Logger; 040 041 import javax.swing.JComponent; 042 import javax.swing.JLabel; 043 import javax.swing.UIManager; 044 import javax.swing.plaf.ComponentUI; 045 import javax.swing.plaf.UIResource; 046 import javax.swing.plaf.basic.BasicHTML; 047 import javax.swing.text.View; 048 049 import org.jdesktop.swingx.JXHeader; 050 import org.jdesktop.swingx.JXLabel; 051 import org.jdesktop.swingx.JXHeader.IconPosition; 052 import org.jdesktop.swingx.painter.MattePainter; 053 import org.jdesktop.swingx.painter.Painter; 054 import org.jdesktop.swingx.plaf.HeaderUI; 055 import org.jdesktop.swingx.plaf.PainterUIResource; 056 import org.jdesktop.swingx.plaf.UIManagerExt; 057 058 /** 059 * Base implementation of <code>Header</code> UI. <p> 060 * 061 * PENDING JW: This implementation is unusual in that it does not keep a reference 062 * to the component it controls. Typically, such is only the case if the ui is 063 * shared between instances. Historical? A consequence is that the un/install methods 064 * need to carry the header as parameter. Which looks funny when at the same time 065 * the children of the header are instance fields in this. Should think about cleanup: 066 * either get rid off the instance fields here, or reference the header and remove 067 * the param (would break subclasses).<p> 068 * 069 * PENDING JW: keys for uidefaults are inconsistent - most have prefix "JXHeader." while 070 * defaultIcon has prefix "Header." <p> 071 * 072 * @author rbair 073 * @author rah003 074 * @author Jeanette Winzenburg 075 */ 076 public class BasicHeaderUI extends HeaderUI { 077 @SuppressWarnings("unused") 078 private static final Logger LOG = Logger.getLogger(BasicHeaderUI.class 079 .getName()); 080 // Implementation detail. Neeeded to expose getMultiLineSupport() method to allow restoring view 081 // lost after LAF switch 082 protected class DescriptionPane extends JXLabel { 083 @Override 084 public void paint(Graphics g) { 085 // LOG.info(getText() + ": all hints " + ((Graphics2D)g).getRenderingHints() 086 // + "\n " + ": aliased " + ((Graphics2D)g).getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING)); 087 // switch off jxlabel default antialiasing 088 // JW: that cost me dearly to track down - it's the default foreground painter 089 // which is an AbstractPainter which has _global_ antialiased on by default 090 // and here the _text_ antialiased is turned off 091 ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, 092 RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); 093 super.paint(g); 094 } 095 096 @Override 097 public MultiLineSupport getMultiLineSupport() { 098 return super.getMultiLineSupport(); 099 } 100 } 101 102 protected JLabel titleLabel; 103 protected DescriptionPane descriptionPane; 104 protected JLabel imagePanel; 105 private PropertyChangeListener propListener; 106 private HierarchyBoundsListener boundsListener; 107 private Color gradientLightColor; 108 private Color gradientDarkColor; 109 110 /** Creates a new instance of BasicHeaderUI */ 111 public BasicHeaderUI() { 112 } 113 114 /** 115 * Returns an instance of the UI delegate for the specified component. 116 * Each subclass must provide its own static <code>createUI</code> 117 * method that returns an instance of that UI delegate subclass. 118 * If the UI delegate subclass is stateless, it may return an instance 119 * that is shared by multiple components. If the UI delegate is 120 * stateful, then it should return a new instance per component. 121 * The default implementation of this method throws an error, as it 122 * should never be invoked. 123 */ 124 public static ComponentUI createUI(JComponent c) { 125 return new BasicHeaderUI(); 126 } 127 128 /** 129 * Configures the specified component appropriate for the look and feel. 130 * This method is invoked when the <code>ComponentUI</code> instance is being installed 131 * as the UI delegate on the specified component. This method should 132 * completely configure the component for the look and feel, 133 * including the following: 134 * <ol> 135 * <li>Install any default property values for color, fonts, borders, 136 * icons, opacity, etc. on the component. Whenever possible, 137 * property values initialized by the client program should <i>not</i> 138 * be overridden. 139 * <li>Install a <code>LayoutManager</code> on the component if necessary. 140 * <li>Create/add any required sub-components to the component. 141 * <li>Create/install event listeners on the component. 142 * <li>Create/install a <code>PropertyChangeListener</code> on the component in order 143 * to detect and respond to component property changes appropriately. 144 * <li>Install keyboard UI (mnemonics, traversal, etc.) on the component. 145 * <li>Initialize any appropriate instance data. 146 * </ol> 147 * @param c the component where this UI delegate is being installed 148 * 149 * @see #uninstallUI 150 * @see javax.swing.JComponent#setUI 151 * @see javax.swing.JComponent#updateUI 152 */ 153 @Override 154 public void installUI(JComponent c) { 155 super.installUI(c); 156 assert c instanceof JXHeader; 157 JXHeader header = (JXHeader)c; 158 159 installDefaults(header); 160 installComponents(header); 161 installListeners(header); 162 } 163 164 /** 165 * Reverses configuration which was done on the specified component during 166 * <code>installUI</code>. This method is invoked when this 167 * <code>UIComponent</code> instance is being removed as the UI delegate 168 * for the specified component. This method should undo the 169 * configuration performed in <code>installUI</code>, being careful to 170 * leave the <code>JComponent</code> instance in a clean state (no 171 * extraneous listeners, look-and-feel-specific property objects, etc.). 172 * This should include the following: 173 * <ol> 174 * <li>Remove any UI-set borders from the component. 175 * <li>Remove any UI-set layout managers on the component. 176 * <li>Remove any UI-added sub-components from the component. 177 * <li>Remove any UI-added event/property listeners from the component. 178 * <li>Remove any UI-installed keyboard UI from the component. 179 * <li>Nullify any allocated instance data objects to allow for GC. 180 * </ol> 181 * @param c the component from which this UI delegate is being removed; 182 * this argument is often ignored, 183 * but might be used if the UI object is stateless 184 * and shared by multiple components 185 * 186 * @see #installUI 187 * @see javax.swing.JComponent#updateUI 188 */ 189 @Override 190 public void uninstallUI(JComponent c) { 191 assert c instanceof JXHeader; 192 JXHeader header = (JXHeader)c; 193 194 uninstallListeners(header); 195 uninstallComponents(header); 196 uninstallDefaults(header); 197 } 198 199 /** 200 * Installs default header properties. 201 * <p> 202 * 203 * NOTE: this method is called before the children are created, so must not 204 * try to access any of those!. 205 * 206 * @param header the header to install. 207 */ 208 protected void installDefaults(JXHeader header) { 209 gradientLightColor = UIManagerExt.getColor("JXHeader.startBackground"); 210 if (gradientLightColor == null) { 211 // fallback to white 212 gradientLightColor = Color.WHITE; 213 } 214 gradientDarkColor = UIManagerExt.getColor("JXHeader.background"); 215 // for backwards compatibility (mostly for substance and synthetica, 216 // I suspect) I'll fall back on the "control" color if 217 // JXHeader.background 218 // isn't specified. 219 if (gradientDarkColor == null) { 220 gradientDarkColor = UIManagerExt.getColor("control"); 221 } 222 223 if (isUIInstallable(header.getBackgroundPainter())) { 224 header.setBackgroundPainter(createBackgroundPainter()); 225 } 226 227 // title properties 228 if (isUIInstallable(header.getTitleFont())) { 229 Font titleFont = UIManager.getFont("JXHeader.titleFont"); 230 // fallback to label font 231 header.setTitleFont(titleFont != null ? titleFont : UIManager 232 .getFont("Label.font")); 233 } 234 if (isUIInstallable(header.getTitleForeground())) { 235 Color titleForeground = UIManagerExt 236 .getColor("JXHeader.titleForeground"); 237 // fallback to label foreground 238 header.setTitleForeground(titleForeground != null ? titleForeground 239 : UIManagerExt.getColor("Label.foreground")); 240 } 241 242 // description properties 243 if (isUIInstallable(header.getDescriptionFont())) { 244 Font descFont = UIManager.getFont("JXHeader.descriptionFont"); 245 // fallback to label font 246 header.setDescriptionFont(descFont != null ? descFont : UIManager 247 .getFont("Label.font")); 248 } 249 if (isUIInstallable(header.getDescriptionForeground())) { 250 Color descForeground = UIManagerExt 251 .getColor("JXHeader.descriptionForeground"); 252 // fallback to label foreground 253 header.setDescriptionForeground(descForeground != null ? descForeground 254 : UIManagerExt.getColor("Label.foreground")); 255 } 256 257 // icon label properties 258 if (isUIInstallable(header.getIcon())) { 259 header.setIcon(UIManager.getIcon("Header.defaultIcon")); 260 } 261 } 262 263 /** 264 * Uninstalls the given header's default properties. This implementation 265 * does nothing. 266 * 267 * @param h the header to ininstall the properties from. 268 */ 269 protected void uninstallDefaults(JXHeader h) { 270 } 271 272 /** 273 * Creates, configures, adds contained components. 274 * PRE: header's default properties must be set before calling this. 275 * 276 * @param header the header to install the components into. 277 */ 278 protected void installComponents(JXHeader header) { 279 titleLabel = new JLabel(); 280 descriptionPane = new DescriptionPane(); 281 imagePanel = new JLabel(); 282 installComponentDefaults(header); 283 header.setLayout(new GridBagLayout()); 284 resetLayout(header); 285 } 286 287 /** 288 * Unconfigures, removes and nulls contained components. 289 * 290 * @param header the header to install the components into. 291 */ 292 protected void uninstallComponents(JXHeader header) { 293 uninstallComponentDefaults(header); 294 header.remove(titleLabel); 295 header.remove(descriptionPane); 296 header.remove(imagePanel); 297 titleLabel = null; 298 descriptionPane = null; 299 imagePanel = null; 300 } 301 302 /** 303 * Configures the component default properties from the given header. 304 * 305 * @param header the header to install the components into. 306 */ 307 protected void installComponentDefaults(JXHeader header) { 308 // JW: force a not UIResource for properties which have ui default values 309 // like color, font, ?? 310 titleLabel.setFont(getAsNotUIResource(header.getTitleFont())); 311 titleLabel.setForeground(getAsNotUIResource(header.getTitleForeground())); 312 titleLabel.setText(header.getTitle()); 313 descriptionPane.setFont(getAsNotUIResource(header.getDescriptionFont())); 314 descriptionPane.setForeground(getAsNotUIResource(header.getDescriptionForeground())); 315 descriptionPane.setOpaque(false); 316 descriptionPane.setText(header.getDescription()); 317 descriptionPane.setLineWrap(true); 318 319 imagePanel.setIcon(header.getIcon()); 320 321 } 322 323 /** 324 * Returns a Font based on the param which is not of type UIResource. 325 * 326 * @param font the base font 327 * @return a font not of type UIResource, may be null. 328 */ 329 private Font getAsNotUIResource(Font font) { 330 if (!(font instanceof UIResource)) return font; 331 // PENDING JW: correct way to create another font instance? 332 return font.deriveFont(font.getAttributes()); 333 } 334 335 /** 336 * Returns a Color based on the param which is not of type UIResource. 337 * 338 * @param color the base color 339 * @return a color not of type UIResource, may be null. 340 */ 341 private Color getAsNotUIResource(Color color) { 342 if (!(color instanceof UIResource)) return color; 343 // PENDING JW: correct way to create another color instance? 344 float[] rgb = color.getRGBComponents(null); 345 return new Color(rgb[0], rgb[1], rgb[2], rgb[3]); 346 } 347 348 /** 349 * Checks and returns whether the given property should be replaced 350 * by the UI's default value.<p> 351 * 352 * PENDING JW: move as utility method ... where? 353 * 354 * @param property the property to check. 355 * @return true if the given property should be replaced by the UI#s 356 * default value, false otherwise. 357 */ 358 private boolean isUIInstallable(Object property) { 359 return (property == null) || (property instanceof UIResource); 360 } 361 362 /** 363 * Uninstalls component defaults. This implementation does nothing. 364 * 365 * @param header the header to uninstall from. 366 */ 367 protected void uninstallComponentDefaults(JXHeader header) { 368 } 369 370 371 protected void installListeners(final JXHeader header) { 372 propListener = new PropertyChangeListener() { 373 public void propertyChange(PropertyChangeEvent evt) { 374 onPropertyChange(header, evt.getPropertyName(), evt.getOldValue(), evt.getNewValue()); 375 } 376 }; 377 boundsListener = new HierarchyBoundsAdapter() { 378 @Override 379 public void ancestorResized(HierarchyEvent e) { 380 if (header == e.getComponent()) { 381 View v = (View) descriptionPane.getClientProperty(BasicHTML.propertyKey); 382 // view might get lost on LAF change ... 383 if (v == null) { 384 descriptionPane.putClientProperty(BasicHTML.propertyKey, 385 descriptionPane.getMultiLineSupport().createView(descriptionPane)); 386 v = (View) descriptionPane.getClientProperty(BasicHTML.propertyKey); 387 } 388 if (v != null) { 389 Container tla = header.getTopLevelAncestor(); 390 if (tla == null) { 391 tla = header.getParent(); 392 while (tla.getParent() != null) { 393 tla = tla.getParent(); 394 } 395 } 396 int h = Math.max(descriptionPane.getHeight(), tla.getHeight()); 397 int w = Math.min(tla.getWidth(), header.getParent().getWidth()); 398 // 35 = description pane insets, TODO: obtain dynamically 399 w -= 35 + header.getInsets().left + header.getInsets().right + descriptionPane.getInsets().left + descriptionPane.getInsets().right + imagePanel.getInsets().left + imagePanel.getInsets().right + imagePanel.getWidth() + descriptionPane.getBounds().x; 400 v.setSize(w, h); 401 descriptionPane.setSize(w, (int) Math.ceil(v.getPreferredSpan(View.Y_AXIS))); 402 } 403 } 404 }}; 405 header.addPropertyChangeListener(propListener); 406 header.addHierarchyBoundsListener(boundsListener); 407 } 408 409 protected void uninstallListeners(JXHeader h) { 410 h.removePropertyChangeListener(propListener); 411 h.removeHierarchyBoundsListener(boundsListener); 412 } 413 414 protected void onPropertyChange(JXHeader h, String propertyName, Object oldValue, final Object newValue) { 415 if ("title".equals(propertyName)) { 416 titleLabel.setText(h.getTitle()); 417 } else if ("description".equals(propertyName)) { 418 descriptionPane.setText(h.getDescription()); 419 } else if ("icon".equals(propertyName)) { 420 imagePanel.setIcon(h.getIcon()); 421 } else if ("enabled".equals(propertyName)) { 422 boolean enabled = h.isEnabled(); 423 titleLabel.setEnabled(enabled); 424 descriptionPane.setEnabled(enabled); 425 imagePanel.setEnabled(enabled); 426 } else if ("titleFont".equals(propertyName)) { 427 titleLabel.setFont((Font)newValue); 428 } else if ("descriptionFont".equals(propertyName)) { 429 descriptionPane.setFont((Font)newValue); 430 } else if ("titleForeground".equals(propertyName)) { 431 titleLabel.setForeground((Color)newValue); 432 } else if ("descriptionForeground".equals(propertyName)) { 433 descriptionPane.setForeground((Color)newValue); 434 } else if ("iconPosition".equals(propertyName)) { 435 resetLayout(h); 436 } 437 } 438 439 private void resetLayout(JXHeader h) { 440 h.remove(titleLabel); 441 h.remove(descriptionPane); 442 h.remove(imagePanel); 443 if (h.getIconPosition() == null || h.getIconPosition() == IconPosition.RIGHT) { 444 h.add(titleLabel, new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(12, 12, 0, 11), 0, 0)); 445 h.add(descriptionPane, new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(0, 24, 12, 11), 0, 0)); 446 h.add(imagePanel, new GridBagConstraints(1, 0, 1, 2, 0.0, 1.0, GridBagConstraints.FIRST_LINE_END, GridBagConstraints.NONE, new Insets(12, 0, 11, 11), 0, 0)); 447 } else { 448 h.add(titleLabel, new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(12, 12, 0, 11), 0, 0)); 449 h.add(descriptionPane, new GridBagConstraints(1, 1, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(0, 24, 12, 11), 0, 0)); 450 h.add(imagePanel, new GridBagConstraints(0, 0, 1, 2, 0.0, 1.0, GridBagConstraints.FIRST_LINE_END, GridBagConstraints.NONE, new Insets(12, 11, 0, 11), 0, 0)); 451 } 452 } 453 454 455 456 protected Painter createBackgroundPainter() { 457 MattePainter p = new MattePainter(new GradientPaint(0, 0, gradientLightColor, 1, 0, gradientDarkColor)); 458 p.setPaintStretched(true); 459 return new PainterUIResource(p); 460 } 461 462 463 }