OSDN Git Service

Imported GNU Classpath 0.90
[pf3gnuchains/gcc-fork.git] / libjava / classpath / javax / swing / table / DefaultTableColumnModel.java
1 /* DefaultTableColumnModel.java --
2    Copyright (C) 2002, 2004, 2005, 2006,  Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37
38
39 package javax.swing.table;
40
41 import java.beans.PropertyChangeEvent;
42 import java.beans.PropertyChangeListener;
43 import java.io.Serializable;
44 import java.util.Enumeration;
45 import java.util.EventListener;
46 import java.util.Vector;
47
48 import javax.swing.DefaultListSelectionModel;
49 import javax.swing.JTable;
50 import javax.swing.ListSelectionModel;
51 import javax.swing.event.ChangeEvent;
52 import javax.swing.event.EventListenerList;
53 import javax.swing.event.ListSelectionEvent;
54 import javax.swing.event.ListSelectionListener;
55 import javax.swing.event.TableColumnModelEvent;
56 import javax.swing.event.TableColumnModelListener;
57
58 /**
59  * A model that stores information about the columns used in a {@link JTable}.
60  * 
61  * @see JTable#setColumnModel(TableColumnModel)
62  * 
63  * @author      Andrew Selkirk
64  */
65 public class DefaultTableColumnModel
66   implements TableColumnModel, PropertyChangeListener, ListSelectionListener,
67              Serializable
68 {
69   private static final long serialVersionUID = 6580012493508960512L;
70
71   /**
72    * Storage for the table columns.
73    */
74   protected Vector tableColumns;
75
76   /**
77    * A selection model that keeps track of column selections.
78    */
79   protected ListSelectionModel selectionModel;
80
81   /**
82    * The space between the columns (the default value is <code>1</code>).
83    */
84   protected int columnMargin;
85
86   /**
87    * Storage for the listeners registered with the model.
88    */
89   protected EventListenerList listenerList = new EventListenerList();
90
91   /**
92    * A change event used when notifying listeners of a change to the 
93    * <code>columnMargin</code> field.  This single event is reused for all
94    * notifications.
95    */
96   // FIXME: use lazy instantiation
97   protected transient ChangeEvent changeEvent = new ChangeEvent(this);
98
99   /**
100    * A flag that indicates whether or not columns can be selected. 
101    */
102   protected boolean columnSelectionAllowed;
103
104   /**
105    * The total width of all the columns in this model.
106    */
107   protected int totalColumnWidth;
108
109   /**
110    * Creates a new table column model with zero columns.  A default column 
111    * selection model is created by calling {@link #createSelectionModel()}.
112    * The default value for <code>columnMargin</code> is <code>1</code> and
113    * the default value for <code>columnSelectionAllowed</code> is 
114    * <code>false</code>.
115    */
116   public DefaultTableColumnModel()
117   {
118     tableColumns = new Vector();
119     selectionModel = createSelectionModel();
120     selectionModel.addListSelectionListener(this);
121     columnMargin = 1;
122     columnSelectionAllowed = false;
123   }
124
125   /**
126    * Adds a column to the model then calls 
127    * {@link #fireColumnAdded(TableColumnModelEvent)} to notify the registered
128    * listeners.  The model registers itself with the column as a
129    * {@link PropertyChangeListener} so that changes to the column width will
130    * invalidate the cached {@link #totalColumnWidth} value.
131    *
132    * @param column  the column (<code>null</code> not permitted).
133    * 
134    * @throws IllegalArgumentException if <code>column</code> is 
135    *     <code>null</code>.
136    * 
137    * @see #removeColumn(TableColumn)
138    */
139   public void addColumn(TableColumn column)
140   {
141     if (column == null)
142       throw new IllegalArgumentException("Null 'col' argument.");
143     tableColumns.add(column);
144     column.addPropertyChangeListener(this);
145     invalidateWidthCache();
146     fireColumnAdded(new TableColumnModelEvent(this, 0, 
147                                               tableColumns.size() - 1));
148   }
149
150   /**
151    * Removes a column from the model then calls
152    * {@link #fireColumnRemoved(TableColumnModelEvent)} to notify the registered
153    * listeners.  If the specified column does not belong to the model, or is 
154    * <code>null</code>, this method does nothing.
155    *
156    * @param column the column to be removed (<code>null</code> permitted).
157    * 
158    * @see #addColumn(TableColumn)
159    */
160   public void removeColumn(TableColumn column)
161   {
162     int index = this.tableColumns.indexOf(column);
163     if (index < 0)
164       return;
165     tableColumns.remove(column);
166     fireColumnRemoved(new TableColumnModelEvent(this, index, 0));    
167     column.removePropertyChangeListener(this);
168     invalidateWidthCache();
169   }
170
171   /**
172    * Moves the column at index i to the position specified by index j, then 
173    * calls {@link #fireColumnMoved(TableColumnModelEvent)} to notify registered
174    * listeners.
175    *
176    * @param i index of the column that will be moved.
177    * @param j index of the column's new location.
178    * 
179    * @throws IllegalArgumentException if <code>i</code> or <code>j</code> are
180    *     outside the range <code>0</code> to <code>N-1</code>, where 
181    *     <code>N</code> is the column count.
182    */
183   public void moveColumn(int i, int j)
184   {
185     int columnCount = getColumnCount();
186     if (i < 0 || i >= columnCount)
187       throw new IllegalArgumentException("Index 'i' out of range.");
188     if (j < 0 || j >= columnCount)
189       throw new IllegalArgumentException("Index 'j' out of range.");
190     Object column = tableColumns.remove(i);
191     tableColumns.add(j, column);
192     fireColumnMoved(new TableColumnModelEvent(this, i, j));
193   }
194
195   /**
196    * Sets the column margin then calls {@link #fireColumnMarginChanged()} to
197    * notify the registered listeners.
198    * 
199    * @param margin  the column margin.
200    * 
201    * @see #getColumnMargin()
202    */
203   public void setColumnMargin(int margin)
204   {
205     columnMargin = margin;
206     fireColumnMarginChanged();
207   }
208
209   /**
210    * Returns the number of columns in the model.
211    * 
212    * @return The column count.
213    */
214   public int getColumnCount()
215   {
216     return tableColumns.size();
217   }
218
219   /**
220    * Returns an enumeration of the columns in the model.
221    * 
222    * @return An enumeration of the columns in the model.
223    */
224   public Enumeration getColumns()
225   {
226     return tableColumns.elements();
227   }
228
229   /**
230    * Returns the index of the {@link TableColumn} with the given identifier.
231    *
232    * @param identifier  the identifier (<code>null</code> not permitted).
233    * 
234    * @return The index of the {@link TableColumn} with the given identifier.
235    * 
236    * @throws IllegalArgumentException if <code>identifier</code> is 
237    *         <code>null</code> or there is no column with that identifier.
238    */
239   public int getColumnIndex(Object identifier)
240   {
241     if (identifier == null)
242       throw new IllegalArgumentException("Null identifier.");
243     int columnCount = tableColumns.size();
244     for (int i = 0; i < columnCount; i++) 
245     {
246       TableColumn tc = (TableColumn) tableColumns.get(i);
247       if (identifier.equals(tc.getIdentifier()))
248         return i;
249     }
250     throw new IllegalArgumentException("No TableColumn with that identifier.");
251   }
252
253   /**
254    * Returns the column at the specified index.
255    * 
256    * @param columnIndex  the column index (in the range from <code>0</code> to 
257    *     <code>N-1</code>, where <code>N</code> is the number of columns in 
258    *     the model).
259    * 
260    * @return The column at the specified index.
261    * 
262    * @throws ArrayIndexOutOfBoundsException if <code>i</code> is not within
263    *     the specified range.
264    */
265   public TableColumn getColumn(int columnIndex)
266   {
267     return (TableColumn) tableColumns.get(columnIndex);
268   }
269
270   /**
271    * Returns the column margin.
272    * 
273    * @return The column margin.
274    * 
275    * @see #setColumnMargin(int)
276    */
277   public int getColumnMargin()
278   {
279     return columnMargin;
280   }
281
282   /**
283    * Returns the index of the column that contains the specified x-coordinate.
284    * This method assumes that:
285    * <ul>
286    * <li>column zero begins at position zero;</li>
287    * <li>all columns appear in order;</li>
288    * <li>individual column widths are taken into account, but the column margin
289    *     is ignored.</li>
290    * </ul>
291    * If no column contains the specified position, this method returns 
292    * <code>-1</code>.
293    * 
294    * @param x  the x-position.
295    * 
296    * @return The column index, or <code>-1</code>.
297    */
298   public int getColumnIndexAtX(int x)
299   {    
300     for (int i = 0; i < tableColumns.size(); ++i)
301       {
302         int w = ((TableColumn) tableColumns.get(i)).getWidth();
303         if (0 <= x && x < w)
304           return i;
305         else
306           x -= w;
307       }
308     return -1;
309   }
310
311   /**
312    * Returns total width of all the columns in the model, ignoring the
313    * {@link #columnMargin}.
314    *
315    * @return The total width of all the columns.
316    */
317   public int getTotalColumnWidth()
318   {
319     if (totalColumnWidth == -1)
320       recalcWidthCache();
321     return totalColumnWidth;
322   }
323
324   /**
325    * Sets the selection model that will be used to keep track of the selected 
326    * columns.
327    *
328    * @param model  the selection model (<code>null</code> not permitted).
329    * 
330    * @throws IllegalArgumentException if <code>model</code> is 
331    *     <code>null</code>.
332    *     
333    * @see #getSelectionModel()
334    */
335   public void setSelectionModel(ListSelectionModel model)
336   {
337     if (model == null)
338       throw new IllegalArgumentException();
339     
340     selectionModel.removeListSelectionListener(this);
341     selectionModel = model;
342     selectionModel.addListSelectionListener(this);
343   }
344
345   /**
346    * Returns the selection model used to track table column selections.
347    * 
348    * @return The selection model.
349    * 
350    * @see #setSelectionModel(ListSelectionModel)
351    */
352   public ListSelectionModel getSelectionModel()
353   {
354     return selectionModel;
355   }
356
357   /**
358    * Sets the flag that indicates whether or not column selection is allowed.
359    *
360    * @param flag  the new flag value.
361    * 
362    * @see #getColumnSelectionAllowed()
363    */
364   public void setColumnSelectionAllowed(boolean flag)
365   {
366     columnSelectionAllowed = flag;
367   }
368
369   /**
370    * Returns <code>true</code> if column selection is allowed, and 
371    * <code>false</code> if column selection is not allowed.
372    *
373    * @return A boolean.
374    * 
375    * @see #setColumnSelectionAllowed(boolean)
376    */
377   public boolean getColumnSelectionAllowed()
378   {
379     return columnSelectionAllowed;
380   }
381
382   /**
383    * Returns an array containing the indices of the selected columns.
384    *
385    * @return An array containing the indices of the selected columns.
386    */
387   public int[] getSelectedColumns()
388   {
389     // FIXME: Implementation of this method was taken from private method 
390     // JTable.getSelections(), which is used in various places in JTable
391     // including selected row calculations and cannot be simply removed.
392     // This design should be improved to illuminate duplication of code.
393     
394     ListSelectionModel lsm = this.selectionModel;    
395     int sz = getSelectedColumnCount();
396     int [] ret = new int[sz];
397
398     int lo = lsm.getMinSelectionIndex();
399     int hi = lsm.getMaxSelectionIndex();
400     int j = 0;
401     java.util.ArrayList ls = new java.util.ArrayList();
402     if (lo != -1 && hi != -1)
403       {
404         switch (lsm.getSelectionMode())
405           {
406           case ListSelectionModel.SINGLE_SELECTION:
407             ret[0] = lo;
408             break;      
409       
410           case ListSelectionModel.SINGLE_INTERVAL_SELECTION:            
411             for (int i = lo; i <= hi; ++i)
412               ret[j++] = i;
413             break;
414             
415           case ListSelectionModel.MULTIPLE_INTERVAL_SELECTION:        
416             for (int i = lo; i <= hi; ++i)
417               if (lsm.isSelectedIndex(i))        
418                 ret[j++] = i;
419             break;
420           }
421       }
422     return ret;
423   }
424
425   /**
426    * Returns the number of selected columns in the model.
427    * 
428    * @return The selected column count.
429    * 
430    * @see #getSelectionModel()
431    */
432   public int getSelectedColumnCount()
433   {
434     // FIXME: Implementation of this method was taken from private method 
435     // JTable.countSelections(), which is used in various places in JTable
436     // including selected row calculations and cannot be simply removed.
437     // This design should be improved to illuminate duplication of code.
438    
439     ListSelectionModel lsm = this.selectionModel;
440     int lo = lsm.getMinSelectionIndex();
441     int hi = lsm.getMaxSelectionIndex();
442     int sum = 0;
443     
444     if (lo != -1 && hi != -1)
445       {
446         switch (lsm.getSelectionMode())
447           {
448           case ListSelectionModel.SINGLE_SELECTION:
449             sum = 1;
450             break;
451             
452           case ListSelectionModel.SINGLE_INTERVAL_SELECTION:
453             sum = hi - lo + 1;
454             break;
455             
456           case ListSelectionModel.MULTIPLE_INTERVAL_SELECTION:        
457             for (int i = lo; i <= hi; ++i)
458               if (lsm.isSelectedIndex(i))        
459                 ++sum;
460             break;
461           }
462       }
463      
464      return sum;
465   }
466
467   /**
468    * Registers a listener with the model, so that it will receive
469    * {@link TableColumnModelEvent} notifications.
470    *
471    * @param listener the listener (<code>null</code> ignored).
472    */
473   public void addColumnModelListener(TableColumnModelListener listener)
474   {
475     listenerList.add(TableColumnModelListener.class, listener);
476   }
477
478   /**
479    * Deregisters a listener so that it no longer receives notification of 
480    * changes to this model.
481    *
482    * @param listener  the listener to remove
483    */
484   public void removeColumnModelListener(TableColumnModelListener listener)
485   {
486     listenerList.remove(TableColumnModelListener.class, listener);
487   }
488
489   /**
490    * Returns an array containing the listeners that are registered with the
491    * model.  If there are no listeners, an empty array is returned.
492    * 
493    * @return An array containing the listeners that are registered with the
494    *     model.
495    *     
496    * @see #addColumnModelListener(TableColumnModelListener)
497    * @since 1.4
498    */
499   public TableColumnModelListener[] getColumnModelListeners()
500   {
501     return (TableColumnModelListener[])
502       listenerList.getListeners(TableColumnModelListener.class);
503   }       
504
505   /**
506    * Sends the specified {@link TableColumnModelEvent} to all registered 
507    * listeners, to indicate that a column has been added to the model.  The
508    * event's <code>toIndex</code> attribute should contain the index of the
509    * added column.  
510    * 
511    * @param e  the event.
512    * 
513    * @see #addColumn(TableColumn)
514    */
515   protected void fireColumnAdded(TableColumnModelEvent e)
516   {    
517     TableColumnModelListener[] listeners = getColumnModelListeners();
518
519     for (int i = 0; i < listeners.length; i++)
520       listeners[i].columnAdded(e);        
521   }
522
523   /**
524    * Sends the specified {@link TableColumnModelEvent} to all registered 
525    * listeners, to indicate that a column has been removed from the model.  The
526    * event's <code>fromIndex</code> attribute should contain the index of the
527    * removed column.  
528    * 
529    * @param e  the event.
530    * 
531    * @see #removeColumn(TableColumn)
532    */
533   protected void fireColumnRemoved(TableColumnModelEvent e)
534   {
535     TableColumnModelListener[] listeners = getColumnModelListeners();
536
537     for (int i = 0; i < listeners.length; i++)
538       listeners[i].columnRemoved(e);        
539   }
540
541   /**
542    * Sends the specified {@link TableColumnModelEvent} to all registered 
543    * listeners, to indicate that a column in the model has been moved.  The
544    * event's <code>fromIndex</code> attribute should contain the old column
545    * index, and the <code>toIndex</code> attribute should contain the new
546    * column index.  
547    * 
548    * @param e  the event.
549    * 
550    * @see #moveColumn(int, int)
551    */
552   protected void fireColumnMoved(TableColumnModelEvent e)
553   {
554     TableColumnModelListener[] listeners = getColumnModelListeners();
555
556     for (int i = 0; i < listeners.length; i++)
557       listeners[i].columnMoved(e);        
558   }
559
560   /**
561    * Sends the specified {@link ListSelectionEvent} to all registered listeners,
562    * to indicate that the column selections have changed.
563    *
564    * @param e  the event.
565    * 
566    * @see #valueChanged(ListSelectionEvent)
567    */
568   protected void fireColumnSelectionChanged(ListSelectionEvent e)
569   {
570     EventListener [] listeners = getListeners(TableColumnModelListener.class);
571     for (int i = 0; i < listeners.length; ++i)
572       ((TableColumnModelListener) listeners[i]).columnSelectionChanged(e);
573   }
574
575   /**
576    * Sends a {@link ChangeEvent} to the model's registered listeners to 
577    * indicate that the column margin was changed.  
578    * 
579    * @see #setColumnMargin(int)
580    */
581   protected void fireColumnMarginChanged()
582   {
583     EventListener [] listeners = getListeners(TableColumnModelListener.class);
584     for (int i = 0; i < listeners.length; ++i)
585       ((TableColumnModelListener) listeners[i]).columnMarginChanged(changeEvent);
586   }
587
588   /**
589    * Returns an array containing the listeners (of the specified type) that 
590    * are registered with this model.
591    * 
592    * @param listenerType  the listener type (must indicate a subclass of 
593    *     {@link EventListener}, <code>null</code> not permitted).
594    *
595    * @return An array containing the listeners (of the specified type) that 
596    *     are registered with this model.
597    */
598   public EventListener[] getListeners(Class listenerType)
599   {
600     return listenerList.getListeners(listenerType);
601   }
602
603   /**
604    * Receives notification of property changes for the columns in the model.
605    * If the <code>width</code> property for any column changes, we invalidate
606    * the {@link #totalColumnWidth} value here. 
607    *
608    * @param event  the event.
609    */
610   public void propertyChange(PropertyChangeEvent event)
611   {
612     if (event.getPropertyName().equals("width"))
613           invalidateWidthCache(); 
614   }
615
616   /**
617    * Receives notification of the change to the list selection model, and 
618    * responds by calling 
619    * {@link #fireColumnSelectionChanged(ListSelectionEvent)}.
620    * 
621    * @param e  the list selection event.
622    * 
623    * @see #getSelectionModel()
624    */
625   public void valueChanged(ListSelectionEvent e)
626   {
627     fireColumnSelectionChanged(e);
628   }
629
630   /**
631    * Creates a default selection model to track the currently selected 
632    * column(s).  This method is called by the constructor and returns a new
633    * instance of {@link DefaultListSelectionModel}.
634    *
635    * @return A new default column selection model.
636    */
637   protected ListSelectionModel createSelectionModel()
638   {    
639     return new DefaultListSelectionModel();
640   }
641
642   /**
643    * Recalculates the total width of the columns, if the cached value is
644    * <code>-1</code>.  Otherwise this method does nothing.
645    * 
646    * @see #getTotalColumnWidth()
647    */
648   protected void recalcWidthCache()
649   {
650     if (totalColumnWidth == -1)
651       {
652         totalColumnWidth = 0;
653         for (int i = 0; i < tableColumns.size(); ++i)
654           {
655             totalColumnWidth += ((TableColumn) tableColumns.get(i)).getWidth();
656           }
657       }
658   }
659
660   /**
661    * Sets the {@link #totalColumnWidth} field to <code>-1</code>.
662    * 
663    * @see #recalcWidthCache()
664    */
665   private void invalidateWidthCache()
666   {
667     totalColumnWidth = -1;
668   }
669 }