View Javadoc

1   /*
2    *  File: StdHierarchicalTimeBarModel.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    *  This program 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 of the License, or
9    *  (at your option) any later version.
10   *
11   *  This program is distributed in the hope that it will be useful,
12   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   *  GNU General Public License for more details.
15   *
16   *  You should have received a copy of the GNU General Public License
17   *  along with this program; if not, write to the Free Software
18   *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19   */
20  package de.jaret.util.ui.timebars.model;
21  
22  import java.util.ArrayList;
23  import java.util.HashMap;
24  import java.util.List;
25  import java.util.Map;
26  
27  /**
28   * An implementation of of a "normal" TimeBarModel based on a hierarchical model and a viewstate. Intended for internal
29   * use when using a hierarchical model.
30   * 
31   * MAYBE Filter some of the events out of the node for visibility (will not hurt if not ...)
32   * 
33   * @author Peter Kliem
34   * @version $Id: StdHierarchicalTimeBarModel.java 800 2008-12-27 22:27:33Z kliem $
35   */
36  public class StdHierarchicalTimeBarModel extends AbstractTimeBarModel implements HierarchicalViewStateListener,
37          TimeBarNodeListener {
38      /**
39       * list of timebar nodes (that are the rows of the model). The list contains always only the visible nodes.
40       */
41      protected List<TimeBarNode> _rows;
42  
43      /** the underlying hierarchical model. */
44      protected HierarchicalTimeBarModel _hModel;
45      /** hierarchical viewstate holding the folded/unfolded information. */
46      protected HierarchicalViewState _hvs;
47  
48      /** if set to true the roor node will be seen expanded and will not be represented by a row. */
49      protected boolean _hideRoot = false;
50  
51      /**
52       * Construct a new model.
53       * 
54       * @param hModel hierachical model
55       * @param hvs hierarchical viewstate
56       */
57      public StdHierarchicalTimeBarModel(HierarchicalTimeBarModel hModel, HierarchicalViewState hvs) {
58          _hModel = hModel;
59          _hvs = hvs;
60          _hvs.addHierarchicalViewstateListener(this);
61          updateRowList();
62          checkMinMax(_hModel.getRootNode());
63      }
64  
65      /**
66       * Fill the rowlist with the visible nodes.
67       * 
68       */
69      private void updateRowList() {
70          _rows = new ArrayList<TimeBarNode>();
71          if (!_hideRoot) {
72              updateRowList(_rows, 0, _hModel.getRootNode(), true);
73          } else {
74              if (_hModel.getRootNode().getChildren() != null) {
75                  for (TimeBarNode n : _hModel.getRootNode().getChildren()) {
76                      updateRowList(_rows, 0, n, true);
77                  }
78              }
79          }
80      }
81  
82      /**
83       * Fill a given list with the visible nodes.
84       * 
85       * @param rows list to fill
86       * @param level currect level for informing the node about its level
87       * @param node current node to add
88       * @param visible is the node visible or not?
89       */
90      private void updateRowList(List<TimeBarNode> rows, int level, TimeBarNode node, boolean visible) {
91          if (visible) {
92              rows.add(node);
93          }
94  
95          node.addTimeBarNodeListener(this);
96          node.setLevel(level);
97  
98          if (_minDate == null) {
99              _minDate = node.getMinDate();
100             _maxDate = node.getMaxDate();
101         } else if (node.getMinDate() != null && node.getMaxDate() != null) {
102             if (_minDate.compareTo(node.getMinDate()) > 0) {
103                 _minDate = node.getMinDate();
104             }
105             if (_maxDate.compareTo(node.getMaxDate()) < 0) {
106                 _maxDate = node.getMaxDate();
107             }
108         }
109         if (node.getChildren() != null) {
110             for (TimeBarNode n : node.getChildren()) {
111                 updateRowList(rows, level + 1, n, _hvs.isExpanded(node) && visible);
112             }
113         }
114         if (level > _hModel.getDepth()) {
115             _hModel.setDepth(level);
116         }
117     }
118 
119     /**
120      * Checks the min and maxdate by looking at all nodes.
121      * 
122      * @param node starting node (called recursive)
123      */
124     private void checkMinMax(TimeBarNode node) {
125         if (_minDate == null) {
126             _minDate = node.getMinDate();
127             _maxDate = node.getMaxDate();
128         } else if (node.getMinDate() != null && node.getMaxDate() != null) {
129             if (_minDate.compareTo(node.getMinDate()) > 0) {
130                 _minDate = node.getMinDate();
131             }
132             if (_maxDate.compareTo(node.getMaxDate()) < 0) {
133                 _maxDate = node.getMaxDate();
134             }
135         }
136         if (node.getChildren() != null) {
137             for (TimeBarNode child : node.getChildren()) {
138                 checkMinMax(child);
139             }
140         }
141 
142     }
143 
144     /**
145      * Check whether there are sibling nodes for a given node on a specified level.
146      * 
147      * @param node node to check
148      * @param level level
149      * @return true if the node itself has more siblings on the given level or if there are nodes on the given level
150      */
151     public boolean moreSiblings(TimeBarNode node, int level) {
152         int idx = _rows.indexOf(node);
153         if (idx == -1) {
154             return false;
155             // throw new RuntimeException();
156         }
157         if (node.getLevel() == level) {
158             return getNextSibling(node) != null;
159         } else {
160             TimeBarNode n = node;
161             for (int l = node.getLevel(); l > level + 1; l--) {
162                 n = getParent(n);
163             }
164             return getNextSibling(n) != null;
165         }
166     }
167 
168     /**
169      * Retrieve the next sibling of a given node.
170      * 
171      * @param node node to search sibling for
172      * @return next sibling or null if none could be found
173      */
174     public TimeBarNode getNextSibling(TimeBarNode node) {
175         TimeBarNode parent = getParent(node);
176         if (parent == null) {
177             return null;
178         }
179         int idx = parent.getChildren().indexOf(node);
180         if (parent.getChildren().size() > idx + 1) {
181             return parent.getChildren().get(idx + 1);
182         } else {
183             return null;
184         }
185 
186     }
187 
188     /**
189      * Get the parent of a given node.
190      * 
191      * @param node node to search the parent for
192      * @return parent of the node or null if there is no parent
193      */
194     private TimeBarNode getParent(TimeBarNode node) {
195         int idx = _rows.indexOf(node);
196         if (idx == -1) {
197             return null;
198         }
199         for (int i = idx - 1; i >= 0; i--) {
200             TimeBarNode n = _rows.get(i);
201             if (n.getChildren().contains(node)) {
202                 return n;
203             }
204         }
205         return null;
206     }
207 
208     /**
209      * Check whether a node is visible.
210      * 
211      * @param node node to chack
212      * @return true if the node ist visible
213      */
214     boolean isVisible(TimeBarNode node) {
215         return getIdxForNode(node) != -1;
216     }
217 
218     /**
219      * Get index of a node.
220      * 
221      * @param node node
222      * @return idx or -1
223      */
224     int getIdxForNode(TimeBarNode node) {
225         return _rows.indexOf(node);
226     }
227 
228     /**
229      * {@inheritDoc}
230      */
231     public TimeBarRow getRow(int rowIdx) {
232         return _rows.get(rowIdx);
233     }
234 
235     /**
236      * {@inheritDoc}
237      */
238     public int getRowCount() {
239         return _rows.size();
240     }
241 
242     /**
243      * {@inheritDoc}
244      */
245     public void nodeAdded(TimeBarNode parent, TimeBarNode newChild) {
246         checkMinMax(newChild);
247         newChild.addTimeBarNodeListener(this);
248         if (_hvs.isExpanded(parent)) {
249             // search the position of the new child and add the row
250             Map<TimeBarNode, Integer> map = new HashMap<TimeBarNode, Integer>();
251             posForNode(parent, map);
252             int pos = map.get(newChild);
253             _rows.add(pos, newChild);
254             fireRowAdded(newChild);
255             // if the new child has children and is expanded, add all of its children
256             List<TimeBarNode> toAdd = new ArrayList<TimeBarNode>();
257             enumerateChildren(newChild, toAdd);
258             pos++;
259             for (TimeBarNode timeBarNode : toAdd) {
260                 _rows.add(pos, timeBarNode);
261                 fireRowAdded(timeBarNode);
262                 pos++;
263             }
264 
265         }
266     }
267 
268     /**
269      * Fill a map with the index positions for the underlying nodes.
270      * 
271      * @param node starting node
272      * @param map map to fill with the indizes
273      * @return "inserted" count for recursive call
274      */
275     private int posForNode(TimeBarNode node, Map<TimeBarNode, Integer> map) {
276         int idx = getIdxForNode(node);
277         int count = node.getChildren().size();
278         int inserted = 0;
279         for (int i = 0; i < count; i++) {
280             TimeBarNode n = node.getChildren().get(i);
281             map.put(n, idx + 1 + inserted);
282             inserted++;
283             if (_hvs.isExpanded(n) && n.getChildren().size() > 0) {
284                 inserted += posForNode(n, map);
285             }
286         }
287         return inserted;
288     }
289 
290     /**
291      * {@inheritDoc}
292      */
293     public void nodeRemoved(TimeBarNode parent, TimeBarNode removedChild) {
294         checkMinMax(_hModel.getRootNode()); // check the complete range
295         removedChild.removeTimeBarNodeListener(this);
296         if (_hvs.isExpanded(parent)) {
297             // remove the row of the child
298             _rows.remove(removedChild);
299             fireRowRemoved(removedChild);
300             // remove the rows of all visible children
301             List<TimeBarNode> toRemove = new ArrayList<TimeBarNode>();
302             enumerateChildren(removedChild, toRemove);
303             for (TimeBarNode timeBarNode : toRemove) {
304                 _rows.remove(timeBarNode);
305                 fireRowRemoved(timeBarNode);
306             }
307         }
308     }
309 
310     /**
311      * Fill a list with all children of the given node that are visible.
312      * 
313      * @param node starting ndoe
314      * @param children list to fill
315      */
316     private void enumerateChildren(TimeBarNode node, List<TimeBarNode> children) {
317         if (node.getChildren() != null && _hvs.isExpanded(node)) {
318             for (TimeBarNode timeBarNode : node.getChildren()) {
319                 children.add(timeBarNode);
320                 enumerateChildren(timeBarNode, children);
321             }
322         }
323     }
324 
325     /**
326      * Handle expansion of a node. This means adding all rows that become visible when expanding.
327      * 
328      * @param node the has been expanded
329      */
330     public void nodeExpanded(TimeBarNode node) {
331         nodeExpanded2(node);
332         checkMinMax(node); // TODO could be done more efficient (listen to changes on the row itself)
333     }
334 
335     /**
336      * Handle expansion of a node. This means adding all rows that become visible when expanding.
337      * 
338      * @param node the has been expanded
339      * @return number of added rows (that became visible)
340      */
341     public int nodeExpanded2(TimeBarNode node) {
342         int idx = getIdxForNode(node);
343         int count = node.getChildren().size();
344         int inserted = 0;
345         for (int i = 0; i < count; i++) {
346             TimeBarNode n = node.getChildren().get(i);
347             _rows.add(idx + 1 + inserted, n);
348             inserted++;
349             fireRowAdded(n);
350             if (_hvs.isExpanded(n) && n.getChildren().size() > 0) {
351                 inserted += nodeExpanded2(n);
352             }
353         }
354         return inserted;
355     }
356 
357     /**
358      * Handle folding of a node. This means removing all rows that "disappear" with folding.
359      * 
360      * @param node node that has been folded
361      */
362     public void nodeFolded(TimeBarNode node) {
363         int count = node.getChildren().size();
364         for (int i = 0; i < count; i++) {
365             TimeBarNode n = node.getChildren().get(i);
366             if (_hvs.isExpanded(n) && n.getChildren().size() > 0) {
367                 nodeFolded(n);
368             }
369             int idx2 = getIdxForNode(n);
370             // maybe the node is already hidden ...
371             if (idx2 != -1) {
372                 _rows.remove(idx2);
373             }
374             fireRowRemoved(n);
375         }
376         checkMinMax(_hModel.getRootNode()); // TODO could be done more efficient (listen to changes on the row itself)
377     }
378 
379     /**
380      * Retrieve whether the root node is included in the outgoing model.
381      * 
382      * @return <code>true</code> when the root node is included in the resulting flat model
383      */
384     public boolean getHideRoot() {
385         return _hideRoot;
386     }
387 
388     /**
389      * Set whether the root node should be represented in the flat outgoing model.
390      * 
391      * @param hideRoot set to <code>true</code> if the root node should not be included in the outgoing flat model
392      */
393     public void setHideRoot(boolean hideRoot) {
394         if (hideRoot != _hideRoot) {
395             _hideRoot = hideRoot;
396             updateRowList();
397             fireModelDataChanged();
398         }
399     }
400 }