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