001/** SortableTreeTableModel.java                          PortfolioApp
002 *
003 * Created 19/10/2007 11:54:13 AM
004 * see http://svn.chorem.org/svn/jtimer/tags/jtimer-1.0-beta4/src/java/org/codelutin/jtimer/ui/treetable/
005 *
006 * @author Ray Turnbull
007 */
008// package org.codelutin.jtimer.ui.treetable.sorting;
009package ca.bc.webarts.widgets;
010
011import java.util.ArrayList;
012import java.util.List;
013
014import javax.swing.event.TreeExpansionEvent;
015import javax.swing.event.TreeExpansionListener;
016import javax.swing.table.JTableHeader;
017import javax.swing.tree.TreePath;
018import javax.swing.SortOrder;
019
020//import org.jdesktop.swingx.decorator.SortOrder;
021import org.jdesktop.swingx.sort.SortUtils;
022import org.jdesktop.swingx.treetable.AbstractSortableTreeTableModel;
023import org.jdesktop.swingx.treetable.DefaultTreeTableModel;
024import org.jdesktop.swingx.treetable.MutableTreeTableNode;
025import org.jdesktop.swingx.treetable.TreeTableNode;
026
027/**
028 * This is designed to be used with AbstractSortableTreeTableNode.<br>
029 * For user interaction by clicking column headers, SortableTreeTable (extends
030 * JXTreeTable) should be used also.<br>
031 * The model must be set up using string Column Identifiers.
032 * <p>
033 * It controls the sorting of the children of each node.<br>
034 * <br>
035 * Note that this does not support sorting the underlying table, as that is
036 * meaningless for a TreeTable.<br>
037 * All nodes wiil have their immediate children sorted seperately, and the
038 * process will continue down through each generation, starting from the
039 * specified node.<br>
040 * If any of the sort options are set or changed, sorting will start from the
041 * root node. If a node has a child added or removed, sorting will start from
042 * that node. The sorts may also be called manually, e.g if sort parameters are
043 * loaded before the model is attached to the TreeTable
044 * <p>
045 * This class does not handle changes to the values of fields being sorted. This
046 * must be done manually by calling sort.
047 *
048 * @see #sort(TreeTableNode)
049 * @see #sort()
050 *
051 */
052public class SortableTreeTableModel extends DefaultTreeTableModel implements TreeExpansionListener
053{
054
055  private String sortColumn = null;
056  private SortOrder sortOrder = SortOrder.UNSORTED;
057  private int columnIndex = -1;
058  private List<TreePath> expanded;
059  private SortableTreeTable treeTable = null;
060  private JTableHeader header = null;
061  boolean expanding = false;
062
063
064  public SortableTreeTableModel()
065  {
066  }
067
068
069  public SortableTreeTableModel(TreeTableNode root)
070  {
071    super(root);
072  }
073
074
075  public SortableTreeTableModel(TreeTableNode root, List<?> columnNames)
076  {
077    super(root, columnNames);
078  }
079
080
081  // =========================================================== public api
082
083  /**
084   * Set name of column to sort. If null, will reset to unsorted. Sort Order
085   * will be set ASCENDING<br>
086   * Name must exist in the Column Identifiers.
087   *
088   * @param column - name of column to sort on
089   */
090  public void setSortColumn(String column)
091  {
092    if (column == null)
093    {
094      sortColumn = null;
095      if (SortUtils.isSorted(sortOrder))
096      {
097        sortOrder = SortOrder.UNSORTED;
098        reset();
099      }
100    }
101    else
102    {
103      setKeys(column, SortOrder.ASCENDING);
104    }
105  }
106
107
108  /**
109   * Set column to sort by column index. If -1 or greater than number of
110   * columns - 1, will be set Unsorted, otherwise sort order will be set
111   * Ascending.
112   *
113   * @param column - index of column name in columnNames List
114   */
115  public void setSortColumn(int column)
116  {
117    if (column == -1 || column > columnIdentifiers.size() - 1)
118    {
119      sortColumn = null;
120      if (SortUtils.isSorted(sortOrder))
121      {
122        sortOrder = SortOrder.UNSORTED;
123        reset();
124      }
125    }
126    else
127    {
128      setKeys(columnIdentifiers.get(column).toString(), SortOrder.ASCENDING);
129    }
130  }
131
132
133  /**
134   * Set Sort Order. If null, order will be Unsorted.<br>
135   * If no sort column has been set has no effect.
136   * @param order
137   */
138  public void setSortOrder(SortOrder order)
139  {
140    if (sortColumn == null)
141    {
142      return;
143    }
144    if (order == null)
145    {
146      order = SortOrder.UNSORTED;
147    }
148    setKeys(sortColumn, order);
149  }
150
151
152  /**
153   * Toggle sort order. Unsorted will be sorted ascending, and sorted columns
154   * order will be reversed. If sort column not set, has no effect.
155   */
156  public void toggleSortOrder()
157  {
158    if (SortUtils.isAscending(sortOrder))
159    {
160      setSortOrder(SortOrder.DESCENDING);
161    }
162    else
163    {
164      setSortOrder(SortOrder.ASCENDING);
165    }
166  }
167
168
169  /**
170   * Set sort options. If either option is null, will be set Unsorted
171   *
172   * @param column - name of column to sort. Must be a column in column names
173   *        list.
174   * @param order - see Swingx org.jdesktop.swingx.decorator.SortOrder
175   */
176  public void setSortOptions(String column, SortOrder order)
177  {
178    if (column == null)
179    {
180      setSortColumn(null);
181      return;
182    }
183    if (order == null)
184    {
185      order = SortOrder.UNSORTED;
186    }
187    setKeys(column, order);
188  }
189
190
191  /**
192   * Sorts complete TreeTable. This will be called automatically if any of the
193   * sort options (column or order) are changed. It is not usually neccessary
194   * to called this directly unless TreeTable is changed external to model, or
195   * TreeTable data changed.
196   */
197  public void sort()
198  {
199    if (!SortUtils.isSorted(sortOrder))
200    {
201      reset();
202    }
203    else
204    {
205      doFullSort(false);
206    }
207  }
208
209
210  /**
211   * Sorts children of node, and all their children. Node need not be
212   * sortable. (Although if it is not and neither are any descendants, nothing
213   * will happen.) Called automatically if a child is added to or removed from
214   * a node. Usually not necessary to call this directly unless node added
215   * outside model or data changed.
216   *
217   * @param parent - first node to be sorted.
218   */
219  public void sort(TreeTableNode parent)
220  {
221    boolean reset;
222    if (!SortUtils.isSorted(sortOrder))
223    {
224      reset = true;
225    }
226    else
227    {
228      reset = false;
229    }
230    doSort(parent, reset);
231    TreePath path = new TreePath(getPathToRoot(parent));
232    modelSupport.fireTreeStructureChanged(path);
233    reExpand();
234  }
235
236
237  public int getSortColumnIndex()
238  {
239    return columnIndex;
240  }
241
242
243  public SortOrder getSortOrder()
244  {
245    return sortOrder;
246  }
247
248
249  public void insertNodeInto(MutableTreeTableNode newChild, MutableTreeTableNode parent)
250  {
251    int index = getChildCount(parent);
252    insertNodeInto(newChild, parent, index);
253  }
254
255
256  public void setSortable(MutableTreeTableNode node, boolean sortable)
257  {
258    if (! (node instanceof AbstractSortableTreeTableNode))
259    {
260      return;
261    }
262    AbstractSortableTreeTableNode n = (AbstractSortableTreeTableNode) node;
263    n.setSortable(sortable);
264    if (n.isSorted() && !sortable)
265    {
266      n.reset();
267      TreePath path = new TreePath(getPathToRoot(n));
268      modelSupport.firePathChanged(path);
269    }
270  }
271
272
273  // ==================================================== overridden methods
274
275  public void insertNodeInto(MutableTreeTableNode newChild, MutableTreeTableNode parent, int index)
276  {
277    parent.insert(newChild, index);
278    if (SortUtils.isSorted(sortOrder))
279    {
280      sort(parent);
281    }
282    else
283    {
284      modelSupport.fireChildAdded(new TreePath(getPathToRoot(parent)), index, newChild);
285    }
286  }
287
288
289  public void removeNodeFromParent(MutableTreeTableNode node)
290  {
291    MutableTreeTableNode parent = (MutableTreeTableNode) node.getParent();
292    if (parent == null)
293    {
294      throw new IllegalArgumentException("node does not have a parent.");
295    }
296    // int index = parent.getIndex(node);
297    int index = getIndexOfChild(parent, node);
298    expanded.remove(new TreePath(getPathToRoot(node)));
299    node.removeFromParent();
300    if (SortUtils.isSorted(sortOrder))
301    {
302      sort(parent);
303    }
304    else
305    {
306      modelSupport.fireChildRemoved(new TreePath(getPathToRoot(parent)), index, node);
307    }
308  }
309
310
311  @Override
312  public void setRoot(TreeTableNode root)
313  {
314    expanded = new ArrayList<TreePath>();
315    super.setRoot(root);
316  }
317
318
319  // ======================================================= private methods
320
321  /**
322   * this is a hack to enable expanded nodes to be re-expanded after
323   * structure change. It is called from SortableTreeTable<br>
324   * It is also used to retrieve the table header to repaint if sort options
325   * changed programatically<p>
326   * It means model can only be used in one treetable.
327   *
328   */
329  void setTreeTable(SortableTreeTable treeTable)
330  {
331    this.treeTable = treeTable;
332    expanded = new ArrayList<TreePath>();
333    header = treeTable.getTableHeader();
334  }
335
336
337  private void setKeys(String column, SortOrder order)
338  {
339    if (sortColumn != null && sortColumn.equals(column))
340    {
341      if (sortOrder.equals(order))
342      {
343        return;
344      }
345    }
346    else
347    {
348      if (column != null)
349      {
350        int x = columnIdentifiers.indexOf(column);
351        if (x == -1)
352        {
353          throw new IllegalArgumentException("Column " + column + " not in Column Identifiers");
354        }
355        else
356        {
357          columnIndex = x;
358        }
359      }
360    }
361    sortColumn = column;
362    sortOrder = order;
363    sort();
364    if (header != null)
365    {
366      header.repaint();
367    }
368  }
369
370
371  private void reset()
372  {
373    doFullSort(true);
374  }
375
376
377  private void doFullSort(boolean reset)
378  {
379    TreeTableNode root = getRoot();
380    if (root == null)
381    {
382      return;
383    }
384    doSort(root, reset);
385    modelSupport.fireTreeStructureChanged(new TreePath(root));
386    reExpand();
387  }
388
389
390  /*
391   * Start from node and drill down all nodes looking to sort.
392   */
393  private void doSort(TreeTableNode parent, boolean reset)
394  {
395    boolean canSort;
396    AbstractSortableTreeTableNode node;
397    if (parent instanceof AbstractSortableTreeTableNode)
398    {
399      node = (AbstractSortableTreeTableNode) parent;
400      canSort = true;
401    }
402    else
403    {
404      node = null;
405      canSort = false;
406    }
407    if (canSort)
408    {
409      canSort = node.canSort();
410    }
411    if (canSort)
412    {
413      if (reset)
414      {
415        canSort = node.isSorted();
416      }
417      else
418      {
419        canSort = node.canSort(sortColumn);
420      }
421    }
422    if (canSort)
423    {
424      if (reset)
425      {
426        node.reset();
427      }
428      else
429      {
430        node.sort(columnIndex, sortOrder);
431      }
432    }
433    else
434    {
435      node.reset();
436    }
437    // check children
438    // Enumeration<? extends TreeTableNode> kids = parent.children();
439    // while (kids.hasMoreElements()) {
440    // TreeTableNode child = (TreeTableNode) kids.nextElement();
441    // doSort(child, reset);
442    // }
443
444    // model use version
445    for (int i = 0; i < getChildCount(parent); ++i)
446    {
447      TreeTableNode child = (TreeTableNode) getChild(parent, i);
448      doSort(child, reset);
449    }
450  }
451
452  private void reExpand()
453  {
454    if (treeTable == null)
455    {
456      return;
457    }
458    expanding = true;
459    for (TreePath path : expanded)
460    {
461      treeTable.expandPath(path);
462    }
463    expanding = false;
464  }
465
466  /*
467   * Inherited
468   */
469  @Override
470  public void treeCollapsed(TreeExpansionEvent arg0)
471  {
472    TreePath p = arg0.getPath();
473    expanded.remove(p);
474  }
475
476  /*
477   * Inherited
478   */
479  @Override
480  public void treeExpanded(TreeExpansionEvent arg0)
481  {
482    if (expanding)
483    {
484      return;
485    }
486    TreePath p = arg0.getPath();
487    if (!expanded.contains(p))
488    {
489      expanded.add(p);
490    }
491  }
492
493}
494