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 }