001 /* 002 * $Id: DefaultTableColumnModelExt.java 3397 2009-07-22 08:20:13Z 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 022 package org.jdesktop.swingx.table; 023 024 import java.beans.PropertyChangeEvent; 025 import java.beans.PropertyChangeListener; 026 import java.io.Serializable; 027 import java.util.ArrayList; 028 import java.util.Collections; 029 import java.util.Iterator; 030 import java.util.List; 031 032 import javax.swing.event.EventListenerList; 033 import javax.swing.event.TableColumnModelListener; 034 import javax.swing.table.DefaultTableColumnModel; 035 import javax.swing.table.TableColumn; 036 037 import org.jdesktop.swingx.event.TableColumnModelExtListener; 038 039 040 /** 041 * A default implementation of <code>TableColumnModelExt</code>. 042 * <p> 043 * 044 * TODO: explain sub-optimal notification on showing/hiding columns. 045 * (hot fixed issues #156, #157. To really do it 046 * need enhanced TableColumnModelEvent and -Listeners that are 047 * aware of the event.) 048 * 049 * 050 * @author Richard Bair 051 * @author Jeanette Winzenburg 052 */ 053 public class DefaultTableColumnModelExt extends DefaultTableColumnModel 054 implements TableColumnModelExt { 055 /** flag to distinguish a shown/hidden column from really added/removed 056 * columns during notification. This is brittle! 057 */ 058 // private static final String IGNORE_EVENT = "TableColumnModelExt.ignoreEvent"; 059 private boolean isVisibilityChange; 060 /** 061 * contains a list of all columns, in the order in which were 062 * added to the model. 063 */ 064 private List<TableColumn> initialColumns = new ArrayList<TableColumn>(); 065 066 /** 067 * contains a list of all column, in the order they would appear if 068 * all were visible. 069 */ 070 private List<TableColumn> currentColumns = new ArrayList<TableColumn>(); 071 072 /** 073 * Listener attached to TableColumnExt instances to listen for changes 074 * to their visibility status, and to hide/show the column as oppropriate 075 */ 076 private VisibilityListener visibilityListener = new VisibilityListener(); 077 078 /** 079 * Creates a an empty DefaultTableColumnModelExt. 080 */ 081 public DefaultTableColumnModelExt() { 082 super(); 083 } 084 085 //----------------------- implement TableColumnModelExt 086 087 /** 088 * {@inheritDoc} 089 */ 090 public List<TableColumn> getColumns(boolean includeHidden) { 091 if (includeHidden) { 092 return new ArrayList<TableColumn>(initialColumns); 093 } 094 return Collections.list(getColumns()); 095 } 096 097 /** 098 * {@inheritDoc} 099 */ 100 public int getColumnCount(boolean includeHidden) { 101 if (includeHidden) { 102 return initialColumns.size(); 103 } 104 return getColumnCount(); 105 } 106 107 /** 108 * {@inheritDoc} 109 */ 110 public TableColumnExt getColumnExt(Object identifier) { 111 for (Iterator<TableColumn> iter = initialColumns.iterator(); iter.hasNext();) { 112 TableColumn column = iter.next(); 113 if ((column instanceof TableColumnExt) && (identifier.equals(column.getIdentifier()))) { 114 return (TableColumnExt) column; 115 } 116 } 117 return null; 118 } 119 120 /** 121 * {@inheritDoc} 122 */ 123 public TableColumnExt getColumnExt(int columnIndex) { 124 TableColumn column = getColumn(columnIndex); 125 if (column instanceof TableColumnExt) { 126 return (TableColumnExt) column; 127 } 128 return null; 129 } 130 131 /** 132 * hot fix for #157: listeners that are aware of 133 * the possible existence of invisible columns 134 * should check if the received columnRemoved originated 135 * from moving a column from visible to invisible. 136 * 137 * @param oldIndex the fromIndex of the columnEvent 138 * @return true if the column was moved to invisible 139 */ 140 public boolean isRemovedToInvisibleEvent(int oldIndex) { 141 return isVisibilityChange; 142 } 143 144 /** 145 * hot fix for #157: listeners that are aware of 146 * the possible existence of invisible columns 147 * should check if the received columnAdded originated 148 * from moving a column from invisible to visible. 149 * 150 * @param newIndex the toIndex of the columnEvent 151 * @return true if the column was moved to visible 152 */ 153 public boolean isAddedFromInvisibleEvent(int newIndex) { 154 return isVisibilityChange; 155 } 156 157 //------------------------ TableColumnModel 158 159 /** 160 * {@inheritDoc} <p> 161 * 162 * Overridden to update internals related to column visibility. 163 */ 164 @Override 165 public void removeColumn(TableColumn column) { 166 //remove the visibility listener if appropriate 167 if (column instanceof TableColumnExt) { 168 ((TableColumnExt)column).removePropertyChangeListener(visibilityListener); 169 } 170 currentColumns.remove(column); 171 initialColumns.remove(column); 172 //let the superclass handle notification etc 173 super.removeColumn(column); 174 } 175 176 /** 177 * {@inheritDoc} <p> 178 * 179 * Overridden to update internals related to column visibility. 180 */ 181 @Override 182 public void addColumn(TableColumn aColumn) { 183 // hacking to guarantee correct events 184 // two step: add as visible, setVisible 185 boolean oldVisible = true; 186 //add the visibility listener if appropriate 187 if (aColumn instanceof TableColumnExt) { 188 TableColumnExt xColumn = (TableColumnExt) aColumn; 189 oldVisible = xColumn.isVisible(); 190 xColumn.setVisible(true); 191 xColumn.addPropertyChangeListener(visibilityListener); 192 } 193 // append the column to the end of both initial- and currentColumns. 194 currentColumns.add(aColumn); 195 initialColumns.add(aColumn); 196 // let super handle the event notification, super.book-keeping 197 super.addColumn(aColumn); 198 if (aColumn instanceof TableColumnExt) { 199 // reset original visibility 200 ((TableColumnExt) aColumn).setVisible(oldVisible); 201 } 202 203 } 204 205 /** 206 * {@inheritDoc} <p> 207 * 208 * Overridden to update internals related to column visibility. 209 */ 210 @Override 211 public void moveColumn(int columnIndex, int newIndex) { 212 if (columnIndex != newIndex) { 213 updateCurrentColumns(columnIndex, newIndex); 214 } 215 super.moveColumn(columnIndex, newIndex); 216 } 217 218 /** 219 * Adjusts the current column sequence when a visible column is moved. 220 * 221 * @param oldIndex the old visible position. 222 * @param newIndex the new visible position. 223 */ 224 private void updateCurrentColumns(int oldIndex, int newIndex) { 225 TableColumn movedColumn = tableColumns.elementAt(oldIndex); 226 int oldPosition = currentColumns.indexOf(movedColumn); 227 TableColumn targetColumn = tableColumns.elementAt(newIndex); 228 int newPosition = currentColumns.indexOf(targetColumn); 229 currentColumns.remove(oldPosition); 230 currentColumns.add(newPosition, movedColumn); 231 232 } 233 234 /** 235 * Update internal state after the visibility of the column 236 * was changed to invisible. The given column is assumed to 237 * be contained in this model. 238 * 239 * @param col the column which was hidden. 240 */ 241 protected void moveToInvisible(TableColumnExt col) { 242 isVisibilityChange = true; 243 super.removeColumn(col); 244 isVisibilityChange = false; 245 } 246 247 248 /** 249 * Update internal state after the visibility of the column 250 * was changed to visible. The given column is assumed to 251 * be contained in this model. 252 * 253 * @param col the column which was made visible. 254 */ 255 protected void moveToVisible(TableColumnExt col) { 256 isVisibilityChange = true; 257 // two step process: first add at end of columns 258 // then move to "best" position relative to where it 259 // was before hiding. 260 super.addColumn(col); 261 // this is analogous to the proposed fix in #253-swingx 262 // but uses the currentColumns as reference. 263 Integer addIndex = currentColumns.indexOf(col); 264 for (int i = 0; i < (getColumnCount() - 1); i++) { 265 TableColumn tableCol = getColumn(i); 266 int actualPosition = currentColumns.indexOf(tableCol); 267 if (actualPosition > addIndex) { 268 super.moveColumn(getColumnCount() - 1, i); 269 break; 270 } 271 } 272 isVisibilityChange = false; 273 } 274 275 276 /** 277 * TODO JW: move into propertyChanged! No need for a dedicated listener. 278 * Changed evaluation JW: may still be required as super removes itself as 279 * propertyChangeListener if column is hidden 280 */ 281 private class VisibilityListener implements PropertyChangeListener, Serializable { 282 public void propertyChange(PropertyChangeEvent evt) { 283 if ("visible".equals(evt.getPropertyName())) { 284 TableColumnExt columnExt = (TableColumnExt)evt.getSource(); 285 286 if (columnExt.isVisible()) { 287 moveToVisible(columnExt); 288 fireColumnPropertyChange(evt); 289 } else { 290 moveToInvisible(columnExt); 291 } 292 } else if (!((TableColumnExt) evt.getSource()).isVisible()) { 293 fireColumnPropertyChange(evt); 294 } 295 } 296 } 297 298 // enhanced listener notification 299 300 301 /** 302 * Exposed for testing only - don't use! Will be removed again! 303 * @return super's listener list 304 */ 305 protected EventListenerList getEventListenerList() { 306 return listenerList; 307 } 308 309 310 311 /** 312 * {@inheritDoc} 313 */ 314 @Override 315 public void propertyChange(PropertyChangeEvent evt) { 316 super.propertyChange(evt); 317 fireColumnPropertyChange(evt); 318 } 319 320 /** 321 * Notifies <code>TableColumnModelExtListener</code>s about property 322 * changes of contained columns. The event instance 323 * is the original as fired by the <code>TableColumn</code>. 324 * @param evt the event received 325 * @see EventListenerList 326 */ 327 protected void fireColumnPropertyChange(PropertyChangeEvent evt) { 328 // if (IGNORE_EVENT.equals(evt.getPropertyName())) return; 329 // Guaranteed to return a non-null array 330 Object[] listeners = listenerList.getListenerList(); 331 // Process the listeners last to first, notifying 332 // those that are interested in this event 333 for (int i = listeners.length-2; i>=0; i-=2) { 334 if (listeners[i]==TableColumnModelExtListener.class) { 335 ((TableColumnModelExtListener)listeners[i+1]). 336 columnPropertyChange(evt); 337 } 338 } 339 } 340 341 342 /** 343 * {@inheritDoc} <p> 344 * 345 * 346 * Overridden to install enhanced notification of listeners of type. 347 * TableColumnModelListenerExt about property changes of contained columns. 348 * 349 */ 350 @Override 351 public void addColumnModelListener(TableColumnModelListener x) { 352 super.addColumnModelListener(x); 353 if (x instanceof TableColumnModelExtListener) { 354 listenerList.add(TableColumnModelExtListener.class, (TableColumnModelExtListener) x); 355 } 356 } 357 358 /** 359 * {@inheritDoc} <p> 360 * 361 * Overridden to uninstall enhanced notification of listeners of type. 362 * TableColumnModelListenerExt about property changes of contained columns. 363 */ 364 @Override 365 public void removeColumnModelListener(TableColumnModelListener x) { 366 super.removeColumnModelListener(x); 367 if (x instanceof TableColumnModelExtListener) { 368 listenerList.remove(TableColumnModelExtListener.class, (TableColumnModelExtListener) x); 369 } 370 } 371 372 /** 373 * @return array of all registered listeners 374 */ 375 public TableColumnModelExtListener[] getTableColumnModelExtListeners() { 376 return listenerList.getListeners(TableColumnModelExtListener.class); 377 } 378 }