View Javadoc

1   /*
2    *  File: StdHierarchicalTableModel.java 
3    *  Copyright (c) 2004-2007  Peter Kliem (Peter.Kliem@jaret.de)
4    *  A commercial license is available, see http://www.jaret.de.
5    *
6    * All rights reserved. This program and the accompanying materials
7    * are made available under the terms of the Common Public License v1.0
8    * which accompanies this distribution, and is available at
9    * http://www.eclipse.org/legal/cpl-v10.html
10   */
11  package de.jaret.util.ui.table.model;
12  
13  import java.beans.PropertyChangeEvent;
14  import java.beans.PropertyChangeListener;
15  import java.util.ArrayList;
16  import java.util.HashMap;
17  import java.util.List;
18  import java.util.Map;
19  
20  import de.jaret.util.misc.PropertyObservable;
21  
22  /***
23   * Implementation of a "normal" jaret table model based on a hierarchical jaret table model. The StdHierarchicalmodel
24   * will listen on propchanges if the nodes are PropertyObservables.
25   * 
26   * @author Peter Kliem
27   * @version $Id: StdHierarchicalTableModel.java 1076 2010-12-05 13:34:42Z kliem $
28   */
29  public class StdHierarchicalTableModel extends AbstractJaretTableModel implements IHierarchicalTableViewStateListener,
30          ITableNodeListener, PropertyChangeListener {
31      /***
32       * Current row list = list of visible nodes.
33       */
34      protected List<ITableNode> _rows;
35  
36      /*** the hierarchical model this "normal" model maps. */
37      protected IHierarchicalJaretTableModel _hModel;
38  
39      /*** the hierarchical viewstate responsible for node visibility. */
40      protected IHierarchicalTableViewState _hvs;
41  
42      /*** the list of columns. */
43      protected List<IColumn> _cols = new ArrayList<IColumn>();
44  
45      protected boolean _excludeRootNode = false;
46  
47      /***
48       * Construct a new stdhierarchical table model for a viewstate and a hierarchical model.
49       * 
50       * @param hModel hierarchical table model
51       * @param hvs hierarchical viewstate
52       */
53      public StdHierarchicalTableModel(IHierarchicalJaretTableModel hModel, IHierarchicalTableViewState hvs) {
54          _hModel = hModel;
55          _hvs = hvs;
56          _hvs.addHierarchicalViewstateListener(this);
57          updateRowList();
58      }
59  
60      /***
61       * Register as propchange listener if node is observable.
62       * 
63       * @param node node
64       */
65      private void registerPropChange(ITableNode node) {
66          // listen for propertychanges if we are dealing with a propertyobservable
67          if (node instanceof PropertyObservable) {
68              ((PropertyObservable) node).addPropertyChangeListener(this);
69          }
70      }
71  
72      /***
73       * Deregister as propchange listener if node is observable.
74       * 
75       * @param node node
76       */
77      private void deRegisterPropChange(ITableNode node) {
78          if (node instanceof PropertyObservable) {
79              ((PropertyObservable) node).removePropertyChangeListener(this);
80          }
81      }
82  
83      /***
84       * Update the internal rowlist by traversing the hierarchy.
85       */
86      private void updateRowList() {
87          _rows = new ArrayList<ITableNode>();
88          updateRowList(_rows, 0, _hModel.getRootNode(), true);
89      }
90  
91      /***
92       * Recursive creation of the list of rows.
93       * 
94       * @param rows list to be filled
95       * @param level current level
96       * @param node current node
97       * @param visible true if visible
98       */
99      private void updateRowList(List<ITableNode> rows, int level, ITableNode node, boolean visible) {
100         if (visible) {
101             if (!(_excludeRootNode && node.equals(_hModel.getRootNode()))) {
102                 rows.add(node);
103                 registerPropChange(node);
104             }
105         }
106 
107         node.addTableNodeListener(this);
108 
109         // set the level of the node
110         node.setLevel(level);
111 
112         for (ITableNode n : node.getChildren()) {
113             updateRowList(rows, level + 1, n, _hvs.isExpanded(node) && visible);
114         }
115     }
116 
117     /***
118      * Check whether more siblings exist for a given node on a given level.
119      * 
120      * @param node node
121      * @param level level
122      * @return true if more siblings exist (even with other parent)
123      */
124     public boolean moreSiblings(ITableNode node, int level) {
125         int idx = _rows.indexOf(node);
126         if (idx == -1) {
127             throw new RuntimeException();
128         }
129         if (node.getLevel() == level) {
130             return getNextSibling(node) != null;
131         } else {
132             ITableNode n = node;
133             for (int l = node.getLevel(); l > level + 1; l--) {
134                 n = getParent(n);
135             }
136             return getNextSibling(n) != null;
137         }
138     }
139 
140     /***
141      * Return the next sibling of a node.
142      * 
143      * @param node node to get the next sibling for
144      * @return the sibling or <code>null</code> if there is none
145      */
146     public ITableNode getNextSibling(ITableNode node) {
147         ITableNode parent = getParent(node);
148         if (parent == null) {
149             return null;
150         }
151         int idx = parent.getChildren().indexOf(node);
152         if (parent.getChildren().size() > idx + 1) {
153             return parent.getChildren().get(idx + 1);
154         } else {
155             return null;
156         }
157     }
158 
159     /***
160      * Retrieve the parent of a particular node.
161      * 
162      * @param node node
163      * @return parent of the node or <code>null</code> if it is the root node or is not in the list of nodes (not
164      * visible)
165      */
166     private ITableNode getParent(ITableNode node) {
167         int idx = _rows.indexOf(node);
168         if (idx == -1) {
169             return null;
170         }
171         for (int i = idx - 1; i >= 0; i--) {
172             ITableNode n = _rows.get(i);
173             if (n.getChildren().contains(node)) {
174                 return n;
175             }
176         }
177         return null;
178     }
179 
180     /***
181      * Check whether a node is visible.
182      * 
183      * @param node node to check
184      * @return true if the node is visible
185      */
186     public boolean isVisible(ITableNode node) {
187         return getIdxForNode(node) != -1;
188     }
189 
190     /***
191      * Get the index of a node in the list of visible nodes.
192      * 
193      * @param node node to check
194      * @return index or -1 if not found
195      */
196     private int getIdxForNode(ITableNode node) {
197         return _rows.indexOf(node);
198     }
199 
200     /***
201      * {@inheritDoc}
202      */
203     public IRow getRow(int rowIdx) {
204         return _rows.get(rowIdx);
205     }
206 
207     /***
208      * {@inheritDoc}
209      */
210     public int getRowCount() {
211         return _rows.size();
212     }
213 
214     /***
215      * {@inheritDoc}
216      */
217     public void nodeAdded(ITableNode parent, ITableNode newChild) {
218         newChild.addTableNodeListener(this);
219         if (_hvs.isExpanded(parent)) {
220             // search the position of the new child and add the row
221             Map<ITableNode, Integer> map = new HashMap<ITableNode, Integer>();
222             posForNode(parent, map);
223             int pos = map.get(newChild);
224             _rows.add(pos, newChild);
225             fireRowAdded(pos, newChild);
226             // if the new child has children and is expanded, add all of its children
227             List<ITableNode> toAdd = new ArrayList<ITableNode>();
228             enumerateChildren(newChild, toAdd);
229             pos++;
230             for (ITableNode tableNode : toAdd) {
231                 _rows.add(pos, tableNode);
232                 fireRowAdded(pos, tableNode);
233                 pos++;
234             }
235 
236         }
237 
238     }
239 
240     /***
241      * Fill a list with all children of the given node that are visible.
242      * 
243      * @param node starting ndoe
244      * @param children list to fill
245      */
246     private void enumerateChildren(ITableNode node, List<ITableNode> children) {
247         if (node.getChildren() != null && _hvs.isExpanded(node)) {
248             for (ITableNode tableNode : node.getChildren()) {
249                 children.add(tableNode);
250                 enumerateChildren(tableNode, children);
251             }
252         }
253     }
254 
255     /***
256      * Fill a map with the index positions for the underlying nodes.
257      * 
258      * @param node starting node
259      * @param map map to fill with the indizes
260      * @return "inserted" count for recursive call
261      */
262     private int posForNode(ITableNode node, Map<ITableNode, Integer> map) {
263         int idx = getIdxForNode(node);
264         int count = node.getChildren().size();
265         int inserted = 0;
266         for (int i = 0; i < count; i++) {
267             ITableNode n = node.getChildren().get(i);
268             map.put(n, idx + 1 + inserted);
269             inserted++;
270             if (_hvs.isExpanded(n) && n.getChildren().size() > 0) {
271                 inserted += posForNode(n, map);
272             }
273         }
274         return inserted;
275     }
276 
277     /***
278      * {@inheritDoc}
279      */
280     public void nodeRemoved(ITableNode parent, ITableNode removedChild) {
281         removedChild.removeTableNodeListener(this);
282         if (_hvs.isExpanded(parent)) {
283             // remove the row of the child
284             _rows.remove(removedChild);
285             fireRowRemoved(removedChild);
286             // remove the rows of all visible children
287             List<ITableNode> toRemove = new ArrayList<ITableNode>();
288             enumerateChildren(removedChild, toRemove);
289             for (ITableNode tableNode : toRemove) {
290                 _rows.remove(tableNode);
291                 fireRowRemoved(tableNode);
292             }
293         }
294     }
295 
296     /***
297      * {@inheritDoc }Handle expansion of a node. This means adding all rows that become visible when expanding.
298      */
299     public void nodeExpanded(ITableNode node) {
300         nodeExpanded2(node);
301     }
302 
303     /***
304      * Handle expansion of a node by traversing all children of the node to check whether they are visible.
305      * 
306      * @param node node expanded
307      * @return count of nodes that became visible ()= number of insertedlines)
308      */
309     private int nodeExpanded2(ITableNode node) {
310         int idx = getIdxForNode(node);
311         int count = node.getChildren().size();
312         int inserted = 0;
313         for (int i = 0; i < count; i++) {
314             ITableNode n = node.getChildren().get(i);
315             int newIdx = idx + 1 + inserted;
316             _rows.add(newIdx, n);
317             inserted++;
318             fireRowAdded(newIdx, n);
319             registerPropChange(n);
320             if (_hvs.isExpanded(n) && n.getChildren().size() > 0) {
321                 inserted += nodeExpanded2(n);
322             }
323         }
324         return inserted;
325     }
326 
327     /***
328      * {@inheritDoc} Handle folding of a node. This means removing all rows that "disappear" with folding.
329      */
330     public void nodeFolded(ITableNode node) {
331         int count = node.getChildren().size();
332         for (int i = 0; i < count; i++) {
333             ITableNode n = node.getChildren().get(i);
334             if (_hvs.isExpanded(n) && n.getChildren().size() > 0) {
335                 nodeFolded(n);
336             }
337             int idx2 = getIdxForNode(n);
338             // maybe the node is already hidden ...
339             if (idx2 != -1) {
340                 _rows.remove(idx2);
341             }
342             fireRowRemoved(n);
343             deRegisterPropChange(n);
344         }
345     }
346 
347     /***
348      * {@inheritDoc}
349      */
350     public int getColumnCount() {
351         return _cols.size();
352     }
353 
354     /***
355      * {@inheritDoc}
356      */
357     public IColumn getColumn(int idx) {
358         return _cols.get(idx);
359     }
360 
361     /***
362      * Add a column to the list of columns.
363      * 
364      * @param column column to add
365      */
366     public void addColumn(IColumn column) {
367         _cols.add(column);
368         fireColumnAdded(_cols.size() - 1, column);
369     }
370 
371     /***
372      * Check whether the root node should be excluded.
373      * 
374      * @return true if the root node should be excluded
375      */
376     public boolean getExcludeRootNode() {
377         return _excludeRootNode;
378     }
379 
380     /***
381      * Set whether the rot node should be excluded in the display.
382      * 
383      * @param excludeRootNode true to hide the root node
384      */
385     public void setExcludeRootNode(boolean excludeRootNode) {
386         boolean old = _excludeRootNode;
387         _excludeRootNode = excludeRootNode;
388         if (old != _excludeRootNode) {
389             updateRowList();
390             fireTableDataChanged();
391         }
392     }
393 
394     /***
395      * {@inheritDoc}
396      */
397     public void propertyChange(PropertyChangeEvent evt) {
398         if (evt.getSource() instanceof IRow) {
399             fireRowChanged((IRow) evt.getSource());
400         }
401     }
402 }