001 /*
002 * $Id: Highlighter.java,v 1.11 2006/05/14 15:55:53 dmouse Exp $
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.decorator;
023
024 import java.awt.Color;
025 import java.awt.Component;
026
027 import javax.swing.BoundedRangeModel;
028 import javax.swing.event.ChangeEvent;
029 import javax.swing.event.ChangeListener;
030 import javax.swing.event.EventListenerList;
031
032 /**
033 * <p><code>Highlighter</code> is a lightweight mechanism to modify the behavior
034 * and attributes of cell renderers such as {@link javax.swing.ListCellRenderer},
035 * {@link javax.swing.table.TableCellRenderer}, and
036 * {@link javax.swing.tree.TreeCellRenderer} in a simple layered fashion.
037 * While cell renderers are split along component lines, highlighters provide a
038 * <em>common interface</em> for decorating cell renderers.
039 * <code>Highlighter</code> achieves this by vectoring access to all component-specific
040 * state and functionality through a {@link ComponentAdapter} object.</p>
041 *
042 * <p>The primary purpose of <code>Highlighter</code> is to decorate a cell
043 * renderer in <em>controlled</em> ways, such as by applying a different color
044 * or font to it. For example, {@link AlternateRowHighlighter} highlights cell
045 * renderers with alternating background colors. In data visualization components
046 * that support multiple columns with potentially different types of data, this
047 * highlighter imparts the same background color consistently across <em>all</em>
048 * columns of the {@link ComponentAdapter#target target} component
049 * regardless of the actual cell renderer registered for any specific column.
050 * Thus, the <code>Highlighter</code> mechanism is orthogonal to the cell
051 * rendering mechanism.</p>
052 *
053 * <p>To use <code>Highlighter</code> you must first set up a
054 * {@link HighlighterPipeline} using an array of <code>Highlighter</code> objects,
055 * and then call setHighlighters() on a data visualization component, passing in
056 * the highligher pipeline. If the array of highlighters is not null and is not
057 * empty, the highlighters are applied to the selected renderer for each cell in
058 * the order they appear in the array.
059 * When it is time to render a cell, the cell renderer is primed as usual, after
060 * which, the {@link Highlighter#highlight highlight} method of the first
061 * highlighter in the {@link HighlighterPipeline} is invoked. The prepared
062 * renderer, and a suitable {@link ComponentAdapter} object is passed to the
063 * <code>highlight</code> method. The highlighter is expected to modify the
064 * renderer in controlled ways, and return the modified renderer (or a substitute)
065 * that is passed to the next highlighter, if any, in the pipeline. The renderer
066 * returned by the <code>highlight</code> method of the last highlighter in the
067 * pipeline is ultimately used to render the cell.</p>
068 *
069 * <p>The <code>Highlighter</code> mechanism enables multiple degrees of
070 * freedom. In addition to specifying the actual cell renderer class, now you
071 * can also specify the number, order, and class of highlighter objects. Using
072 * highlighters is really simple, as shown by the following example:</p>
073 *
074 * <pre>
075 Highlighter[] highlighters = new Highlighter[] {
076 new <b>AlternateRowHighlighter</b>(Color.white,
077 new Color(0xF0, 0xF0, 0xE0), null),
078 new <b>PatternHighlighter</b>(null, Color.red, "^s", 0, 0)
079 };
080
081 HighlighterPipeline highlighterPipeline = new HighlighterPipeline(highlighters);
082 JXTable table = new JXTable();
083 table.setHighlighters(highlighterPipeline);
084 * </pre>
085 *
086 * <p>The above example allocates an array of <code>Highlighter</code> and populates
087 * it with a new {@link AlternateRowHighlighter} and {@link PatternHighlighter}.
088 * The first one in this example highlights all cells in odd rows with a white
089 * background, and all cells in even rows with a silver background, but it does
090 * not specify a foreground color explicitly. The second highlighter does not
091 * specify a background color explicitly, but sets the foreground color to red
092 * <em>if certain conditions are met</em> (see {@link PatternHighlighter} for
093 * more details). In this example, if the cells in the first column of any
094 * row start with the letter 's', then all cells in that row are highlighted with
095 * a red foreground. Also, as mentioned earlier, the highlighters are applied in
096 * the order they appear in the list.</p>
097 *
098 * <p> Highlighters are mutable by default, that is all there properties can be
099 * changed dynamically. If so they fire changeEvents to registered ChangeListeners.
100 * They can be marked as immutable at instantiation time - if so, trying to mutate
101 * all properties will not have any effect, ChangeListeners are not registered and
102 * no events are fired. </p>
103 *
104 * <p> This base class has properties background/foreground and corresponding
105 * selectionBackground/selectionForeground. It will apply those colors "absolutely",
106 * that is without actually computing any derived color. It's up to subclasses to
107 * implement color computation, if desired. </p>
108 *
109 * @author Ramesh Gupta
110 * @author Jeanette Winzenburg
111 *
112 * @see ComponentAdapter
113 * @see javax.swing.ListCellRenderer
114 * @see javax.swing.table.TableCellRenderer
115 * @see javax.swing.tree.TreeCellRenderer
116 */
117 public class Highlighter {
118 /**
119 * Only one <code>ChangeEvent</code> is needed per model instance since the
120 * event's only (read-only) state is the source property. The source
121 * of events generated here is always "this".
122 */
123 protected transient ChangeEvent changeEvent = null;
124
125 /** The listeners waiting for model changes. */
126 protected EventListenerList listenerList = new EventListenerList();
127
128 /** flag to indicate whether the Highlighter is mutable in every respect. */
129 protected final boolean immutable;
130
131 /**
132 * Predefined <code>Highlighter</code> that highlights the background of
133 * each cell with a pastel green "ledger" background color, and is most
134 * effective when the {@link ComponentAdapter#target} component has
135 * horizontal gridlines in <code>Color.cyan.darker()</code> color.
136 *
137 * @deprecated set the component's background color instead!
138 */
139 public final static Highlighter ledgerBackground =
140 new Highlighter(new Color(0xF5, 0xFF, 0xF5), null, true);
141
142 /**
143 * Predefined <code>Highlighter</code> that decorates the background of
144 * each cell with a pastel yellow "notepad" background color, and is most
145 * effective when the {@link ComponentAdapter#target} component has
146 * horizontal gridlines in <code>Color.cyan.darker()</code> color.
147 *
148 * @deprecated set the component's background color instead!
149 */
150 public final static Highlighter notePadBackground =
151 new Highlighter(new Color(0xFF, 0xFF, 0xCC), null, true);
152
153 private Color background = null;
154 private Color foreground = null;
155 private Color selectedBackground = null;
156 private Color selectedForeground = null;
157
158 /**
159 * Default constructor for mutable Highlighter.
160 * Initializes background, foreground, selectedBackground, and
161 * selectedForeground to null.
162 */
163 public Highlighter() {
164 this(null, null);
165 }
166
167 /**
168 * Constructs a mutable <code>Highlighter</code> with the specified
169 * background and foreground colors, selectedBackground and
170 * selectedForeground to null.
171 *
172 * @param cellBackground background color for the renderer, or null,
173 * to compute a suitable background
174 * @param cellForeground foreground color for the renderer, or null,
175 * to compute a suitable foreground
176 */
177 public Highlighter(Color cellBackground, Color cellForeground) {
178 this(cellBackground, cellForeground, false);
179 }
180
181 public Highlighter(Color cellBackground, Color cellForeground, boolean immutable) {
182 this(cellBackground, cellForeground, null, null, immutable);
183 }
184
185 /**
186 * Constructs a mutable <code>Highlighter</code> with the specified
187 * background and foreground colors.
188 *
189 * @param cellBackground background color for the renderer, or null,
190 * to compute a suitable background
191 * @param cellForeground foreground color for the renderer, or null,
192 * to compute a suitable foreground
193 */
194 public Highlighter(Color cellBackground, Color cellForeground,
195 Color selectedBackground, Color selectedForeground) {
196 this(cellBackground, cellForeground, selectedBackground, selectedForeground, false);
197 }
198
199 /**
200 * Constructs a <code>Highlighter</code> with the specified
201 * background and foreground colors with mutability depending on
202 * given flag.
203 *
204 * @param cellBackground background color for the renderer, or null,
205 * to compute a suitable background
206 * @param cellForeground foreground color for the renderer, or null,
207 * to compute a suitable foreground
208 */
209 public Highlighter(Color cellBackground, Color cellForeground,
210 Color selectedBackground, Color selectedForeground, boolean immutable) {
211 this.immutable = immutable;
212 this.background = cellBackground;
213 this.foreground = cellForeground;
214 this.selectedBackground = selectedBackground;
215 this.selectedForeground = selectedForeground;
216 }
217
218 /**
219 * Decorates the specified cell renderer component for the given component
220 * data adapter using highlighters that were previously set for the component.
221 * This method unconditionally invokes {@link #doHighlight doHighlight} with
222 * the same arguments as were passed in.
223 *
224 * @param renderer the cell renderer component that is to be decorated
225 * @param adapter the {@link ComponentAdapter} for this decorate operation
226 * @return the decorated cell renderer component
227 */
228 public Component highlight(Component renderer, ComponentAdapter adapter) {
229 return doHighlight(renderer, adapter);
230 }
231
232 /**
233 * This is the bottleneck decorate method that all highlighters must invoke
234 * to decorate the cell renderer. This method invokes {@link #applyBackground
235 * applyBackground}, {@link #applyForeground applyForeground},
236 * to decorate the corresponding
237 * attributes of the specified component within the given adapter. <p>
238 *
239 * Subclasses which want to decorate additional properties must override
240 * this and additionally call custom applyXX methods.
241 *
242 * @param renderer the cell renderer component that is to be decorated
243 * @param adapter the {@link ComponentAdapter} for this decorate operation
244 * @return the decorated cell renderer component
245 */
246 protected Component doHighlight(Component renderer, ComponentAdapter adapter) {
247 applyBackground(renderer, adapter);
248 applyForeground(renderer, adapter);
249 return renderer;
250 }
251
252 /**
253 * Applies a suitable background for the renderer component within the
254 * specified adapter. <p>
255 *
256 * This implementation calls {@link #computeBackground computeBackground}
257 * and applies the computed color to the component if the returned value is
258 * != null. Otherwise it does nothing.
259 *
260 * @param renderer the cell renderer component that is to be decorated
261 * @param adapter the {@link ComponentAdapter} for this decorate operation
262 */
263 protected void applyBackground(Component renderer, ComponentAdapter adapter) {
264 Color color = computeBackground(renderer, adapter);
265 if (color != null) {
266 renderer.setBackground(color);
267 }
268 }
269
270 /**
271 * Applies a suitable foreground for the renderer component within the
272 * specified adapter. <p>
273 *
274 * This implementation calls {@link #computeForeground computeForeground}
275 * and applies the computed color to the component if the returned value
276 * is != null. Otherwise it does nothing.
277 *
278 * @param renderer the cell renderer component that is to be decorated
279 * @param adapter the {@link ComponentAdapter} for this decorate operation
280 */
281 protected void applyForeground(Component renderer, ComponentAdapter adapter) {
282 Color color = computeForeground(renderer, adapter);
283 if (color != null) {
284 renderer.setForeground(color);
285 }
286 }
287
288 /**
289 * <p>Computes a suitable background for the renderer component within the
290 * specified adapter and returns the computed color.
291 *
292 * <p> In this implementation the returned color depends
293 * on {@link ComponentAdapter#isSelected isSelected}: it will
294 * return computSelected/-UnselectedBackground, respectively.</p>
295 *
296 * @param renderer the cell renderer component that is to be decorated
297 * @param adapter the {@link ComponentAdapter} for this decorate operation
298 * @return a suitable background color for the specified component and adapter
299 */
300 protected Color computeBackground(Component renderer, ComponentAdapter adapter) {
301 return adapter.isSelected() ? computeSelectedBackground(renderer, adapter) :
302 computeUnselectedBackground(renderer, adapter);
303 }
304
305
306
307 /**
308 * <p>Computes a suitable unselected background for the renderer component within the
309 * specified adapter and returns the computed color.
310 *
311 * This implementation returns getBackground().
312 *
313 * @param renderer
314 * @param adapter
315 * @return unselected background color
316 */
317 protected Color computeUnselectedBackground(Component renderer, ComponentAdapter adapter) {
318 return getBackground();
319 }
320
321 /**
322 * <p>Computes a suitable selected background for the renderer component within the
323 * specified adapter and returns the computed color.
324 *
325 * This implementation returns getSelectedBackground().
326 *
327 * @param renderer
328 * @param adapter
329 * @return selected background color
330 */
331 protected Color computeSelectedBackground(Component renderer, ComponentAdapter adapter) {
332 return getSelectedBackground();
333 }
334
335 /**
336 * <p>Computes a suitable foreground for the renderer component within the
337 * specified adapter and returns the computed color.
338 * In this implementation the returned color depends
339 * on {@link ComponentAdapter#isSelected isSelected}: it will
340 * return computSelected/-UnselectedForeground, respectively.</p>
341 *</p>
342 *
343 * @param renderer the cell renderer component that is to be decorated
344 * @param adapter the {@link ComponentAdapter} for this decorate operation
345 * @return a suitable foreground color for the specified component and adapter
346 */
347 protected Color computeForeground(Component renderer, ComponentAdapter adapter) {
348 return adapter.isSelected() ? computeSelectedForeground(renderer, adapter) :
349 computeUnselectedForeground(renderer, adapter);
350 }
351
352 /**
353 * <p>Computes a suitable unselected foreground for the renderer component within the
354 * specified adapter and returns the computed color.
355 *
356 * This implementation returns getForeground().
357 *
358 * @param renderer
359 * @param adapter
360 * @return unselected foreground color
361 */
362 protected Color computeUnselectedForeground(Component renderer, ComponentAdapter adapter) {
363 return getForeground();
364 }
365
366 /**
367 * <p>Computes a suitable selected foreground for the renderer component within the
368 * specified adapter and returns the computed color.
369 *
370 * This implementation returns getSelectedForeground().
371 *
372 * @param renderer
373 * @param adapter
374 * @return selected foreground color
375 */
376 protected Color computeSelectedForeground(Component renderer, ComponentAdapter adapter) {
377 return getSelectedForeground();
378 }
379
380 /**
381 * Computes the selected background color.
382 *
383 * This implementation simply returns the selectedBackground property.
384 *
385 * @deprecated this is no longer used by this implementation
386 * @param seed initial background color; must cope with null!
387 * @return the background color for a selected cell
388 */
389 protected Color computeSelectedBackground(Color seed) {
390 // JW: first go on fixing #178-swingx - return absolute color
391 // this moves the responsibility of computation to subclasses.
392 return selectedBackground;
393 }
394
395 /**
396 * Computes the selected foreground color.
397 *
398 * This implementation simply returns the selectedBackground property.
399 *
400 * @deprecated this method is longer called by this implementation
401 *
402 * @param seed initial foreground color; must cope with null!
403 * @return the foreground color for a selected cell
404 */
405 protected Color computeSelectedForeground(Color seed) {
406 // JW: first go on fixing #178-swingx - return absolute color
407 // this moves the responsibility of computation to subclasses.
408 return selectedForeground;
409 }
410
411 /**
412 * Returns immutable flag: if true, none of the setXX methods have
413 * any effects, there are no listeners added and no change events fired.
414 * @return true if none of the setXX methods have any effect
415 */
416 public boolean isImmutable() {
417 return immutable;
418 }
419 /**
420 * Returns the background color of this <code>Highlighter</code>.
421 *
422 * @return the background color of this <code>Highlighter</code>,
423 * or null, if no background color has been set
424 */
425 public Color getBackground() {
426 return background;
427 }
428
429 /**
430 * Sets the background color of this <code>Highlighter</code> and
431 * notifies registered ChangeListeners if this
432 * is mutable. Does nothing if immutable.
433 *
434 * @param color the background color of this <code>Highlighter</code>,
435 * or null, to clear any existing background color
436 */
437 public void setBackground(Color color) {
438 if (isImmutable()) return;
439 background = color;
440 fireStateChanged();
441 }
442
443 /**
444 * Returns the foreground color of this <code>Highlighter</code>.
445 *
446 * @return the foreground color of this <code>Highlighter</code>,
447 * or null, if no foreground color has been set
448 */
449 public Color getForeground() {
450 return foreground;
451 }
452
453 /**
454 * Sets the foreground color of this <code>Highlighter</code> and notifies
455 * registered ChangeListeners if this is mutable. Does nothing if
456 * immutable.
457 *
458 * @param color the foreground color of this <code>Highlighter</code>,
459 * or null, to clear any existing foreground color
460 */
461 public void setForeground(Color color) {
462 if (isImmutable()) return;
463 foreground = color;
464 fireStateChanged();
465 }
466
467 /**
468 * Returns the selected background color of this <code>Highlighter</code>.
469 *
470 * @return the selected background color of this <code>Highlighter</code>,
471 * or null, if no selected background color has been set
472 */
473 public Color getSelectedBackground() {
474 return selectedBackground;
475 }
476
477 /**
478 * Sets the selected background color of this <code>Highlighter</code>
479 * and notifies registered ChangeListeners if this is mutable. Does nothing
480 * if immutable.
481 *
482 * @param color the selected background color of this <code>Highlighter</code>,
483 * or null, to clear any existing selected background color
484 */
485 public void setSelectedBackground(Color color) {
486 if (isImmutable()) return;
487 selectedBackground = color;
488 fireStateChanged();
489 }
490
491 /**
492 * Returns the selected foreground color of this <code>Highlighter</code>.
493 *
494 * @return the selected foreground color of this <code>Highlighter</code>,
495 * or null, if no selected foreground color has been set
496 */
497 public Color getSelectedForeground() {
498 return selectedForeground;
499 }
500
501 /**
502 * Sets the selected foreground color of this <code>Highlighter</code> and
503 * notifies registered ChangeListeners if this is mutable. Does nothing if
504 * immutable.
505 *
506 * @param color the selected foreground color of this <code>Highlighter</code>,
507 * or null, to clear any existing selected foreground color
508 */
509 public void setSelectedForeground(Color color) {
510 if (isImmutable()) return;
511 selectedForeground = color;
512 fireStateChanged();
513 }
514
515 /**
516 * Adds a <code>ChangeListener</code> if this is mutable. ChangeListeners are
517 * notified after changes of any attribute. Does nothing if immutable.
518 *
519 * @param l the ChangeListener to add
520 * @see #removeChangeListener
521 */
522 public void addChangeListener(ChangeListener l) {
523 if (isImmutable()) return;
524 listenerList.add(ChangeListener.class, l);
525 }
526
527
528 /**
529 * Removes a <code>ChangeListener</code> if this is mutable.
530 * Does nothis if immutable.
531 *
532 * @param l the <code>ChangeListener</code> to remove
533 * @see #addChangeListener
534 */
535 public void removeChangeListener(ChangeListener l) {
536 if (isImmutable()) return;
537 listenerList.remove(ChangeListener.class, l);
538 }
539
540
541 /**
542 * Returns an array of all the change listeners
543 * registered on this <code>Highlighter</code>.
544 *
545 * @return all of this model's <code>ChangeListener</code>s
546 * or an empty
547 * array if no change listeners are currently registered
548 *
549 * @see #addChangeListener
550 * @see #removeChangeListener
551 *
552 * @since 1.4
553 */
554 public ChangeListener[] getChangeListeners() {
555 return (ChangeListener[])listenerList.getListeners(
556 ChangeListener.class);
557 }
558
559
560 /**
561 * Runs each <code>ChangeListener</code>'s <code>stateChanged</code> method.
562 *
563 */
564 protected void fireStateChanged() {
565 if (isImmutable()) return;
566 Object[] listeners = listenerList.getListenerList();
567 for (int i = listeners.length - 2; i >= 0; i -=2 ) {
568 if (listeners[i] == ChangeListener.class) {
569 if (changeEvent == null) {
570 changeEvent = new ChangeEvent(this);
571 }
572 ((ChangeListener)listeners[i+1]).stateChanged(changeEvent);
573 }
574 }
575 }
576
577
578 public interface UIHighlighter {
579
580 void updateUI();
581 }
582 }