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 }