001/* 002 * $Id: ComponentAdapter.java 4158 2012-02-03 18:29:40Z kschaefe $ 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.decorator; 023 024import java.awt.Rectangle; 025 026import javax.swing.JComponent; 027 028import org.jdesktop.swingx.renderer.StringValues; 029 030/** 031 * Abstract base class for all component data adapter classes. A 032 * <code>ComponentAdapter</code> allows the decoration collaborators like f.i. 033 * {@link Highlighter} to interact with a {@link #target} component through a 034 * common API. <p> 035 * 036 * It has two aspects: 037 * <ul> 038 * <li> interact with the view state for the "current" cell. The row/column 039 * fields and the parameterless methods service this aspect. The coordinates are 040 * in view coordinate system. 041 * <li> interact with the data of the component. The methods for this are those 042 * taking row/column indices as parameters. The coordinates are in model 043 * coordinate system. 044 * </ul> 045 * 046 * Typically, application code is interested in the first aspect. An example is 047 * highlighting the background of a row in a JXTable based on the value of a 048 * cell in a specific column. The solution is to implement a custom 049 * HighlightPredicate which decides if a given cell should be highlighted and 050 * configure a ColorHighlighter with the predicate and an appropriate background 051 * color. 052 * 053 * <pre><code> 054 * HighlightPredicate feverWarning = new HighlightPredicate() { 055 * int temperatureColumn = 10; 056 * 057 * public boolean isHighlighted(Component component, ComponentAdapter adapter) { 058 * return hasFever(adapter.getValue(temperatureColumn)); 059 * } 060 * 061 * private boolean hasFever(Object value) { 062 * if (!value instanceof Number) 063 * return false; 064 * return ((Number) value).intValue() > 37; 065 * } 066 * }; 067 * 068 * Highlighter hl = new ColorHighlighter(feverWarning, Color.RED, null); 069 * </code></pre> 070 * 071 * The adapter is responsible for mapping column and row coordinates. 072 * 073 * All input column indices are in model coordinates with exactly two 074 * exceptions: 075 * <ul> 076 * <li> {@link #column} in column view coordinates 077 * <li> the mapping method {@link #convertColumnIndexToModel(int)} in view coordinates 078 * </ul> 079 * 080 * All input row indices are in model coordinates with exactly four exceptions: 081 * <ul> 082 * <li> {@link #row} in row view coordinates 083 * <li> the mapping method {@link #convertRowIndexToModel(int)} in view coordinates 084 * <li> the getter for the filtered value {@link #getFilteredValueAt(int, int)} 085 * takes the row in view coordinates. 086 * <li> the getter for the filtered string representation {@link #getFilteredStringAt(int, int)} 087 * takes the row in view coordinates. 088* </ul> 089 * 090 * 091 * PENDING JW: anything to gain by generics here?<p> 092 * PENDING JW: formally document that row/column coordinates must be valid in all methods taking 093 * model coordinates, that is 0<= row < getRowCount(). 094 * 095 * @author Ramesh Gupta 096 * @author Karl Schaefer 097 * @author Jeanette Winzenburg 098 * 099 * @see org.jdesktop.swingx.decorator.HighlightPredicate 100 * @see org.jdesktop.swingx.decorator.Highlighter 101 */ 102public abstract class ComponentAdapter { 103 public static final Object DEFAULT_COLUMN_IDENTIFIER = "Column0"; 104 /** current row in view coordinates. */ 105 public int row = 0; 106 /** current column in view coordinates. */ 107 public int column = 0; 108 protected final JComponent target; 109 110 /** 111 * Constructs a ComponentAdapter, setting the specified component as the 112 * target component. 113 * 114 * @param component target component for this adapter 115 */ 116 public ComponentAdapter(JComponent component) { 117 target = component; 118 } 119 120 /** 121 * Returns the component which is this adapter's target. 122 * 123 * @return the component which is this adapter's target. 124 */ 125 public JComponent getComponent() { 126 return target; 127 } 128 129//---------------------------- accessing the target's model: column meta data 130 131 /** 132 * Returns the column's display name (= headerValue) of the column 133 * at columnIndex in model coordinates. 134 * 135 * Used f.i. in SearchPanel to fill the field with the 136 * column name.<p> 137 * 138 * Note: it's up to the implementation to decide for which 139 * columns it returns a name - most will do so for the 140 * subset with isTestable = true. 141 * 142 * This implementation delegates to getColumnIdentifierAt and returns it's 143 * toString or null. 144 * 145 * @param columnIndex in model coordinates 146 * @return column name or null if not found 147 */ 148 public String getColumnName(int columnIndex) { 149 Object identifier = getColumnIdentifierAt(columnIndex); 150 return identifier != null ? identifier.toString() : null; 151 } 152 153 154 /** 155 * Returns logical identifier of the column at 156 * columnIndex in model coordinates. 157 * 158 * Note: it's up to the implementation to decide for which 159 * columns it returns an identifier - most will do so for the 160 * subset with isTestable = true.<p> 161 * 162 * This implementation returns DEFAULT_COLUMN_IDENTIFIER. 163 * 164 * PENDING JW: This method replaces the old getColumnIdentifier(int) 165 * which returned a String which is overly restrictive. 166 * The only way to gently replace this method was 167 * to add this with a different name - which makes this name suboptimal. 168 * Probably should rename again once the old has died out ;-) 169 * 170 * @param columnIndex in model coordinates, must be valid. 171 * @return the identifier of the column at columnIndex or null if it has none. 172 * @throws ArrayIndexOutOfBoundsException if columnIndex < 0 or columnIndex >= getColumnCount(). 173 * 174 * 175 * @see #getColumnIndex(Object) 176 */ 177 public Object getColumnIdentifierAt(int columnIndex) { 178 if ((columnIndex < 0) || (columnIndex >= getColumnCount())) { 179 throw new ArrayIndexOutOfBoundsException("invalid column index: " + columnIndex); 180 } 181 return DEFAULT_COLUMN_IDENTIFIER; 182 } 183 184 /** 185 * Returns the column index in model coordinates for the logical identifier. 186 * <p> 187 * 188 * This implementation returns 0 if the identifier is the same as the one 189 * known identifier returned from getColumnIdentifierAt(0), or -1 otherwise. 190 * So subclasses with one column and a customizable identifier need not 191 * override. Subclasses which support multiple columns must override this as 192 * well to keep the contract as in (assuming that the lookup succeeded): 193 * 194 * <pre><code> 195 * Object id = getColumnIdentifierAt(index); 196 * assertEquals(index, getColumnIndex(index); 197 * // and the reverse 198 * int column = getColumnIndex(identifier); 199 * assertEquals(identifier, getColumnIdentifierAt(column)); 200 * </code></pre> 201 * 202 * 203 * @param identifier the column's identifier, must not be null 204 * @return the index of the column identified by identifier in model 205 * coordinates or -1 if no column with the given identifier is 206 * found. 207 * @throws NullPointerException if identifier is null. 208 * @see #getColumnIdentifierAt(int) 209 */ 210 public int getColumnIndex(Object identifier) { 211 if (identifier.equals(getColumnIdentifierAt(0))) { 212 return 0; 213 } 214 return -1; 215 } 216 217 /** 218 * Returns true if the column should be included in testing.<p> 219 * 220 * Here: returns true if visible (that is modelToView gives a valid 221 * view column coordinate). 222 * 223 * @param column the column index in model coordinates 224 * @return true if the column should be included in testing 225 */ 226 public boolean isTestable(int column) { 227 return convertColumnIndexToView(column) >= 0; 228 } 229 230 /** 231 * Returns the common class of all data column identified by the given 232 * column index in model coordinates.<p> 233 * 234 * This implementation returns <code>Object.class</code>. Subclasses should 235 * implement as appropriate. 236 * 237 * @return the common class of all data given column in model coordinates. 238 * 239 * @see #getColumnClass() 240 */ 241 public Class<?> getColumnClass(int column) { 242 return Object.class; 243 } 244 245 246 /** 247 * Returns the common class of all data in the current column.<p> 248 * 249 * This implementation delegates to getColumnClass(int) with the current 250 * column converted to model coordinates. 251 * 252 * @return the common class of all data in the current column. 253 * @see #getColumnClass(int) 254 */ 255 public Class<?> getColumnClass() { 256 return getColumnClass(convertColumnIndexToModel(column)); 257 } 258 259 260 261//---------------------------- accessing the target's model: meta data 262 /** 263 * Returns the number of columns in the target's data model. 264 * 265 * @return the number of columns in the target's data model. 266 */ 267 public int getColumnCount() { 268 return 1; // default for combo-boxes, lists, and trees 269 } 270 271 /** 272 * Returns the number of rows in the target's data model. 273 * 274 * @return the number of rows in the target's data model. 275 */ 276 public int getRowCount() { 277 return 0; 278 } 279 280//---------------------------- accessing the target's model: data 281 /** 282 * Returns the value of the target component's cell identified by the 283 * specified row and column in model coordinates. 284 * 285 * @param row in model coordinates 286 * @param column in model coordinates 287 * @return the value of the target component's cell identified by the 288 * specified row and column 289 */ 290 public abstract Object getValueAt(int row, int column); 291 292 /** 293 * Determines whether this cell is editable. 294 * 295 * @param row the row to query in model coordinates 296 * @param column the column to query in model coordinates 297 * @return <code>true</code> if the cell is editable, <code>false</code> 298 * otherwise 299 */ 300 public abstract boolean isCellEditable(int row, int column); 301 302 303 /** 304 * Returns the String representation of the value of the cell identified by this adapter. That is, 305 * for the at position (adapter.row, adapter.column) in view coordinates.<p> 306 * 307 * NOTE: this implementation assumes that view coordinates == model 308 * coordinates, that is simply calls getValueAt(this.row, this.column). It is 309 * up to subclasses to override appropriately is they support model/view 310 * coordinate transformation. <p> 311 * 312 * This implementation messages the StringValue.TO_STRING with the getValue, 313 * subclasses should re-implement and use the API appropriate for the target component type. 314 * 315 * @return the String representation of value of the cell identified by this adapter 316 * @see #getValueAt(int, int) 317 * @see #getFilteredValueAt(int, int) 318 * @see #getValue(int) 319 */ 320 public String getString() { 321 return getString(convertColumnIndexToModel(column)); 322 } 323 324 /** 325 * Returns the String representation of the value of the cell identified by the current 326 * adapter row and the given column index in model coordinates.<p> 327 * 328 * @param modelColumnIndex the column index in model coordinates 329 * @return the String representation of the value of the cell identified by this adapter 330 * 331 * @see #getFilteredStringAt(int, int) 332 * @see #getString() 333 */ 334 public String getString(int modelColumnIndex) { 335 return getFilteredStringAt(row, modelColumnIndex); 336 } 337 338 /** 339 * Returns the String representation of the filtered value of the cell identified by the row 340 * in view coordinate and the column in model coordinates.<p> 341 * 342 * Note: the asymetry of the coordinates is intentional - clients like 343 * Highlighters are interested in view values but might need to access 344 * non-visible columns for testing. While it is possible to access 345 * row coordinates different from the current (that is this.row) it is not 346 * safe to do so for row > this.row because the adapter doesn't allow to 347 * query the count of visible rows.<p> 348 * 349 * This implementation messages the StringValue.TO_STRING with the filteredValue, 350 * subclasses should re-implement and use the API appropriate for the target component type.<p> 351 * 352 * PENDING JW: what about null cell values? StringValue has a contract to return a 353 * empty string then, would that be okay here as well? 354 * 355 * @param row the row of the cell in view coordinates 356 * @param column the column of the cell in model coordinates. 357 * @return the String representation of the filtered value of the cell identified by the row 358 * in view coordinate and the column in model coordinates 359 */ 360 public String getFilteredStringAt(int row, int column) { 361 return getStringAt(convertRowIndexToModel(row), column); 362 } 363 364 /** 365 * Returns the String representation of the value of the cell identified by the row 366 * specified row and column in model coordinates.<p> 367 * 368 * This implementation messages the StringValue.TO_STRING with the valueAt, 369 * subclasses should re-implement and use the api appropriate for the target component type.<p> 370 * 371 * @param row in model coordinates 372 * @param column in model coordinates 373 * @return the value of the target component's cell identified by the 374 * specified row and column 375 */ 376 public String getStringAt(int row, int column) { 377 return StringValues.TO_STRING.getString(getValueAt(row, column)); 378 } 379 380 /** 381 * Returns the value of the cell identified by this adapter. That is, 382 * for the at position (adapter.row, adapter.column) in view coordinates.<p> 383 * 384 * NOTE: this implementation assumes that view coordinates == model 385 * coordinates, that is simply calls getValueAt(this.row, this.column). It is 386 * up to subclasses to override appropriately is they support model/view 387 * coordinate transformation. 388 * 389 * @return the value of the cell identified by this adapter 390 * @see #getValueAt(int, int) 391 * @see #getFilteredValueAt(int, int) 392 * @see #getValue(int) 393 */ 394 public Object getValue() { 395 return getValue(convertColumnIndexToModel(column)); 396 } 397 398 399 /** 400 * Returns the value of the cell identified by the current 401 * adapter row and the given column index in model coordinates.<p> 402 * 403 * @param modelColumnIndex the column index in model coordinates 404 * @return the value of the cell identified by this adapter 405 * @see #getValueAt(int, int) 406 * @see #getFilteredValueAt(int, int) 407 * @see #getValue(int) 408 */ 409 public Object getValue(int modelColumnIndex) { 410 return getFilteredValueAt(row, modelColumnIndex); 411 } 412 413 /** 414 * Returns the filtered value of the cell identified by the row 415 * in view coordinate and the column in model coordinates. 416 * 417 * Note: the asymmetry of the coordinates is intentional - clients like 418 * Highlighters are interested in view values but might need to access 419 * non-visible columns for testing. While it is possible to access 420 * row coordinates different from the current (that is this.row) it is not 421 * safe to do so for row > this.row because the adapter doesn't allow to 422 * query the count of visible rows. 423 * 424 * @param row the row of the cell in view coordinates 425 * @param column the column of the cell in model coordinates. 426 * @return the filtered value of the cell identified by the row 427 * in view coordinate and the column in model coordinates 428 */ 429 public Object getFilteredValueAt(int row, int column) { 430 return getValueAt(convertRowIndexToModel(row), column); 431 } 432 433 //----------------------- accessing the target's view state 434 435 /** 436 * Returns the bounds of the cell identified by this adapter.<p> 437 * 438 * @return the bounds of the cell identified by this adapter 439 */ 440 public Rectangle getCellBounds() { 441 return target.getBounds(); 442 } 443 444 /** 445 * Returns true if the cell identified by this adapter currently has focus. 446 * Otherwise, it returns false. 447 * 448 * @return true if the cell identified by this adapter currently has focus; 449 * Otherwise, return false 450 */ 451 public abstract boolean hasFocus(); 452 453 /** 454 * Returns true if the cell identified by this adapter is currently selected. 455 * Otherwise, it returns false. 456 * 457 * @return true if the cell identified by this adapter is currently selected; 458 * Otherwise, return false 459 */ 460 public abstract boolean isSelected(); 461 462 /** 463 * Returns {@code true} if the cell identified by this adapter is editable, 464 * {@code false} otherwise. 465 * 466 * @return {@code true} if the cell is editable, {@code false} otherwise 467 */ 468 public abstract boolean isEditable(); 469 470 /** 471 * Returns true if the cell identified by this adapter is currently expanded. 472 * Otherwise, it returns false. For components that do not support 473 * hierarchical data, this method always returns true because the cells in 474 * such components can never be collapsed. 475 * 476 * @return true if the cell identified by this adapter is currently expanded; 477 * Otherwise, return false 478 */ 479 public boolean isExpanded() { 480 return true; // sensible default for JList and JTable 481 } 482 483 /** 484 * Returns true if the cell identified by this adapter is a leaf node. 485 * Otherwise, it returns false. For components that do not support 486 * hierarchical data, this method always returns true because the cells in 487 * such components can never have children. 488 * 489 * @return true if the cell identified by this adapter is a leaf node; 490 * Otherwise, return false 491 */ 492 public boolean isLeaf() { 493 return true; // sensible default for JList and JTable 494 } 495 496 /** 497 * Returns true if the cell identified by this adapter displays the hierarchical node. 498 * Otherwise, it returns false. For components that do not support 499 * hierarchical data, this method always returns false because the cells in 500 * such components can never have children. 501 * 502 * @return true if the cell identified by this adapter displays the hierarchical node; 503 * Otherwise, return false 504 */ 505 public boolean isHierarchical() { 506 return false; // sensible default for JList and JTable 507 } 508 509 /** 510 * Returns the depth of this row in the hierarchy where the root is 0. For 511 * components that do not contain hierarchical data, this method returns 1. 512 * 513 * @return the depth for this adapter 514 */ 515 public int getDepth() { 516 return 1; // sensible default for JList and JTable 517 } 518 519//-------------------- cell coordinate transformations 520 521 /** 522 * For target components that support multiple columns in their model, 523 * along with column reordering in the view, this method transforms the 524 * specified columnIndex from model coordinates to view coordinates. For all 525 * other types of target components, this method returns the columnIndex 526 * unchanged. 527 * 528 * @param columnModelIndex index of a column in model coordinates 529 * @return index of the specified column in view coordinates 530 */ 531 public int convertColumnIndexToView(int columnModelIndex) { 532 return columnModelIndex; // sensible default for JList and JTree 533 } 534 535 /** 536 * For target components that support multiple columns in their model, along 537 * with column reordering in the view, this method transforms the specified 538 * columnIndex from view coordinates to model coordinates. For all other 539 * types of target components, this method returns the columnIndex 540 * unchanged. 541 * 542 * @param columnViewIndex index of a column in view coordinates 543 * @return index of the specified column in model coordinates 544 */ 545 public int convertColumnIndexToModel(int columnViewIndex) { 546 return columnViewIndex; // sensible default for JList and JTree 547 } 548 549 /** 550 * Converts a row index in model coordinates to an index in view coordinates. 551 * 552 * @param rowModelIndex index of a row in model coordinates 553 * @return index of the specified row in view coordinates 554 */ 555 public int convertRowIndexToView(int rowModelIndex) { 556 return rowModelIndex; // sensible default for JTree 557 } 558 559 /** 560 * Converts a row index in view coordinates to an index in model coordinates. 561 * 562 * @param rowViewIndex index of a row in view coordinates 563 * @return index of the specified row in model coordinates 564 */ 565 public int convertRowIndexToModel(int rowViewIndex) { 566 return rowViewIndex; // sensible default for JTree 567 } 568}