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