001/* 002 * $Id: BasicHeaderUI.java 3927 2011-02-22 16:34:11Z kleopatra $ 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.plaf.basic; 023 024import java.awt.Color; 025import java.awt.Container; 026import java.awt.Font; 027import java.awt.GradientPaint; 028import java.awt.Graphics; 029import java.awt.GridBagConstraints; 030import java.awt.GridBagLayout; 031import java.awt.Insets; 032import java.awt.event.HierarchyBoundsAdapter; 033import java.awt.event.HierarchyBoundsListener; 034import java.awt.event.HierarchyEvent; 035import java.beans.PropertyChangeEvent; 036import java.beans.PropertyChangeListener; 037import java.util.logging.Logger; 038 039import javax.swing.JComponent; 040import javax.swing.JLabel; 041import javax.swing.UIManager; 042import javax.swing.plaf.ComponentUI; 043import javax.swing.plaf.UIResource; 044import javax.swing.plaf.basic.BasicHTML; 045import javax.swing.text.View; 046 047import org.jdesktop.swingx.JXHeader; 048import org.jdesktop.swingx.JXLabel; 049import org.jdesktop.swingx.JXHeader.IconPosition; 050import org.jdesktop.swingx.JXLabel.MultiLineSupport; 051import org.jdesktop.swingx.painter.MattePainter; 052import org.jdesktop.swingx.painter.Painter; 053import org.jdesktop.swingx.plaf.HeaderUI; 054import org.jdesktop.swingx.plaf.PainterUIResource; 055import org.jdesktop.swingx.plaf.UIManagerExt; 056 057/** 058 * Base implementation of <code>Header</code> UI. <p> 059 * 060 * PENDING JW: This implementation is unusual in that it does not keep a reference 061 * to the component it controls. Typically, such is only the case if the ui is 062 * shared between instances. Historical? A consequence is that the un/install methods 063 * need to carry the header as parameter. Which looks funny when at the same time 064 * the children of the header are instance fields in this. Should think about cleanup: 065 * either get rid off the instance fields here, or reference the header and remove 066 * the param (would break subclasses).<p> 067 * 068 * PENDING JW: keys for uidefaults are inconsistent - most have prefix "JXHeader." while 069 * defaultIcon has prefix "Header." <p> 070 * 071 * @author rbair 072 * @author rah003 073 * @author Jeanette Winzenburg 074 */ 075public class BasicHeaderUI extends HeaderUI { 076 @SuppressWarnings("unused") 077 private static final Logger LOG = Logger.getLogger(BasicHeaderUI.class 078 .getName()); 079 // Implementation detail. Neeeded to expose getMultiLineSupport() method to allow restoring view 080 // lost after LAF switch 081 protected class DescriptionPane extends JXLabel { 082 @Override 083 public void paint(Graphics g) { 084 // switch off jxlabel default antialiasing 085 // JW: that cost me dearly to track down - it's the default foreground painter 086 // which is an AbstractPainter which has _global_ antialiased on by default 087 // and here the _text_ antialiased is turned off 088 // changed JXLabel default foregroundPainter to have antialiasing false by 089 // default, so remove interference here 090 // part of fix for #920 - the other part is in JXLabel, fix 1164 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 @Override 374 public void propertyChange(PropertyChangeEvent evt) { 375 onPropertyChange(header, evt.getPropertyName(), evt.getOldValue(), evt.getNewValue()); 376 } 377 }; 378 boundsListener = new HierarchyBoundsAdapter() { 379 @Override 380 public void ancestorResized(HierarchyEvent e) { 381 if (header == e.getComponent()) { 382 View v = (View) descriptionPane.getClientProperty(BasicHTML.propertyKey); 383 // view might get lost on LAF change ... 384 if (v == null) { 385 descriptionPane.putClientProperty(BasicHTML.propertyKey, 386 MultiLineSupport.createView(descriptionPane)); 387 v = (View) descriptionPane.getClientProperty(BasicHTML.propertyKey); 388 } 389 if (v != null) { 390 Container tla = header.getTopLevelAncestor(); 391 if (tla == null) { 392 tla = header.getParent(); 393 while (tla.getParent() != null) { 394 tla = tla.getParent(); 395 } 396 } 397 int h = Math.max(descriptionPane.getHeight(), tla.getHeight()); 398 int w = Math.min(tla.getWidth(), header.getParent().getWidth()); 399 // 35 = description pane insets, TODO: obtain dynamically 400 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; 401 v.setSize(w, h); 402 descriptionPane.setSize(w, (int) Math.ceil(v.getPreferredSpan(View.Y_AXIS))); 403 } 404 } 405 }}; 406 header.addPropertyChangeListener(propListener); 407 header.addHierarchyBoundsListener(boundsListener); 408 } 409 410 protected void uninstallListeners(JXHeader h) { 411 h.removePropertyChangeListener(propListener); 412 h.removeHierarchyBoundsListener(boundsListener); 413 } 414 415 protected void onPropertyChange(JXHeader h, String propertyName, Object oldValue, final Object newValue) { 416 if ("title".equals(propertyName)) { 417 titleLabel.setText(h.getTitle()); 418 } else if ("description".equals(propertyName)) { 419 descriptionPane.setText(h.getDescription()); 420 } else if ("icon".equals(propertyName)) { 421 imagePanel.setIcon(h.getIcon()); 422 } else if ("enabled".equals(propertyName)) { 423 boolean enabled = h.isEnabled(); 424 titleLabel.setEnabled(enabled); 425 descriptionPane.setEnabled(enabled); 426 imagePanel.setEnabled(enabled); 427 } else if ("titleFont".equals(propertyName)) { 428 titleLabel.setFont((Font)newValue); 429 } else if ("descriptionFont".equals(propertyName)) { 430 descriptionPane.setFont((Font)newValue); 431 } else if ("titleForeground".equals(propertyName)) { 432 titleLabel.setForeground((Color)newValue); 433 } else if ("descriptionForeground".equals(propertyName)) { 434 descriptionPane.setForeground((Color)newValue); 435 } else if ("iconPosition".equals(propertyName)) { 436 resetLayout(h); 437 } 438 } 439 440 private void resetLayout(JXHeader h) { 441 h.remove(titleLabel); 442 h.remove(descriptionPane); 443 h.remove(imagePanel); 444 if (h.getIconPosition() == null || h.getIconPosition() == IconPosition.RIGHT) { 445 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)); 446 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)); 447 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)); 448 } else { 449 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)); 450 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)); 451 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)); 452 } 453 } 454 455 456 457 protected Painter createBackgroundPainter() { 458 MattePainter p = new MattePainter(new GradientPaint(0, 0, gradientLightColor, 1, 0, gradientDarkColor)); 459 p.setPaintStretched(true); 460 return new PainterUIResource(p); 461 } 462 463 464}