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 }