View Javadoc

1   /*
2    *  File: TimeBarViewerDelegate.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;
21  
22  import java.awt.Cursor;
23  import java.awt.Point;
24  import java.awt.Rectangle;
25  import java.awt.event.InputEvent;
26  import java.awt.event.KeyEvent;
27  import java.beans.PropertyChangeEvent;
28  import java.beans.PropertyChangeListener;
29  import java.util.ArrayList;
30  import java.util.Collections;
31  import java.util.Comparator;
32  import java.util.HashSet;
33  import java.util.List;
34  import java.util.Set;
35  import java.util.Vector;
36  
37  import de.jaret.util.date.Interval;
38  import de.jaret.util.date.IntervalImpl;
39  import de.jaret.util.date.JaretDate;
40  import de.jaret.util.misc.Pair;
41  import de.jaret.util.ui.timebars.TimeBarViewerInterface.Orientation;
42  import de.jaret.util.ui.timebars.mod.IIntervalModificator;
43  import de.jaret.util.ui.timebars.mod.IntervalModificator;
44  import de.jaret.util.ui.timebars.model.DefaultRowHeader;
45  import de.jaret.util.ui.timebars.model.DefaultTimeBarNode;
46  import de.jaret.util.ui.timebars.model.DefaultTimeBarViewState;
47  import de.jaret.util.ui.timebars.model.FocussedIntervalListener;
48  import de.jaret.util.ui.timebars.model.HierarchicalTimeBarModel;
49  import de.jaret.util.ui.timebars.model.HierarchicalViewState;
50  import de.jaret.util.ui.timebars.model.HierarchicalViewStateImpl;
51  import de.jaret.util.ui.timebars.model.IIntervalRelation;
52  import de.jaret.util.ui.timebars.model.ISelectionRectListener;
53  import de.jaret.util.ui.timebars.model.ITimeBarChangeListener;
54  import de.jaret.util.ui.timebars.model.ITimeBarViewState;
55  import de.jaret.util.ui.timebars.model.ITimeBarViewStateListener;
56  import de.jaret.util.ui.timebars.model.PPSInterval;
57  import de.jaret.util.ui.timebars.model.StdHierarchicalTimeBarModel;
58  import de.jaret.util.ui.timebars.model.TBRect;
59  import de.jaret.util.ui.timebars.model.TimeBarModel;
60  import de.jaret.util.ui.timebars.model.TimeBarModelListener;
61  import de.jaret.util.ui.timebars.model.TimeBarNode;
62  import de.jaret.util.ui.timebars.model.TimeBarRow;
63  import de.jaret.util.ui.timebars.model.TimeBarRowHeader;
64  import de.jaret.util.ui.timebars.model.TimeBarRowListener;
65  import de.jaret.util.ui.timebars.model.TimeBarSelectionListener;
66  import de.jaret.util.ui.timebars.model.TimeBarSelectionModel;
67  import de.jaret.util.ui.timebars.model.TimeBarSelectionModelImpl;
68  import de.jaret.util.ui.timebars.strategy.DefaultIntervalSelectionStrategy;
69  import de.jaret.util.ui.timebars.strategy.DefaultOverlapStrategy;
70  import de.jaret.util.ui.timebars.strategy.IIntervalSelectionStrategy;
71  import de.jaret.util.ui.timebars.strategy.IOverlapStrategy;
72  import de.jaret.util.ui.timebars.strategy.OverlapInfo;
73  
74  /**
75   * The delegate for supporting a TimeBarViewer. This may be the Swing or the SWT version of the time bar viewer. The
76   * delegate encapsulates most of the calculations. It communicates with the viewer for the specific toolkit via the
77   * TimeBarViewerInterface.
78   * 
79   * @author Peter Kliem
80   * @version $Id: TimeBarViewerDelegate.java 1097 2011-11-06 21:44:47Z kliem $
81   */
82  public class TimeBarViewerDelegate implements TimeBarModelListener, TimeBarSelectionListener, TimeBarMarkerListener,
83          PropertyChangeListener {
84  
85      /** default value for the pps value. */
86      private static final double DEFAULT_PIXEL_PER_SECOND = 2000.0 / (24.0 * 60 * 60);
87  
88      /**
89       * pixel delta added on the left and right of the viewer area to ensure elements on the border can be selected.
90       */
91      private static final int PADDING_PIXEL = 30;
92  
93      /**
94       * minimal width for the hierarchy area when dragging. Ensures the area will be selectable after drag.
95       */
96      private static final int MIN_DRAG_HIERARCHY_WIDTH = 2;
97      /**
98       * minimal width for the header area when dragging. Ensures the area will be selectable after drag.
99       */
100     private static final int MIN_DRAG_HEADER_WIDTH = 2;
101 
102     /** minimal height for a row when dragging the row height. */
103     private static final int MIN_ROW_HEIGHT = 10;
104 
105     /** factor for scaling the second vaule to milliseconds. */
106     private static final double MILLISCALING = 1000.0;
107 
108     /** default delta for modification snap/detection. */
109     private static final int DEFAULT_SEL_DELTA = 2;
110 
111     /** delta for detecting modification clicks and drags. */
112     private int _selectionDelta = DEFAULT_SEL_DELTA;
113 
114     /** the delegating time bar viewer. */
115     protected final TimeBarViewerInterface _tbvi;
116 
117     /** scale. */
118     protected double _pixelPerSeconds = DEFAULT_PIXEL_PER_SECOND;
119 
120     /** flag indicating a variable xscale. */
121     protected boolean _variableXScale = false;
122     /**
123      * row holding intervals with pps values overriding the default for intervals.
124      */
125     protected TimeBarNode _xScalePPSIntervalRow;
126 
127     /**
128      * If true the viewer is used with very short intervals and the scrolling is done using milliseconds.
129      */
130     protected boolean _milliAccuracy = false;
131 
132     /** height of the time scale. */
133     protected int _xAxisHeight = TimeBarViewerInterface.DEFAULT_XAXISHEIGHT;
134 
135     /** width of the y axis. */
136     protected int _yAxisWidth = TimeBarViewerInterface.DEFAULT_YAXISWIDTH;
137 
138     /** width of the hierarchy header (in case of an hierachical model). */
139     protected int _hierarchyWidth = 0;
140 
141     /** true if dragging of the hierarchy and header delimiter is allowed. */
142     protected boolean _lineDraggingAllowed = true;
143 
144     /** the orientation of the viewer. */
145     protected Orientation _orientation = Orientation.HORIZONTAL;
146 
147     /** The model. */
148     protected TimeBarModel _model;
149     /** The hierarchical model when used. */
150     protected HierarchicalTimeBarModel _hierarchicalModel;
151 
152     /** standard view state. */
153     protected ITimeBarViewState _timeBarViewState;
154 
155     /** Viewstate holding state information for hierarchical operation. */
156     protected HierarchicalViewState _hierarchicalViewState;
157 
158     /** title of the viewer. */
159     protected String _title;
160 
161     /** index of the first row displayed. */
162     protected int _firstRow = 0;
163 
164     /** pixeloffset of the first row. */
165     protected int _firstRowPixelOffset = 0;
166 
167     /** filtered and sorted list. */
168     protected List<TimeBarRow> _rowList = new ArrayList<TimeBarRow>(); // initialize with an empty list to prevent npes
169 
170     /**
171      * Flag controlling min/max setting behaviour. If true the min and max of the viewer are adapted to the models
172      * min/max dates.
173      */
174     protected boolean _adjustMinMaxDatesByModel = true;
175 
176     /** if set to true scrolling optimizations will be used. */
177     protected boolean _optimizeScrolling = true;
178 
179     /** Date marking the first painted date. */
180     protected JaretDate _startDate = new JaretDate();
181 
182     /** Date marking the last painted date. */
183     protected JaretDate _endDate = new JaretDate();
184 
185     /** Minimum date to be displayed by the viewer. */
186     protected JaretDate _minDate;
187 
188     /** Maximum date to be displayed by the viewer. */
189     protected JaretDate _maxDate;
190 
191     /** start date of the viewer for that the last paint completed. */
192     protected JaretDate _lastStartDate;
193 
194     /** Filter filtering rows displayed. */
195     protected TimeBarRowFilter _rowFilter;
196     /** sorter for the rows in the model. */
197     protected TimeBarRowSorter _rowSorter;
198     /** filter filtering intervals displayed. */
199     protected TimeBarIntervalFilter _intervalFilter;
200 
201     /** selection model, default implementation as default. */
202     protected TimeBarSelectionModel _selectionModel = new TimeBarSelectionModelImpl();
203 
204     /** markers added to the viewer. */
205     protected List<TimeBarMarker> _markers;
206 
207     /** when true the grid/background will be painted. */
208     protected boolean _drawGrid = true;
209 
210     /** if true a row grid will be painted. */
211     protected boolean _drawRowGrid = false; // true -> row grid will be painted
212 
213     /** if set to true and drawOverlapping=false all intervals in a row use the same height. */
214     private boolean _useUniformHeight = false;
215 
216     /**
217      * position of the timescale. One of the constants from the viewer interface.
218      */
219     protected int _timeScalePosition = TimeBarViewerInterface.TIMESCALE_POSITION_BOTTOM;
220 
221     /** area in which the actual rows are painted. */
222     protected Rectangle _diagramRect = new Rectangle(0, 0, 0, 0);
223 
224     /** area in which the x axis (time scale) will be painted. */
225     protected Rectangle _xAxisRect = new Rectangle();;
226 
227     /** area in which the y axis will be painted. */
228     protected Rectangle _yAxisRect = new Rectangle();
229 
230     /** Title area. */
231     protected Rectangle _titleRect = new Rectangle();;
232 
233     /** drawing area of the hierarchy elements. */
234     protected Rectangle _hierarchyRect = new Rectangle();;
235 
236     /** pixeloffset when rendering (top). */
237     protected int _offsetTop;
238     /** pixeloffset when rendering (left). */
239     protected int _offsetLeft;
240 
241     /** true if the selection by a selection rectangle schould be allowed. */
242     private boolean _rectSelectionEnabled = true;
243 
244     /** marker currently involved in a drag operation. */
245     protected TimeBarMarker _draggedMarker;
246 
247     /** interval currently changed by dragging. */
248     protected Interval _changingInterval;
249 
250     /** true indicates dragging of an interval as a whole. */
251     protected boolean _draggedInterval;
252 
253     /** if not dragging the whole interval true means left bound. */
254     protected boolean _draggedIntervalEdgeLeft;
255 
256     /** default for the autoscroll delta. */
257     protected static final int DEFAULT_AUTOSCROLL_DELTA = 10;
258     /** the amount of pixels that is assumed to scroll/drag when the cursor left the diagram area. */
259     protected int _autoscrollDelta = DEFAULT_AUTOSCROLL_DELTA;
260 
261     /** flag indicating whether all selcted intervals should be dragged whe draggig a iterval. */
262     protected boolean _dragAllSelectedIntervals = false;
263 
264     /** true if dragging of the hierarchy limiting line is ongoing. */
265     protected boolean _hierarchyLineDragging = false;
266     /** true if dragging of the header limiting line is ongoing. */
267     protected boolean _headerLineDragging = false;
268     /** true if the dragginng of rows is allowed. */
269     protected boolean _rowHeightDraggingAllowed = false;
270 
271     /** row thats height is currently dragged or <code>null</code> when no row height is beeing dragged. */
272     protected TimeBarRow _heightDraggedRow = null;
273 
274     /** controls autoscroll behaviour: true -> enabled. */
275     protected boolean _autoscroll = true;
276 
277     /** the current selection rectangle. */
278     protected Rectangle _selectionRect = null;
279 
280     /** the last selection rect if existing. */
281     protected Rectangle _lastSelRect;
282 
283     /** highlighted row if any. */
284     protected TimeBarRow _highlightedRow;
285 
286     /** name of the viewer. */
287     private String _name;
288 
289     /** flag controlling the drawing behaviour for overlapping intervals. */
290     protected boolean _drawOverlapping = false;
291 
292     /** Change delta for keyboard modification in seconds. */
293     protected int _keyboardChangeDelta = 60 * 60;
294 
295     /** if true the viewer will try to scroll to a newly focussed interval. */
296     protected boolean _scrollOnFocus = true;
297 
298     /**
299      * Currenty focussed interval or <code>null</code> if no interval has the focus.
300      */
301     protected Interval _focussedInterval;
302 
303     /**
304      * Row of the focussed interval or <code>null</code> if no interval is focussed.
305      */
306     protected TimeBarRow _focussedRow;
307 
308     /** timebar row listener that repaints on every change. */
309     private TimeBarRowListener _repaintingRowListener;
310 
311     /** number of rows to scale to. -1 for no scaling. */
312     private int _autoScaleRows = -1;
313 
314     /** List of FocussedInteralListeners. */
315     protected List<FocussedIntervalListener> _focussedIntervalListeners;
316 
317     /** List of TimeBarChangeListenes. */
318     protected List<ITimeBarChangeListener> _timeBarChangeListeners = new Vector<ITimeBarChangeListener>();
319 
320     /** List of ISelectionRectListeners. */
321     protected List<ISelectionRectListener> _selectionRectListeners = new Vector<ISelectionRectListener>();
322 
323     /** List of intervalmodificators that have been registered. */
324     protected List<IntervalModificator> _intervalModificators = new ArrayList<IntervalModificator>(2);
325 
326     /**
327      * If set to true, intervals are filtered strictly by their interval bounds, disallowing rendering beyond the
328      * bounding box calculated by the interval bounds.
329      */
330     protected boolean _strictClipTimeCheck = false;
331 
332     /** additional time to take into account (looking back) when determining what intervals have to be painted. */
333     protected int _scrollLookBackMinutes = 120;
334     /** additional time to take into account (looking forward) when determining what intervals have to be painted. */
335     protected int _scrollLookForwardMinutes = 120;
336 
337     /** factor used to scale the time scroll bar if integer is not enough. */
338     protected double _timeFactor = 1.0;
339 
340     /** overlap strategy to be used when drawing non-overlapped. */
341     protected IOverlapStrategy _overlapStrategy = new DefaultOverlapStrategy(this);
342 
343     /** if set to true and using a hierchical model the root will not be shown. */
344     protected boolean _hideRoot = false;
345 
346     /** insterval selection strategy. */
347     protected IIntervalSelectionStrategy _intervalSelectionStrategy = new DefaultIntervalSelectionStrategy();
348 
349     /** true if selecting from regions is enabled. */
350     protected boolean _regionRectEnabled = false;
351 
352     /** region selection data: current selection. */
353     protected TBRect _regionSelection = null;
354     /** region selection data: last selection. */
355     protected TBRect _lastRegionSelection = null;
356     /** start date when selecting a region. */
357     protected JaretDate _regionStartDate;
358     /** start row when selecting a reion. */
359     protected TimeBarRow _regionStartRow;
360 
361     /**
362      * Constructor will set the TimeBarViewerInterface for the delegate.
363      * 
364      * @param tbvi the viewer as TimeBarViewerInterface
365      */
366     public TimeBarViewerDelegate(TimeBarViewerInterface tbvi) {
367         _tbvi = tbvi;
368         // the delegate listens to selection changes
369         _selectionModel.addTimeBarSelectionListener(this);
370 
371         // initialize the viewstate
372         _timeBarViewState = new DefaultTimeBarViewState(this);
373         _timeBarViewState.setDefaultRowHeight(TimeBarViewerInterface.DEFAULT_ROWHEIGHT);
374 
375         // register a listener on the viewstate to react on changes
376         _timeBarViewState.addTimeBarViewStateListener(new ITimeBarViewStateListener() {
377             public void rowHeightChanged(TimeBarRow row, int newHeight) {
378                 if (_tbvi != null) {
379                     updateRowScrollBar();
380                     _tbvi.repaint();
381                 }
382             }
383 
384             public void viewStateChanged() {
385                 if (_tbvi != null) {
386                     updateRowScrollBar();
387                     _tbvi.repaint();
388                 }
389             }
390         });
391 
392     }
393 
394     /**
395      * Deregister from all models and free any ressources reserved.
396      * 
397      */
398     public void dispose() {
399         if (_model != null) {
400             _model.remTimeBarModelListener(this);
401         }
402         if (_selectionModel != null) {
403             _selectionModel.remTimeBarSelectionListener(this);
404         }
405         if (_markers != null) {
406             for (TimeBarMarker marker : _markers) {
407                 marker.remTimeBarMarkerListener(this);
408             }
409         }
410         if (_rowFilter != null) {
411             _rowFilter.removePropertyChangeListener(this);
412         }
413         if (_rowSorter != null) {
414             _rowSorter.removePropertyChangeListener(this);
415         }
416         if (_focussedIntervalListeners != null) {
417             _focussedIntervalListeners.clear();
418         }
419         if (_selectionRectListeners != null) {
420             _selectionRectListeners.clear();
421         }
422         if (_timeBarChangeListeners != null) {
423             _timeBarChangeListeners.clear();
424         }
425     }
426 
427     /**
428      * Checks whether a given date is currently visible.
429      * 
430      * @param date the date to be checked
431      * @return true if the date is within the visible area of the diagram.
432      */
433     public boolean isDisplayed(JaretDate date) {
434         return _startDate.compareTo(date) <= 0 && (_endDate == null || _endDate.compareTo(date) >= 0);
435     }
436 
437     /**
438      * Set a filter to select a subset of rows in the model to be displayed. The model itself will go unaffected.
439      * 
440      * @param rowFilter TimeBarRowFilter to be used. <code>null</code> is an allowed value indicating no row filtering
441      */
442     public void setRowFilter(TimeBarRowFilter rowFilter) {
443         if ((_rowFilter == null && rowFilter != null) || (_rowFilter != null && !_rowFilter.equals(rowFilter))) {
444             TimeBarRowFilter oldFilter = _rowFilter;
445             if (oldFilter != null) { // dregsiter prop change
446                 oldFilter.removePropertyChangeListener(this);
447             }
448             _rowFilter = rowFilter;
449             if (_rowFilter != null) { // register for prop changes
450                 _rowFilter.addPropertyChangeListener(this);
451             }
452             updateRowList();
453             if (_tbvi != null) {
454                 _tbvi.repaint();
455                 _tbvi.firePropertyChangeX(TimeBarViewerInterface.PROPERTYNAME_ROWFILTER, oldFilter, rowFilter);
456             }
457         }
458     }
459 
460     /**
461      * @return Returns the rowFilter.
462      */
463     public TimeBarRowFilter getRowFilter() {
464         return _rowFilter;
465     }
466 
467     /**
468      * Check whether a row is filtered out.
469      * 
470      * @param row row to check.
471      * @return true if the row is filtered out.
472      */
473     public boolean isFiltered(TimeBarRow row) {
474         if (_rowFilter == null) {
475             return false;
476         }
477         return (!_rowFilter.isInResult(row));
478     }
479 
480     /**
481      * Set a sorter for sorting the displayed rows. The model itself will not be affected.
482      * 
483      * @param rowSorter TimeBarRowSorter to be used. <code>null</code> is an allowed value indicating no special sorting
484      */
485     public void setRowSorter(TimeBarRowSorter rowSorter) {
486         if ((_rowSorter == null && rowSorter != null) || (_rowSorter != null && !_rowSorter.equals(rowSorter))) {
487             TimeBarRowSorter oldSorter = _rowSorter;
488             if (oldSorter != null) { // deregister
489                 oldSorter.removePropertyChangeListener(this);
490             }
491             _rowSorter = rowSorter;
492             if (_rowSorter != null) {
493                 _rowSorter.addPropertyChangeListener(this);
494             }
495             updateRowList();
496             if (_tbvi != null) {
497                 _tbvi.repaint();
498                 _tbvi.firePropertyChangeX(TimeBarViewerInterface.PROPERTYNAME_ROWSORTER, oldSorter, rowSorter);
499             }
500         }
501     }
502 
503     /**
504      * @return Returns the rowSorter.
505      */
506     public TimeBarRowSorter getRowSorter() {
507         return _rowSorter;
508     }
509 
510     /**
511      * Set a filter for displaying only a part of the intervals. The model itself will not be affected.
512      * 
513      * @param intervalFilter TimeBarIntervalFilter to be used. <code>null</code> is an allowed value indicating no
514      * interval filtering
515      */
516     public void setIntervalFilter(TimeBarIntervalFilter intervalFilter) {
517         if ((_intervalFilter == null && intervalFilter != null)
518                 || (_intervalFilter != null && !_intervalFilter.equals(intervalFilter))) {
519             TimeBarIntervalFilter oldFilter = _intervalFilter;
520             if (oldFilter != null) {
521                 oldFilter.removePropertyChangeListener(this);
522             }
523             _intervalFilter = intervalFilter;
524             if (_intervalFilter != null) {
525                 _intervalFilter.addPropertyChangeListener(this);
526                 // reset the overlap cache!
527                 _overlapStrategy.clearCachedData();
528             }
529             _tbvi.repaint();
530             _tbvi.firePropertyChangeX(TimeBarViewerInterface.PROPERTYNAME_INTERVALFILTER, oldFilter, intervalFilter);
531         }
532     }
533 
534     /**
535      * @return Returns the intervalFilter.
536      */
537     public TimeBarIntervalFilter getIntervalFilter() {
538         return _intervalFilter;
539     }
540 
541     /**
542      * Check whether an interval is filtered by an interval filter.
543      * 
544      * @param interval interval to check.
545      * @return true if the interval is filtered.
546      */
547     public boolean isFiltered(Interval interval) {
548         if (_intervalFilter == null) {
549             return false;
550         }
551         return !_intervalFilter.isInResult(interval);
552     }
553 
554     /**
555      * Updates the shadow row list of the displayed rows. The method will update the row axis scrollbar (x or y
556      * depending on the orientation) in the case that the number of rows changed.
557      */
558     public void updateRowList() {
559         if (_model != null) {
560             int oldRowCount = _rowList != null ? _rowList.size() : 0;
561             List<TimeBarRow> newRowList = new ArrayList<TimeBarRow>();
562             // copy filtered if filter is set
563             for (int r = 0; r < _model.getRowCount(); r++) {
564                 if (_rowFilter != null) {
565                     // filter set
566                     if (_rowFilter.isInResult(_model.getRow(r))) {
567                         newRowList.add(_model.getRow(r));
568                     }
569                 } else {
570                     newRowList.add(_model.getRow(r));
571                 }
572             }
573             // sorter set? -> sort the row list
574             if (_rowSorter != null) {
575                 Collections.sort(newRowList, _rowSorter);
576             }
577             // set the rowlist
578             // TODO might be necessary to do this synchronized against an ongoing paint
579             _rowList = newRowList;
580 
581             // maybe the first row idx no longer available
582             if (_firstRow >= newRowList.size())
583                 setLastRow(Math.max(0, newRowList.size() - 1)); // updates _firstRow gracefully
584             
585             if (getRowCount() != oldRowCount && _tbvi != null) {
586                 updateRowScrollBar(); // method switches to the right bar
587             }
588         }
589     }
590 
591     /**
592      * Get a timebar row from the filtered/sorted list by index.
593      * 
594      * @param idx index in the list
595      * @return the TimeBarRow at the index
596      */
597     public TimeBarRow getRow(int idx) {
598         return _rowList.get(idx);
599     }
600 
601     /**
602      * Retrieve the index of a given row.
603      * 
604      * @param row row in question
605      * @return the index or -1 if the row could not be found
606      */
607     public int getRowIndex(TimeBarRow row) {
608         return _rowList.indexOf(row);
609     }
610 
611     /**
612      * Return the size of the row list.
613      * 
614      * @return the actual row count of the possibly filtered list of rows
615      */
616     public int getRowCount() {
617         return _rowList != null ? _rowList.size() : 0;
618     }
619 
620     /**
621      * Calculates the number of seconds the complete model spans. If min or max date is not set, the number of seconds
622      * is 0.
623      * 
624      * @return number of seconds
625      */
626     public long getTotalSeconds() {
627         if (getMaxDate() == null || getMinDate() == null) {
628             return 0;
629         }
630         return getMaxDate().diffSecondsL(getMinDate());
631     }
632 
633     /**
634      * Calculates the number of milliseconds the complete model spans.
635      * 
636      * @return number of milliseconds
637      */
638     public long getTotalMilliSeconds() {
639         return getMaxDate().diffMilliSeconds(getMinDate());
640     }
641 
642     /**
643      * Get the seconds currently displayed by the diagram.
644      * 
645      * @return the number of seconds currently displayed by the diagram geometry
646      */
647     public int getSecondsDisplayed() {
648         if (_orientation == Orientation.HORIZONTAL) {
649             if (!_variableXScale) {
650                 return (int) ((double) (_diagramRect.width) / _pixelPerSeconds);
651             } else {
652                 int endx = _diagramRect.width;
653                 JaretDate endDate = dateForCoord(endx);
654                 return endDate.diffSeconds(_startDate);
655             }
656         } else {
657             if (!_variableXScale) {
658                 return (int) ((double) (_diagramRect.height) / _pixelPerSeconds);
659             } else {
660                 int endy = _diagramRect.height;
661                 JaretDate endDate = dateForCoord(endy);
662                 return endDate.diffSeconds(_startDate);
663             }
664         }
665     }
666 
667     /**
668      * Get the milli seconds currently displayed by the diagram.
669      * 
670      * @return the number of milli seconds currently displayed by the diagram geometry
671      */
672     public long getMilliSecondsDisplayed() {
673         if (_orientation == Orientation.HORIZONTAL) {
674             if (!_variableXScale) {
675                 return (int) ((double) _diagramRect.width / getPixelPerMilliSecond());
676             } else {
677                 int endx = _diagramRect.width;
678                 JaretDate endDate = dateForX(endx);
679                 return endDate.diffMilliSeconds(_startDate);
680             }
681         } else {
682             if (!_variableXScale) {
683                 return (int) ((double) _diagramRect.height / getPixelPerMilliSecond());
684             } else {
685                 int endx = _diagramRect.height;
686                 JaretDate endDate = dateForX(endx);
687                 return endDate.diffMilliSeconds(_startDate);
688             }
689         }
690     }
691 
692     /**
693      * @return Returns the selectionModel.
694      */
695     public TimeBarSelectionModel getSelectionModel() {
696         return _selectionModel;
697     }
698 
699     /**
700      * @param selectionModel The selectionModel to set.
701      */
702     public void setSelectionModel(TimeBarSelectionModel selectionModel) {
703         if (_selectionModel != null) {
704             _selectionModel.remTimeBarSelectionListener(this);
705         }
706         _selectionModel = selectionModel;
707         _selectionModel.addTimeBarSelectionListener(this);
708         _tbvi.repaint();
709     }
710 
711     /**
712      * @return Returns the adjustMinMaxDatesByModel.
713      */
714     public boolean getAdjustMinMaxDatesByModel() {
715         return _adjustMinMaxDatesByModel;
716     }
717 
718     /**
719      * @param adjustMinMaxDatesByModel The adjustMinMaxDatesByModel to set.
720      */
721     public void setAdjustMinMaxDatesByModel(boolean adjustMinMaxDatesByModel) {
722         _adjustMinMaxDatesByModel = adjustMinMaxDatesByModel;
723     }
724 
725     /**
726      * @return Returns the maxDate.
727      */
728     public JaretDate getMaxDate() {
729         return _maxDate;
730     }
731 
732     /**
733      * Set the maximum date for the diagram.
734      * 
735      * @param maxDate The maxDate to set.
736      */
737     public void setMaxDate(JaretDate maxDate) {
738         // add a padding so that elements sitting on the edge are clearly
739         // selectable
740         long milliseconds = (int) ((double) PADDING_PIXEL / (_pixelPerSeconds * MILLISCALING));
741         JaretDate oldVal = _maxDate;
742         _maxDate = maxDate.copy();
743         _maxDate.advanceMillis(milliseconds);
744         if (oldVal == null || !oldVal.equals(maxDate)) {
745             updateTimeScrollBar();
746             if (_tbvi != null) {
747                 _tbvi.repaint();
748                 _tbvi.firePropertyChangeX(TimeBarViewerInterface.PROPERTYNAME_MAXDATE, oldVal, maxDate);
749             }
750         }
751     }
752 
753     /**
754      * @return Returns the minDate.
755      */
756     public JaretDate getMinDate() {
757         return _minDate;
758     }
759 
760     /**
761      * @param minDate The minDate to set.
762      */
763     public void setMinDate(JaretDate minDate) {
764         // add a padding so that elements sitting on the edge are clearly
765         // selectable
766         long milliseconds = (int) ((double) PADDING_PIXEL / (_pixelPerSeconds * MILLISCALING));
767         JaretDate oldVal = _minDate;
768         _minDate = minDate.copy();
769         _minDate.advanceMillis(-milliseconds);
770         if (oldVal == null || !oldVal.equals(minDate)) {
771             updateTimeScrollBar();
772             if (_tbvi != null) {
773                 _tbvi.repaint();
774                 _tbvi.firePropertyChangeX(TimeBarViewerInterface.PROPERTYNAME_MINDATE, oldVal, minDate);
775             }
776         }
777     }
778 
779     /**
780      * Get the starting date, that is the leftmost date displayed.
781      * 
782      * @return Returns the startDate.
783      */
784     public synchronized JaretDate getStartDate() {
785         return _startDate;
786     }
787 
788     /**
789      * Sets the date to be displayed at the position of the yaxis.
790      * 
791      * @param startDate The startDate to set.
792      */
793     public synchronized void setStartDate(JaretDate startDate) {
794         _lastStartDate = _startDate;
795         JaretDate oldVal = _startDate;
796         _startDate = startDate.copy();
797 
798         // if the set was a real change, update and tell everyone that is
799         // interested
800         if ((oldVal == null || !oldVal.equals(_startDate)) && _tbvi != null) {
801             if (_diagramRect != null) {
802                 _endDate = dateForCoord(_diagramRect.x + _diagramRect.width);
803             }
804             updateTimeScrollBar();
805             _tbvi.repaint();
806             _tbvi.firePropertyChangeX(TimeBarViewerInterface.PROPERTYNAME_STARTDATE, oldVal, _startDate);
807         }
808     }
809 
810     /**
811      * Handle a scrolling operation.
812      * 
813      * @param startDate new start date
814      */
815     protected void scrollTo(JaretDate startDate) {
816         if (!_optimizeScrolling || _tbvi == null) {
817             setStartDate(startDate);
818         } else {
819             try {
820                 _lastStartDate = _startDate;
821                 JaretDate oldVal = _startDate;
822                 int oldx = xForDateAbs(_startDate);
823 
824                 int newx = xForDateAbs(startDate);
825                 // recalculate the startdate to prevent rounding errors from causing
826                 // rendering artefacts
827                 JaretDate newStartDate = dateForCoordAbs(newx);
828 
829                 int diff = newx - oldx;
830                 int comp;
831                 if (_orientation.equals(TimeBarViewerInterface.Orientation.HORIZONTAL)) {
832                     comp = _tbvi.getWidth();
833                 } else {
834                     comp = _tbvi.getHeight();
835                 }
836                 if (Math.abs(diff) > comp / 2) {
837                     setStartDate(startDate);
838                     return;
839                 }
840 
841                 // delegate the optimized scrolling to the toolkit specific
842                 // implementation
843                 if (_orientation == Orientation.HORIZONTAL) {
844                     _tbvi.doScrollHorizontal(diff);
845                 } else {
846                     _tbvi.doScrollVertical(diff);
847                 }
848                 _startDate = newStartDate;
849 
850                 // if the set was a real change, update and tell everyone that is
851                 // interested
852                 if ((oldVal == null || !oldVal.equals(_startDate)) && _tbvi != null) {
853                     if (_diagramRect != null) {
854                         _endDate = dateForCoord(_diagramRect.x + _diagramRect.width);
855                     }
856                     updateTimeScrollBar();
857                     _tbvi.firePropertyChangeX(TimeBarViewerInterface.PROPERTYNAME_STARTDATE, oldVal, _startDate);
858                 }
859             } catch (Exception e) {
860                 e.printStackTrace();
861             }
862         }
863     }
864 
865     /**
866      * Get the model the viewer displays.
867      * 
868      * @return TimeBarModel
869      */
870     public TimeBarModel getModel() {
871         return _model;
872     }
873 
874     /**
875      * Set the model to be displayed.
876      * 
877      * @param model TimeBarModel to be displayed
878      */
879     public void setModel(TimeBarModel model) {
880         if (_model != null) {
881             _model.remTimeBarModelListener(this);
882         }
883         _model = model;
884         if (model != null) {
885             // min/max if requested
886             if (_adjustMinMaxDatesByModel) {
887                 // direct manipulation for calculations
888                 _minDate = _model.getMinDate();
889                 _maxDate = _model.getMaxDate();
890                 // now do a proper setting
891                 setMinDate(_model.getMinDate());
892                 setMaxDate(_model.getMaxDate());
893 
894                 setStartDate(_model.getMinDate().copy());
895                 setMinDate(_model.getMinDate());
896                 setMaxDate(_model.getMaxDate());
897             } else {
898                 _minDate = new JaretDate();
899                 _maxDate = new JaretDate();
900                 setStartDate(new JaretDate());
901             }
902 
903             if (_hierarchicalModel == null) {
904                 // if not hierarchical -> forget about the hvs
905                 _hierarchicalViewState = null;
906             }
907 
908             _model.addTimeBarModelListener(this);
909         } else {
910             // model is null
911             // make sure we forget the hierarchical viewstate
912             _hierarchicalViewState = null;
913         }
914         updateRowList(); // update the sorted/filtered list
915         if (_tbvi != null) {
916             updateScrollBars();
917             _tbvi.repaint();
918         }
919     }
920 
921     /**
922      * Set a hierarchical model as the time bar model.
923      * 
924      * @param hModel hierarchical model to be displayed
925      */
926     public void setModel(HierarchicalTimeBarModel hModel) {
927         _hierarchicalViewState = new HierarchicalViewStateImpl();
928         _hierarchicalModel = hModel;
929         TimeBarModel model = null;
930         if (hModel != null) {
931             model = new StdHierarchicalTimeBarModel(hModel, _hierarchicalViewState);
932         }
933         setModel(model);
934     }
935 
936     /**
937      * Retrieve the hierarchical model displayed if present.
938      * 
939      * @return hierarchical model or <code>null</code> if a flat model is used
940      */
941     public HierarchicalTimeBarModel getHierarchicalModel() {
942         return _hierarchicalModel;
943     }
944 
945     /**
946      * Correct the min and max dates of the viewer according to the model.
947      */
948     private void checkAndAdjustMinMax() {
949         if (_adjustMinMaxDatesByModel) {
950             setMinDate(_model.getMinDate().copy());
951             setMaxDate(_model.getMaxDate().copy());
952         }
953     }
954 
955     /**
956      * Sets the scale ox the x axis as pixel per second, thus a value of 1000.0 / (24.0 * 60 * 60) will result in
957      * displaying one day over 1000 pixel. The property is a bound property and can be listened to by a
958      * PropertyChangeListener.
959      * 
960      * @param pixelPerSecond pixel per second.
961      */
962     public void setPixelPerSecond(double pixelPerSecond) {
963         setPixelPerSecond(pixelPerSecond, true);
964     }
965 
966     /**
967      * Internal version of setPixelPerSecond that allows control of the repaint behaviour.
968      * 
969      * @param pixelPerSecond pixel per second
970      * @param repaint <code>true</code> if a repaint should be triggered
971      */
972     protected void setPixelPerSecond(double pixelPerSecond, boolean repaint) {
973         if (pixelPerSecond != _pixelPerSeconds) { // check for real difference
974             // - we don't want to do too
975             // much
976             double oldValue = _pixelPerSeconds;
977             _pixelPerSeconds = pixelPerSecond;
978             if (_variableXScale) {
979                 updateTimeScaleBreaks();
980             }
981             if (_tbvi != null) {
982                 updateTimeScrollBar();
983                 if (repaint) {
984                     _tbvi.repaint(); // repaint the diagram
985                 }
986                 _tbvi.firePropertyChange(TimeBarViewerInterface.PROPERTYNAME_PIXELPERSECOND, oldValue, pixelPerSecond);
987             }
988         }
989     }
990 
991     /**
992      * Recalculate the pps values for breaks in the time scale.
993      */
994     private void updateTimeScaleBreaks() {
995         for (Interval ppsInterval : getPpsRow().getIntervals()) {
996             PPSInterval pi = (PPSInterval) ppsInterval;
997             if (pi.isBreak()) {
998                 long millis = pi.getEnd().diffMilliSeconds(pi.getBegin());
999                 double width = (double) pi.getBreakDisplayWidth();
1000                 double targetPPS = width / ((double) millis / MILLISCALING);
1001                 pi.setPps(targetPPS);
1002             }
1003         }
1004     }
1005 
1006     /**
1007      * Set the scaling of the x axis by specifying the number of seconds that should be displayed. If the viewer has not
1008      * been drawn yet, the method delegates to setInitialDiaplyRange. The center parameter will not be taken into
1009      * account (since it is impossible to do the calculations).
1010      * 
1011      * @param seconds number of seconds that will be displayed on the x axis
1012      * @param center if set to <code>true</code> the center date will be fixed while scaling
1013      */
1014     public void setSecondsDisplayed(int seconds, boolean center) {
1015         if (_orientation.equals(Orientation.HORIZONTAL)) {
1016             if (_diagramRect != null && _diagramRect.width > 1) {
1017                 double pps = (double) _diagramRect.width / (double) seconds;
1018                 if (!center) {
1019                     setPixelPerSecond(pps);
1020                 } else {
1021                     int oldSeconds = getSecondsDisplayed();
1022                     setPixelPerSecond(pps, false);
1023                     int newSeconds = getSecondsDisplayed();
1024                     setStartDate(getStartDate().copy().advanceSeconds((oldSeconds - newSeconds) / 2.0)); // will repaint
1025                 }
1026             } else {
1027                 // calculation not possible since has not been drawn yet
1028                 setInitialDisplayRange(getStartDate(), seconds);
1029             }
1030         } else {
1031             if (_diagramRect != null && _diagramRect.height > 1) {
1032                 double pps = (double) _diagramRect.height / (double) seconds;
1033                 if (!center) {
1034                     setPixelPerSecond(pps);
1035                 } else {
1036                     int oldSeconds = getSecondsDisplayed();
1037                     setPixelPerSecond(pps, false);
1038                     int newSeconds = getSecondsDisplayed();
1039                     setStartDate(getStartDate().copy().advanceSeconds((oldSeconds - newSeconds) / 2.0)); // will repaint
1040                 }
1041             } else {
1042                 // calculation not possible since has not been drawn yet
1043                 setInitialDisplayRange(getStartDate(), seconds);
1044             }
1045         }
1046     }
1047 
1048     /**
1049      * Set the scaling of the x axis by specifying the number of seconds that should be displayed. If the viewer has not
1050      * been drawn yet, the method delegates to setInitialDiaplyRange. The centerDate parameter will not be taken into
1051      * account (since this is impossible to calculate).
1052      * 
1053      * @param seconds number of seconds that will be displayed on the x axis
1054      * @param centerDate date that will be fixed while scaling
1055      */
1056     public void setSecondsDisplayed(int seconds, JaretDate centerDate) {
1057         if (!isDisplayed(centerDate)) {
1058             setSecondsDisplayed(seconds, true);
1059         } else {
1060             if (_orientation.equals(Orientation.HORIZONTAL)) {
1061                 if (_diagramRect != null && _diagramRect.width > 1) {
1062                     double pps = (double) _diagramRect.width / (double) seconds;
1063                     if (centerDate == null) {
1064                         setPixelPerSecond(pps);
1065                     } else {
1066                         // disable optimized scrolling for the operation
1067                         boolean optimizeScrolling = _optimizeScrolling;
1068                         _optimizeScrolling = false;
1069 
1070                         int oldx = xForDate(centerDate);
1071                         // set the new scaling
1072                         setPixelPerSecond(pps, false);
1073                         JaretDate dateAtOldPos = dateForCoord(oldx);
1074                         long diffmsec = centerDate.diffMilliSeconds(dateAtOldPos);
1075 
1076                         setStartDate(getStartDate().copy().advanceMillis(diffmsec)); // will repaint
1077                         _optimizeScrolling = optimizeScrolling;
1078                     }
1079                 } else {
1080                     // calculation not possible since has not been drawn yet
1081                     setInitialDisplayRange(getStartDate(), seconds);
1082                 }
1083             } else {
1084                 if (_diagramRect != null && _diagramRect.height > 1) {
1085                     double pps = (double) _diagramRect.height / (double) seconds;
1086                     if (centerDate == null) {
1087                         setPixelPerSecond(pps);
1088                     } else {
1089                         // disable optimized scrolling for the operation
1090                         boolean optimizeScrolling = _optimizeScrolling;
1091                         _optimizeScrolling = false;
1092 
1093                         int oldx = xForDate(centerDate);
1094                         // set the new scaling
1095                         setPixelPerSecond(pps, false);
1096                         JaretDate dateAtOldPos = dateForCoord(oldx);
1097                         long diffmsec = centerDate.diffMilliSeconds(dateAtOldPos);
1098 
1099                         setStartDate(getStartDate().copy().advanceMillis(diffmsec)); // will repaint
1100                         _optimizeScrolling = optimizeScrolling;
1101                     }
1102                 } else {
1103                     // calculation not possible since has not been drawn yet
1104                     setInitialDisplayRange(getStartDate(), seconds);
1105                 }
1106             }
1107         }
1108     }
1109 
1110     /**
1111      * Return the pixel per second ratio.
1112      * 
1113      * @return pixel per second
1114      */
1115     public double getPixelPerSecond() {
1116         return _pixelPerSeconds;
1117     }
1118 
1119     /**
1120      * Retrieve the corrected pixel per second value for milliseconds.
1121      * 
1122      * @return pixel per millisecond
1123      */
1124     protected double getPixelPerMilliSecond() {
1125         return _pixelPerSeconds / MILLISCALING;
1126     }
1127 
1128     protected JaretDate _initialStartDate;
1129     protected int _initialSecondsDisplayed;
1130 
1131     /**
1132      * Set a date range and scaling that will be set as the initial display right after the viewer is displayed.
1133      * 
1134      * @param startDate start date
1135      * @param secondsDisplayed seconds to be displayed in the viewer
1136      */
1137     public void setInitialDisplayRange(JaretDate startDate, int secondsDisplayed) {
1138         _initialStartDate = startDate;
1139         _initialSecondsDisplayed = secondsDisplayed;
1140     }
1141 
1142     /**
1143      * Set the height for the rows in pixel. This property is bound. If the orientation is vertical this sets the width
1144      * of the columns. If the viewstate is configured to use individual row heights, this will set the default row
1145      * height.
1146      * 
1147      * @param rowHeight new row height or default row height
1148      */
1149     public void setRowHeight(int rowHeight) {
1150         if (rowHeight != _timeBarViewState.getDefaultRowHeight()) { // check for change
1151             int oldVal = _timeBarViewState.getDefaultRowHeight();
1152             _timeBarViewState.setDefaultRowHeight(rowHeight);
1153             if (_tbvi != null) {
1154                 _tbvi.repaint();
1155                 updateRowScrollBar();
1156                 if (_tbvi != null) {
1157                     _tbvi.firePropertyChange(TimeBarViewerInterface.PROPERTYNAME_ROWHEIGHT, oldVal, rowHeight);
1158                 }
1159             }
1160         }
1161     }
1162 
1163     /**
1164      * Retrieve the current row height (col width). If individual row heights are used this will return the default row
1165      * height.
1166      * 
1167      * @return row height in pixel or default row height
1168      */
1169     public int getRowHeight() {
1170         return _timeBarViewState.getDefaultRowHeight();
1171     }
1172 
1173     /**
1174      * Retrieve the last date displayed.
1175      * 
1176      * @return last date displayed
1177      */
1178     public JaretDate getEndDate() {
1179         return _endDate;
1180     }
1181 
1182     /**
1183      * Add a marker to be displayed within the diagram.
1184      * 
1185      * @param marker TimeBarMarkerImpl to be displayed in the diagram
1186      */
1187     public void addMarker(TimeBarMarker marker) {
1188         if (_markers == null) {
1189             _markers = new ArrayList<TimeBarMarker>();
1190         }
1191         _markers.add(marker);
1192         marker.addTimeBarMarkerListener(this);
1193         if (isDisplayed(marker.getDate())) {
1194             // MAYBE optimize
1195             _tbvi.repaint();
1196         }
1197         if (_minDate != null && marker.getDate().compareTo(_minDate) < 0) {
1198             setMinDate(marker.getDate().copy());
1199         } else if (_maxDate != null && marker.getDate().compareTo(_maxDate) > 0) {
1200             setMaxDate(marker.getDate().copy());
1201         }
1202     }
1203 
1204     /**
1205      * Removes a marker from the diagram.
1206      * 
1207      * @param marker TimeBarMarkerImpl to be removed
1208      */
1209     public void remMarker(TimeBarMarker marker) {
1210         if (_markers != null) {
1211             _markers.remove(marker);
1212             marker.remTimeBarMarkerListener(this);
1213             if (isDisplayed(marker.getDate())) {
1214                 // MAYBE optimize
1215                 _tbvi.repaint();
1216             }
1217         }
1218     }
1219 
1220     /**
1221      * Retrive a marker for a coordinate pair (x or y will be used depending on the orientation).
1222      * 
1223      * @param x coordinate
1224      * @param y coordinate
1225      * @return marker or <code>null</code>
1226      */
1227     public TimeBarMarker getMarkerForXY(int x, int y) {
1228         if (_orientation == Orientation.HORIZONTAL) {
1229             return getMarkerForCoord(x);
1230         } else {
1231             return getMarkerForCoord(y);
1232         }
1233     }
1234 
1235     /**
1236      * Find a marker for a given coordinate (depending on the orientation).
1237      * 
1238      * @param coord coordinate
1239      * @return a marker or null if none was found
1240      */
1241     protected TimeBarMarker getMarkerForCoord(int coord) {
1242         TimeBarMarker result = null;
1243         if (_markers != null) {
1244             for (int i = 0; i < _markers.size(); i++) {
1245                 TimeBarMarker marker = (TimeBarMarker) _markers.get(i);
1246                 int mx = xForDate(marker.getDate());
1247                 if (coord - _selectionDelta < mx && coord + _selectionDelta > mx) {
1248                     result = marker;
1249                     break;
1250                 }
1251             }
1252         }
1253         return result;
1254     }
1255 
1256     /**
1257      * Retrieve all markers added.
1258      * 
1259      * @return List of markers.
1260      */
1261     public List<TimeBarMarker> getMarkers() {
1262         return _markers;
1263     }
1264 
1265     /**
1266      * Add a list of markers.
1267      * 
1268      * @param markers list of markers
1269      */
1270     public void addMarkers(List<TimeBarMarker> markers) {
1271         if (markers != null) {
1272             for (TimeBarMarker marker : markers) {
1273                 addMarker(marker);
1274             }
1275         }
1276     }
1277 
1278     /**
1279      * Update the scroll bar bounds to match the current display an data.
1280      * 
1281      */
1282     public void updateScrollBars() {
1283         if (_model != null && _model.getRowCount() != 0) {
1284             updateTimeScrollBar();
1285             updateRowScrollBar();
1286         }
1287     }
1288 
1289     /**
1290      * Update the time scroll bar after a change in the data or presentation of the viewer.
1291      */
1292     private void updateTimeScrollBar() {
1293         if (_tbvi != null && _model != null) {
1294             if (isMilliAccuracy()) {
1295                 long milliSecondsDisplayed = getMilliSecondsDisplayed();
1296                 long totalMilliSeconds = getTotalMilliSeconds();
1297                 long pos = _startDate.diffMilliSeconds(_minDate);
1298 
1299                 // prevent thumb from disappearing if scrolled
1300                 if (totalMilliSeconds - pos < milliSecondsDisplayed) {
1301                     milliSecondsDisplayed = totalMilliSeconds - pos;
1302                 }
1303 
1304                 // correct the number of seconds otally displayed for use with the scrollbar
1305                 // since the scrollbar spans the y axis
1306                 int addWidth = _yAxisWidth + _hierarchyWidth;
1307                 long addMillis;
1308                 if (_orientation.equals(TimeBarViewerInterface.Orientation.HORIZONTAL)) {
1309                     addMillis = dateForCoord(_diagramRect.x + _diagramRect.width).diffMilliSeconds(
1310                             dateForCoord(_diagramRect.x + _diagramRect.width - addWidth));
1311                 } else {
1312                     addMillis = dateForCoord(_diagramRect.y + _diagramRect.height).diffMilliSeconds(
1313                             dateForCoord(_diagramRect.y + _diagramRect.height - addWidth));
1314                 }
1315 
1316                 updateTimeScrollBar((int) totalMilliSeconds, (int) pos, (int) (milliSecondsDisplayed + addMillis));
1317             } else {
1318                 // if using standard (=second) accuracy scrolling is done in
1319                 // seconds
1320                 long secondsDisplayed = getSecondsDisplayed();
1321                 long totalSeconds = getTotalSeconds();
1322                 long pos = _startDate.diffSecondsL(_minDate);
1323 
1324                 // prevent thumb from disappearing if scrolled
1325                 if (totalSeconds - pos < secondsDisplayed) {
1326                     secondsDisplayed = totalSeconds - pos;
1327                 }
1328 
1329                 // correct the number of seconds totally displayed for use with the scrollbar
1330                 // since the scrollbar spans the y axis
1331                 int addWidth = _yAxisWidth + _hierarchyWidth;
1332                 int addSeconds = 0;
1333                 if (_variableXScale) {
1334                     if (_orientation.equals(TimeBarViewerInterface.Orientation.HORIZONTAL)) {
1335                         addSeconds = dateForCoord(_diagramRect.x + _diagramRect.width).diffSeconds(
1336                                 dateForCoord(_diagramRect.x + _diagramRect.width - addWidth));
1337                     } else {
1338                         addSeconds = dateForCoord(_diagramRect.y + _diagramRect.height).diffSeconds(
1339                                 dateForCoord(_diagramRect.y + _diagramRect.height - addWidth));
1340                     }
1341                 }
1342                 int tot = 0;
1343                 int p = 0;
1344                 int displayed = 0;
1345 
1346                 if (totalSeconds < Integer.MAX_VALUE) {
1347                     tot = (int) totalSeconds;
1348                     p = (int) pos;
1349                     displayed = (int) (secondsDisplayed + addSeconds);
1350                     _timeFactor = 1.0;
1351                 } else {
1352                     // integer is not sufficient
1353                     _timeFactor = (double) Integer.MAX_VALUE / (double) totalSeconds;
1354                     tot = (int) ((double) totalSeconds * _timeFactor);
1355                     p = (int) ((double) pos * _timeFactor);
1356                     displayed = (int) ((double) (secondsDisplayed + addSeconds) * _timeFactor);
1357                 }
1358 
1359                 updateTimeScrollBar(tot, p, displayed);
1360                 // updateTimeScrollBar(totalSeconds, pos, secondsDisplayed + addSeconds);
1361             }
1362         }
1363     }
1364 
1365     /**
1366      * Diapatch updating of the time scrollbar according to orientation.
1367      * 
1368      * @param max max value
1369      * @param pos position
1370      * @param secondsDisplayed thumb
1371      */
1372     private void updateTimeScrollBar(int max, int pos, int secondsDisplayed) {
1373         if (_orientation == Orientation.HORIZONTAL) {
1374             _tbvi.updateXScrollBar(max, pos, secondsDisplayed);
1375         } else if (_orientation == Orientation.VERTICAL) {
1376             _tbvi.updateYScrollBar(max, pos, secondsDisplayed);
1377         }
1378     }
1379 
1380     /**
1381      * Handle operation of the horizontal scroll bar.
1382      * 
1383      * @param value new value
1384      * @param redirect if true allow redirecteion to the vertical scroll according to orientation
1385      */
1386     public void handleHorizontalScroll(int value, boolean redirect) {
1387         if (!redirect || _orientation == Orientation.HORIZONTAL) {
1388             if (getMinDate() == null) {
1389                 return;
1390             }
1391             JaretDate date = getMinDate().copy();
1392             if (isMilliAccuracy()) {
1393                 date.advanceMillis((long) value);
1394             } else {
1395                 date.advanceSeconds(value / _timeFactor);
1396             }
1397             scrollTo(date);
1398         } else if (_orientation == Orientation.VERTICAL) {
1399             handleVerticalScroll(value, false);
1400         } else {
1401             throw new RuntimeException("illegal");
1402         }
1403     }
1404 
1405     /**
1406      * Update the row (column) scroll bar of the viewer.
1407      * 
1408      */
1409     private void updateRowScrollBar() {
1410         int first = getFirstRow();
1411         int firstRowAbs = (first >= _rowList.size()) ? 0: getAbsPosForRow(first);
1412         updateRowScrollBar(getTotalHeight(), firstRowAbs + _firstRowPixelOffset, _diagramRect.height);
1413             
1414             // TODO still a minimal glitch with the scrollbar appearance
1415         //updateRowScrollBar(getTotalHeight(), getAbsPosForRow(first) + _firstRowPixelOffset, _diagramRect.height);
1416     }
1417 
1418     /**
1419      * Update the scroll bar controlling the rows.
1420      * 
1421      * @param max max value
1422      * @param pos current value
1423      * @param thumbSize thumb size
1424      */
1425     private void updateRowScrollBar(int max, int pos, int thumbSize) {
1426         if (_orientation == Orientation.HORIZONTAL) {
1427             _tbvi.updateYScrollBar(max, pos, thumbSize);
1428         } else if (_orientation == Orientation.VERTICAL) {
1429             _tbvi.updateXScrollBar(max, pos, thumbSize);
1430         }
1431     }
1432 
1433     /**
1434      * Calculate the row index for an absolute pixel position (referring to all rows stacked).
1435      * 
1436      * @param value absolute position in the imagined all rows display
1437      * @return the index of the row for the given position
1438      */
1439     public int getRowIdxForAbsoluteOffset(int value) {
1440         if (!_timeBarViewState.getUseVariableRowHeights()) {
1441             int row = value / _timeBarViewState.getDefaultRowHeight();
1442             return row;
1443         } else {
1444             int y = 0;
1445             TimeBarRow row = getRow(0);
1446             int height = _timeBarViewState.getRowHeight(row);
1447             for (int i = 0; i < _rowList.size(); i++) {
1448                 if (y <= value && value <= y + height) {
1449                     return i;
1450                 }
1451                 y += height;
1452                 if (i + 1 > _rowList.size() - 1) {
1453                     break;
1454                 }
1455                 row = getRow(i + 1);
1456                 height = _timeBarViewState.getRowHeight(row);
1457             }
1458             // should never happen
1459             throw new RuntimeException("could not find row idx for offset");
1460         }
1461     }
1462 
1463     /**
1464      * Calculate the pixel offset of the first row for an absolute value (referring to the total height of all rows).
1465      * 
1466      * @param rowIdx index of the first row
1467      * @param value absolute value (height/width offset)
1468      * @return pixeloffset (to be used as the pixel offset of the first row)
1469      */
1470     public int getRowPixOffsetForAbsoluteOffset(int rowIdx, int value) {
1471         if (!_timeBarViewState.getUseVariableRowHeights()) {
1472             int off = value % _timeBarViewState.getDefaultRowHeight();
1473             return off;
1474         } else {
1475             int y = getAbsPosForRow(rowIdx);
1476             return value - y;
1477         }
1478     }
1479 
1480     /**
1481      * Calculate the total height/width of all rows.
1482      * 
1483      * @return the total height/row size
1484      */
1485     public int getTotalHeight() {
1486         if (!_timeBarViewState.getUseVariableRowHeights()) {
1487             return getRowCount() * _timeBarViewState.getDefaultRowHeight();
1488         } else {
1489             int h = 0;
1490             for (int i = 0; i < _rowList.size(); i++) {
1491                 TimeBarRow row = getRow(i);
1492                 h += _timeBarViewState.getRowHeight(row);
1493             }
1494             return h;
1495         }
1496     }
1497 
1498     /**
1499      * Get the absolute position (x or y depeding on the orientation) for a row.
1500      * 
1501      * @param rowIdx inde of the row
1502      * @return absolute begin position of the row
1503      */
1504     public int getAbsPosForRow(int rowIdx) {
1505         if (!_timeBarViewState.getUseVariableRowHeights()) {
1506             return rowIdx * _timeBarViewState.getDefaultRowHeight();
1507         } else {
1508             int h = 0;
1509             for (int i = 0; i < rowIdx; i++) {
1510                 TimeBarRow row = getRow(i);
1511                 h += _timeBarViewState.getRowHeight(row);
1512             }
1513             return h;
1514         }
1515     }
1516 
1517     /**
1518      * Handle change of the vertical scroll bar.
1519      * 
1520      * @param value new value of the scroll bar
1521      * @param redirect allow redirection to the horizontal scroll handling according to orientation
1522      */
1523     public void handleVerticalScroll(int value, boolean redirect) {
1524         if (!redirect || _orientation == Orientation.HORIZONTAL) {
1525             int row = getRowIdxForAbsoluteOffset(value);
1526             int offset = getRowPixOffsetForAbsoluteOffset(row, value);
1527             setFirstRow(row, offset);
1528         } else if (_orientation == Orientation.VERTICAL) {
1529             handleHorizontalScroll(value, false);
1530         } else {
1531             throw new RuntimeException("illegal");
1532         }
1533     }
1534 
1535     /**
1536      * Retrieve the date for a position on the screen. According to the orientation x or y will be used.
1537      * 
1538      * @param x x coordinate
1539      * @param y y coordinate
1540      * @return date for the position
1541      */
1542     public JaretDate dateForXY(int x, int y) {
1543         if (_startDate == null) {
1544             return null;
1545         }
1546         int coord;
1547         if (_orientation == Orientation.HORIZONTAL) {
1548             coord = x;
1549         } else {
1550             coord = y;
1551         }
1552         if (!_variableXScale) {
1553             return dateForCoordPlain(coord);
1554         } else {
1555             return dateForCoordVariable(coord);
1556         }
1557 
1558     }
1559 
1560     /**
1561      * Calculate the date for a given x coordinate (or y in case of vertical orientation).
1562      * 
1563      * @param x the x coordinate
1564      * @return the JaretDate for the given x location or <code>null</code> if no start date has been set
1565      * @deprecated use dateForXY or dateForCoord instead
1566      */
1567     public JaretDate dateForX(int x) {
1568         if (_startDate == null) {
1569             return null;
1570         }
1571         if (!_variableXScale) {
1572             return dateForCoordPlain(x);
1573         } else {
1574             return dateForCoordVariable(x);
1575         }
1576     }
1577 
1578     /**
1579      * Calculate the date for a given coordinate on the timescale.
1580      * 
1581      * @param coord the coordinate
1582      * @return the JaretDate for the given location or <code>null</code> if no start date has been set
1583      */
1584     public JaretDate dateForCoord(int coord) {
1585         if (_startDate == null) {
1586             return null;
1587         }
1588         if (!_variableXScale) {
1589             return dateForCoordPlain(coord);
1590         } else {
1591             return dateForCoordVariable(coord);
1592         }
1593     }
1594 
1595     /**
1596      * Calculate the date for a given point.
1597      * 
1598      * @param x x
1599      * @param y y
1600      * @return date or null
1601      */
1602     public JaretDate dateForCoord(int x, int y) {
1603         if (_orientation == Orientation.HORIZONTAL) {
1604             return dateForCoord(x);
1605         } else {
1606             return dateForCoord(y);
1607         }
1608     }
1609 
1610     /**
1611      * Plain date for coordinate (x or y deppending on orientation) function. (no variable scale)
1612      * 
1613      * @param coord coordinate
1614      * @return date for the coordinate along the timescale
1615      */
1616     private JaretDate dateForCoordPlain(int coord) {
1617         long pixDif = coord - (_yAxisWidth + _hierarchyWidth);
1618         long diffMilliSec = (long) ((double) pixDif / _pixelPerSeconds * MILLISCALING);
1619         JaretDate date = new JaretDate(_startDate);
1620         date.advanceMillis(diffMilliSec);
1621         return date;
1622     }
1623 
1624     /**
1625      * Retrieve the date for an absolute coordinate along the timescale (referencing minDate).
1626      * 
1627      * @param coord xposition
1628      * @return date for the position along the time axis
1629      */
1630     public JaretDate dateForCoordAbs(int coord) {
1631         if (!_variableXScale) {
1632             return dateForCoordAbsPlain(coord);
1633         } else {
1634             return dateForCoordAbsVariable(coord);
1635         }
1636     }
1637 
1638     /**
1639      * Retrieve the date for an absolute coordinate along the timescale (referencing minDate).
1640      * 
1641      * @param coord xposition
1642      * @return date for the position along the time axis
1643      */
1644     private JaretDate dateForCoordAbsPlain(int coord) {
1645         long pixDif = coord - (_yAxisWidth + _hierarchyWidth);
1646         JaretDate date = new JaretDate(_minDate);
1647         if (_milliAccuracy) {
1648             long diffMilliSec = (long) ((double) pixDif / _pixelPerSeconds * MILLISCALING);
1649             date.advanceMillis(diffMilliSec);
1650         } else {
1651             long diffSec = (long) ((double) pixDif / _pixelPerSeconds);
1652             date.advanceSeconds(diffSec);
1653         }
1654         return date;
1655     }
1656 
1657     /**
1658      * Retrieve the date for an absolute coordinate along the timescale (referencing minDate).
1659      * 
1660      * @param coord xposition
1661      * @return date for the position along the time axis
1662      */
1663     private JaretDate dateForCoordAbsVariable(int coord) {
1664         int pixDif = coord - (_yAxisWidth + _hierarchyWidth);
1665 
1666         // variable scale
1667         int pixelToGo = pixDif;
1668         if (pixDif <= 0) {
1669             // might be possible, does not matter
1670             return dateForCoordPlain(coord);
1671         }
1672         long milliSeconds = 0;
1673         JaretDate d = _minDate.copy();
1674 
1675         while (pixelToGo > 0) {
1676             PPSInterval interval = getPPSInterval(d);
1677 
1678             double pps = 0;
1679             JaretDate endPPS;
1680             boolean noFollowingPPSInterval = false;
1681             if (interval == null) {
1682                 // default pps;
1683                 pps = _pixelPerSeconds;
1684                 Interval i = nextPPSInterval(d);
1685                 if (i != null) {
1686                     endPPS = i.getBegin().copy();
1687                 } else {
1688                     noFollowingPPSInterval = true;
1689                     endPPS = getMaxDate().copy();
1690                 }
1691             } else {
1692                 pps = interval.getPps();
1693                 endPPS = interval.getEnd().copy();
1694             }
1695             long milliSecondsToEndPPS = endPPS.diffMilliSeconds(d);
1696             int pixelToEndPPS = (int) Math.round(((double) milliSecondsToEndPPS * pps / MILLISCALING));
1697 
1698             if (pixelToGo <= pixelToEndPPS || noFollowingPPSInterval) {
1699                 // all in this section or last section
1700                 milliSeconds = milliSeconds + (long) ((double) pixelToGo / pps * MILLISCALING);
1701                 JaretDate result = _minDate.copy().advanceMillis(milliSeconds);
1702                 return result; // finished
1703             } else {
1704                 // section
1705                 long ms = endPPS.diffMilliSeconds(d);
1706                 milliSeconds = milliSeconds + ms;
1707                 pixelToGo = pixelToGo - pixelToEndPPS;
1708                 d = endPPS.copy();
1709             }
1710         }
1711         return null;
1712     }
1713 
1714     /**
1715      * Date for coordinate along the y axis taking variable scale into account.
1716      * 
1717      * @param coord coordinate
1718      * @return date
1719      */
1720     private JaretDate dateForCoordVariable(int coord) {
1721         int pixDif = coord - (_yAxisWidth + _hierarchyWidth);
1722 
1723         // variable scale
1724         int pixelToGo = pixDif;
1725         if (pixDif <= 0) {
1726             // might be possible, does not matter
1727             return dateForCoordPlain(coord);
1728         }
1729         long milliSeconds = 0;
1730         JaretDate d = _startDate.copy();
1731 
1732         while (pixelToGo > 0) {
1733             PPSInterval interval = getPPSInterval(d);
1734 
1735             double pps = 0;
1736             JaretDate endPPS;
1737             boolean noFollowingPPSInterval = false;
1738             if (interval == null) {
1739                 // default pps;
1740                 pps = _pixelPerSeconds;
1741                 Interval i = nextPPSInterval(d);
1742                 if (i != null) {
1743                     endPPS = i.getBegin().copy();
1744                 } else {
1745                     noFollowingPPSInterval = true;
1746                     endPPS = getMaxDate().copy();
1747                 }
1748             } else {
1749                 pps = interval.getPps();
1750                 endPPS = interval.getEnd().copy();
1751             }
1752             long milliSecondsToEndPPS = endPPS.diffMilliSeconds(d);
1753             int pixelToEndPPS = (int) Math.round(((double) milliSecondsToEndPPS * pps / MILLISCALING));
1754 
1755             if (pixelToGo <= pixelToEndPPS || noFollowingPPSInterval) {
1756                 // all in this section or last section
1757                 milliSeconds = milliSeconds + (long) ((double) pixelToGo / pps * MILLISCALING);
1758                 JaretDate result = _startDate.copy().advanceMillis(milliSeconds);
1759                 return result; // finished
1760             } else {
1761                 // section
1762                 long ms = endPPS.diffMilliSeconds(d);
1763                 milliSeconds = milliSeconds + ms;
1764                 pixelToGo = pixelToGo - pixelToEndPPS;
1765                 d = endPPS.copy();
1766             }
1767         }
1768         return null;
1769     }
1770 
1771     /**
1772      * Calculate the x coordinate for a given date.
1773      * 
1774      * @param date JaretDate for which the coordinate is to be calculated
1775      * @return x coordinate in the current view
1776      */
1777     public int xForDate(JaretDate date) {
1778         if (!_variableXScale) {
1779             return xForDatePlain(date, false);
1780         } else {
1781             return xForDateVariable(date, false);
1782         }
1783     }
1784 
1785     /**
1786      * Calculate the x coordinate for a given date as absolute xvalue beginning with minDate = 0.
1787      * 
1788      * @param date JaretDate for which the coordinate is to be calculated
1789      * @return x coordinate
1790      */
1791     public int xForDateAbs(JaretDate date) {
1792         if (!_variableXScale) {
1793             return xForDatePlain(date, true);
1794         } else {
1795             return xForDateVariable(date, true);
1796         }
1797     }
1798 
1799     /**
1800      * No variable xscale calculation of x for for date.
1801      * 
1802      * @param date date
1803      * @param absolute if set to true absolute x value referencing minDate
1804      * @return x for date
1805      */
1806     protected int xForDatePlain(JaretDate date, boolean absolute) {
1807         int offset = _orientation.equals(TimeBarViewerInterface.Orientation.HORIZONTAL) ? _offsetLeft : _offsetTop;
1808         long milliSeconds;
1809         if (!absolute) {
1810             milliSeconds = date.diffMilliSeconds(_startDate);
1811         } else {
1812             milliSeconds = date.diffMilliSeconds(_minDate);
1813         }
1814         // we use Math.round() to get the rounding errors as small as
1815         // possible
1816         int x = _yAxisWidth + _hierarchyWidth + offset
1817                 + (int) Math.round((((double) milliSeconds) * _pixelPerSeconds / MILLISCALING));
1818         return x;
1819     }
1820 
1821     /**
1822      * Variable calculation of x for date.
1823      * 
1824      * @param date date
1825      * @param absolute if set to true absolute x value referencing minDate
1826      * @return x for date
1827      */
1828     protected int xForDateVariable(JaretDate date, boolean absolute) {
1829         // TODO milliseconds may be overflowing
1830         long milliSeconds;
1831         if (!absolute) {
1832             milliSeconds = date.diffMilliSeconds(_startDate);
1833         } else {
1834             milliSeconds = date.diffMilliSeconds(_minDate);
1835         }
1836         boolean neg = false;
1837         if (milliSeconds <= 0) {
1838             if (absolute) {
1839                 return 0;
1840             }
1841             // when not doing absolute calculation and the requested date is before x = 0
1842             // calculate abs value and correct afterwards
1843             neg = true;
1844             milliSeconds = date.diffMilliSeconds(_minDate);
1845         }
1846 
1847         long milliSecondsToGo = milliSeconds;
1848         JaretDate d = (absolute || neg) ? _minDate.copy() : _startDate.copy();
1849         int x = 0;
1850         while (milliSecondsToGo > 0) {
1851             PPSInterval interval = getPPSInterval(d);
1852 
1853             double pps = 0;
1854             JaretDate endPPS;
1855             boolean noFollowingPPSInterval = false;
1856             if (interval == null) {
1857                 // default pps;
1858                 pps = _pixelPerSeconds;
1859                 Interval i = nextPPSInterval(d);
1860                 if (i != null) {
1861                     endPPS = i.getBegin().copy();
1862                 } else {
1863                     noFollowingPPSInterval = true;
1864                     endPPS = getMaxDate().copy();
1865                 }
1866             } else {
1867                 pps = interval.getPps();
1868                 endPPS = interval.getEnd().copy();
1869             }
1870             if (date.compareTo(endPPS) <= 0 || noFollowingPPSInterval) {
1871                 // all in this section or last section
1872                 x = x + (int) Math.round(((double) milliSecondsToGo * pps / MILLISCALING));
1873                 if (absolute) {
1874                     return x;
1875                 }
1876                 // TODO insets
1877                 if (neg) {
1878                     // correct x value
1879                     int firstOff = xForDateAbs(_startDate);
1880                     x -= firstOff;
1881                 }
1882                 return _yAxisWidth + _hierarchyWidth + x; // finished
1883             } else {
1884                 // section
1885                 long ms = endPPS.diffMilliSeconds(d);
1886                 x = x + (int) Math.round(((double) ms * pps / MILLISCALING));
1887                 milliSecondsToGo = milliSecondsToGo - ms;
1888                 d = endPPS.copy();
1889             }
1890         }
1891         if (absolute) {
1892             return x;
1893         }
1894         // TODO insets
1895         if (neg) {
1896             // correct x value
1897             int firstOff = xForDateAbs(_startDate);
1898             x -= firstOff;
1899         }
1900         return _yAxisWidth + _hierarchyWidth + x;
1901     }
1902 
1903     /**
1904      * Retrieve the appropriate pps interval for a given date.
1905      * 
1906      * @param d date
1907      * @return interval or <code>null</code>
1908      */
1909     public PPSInterval getPPSInterval(JaretDate d) {
1910         List<Interval> l = _xScalePPSIntervalRow.getIntervals(d);
1911         if (l.size() == 0) {
1912             return null;
1913         }
1914         if (l.size() == 1) {
1915             PPSInterval interval = (PPSInterval) l.get(0);
1916             if (interval.getEnd().equals(d)) {
1917                 return null;
1918             }
1919             return interval;
1920         }
1921         if (l.size() == 2) {
1922             PPSInterval interval1 = (PPSInterval) l.get(0);
1923             PPSInterval interval2 = (PPSInterval) l.get(1);
1924             if (interval1.getBegin().equals(d)) {
1925                 return interval1;
1926             }
1927             if (interval2.getBegin().equals(d)) {
1928                 return interval2;
1929             }
1930 
1931         }
1932         throw new RuntimeException("no overlapping intervals in xscale row");
1933     }
1934 
1935     /**
1936      * Get the next interval to a date from the pps row (variable x axis).
1937      * 
1938      * @param d date
1939      * @return next interval (future) to the given date or null if there is no such interval
1940      */
1941     public PPSInterval nextPPSInterval(JaretDate d) {
1942         PPSInterval result = null;
1943         result = getPPSInterval(d);
1944         if (result != null) {
1945             return result;
1946         }
1947         // assumes sorted intervals
1948         List<Interval> list = _xScalePPSIntervalRow.getIntervals();
1949         for (int i = 0; i < list.size(); i++) {
1950             PPSInterval interval = (PPSInterval) list.get(i);
1951             long diffMilliSecs = d.diffMilliSeconds(interval.getBegin());
1952             if (diffMilliSecs <= 0) {
1953                 result = interval;
1954                 break;
1955             }
1956         }
1957         return result;
1958     }
1959 
1960     /**
1961      * Get the row located at x,y.
1962      * 
1963      * @param x x coordinate
1964      * @param y y coordinate
1965      * @return row at the given location or <code>null</code> if no row could be determined
1966      */
1967     public TimeBarRow rowForXY(int x, int y) {
1968         if (_diagramRect.contains(x, y) || _yAxisRect.contains(x, y) || _hierarchyRect.contains(x, y)) {
1969             if (_orientation == Orientation.HORIZONTAL) {
1970                 return rowForY(y);
1971             } else {
1972                 return rowForY(x);
1973             }
1974         } else {
1975             return null;
1976         }
1977     }
1978 
1979     /**
1980      * Retrieve the row for a coordinate (either y or x depending on the orientation).
1981      * 
1982      * @param coord y for horizontal, x for vertical orientation
1983      * @return index of the row at the given position
1984      */
1985     private int rowForCoordInternal(int coord) {
1986         if (!_timeBarViewState.getUseVariableRowHeights()) {
1987             if (_orientation == Orientation.HORIZONTAL) {
1988                 return _firstRow + (coord + _firstRowPixelOffset - _diagramRect.y)
1989                         / _timeBarViewState.getDefaultRowHeight();
1990             } else {
1991                 return _firstRow + (coord + _firstRowPixelOffset - _diagramRect.x)
1992                         / _timeBarViewState.getDefaultRowHeight();
1993             }
1994         }
1995         int maxY = 0;
1996         if (_orientation == Orientation.HORIZONTAL) {
1997             coord -= _diagramRect.y;
1998             maxY = _diagramRect.height;
1999         } else {
2000             coord -= _diagramRect.x;
2001             maxY = _diagramRect.width;
2002         }
2003 
2004         int rowStart = -_firstRowPixelOffset;
2005         int idx = _firstRow;
2006 
2007         while (rowStart < maxY && idx < _rowList.size()) {
2008             int rHeight = _timeBarViewState.getRowHeight(getRow(idx));
2009             if (coord >= rowStart && coord <= rowStart + rHeight) {
2010                 return idx;
2011             }
2012             rowStart += rHeight;
2013             idx++;
2014         }
2015         return -1;
2016 
2017     }
2018 
2019     /**
2020      * Get the row for a given y coordinate in the diagram pane. In case of vertical orientation this has to be read as
2021      * columnForX.
2022      * 
2023      * @param y y coordinate (or x coordinate when using vertical orientation)
2024      * @return the row or null if no row is found for the given y
2025      */
2026     public TimeBarRow rowForY(int y) {
2027         // y = y - _firstRowPixelOffset;
2028         if (_rowList == null || _model == null) {
2029             return null;
2030         }
2031         int idx = -1;
2032         idx = rowForCoordInternal(y);
2033         if (idx < _rowList.size() && idx >= 0) {
2034             return getRow(idx);
2035         } else {
2036             return null;
2037         }
2038     }
2039 
2040     /**
2041      * Calculate the y coordinate in the diagram pane for the given row. Works only on displayed rows!
2042      * 
2043      * @param row row
2044      * @return y coordinate in the diagram pane or -1 if the row is not displayed
2045      */
2046     public int yForRow(TimeBarRow row) {
2047         if (!_timeBarViewState.getUseVariableRowHeights()) {
2048             int idx = _rowList.indexOf(row);
2049             if (idx == -1) {
2050                 return -1;
2051             }
2052             idx = idx - _firstRow;
2053             if (_orientation == Orientation.HORIZONTAL) {
2054                 return idx * _timeBarViewState.getDefaultRowHeight() + _diagramRect.y - _firstRowPixelOffset;
2055             } else {
2056                 return idx * _timeBarViewState.getDefaultRowHeight() + _diagramRect.x - _firstRowPixelOffset;
2057             }
2058         }
2059 
2060         int height;
2061         int offset;
2062         if (_orientation == Orientation.HORIZONTAL) {
2063             height = _diagramRect.height;
2064             offset = _diagramRect.y;
2065         } else {
2066             height = _diagramRect.width;
2067             offset = _diagramRect.x;
2068         }
2069 
2070         int y = -_firstRowPixelOffset;
2071         int idx = _firstRow;
2072         // if no row is displayed at all -1 is the result
2073         if (_rowList.size() == 0)
2074             return -1;
2075         TimeBarRow r = getRow(idx);
2076 
2077         while (y <= height) {
2078             if (row.equals(r)) {
2079                 return y + offset;
2080             }
2081             y += _timeBarViewState.getRowHeight(r);
2082             idx++;
2083             if (idx >= _rowList.size()) {
2084                 break;
2085             }
2086             r = getRow(idx);
2087         }
2088         return -1;
2089 
2090     }
2091 
2092     /**
2093      * Calculate the display index of the row. The display index is 0 for the topmost row.
2094      * 
2095      * @param row row
2096      * @return the display index
2097      */
2098     public int dispIdxForRow(TimeBarRow row) {
2099         int rIdx = _rowList.indexOf(row);
2100         if (rIdx >= _firstRow && rIdx <= _firstRow + getRowsDisplayed()) {
2101             return rIdx - _firstRow;
2102         }
2103         return -1;
2104     }
2105 
2106     /**
2107      * Calculates the bounds of a row. If the row is not displayed the resulting Rectangle will be 0,0,0,0. The
2108      * ractangle includes header etc.
2109      * 
2110      * @param row row to calculate the bounds for
2111      * @return Rectangle describing teh rectangle for the row in the current view
2112      */
2113     public Rectangle getRowBounds(TimeBarRow row) {
2114         if (!isRowDisplayed(row)) {
2115             return new Rectangle(0, 0, 0, 0);
2116         } else {
2117             if (_orientation == Orientation.HORIZONTAL) {
2118                 int y = yForRow(row);
2119                 return new Rectangle(0, y, _tbvi.getWidth(), _timeBarViewState.getRowHeight(row));
2120             } else {
2121                 int x = yForRow(row);
2122                 return new Rectangle(x, 0, _timeBarViewState.getRowHeight(row), _tbvi.getHeight());
2123             }
2124         }
2125     }
2126 
2127     /**
2128      * Calculate the bounds for an interval.
2129      * 
2130      * @param rowIdx index of the row
2131      * @param interval interval
2132      * @return bounding rectangle
2133      */
2134     public Rectangle getIntervalBounds(int rowIdx, Interval interval) {
2135         if (_orientation == Orientation.HORIZONTAL) {
2136             int x = xForDate(interval.getBegin());
2137             int x2 = xForDate(interval.getEnd());
2138             int width = x2 - x;
2139             int y = rowIdx == -1 ? 0 : yForRow(getRow(rowIdx));
2140             int height = rowIdx == -1 ? _tbvi.getHeight() : _timeBarViewState.getRowHeight(getRow(rowIdx));
2141             TimeBarRow row = rowIdx == -1 ? null : _rowList.get(rowIdx);
2142             if (row != null && !getTimeBarViewState().getDrawOverlapping(row)) {
2143                 OverlapInfo oi = _overlapStrategy.getOverlapInfo(row, interval);
2144                 if (oi != null) {
2145                     if (!_useUniformHeight) {
2146                         height = height / (oi.maxOverlapping + 1);
2147                     } else {
2148                         height = height / (_overlapStrategy.getMaxOverlapCount(row));
2149                     }
2150                     y = y + oi.pos * height;
2151                 }
2152             }
2153             return new Rectangle(x, y, width, height);
2154         } else {
2155             int y = xForDate(interval.getBegin());
2156             int y2 = xForDate(interval.getEnd());
2157             int height = y2 - y;
2158             int x = rowIdx == -1 ? 0 : yForRow(getRow(rowIdx));
2159             int width = rowIdx == -1 ? _tbvi.getWidth() : _timeBarViewState.getRowHeight(getRow(rowIdx));
2160             TimeBarRow row = rowIdx == -1 ? null : _rowList.get(rowIdx);
2161             if (row != null && !getTimeBarViewState().getDrawOverlapping(row)) {
2162                 OverlapInfo oi = _overlapStrategy.getOverlapInfo(_rowList.get(rowIdx), interval);
2163                 if (oi != null) {
2164                     if (!_useUniformHeight) {
2165                         width = width / (oi.maxOverlapping + 1);
2166                     } else {
2167                         width = width / (_overlapStrategy.getMaxOverlapCount(row));
2168                     }
2169                     x = x + oi.pos * width;
2170                 }
2171             }
2172             return new Rectangle(x, y, width, height);
2173         }
2174     }
2175 
2176     /**
2177      * Retrieve the bounding rect of an interval. ATTENTION: this uses the row for interval lookup in the model that may
2178      * be imperformant.
2179      * 
2180      * @param interval interval
2181      * @return the bounding rect or null
2182      */
2183     public Rectangle getIntervalBounds(Interval interval) {
2184         TimeBarRow row = _model.getRowForInterval(interval);
2185         if (row != null) {
2186             return getIntervalBounds(row, interval);
2187         }
2188         return null;
2189     }
2190 
2191     /**
2192      * Calculates the bounding rectangle for an interval. The row may be given optional- if it isn't the rectangle will
2193      * span the complete height (vertical: width) of the diagram
2194      * 
2195      * @param row TimeBarRow - may be null
2196      * @param interval Interval in question
2197      * @return Rectangle
2198      */
2199     public Rectangle getIntervalBounds(TimeBarRow row, Interval interval) {
2200         return getIntervalBounds(row == null ? -1 : _rowList.indexOf(row), interval);
2201     }
2202 
2203     /**
2204      * @return the number of rows currently displayed by the viewer
2205      */
2206     public int getRowsDisplayed() {
2207         if (!_timeBarViewState.getUseVariableRowHeights()) {
2208             // correction by 1 to ensure the last row is always displayed
2209             if (_orientation == Orientation.HORIZONTAL) {
2210                 return _diagramRect.height / _timeBarViewState.getDefaultRowHeight() + 1;
2211                 // return _tbvi.getHeight() / _timeBarViewState.getDefaultRowHeight() + 1;
2212             } else {
2213                 return _diagramRect.width / _timeBarViewState.getDefaultRowHeight() + 1;
2214                 // return _tbvi.getWidth() / _timeBarViewState.getDefaultRowHeight() + 1;
2215             }
2216         }
2217         // variable heights
2218         int end;
2219         if (_orientation == Orientation.HORIZONTAL) {
2220             end = _diagramRect.height;
2221         } else {
2222             end = _diagramRect.width;
2223         }
2224         int count = 0;
2225         int idx = _firstRow;
2226         int coord = -_firstRowPixelOffset;
2227         while (coord <= end && idx < _rowList.size()) {
2228             coord += _timeBarViewState.getRowHeight(getRow(idx++));
2229             count++;
2230         }
2231         return count;
2232 
2233     }
2234 
2235     /**
2236      * Check whether a certain row is currently displayed.
2237      * 
2238      * @param row row to be checked
2239      * @return true if the row is in the current view, false otherwise
2240      */
2241     public boolean isRowDisplayed(TimeBarRow row) {
2242         return _rowList.indexOf(row) - _firstRow < getRowsDisplayed();
2243     }
2244 
2245     /**
2246      * Retrieves all intervals at a given point in the diagram pane.
2247      * 
2248      * @param x x coordinate
2249      * @param y y coordinate
2250      * @return List of all intervals at the point
2251      */
2252     public List<Interval> getIntervalsAt(int x, int y) {
2253         TimeBarRow row = rowForXY(x, y);
2254         return getIntervalsAt(row, x, y);
2255     }
2256 
2257     /**
2258      * Retrieves all intervals for a given row and a x coordinate in the diagram pane. Filtered intervals will be left
2259      * out.
2260      * 
2261      * @param row row to search in
2262      * @param x x coordinate, will be ignored if set to -1
2263      * @param y y coordinate, will be ignored if set to -1
2264      * @return List of intervals for the given x and row
2265      */
2266     public List<Interval> getIntervalsAt(TimeBarRow row, int x, int y) {
2267         List<Interval> result = new ArrayList<Interval>();
2268         if (row != null) {
2269             JaretDate date;
2270             if (_orientation == Orientation.HORIZONTAL) {
2271                 date = dateForCoord(x);
2272             } else {
2273                 date = dateForCoord(y);
2274             }
2275             // correct for milli accuracy
2276             // (no problem when dealing with scales in second range or above)
2277             if (_milliAccuracy) {
2278                 date.advanceMillis(1);
2279             }
2280             result = row.getIntervals(date);
2281             // if there is a interval filter
2282             if (_intervalFilter != null) {
2283                 List<Interval> in = result;
2284                 result = new ArrayList<Interval>();
2285                 for (Interval interval : in) {
2286                     if (_intervalFilter.isInResult(interval)) {
2287                         result.add(interval);
2288                     }
2289                 }
2290             }
2291             // check whether up to one interval has been found or no y
2292             // coordinate is given
2293             if (result.size() <= 1 || x == -1 || y == -1) {
2294                 return result;
2295             }
2296             // check exact interval bounds
2297             List<Interval> newResult = new ArrayList<Interval>(1);
2298             for (Interval interval : result) {
2299                 Rectangle rect = getIntervalBounds(row, interval);
2300                 if (rect.contains(x, y)) {
2301                     newResult.add(interval);
2302                 }
2303             }
2304             return newResult;
2305         } else {
2306             result = new ArrayList<Interval>();
2307         }
2308         return result;
2309 
2310     }
2311 
2312     /**
2313      * Retrieve intervals in a given row for a given x coordinate.
2314      * 
2315      * @param row row to search in
2316      * @param x x coordinate
2317      * @return list of intervals
2318      */
2319     public List<Interval> getIntervalsAt(TimeBarRow row, int x) {
2320         if (_orientation == Orientation.HORIZONTAL) {
2321             return getIntervalsAt(row, x, -1);
2322         } else {
2323             return getIntervalsAt(row, -1, x);
2324         }
2325     }
2326 
2327     // *** TimeBarMarkerListener
2328     /**
2329      * {@inheritDoc}
2330      */
2331     public void markerMoved(TimeBarMarker marker, JaretDate oldDate, JaretDate currentDate) {
2332         int width = _tbvi.getMarkerWidth(marker);
2333         if (isDisplayed(currentDate)) {
2334             // repaint only the last position of the marker and the new position
2335             if (_orientation.equals(Orientation.HORIZONTAL)) {
2336                 _tbvi.repaint(xForDate(oldDate) - width / 2 + 1, 0, width + 1, _tbvi.getHeight());
2337                 _tbvi.repaint(xForDate(currentDate) - width / 2 + 1, 0, width + 1, _tbvi.getHeight());
2338             } else {
2339                 // vertical
2340                 _tbvi.repaint(0, xForDate(oldDate) - width / 2 + 1, _tbvi.getWidth(), width + 1);
2341                 _tbvi.repaint(0, xForDate(currentDate) - width / 2 + 1, _tbvi.getWidth(), width + 1);
2342             }
2343         }
2344         if (marker.getDate().compareTo(_minDate) < 0) {
2345             setMinDate(marker.getDate().copy());
2346         } else if (marker.getDate().compareTo(_maxDate) > 0) {
2347             setMaxDate(marker.getDate().copy());
2348         }
2349     }
2350 
2351     /**
2352      * {@inheritDoc}
2353      */
2354     public void markerDescriptionChanged(TimeBarMarker marker, String oldValue, String newValue) {
2355         int width = _tbvi.getMarkerWidth(marker);
2356         if (isDisplayed(marker.getDate())) {
2357             _tbvi.repaint(xForDate(marker.getDate()) - width / 2 + 1, 0, width / 2 + 1, _tbvi.getHeight());
2358         }
2359     }
2360 
2361     // *** End of TimeBarMarkerListener
2362 
2363     // *** TimeBarSelectionListener
2364     /**
2365      * {@inheritDoc}
2366      */
2367     public void selectionChanged(TimeBarSelectionModel selectionModel) {
2368         // this should be rarely called by the SelectionModel
2369         _tbvi.repaint();
2370         // this is swt only: support for the timebar viewer beeing an
2371         // ISelectionProvider
2372         _tbvi.fireSelectionChanged();
2373     }
2374 
2375     /**
2376      * {@inheritDoc}
2377      */
2378     public void elementAddedToSelection(TimeBarSelectionModel selectionModel, Object element) {
2379         if (element instanceof TimeBarRow) {
2380             Rectangle rowRect = getRowBounds((TimeBarRow) element);
2381             _tbvi.repaint(rowRect);
2382         } else if (element instanceof Interval) {
2383             // we don't have a row to go with ... so we have to repaint the
2384             // whole column
2385             Rectangle intervalRect = getIntervalBounds(null, (Interval) element);
2386             _tbvi.repaint(intervalRect);
2387         } else if (element instanceof IIntervalRelation) {
2388             // repaint the whole diagram since there is no knowledge about the relation rendering
2389             _tbvi.repaint(_diagramRect);
2390         } else {
2391             throw new RuntimeException("Unknonw object in elementAddedToSelection " + element.getClass().getName());
2392         }
2393         // this is swt only: support for the timebar viewer beeing an
2394         // ISelectionProvider
2395         _tbvi.fireSelectionChanged();
2396 
2397     }
2398 
2399     /**
2400      * {@inheritDoc}
2401      */
2402     public void elementRemovedFromSelection(TimeBarSelectionModel selectionModel, Object element) {
2403         if (element instanceof TimeBarRow) {
2404             // the row may be removed from the model (repainting is done in the
2405             // model wath then)
2406             if (_rowList.contains((TimeBarRow) element)) {
2407                 Rectangle rowRect = getRowBounds((TimeBarRow) element);
2408                 _tbvi.repaint(rowRect);
2409             }
2410         } else if (element instanceof Interval) {
2411             Interval interval = (Interval) element;
2412             TimeBarRow row = _model.getRowForInterval(interval);
2413             // the interval may have been removed from the model, check
2414             if (row != null && row.getIntervals() != null && row.getIntervals().contains(interval)) {
2415                 Rectangle intervalRect = getIntervalBounds(row, (Interval) element);
2416                 _tbvi.repaint(intervalRect);
2417             }
2418         } else if (element instanceof IIntervalRelation) {
2419             // repaint the whole diagram since there is no knowledge about the relation rendering
2420             _tbvi.repaint(_diagramRect);
2421         } else {
2422             throw new RuntimeException("Unknown object in elementRemovedFromSelection " + element.getClass().getName());
2423         }
2424         // this is swt only: support for the timebar viewer beeing an
2425         // ISelectionProvider
2426         _tbvi.fireSelectionChanged();
2427 
2428     }
2429 
2430     // *** End of TimeBarSelectionListener
2431     // *** TimeBarModelListener
2432     /**
2433      * {@inheritDoc}
2434      */
2435     public void modelDataChanged(TimeBarModel model) {
2436         checkAndAdjustMinMax();
2437         updateRowList();
2438         _overlapStrategy.clearCachedData();
2439         _tbvi.repaint();
2440     }
2441 
2442     /**
2443      * {@inheritDoc}
2444      */
2445     public void rowDataChanged(TimeBarModel model, TimeBarRow row) {
2446         checkAndAdjustMinMax();
2447         if (!getTimeBarViewState().getDrawOverlapping(row)) {
2448             _overlapStrategy.updateOICache(row);
2449         }
2450         if (isRowDisplayed(row)) {
2451             _tbvi.repaint();
2452         }
2453         // check which intervals are in the selection and are not in the model
2454         // anymore
2455         // TODO -> very expensive and not really needed
2456     }
2457 
2458     /**
2459      * {@inheritDoc}
2460      */
2461     public void rowAdded(TimeBarModel model, TimeBarRow row) {
2462         checkAndAdjustMinMax();
2463         updateRowList();
2464         _tbvi.repaint();
2465     }
2466 
2467     /**
2468      * {@inheritDoc}
2469      */
2470     public void rowRemoved(TimeBarModel model, TimeBarRow row) {
2471         checkAndAdjustMinMax();
2472         boolean isDisplayed = isRowDisplayed(row);
2473         updateRowList();
2474         if (isDisplayed) {
2475             _tbvi.repaint();
2476         }
2477         // remove from selection
2478         if (_selectionModel != null) {
2479             _selectionModel.remSelectedRow(row);
2480         }
2481     }
2482 
2483     /**
2484      * {@inheritDoc}
2485      */
2486     public void elementAdded(TimeBarModel model, TimeBarRow row, Interval element) {
2487         checkAndAdjustMinMax();
2488         if (!getTimeBarViewState().getDrawOverlapping(row)) {
2489             _overlapStrategy.updateOICache(row);
2490         }
2491         if (isRowDisplayed(row)) {
2492             _tbvi.repaint();
2493         }
2494     }
2495 
2496     /**
2497      * {@inheritDoc}
2498      */
2499     public void elementRemoved(TimeBarModel model, TimeBarRow row, Interval element) {
2500         checkAndAdjustMinMax();
2501         if (!getTimeBarViewState().getDrawOverlapping(row)) {
2502             _overlapStrategy.updateOICache(row);
2503         }
2504         if (isRowDisplayed(row)) {
2505             _tbvi.repaint();
2506         }
2507         // remove from selection
2508         if (_selectionModel != null) {
2509             _selectionModel.remSelectedInterval(element);
2510         }
2511     }
2512 
2513     /**
2514      * {@inheritDoc}
2515      */
2516     public void elementChanged(TimeBarModel model, TimeBarRow row, Interval element) {
2517         checkAndAdjustMinMax();
2518         if (!getTimeBarViewState().getDrawOverlapping(row)) {
2519             _overlapStrategy.updateOICache(row);
2520         }
2521         if (isRowDisplayed(row)) {
2522             _tbvi.repaint();
2523         }
2524     }
2525 
2526     /**
2527      * {@inheritDoc}
2528      */
2529     public void headerChanged(TimeBarModel model, TimeBarRow row, Object newHeader) {
2530         if (isRowDisplayed(row)) {
2531             _tbvi.repaint();
2532         }
2533     }
2534 
2535     // *** End of TimeBarModelListener
2536 
2537     /**
2538      * @return Returns the firstRow.
2539      */
2540     public int getFirstRow() {
2541         return _firstRow;
2542     }
2543 
2544     /**
2545      * Sets the first row to be displayed with a pixel offset 0.
2546      * 
2547      * @param firstRow The row to be displayed at the topmost position in the viewer.
2548      */
2549     public void setFirstRow(int firstRow) {
2550         setFirstRow(firstRow, 0);
2551     }
2552 
2553     /**
2554      * Set the first row to be displayed.
2555      * 
2556      * @param row row to be the first row with an offset of 0.
2557      */
2558     public void setFirstRow(TimeBarRow row) {
2559         int index = _rowList.indexOf(row);
2560         if (index != -1) {
2561             setFirstRow(index, 0);
2562         }
2563     }
2564 
2565     /**
2566      * Set the first row to be displayed.
2567      * 
2568      * @param firstRow upmost row to be displayed
2569      * @param pixOffset pixel offset
2570      */
2571     public void setFirstRow(int firstRow, int pixOffset) {
2572         if (firstRow != _firstRow || _firstRowPixelOffset != pixOffset) {
2573             int oldVal = _firstRow;
2574             int oldOffset = _firstRowPixelOffset;
2575 
2576             if (_tbvi != null) {
2577                 // pixel difference of the old and the new position
2578                 int diff;
2579                 
2580                 // if called after a change of the row list, the calculation of the difference is not possible
2581                 if (oldVal > _rowList.size()) {
2582                     diff = Integer.MAX_VALUE;
2583                 } else {
2584                     if (!_timeBarViewState.getUseVariableRowHeights()) {
2585                         diff = (firstRow * _timeBarViewState.getDefaultRowHeight() + pixOffset)
2586                                 - (oldVal * _timeBarViewState.getDefaultRowHeight() + oldOffset);
2587                     } else {
2588                         diff = (getAbsPosForRow(firstRow) + pixOffset) - (getAbsPosForRow(oldVal) + oldOffset);
2589                     }
2590                 }
2591                 // if not optimized scrolling or more than 2 thirds difference
2592                 // -> do a complete repaint
2593                 int maxOptScroll = _orientation.equals(Orientation.HORIZONTAL) ? (_diagramRect.height - _diagramRect.height)
2594                         : (_diagramRect.width - _diagramRect.width);
2595 
2596                 if (!_optimizeScrolling || Math.abs(diff) > maxOptScroll / 3) {
2597                     _firstRow = firstRow;
2598                     _firstRowPixelOffset = pixOffset;
2599                     _tbvi.repaint();
2600                 } else {
2601                     // do optimized scrolling
2602                     if (_orientation == Orientation.HORIZONTAL) {
2603                         _tbvi.doScrollVertical(diff);
2604                     } else {
2605                         _tbvi.doScrollHorizontal(diff);
2606                     }
2607 
2608                     _firstRow = firstRow;
2609                     _firstRowPixelOffset = pixOffset;
2610                 }
2611                 if (_tbvi != null) {
2612                     _tbvi.firePropertyChange(TimeBarViewerInterface.PROPERTYNAME_FIRSTROW, oldVal, firstRow);
2613                     _tbvi.firePropertyChange(TimeBarViewerInterface.PROPERTYNAME_FIRSTROWOFFSET, oldOffset, pixOffset);
2614                 }
2615                 updateRowScrollBar();
2616             } else {
2617                 _firstRow = firstRow;
2618                 _firstRowPixelOffset = pixOffset;
2619             }
2620         }
2621     }
2622 
2623     /**
2624      * Retrieve the pixel offset for the first row.
2625      * 
2626      * @return pixel offset of the first row
2627      */
2628     public int getFirstRowOffset() {
2629         return _firstRowPixelOffset;
2630     }
2631 
2632     /**
2633      * Set the pixel offset of the first row.
2634      * 
2635      * @param offset pixel offset for the first row
2636      */
2637     public void setFirstRowOffset(int offset) {
2638         setFirstRow(getFirstRow(), offset);
2639     }
2640 
2641     /**
2642      * Set the last row in the viewer. If there are not enough rows for the row beeing the last row the row will be
2643      * displayed as far down as possible by setting the first row to 0.
2644      * 
2645      * @param index index of the row to be displayed at the bottom of the viewer.
2646      */
2647     public void setLastRow(int index) {
2648         // TimeBarRow row = getRow(index);
2649         // int absY = getAbsPosForRow(index);
2650         // int endY = absY + _timeBarViewState.getRowHeight(row);
2651         //
2652         // int height = _diagramRect.height;
2653         //
2654         // int upperBound = endY - height;
2655         // int y = endY;
2656         // int idx = index;
2657         // while (idx > 0 && y > upperBound) {
2658         // row = getRow(idx);
2659         // y -= _timeBarViewState.getRowHeight(row);
2660         // idx--;
2661         // }
2662         // if (idx >= 0) {
2663         // int offset = Math.abs(y - upperBound);
2664         // setFirstRow(idx + 1, offset);
2665         // } else {
2666         // setFirstRow(0, 0);
2667         // }
2668 
2669         // invalid index -> go for the first row
2670         if (index >= _rowList.size()) {
2671             setFirstRow(0, 0);
2672             return;
2673         }
2674         TimeBarRow row = getRow(index);
2675         int absY = getAbsPosForRow(index);
2676         int endY = absY + _timeBarViewState.getRowHeight(row);
2677 
2678         int height = _diagramRect.height;
2679 
2680         int upperBound = endY - height;
2681         int y = endY;
2682         int idx = index;
2683 
2684         if (endY < height) {
2685             setFirstRow(0, 0);
2686         } else {
2687             y -= _timeBarViewState.getRowHeight(row);
2688             idx--;
2689             while (idx > 0 && y > upperBound) {
2690                 row = getRow(idx);
2691                 y -= _timeBarViewState.getRowHeight(row);
2692                 idx--;
2693             }
2694             int offset = Math.abs(y - upperBound);
2695             if (idx >= 0) {
2696                 setFirstRow(idx + 1, offset);
2697             } else {
2698                 setFirstRow(0, offset);
2699             }
2700 
2701         }
2702 
2703     }
2704 
2705     /**
2706      * Set the last row in the viewer. If there are not enough rows for the row beeing the last row the row will be
2707      * displayed as far down as possible by setting the first row to 0.
2708      * 
2709      * @param row the row to be displayed at the bottom of the viewer.
2710      */
2711     public void setLastRow(TimeBarRow row) {
2712         int index = _rowList.indexOf(row);
2713         if (index != -1) {
2714             setLastRow(index);
2715         }
2716     }
2717 
2718     /**
2719      * @return Returns the timeScalePosition.
2720      */
2721     public int getTimeScalePosition() {
2722         return _timeScalePosition;
2723     }
2724 
2725     /**
2726      * @param timeScalePosition The timeScalePosition to set.
2727      */
2728     public void setTimeScalePosition(int timeScalePosition) {
2729         _timeScalePosition = timeScalePosition;
2730         if (_tbvi != null) {
2731             _tbvi.repaint();
2732         }
2733     }
2734 
2735     /**
2736      * @return Returns the yAxisWidth.
2737      */
2738     public int getYAxisWidth() {
2739         return _yAxisWidth;
2740     }
2741 
2742     /**
2743      * @param axisWidth The yAxisWidth to set.
2744      */
2745     public void setYAxisWidth(int axisWidth) {
2746         int oldval = _yAxisWidth;
2747         _yAxisWidth = axisWidth;
2748         if (oldval != _yAxisWidth) {
2749             if (_tbvi != null) {
2750                 _tbvi.repaint();
2751                 _tbvi.firePropertyChange(TimeBarViewerInterface.PROPERTYNAME_YAXISWIDTH, oldval, _yAxisWidth);
2752             }
2753         }
2754     }
2755 
2756     /**
2757      * @return Returns the xAxisHeight.
2758      */
2759     public int getXAxisHeight() {
2760         return _xAxisHeight;
2761     }
2762 
2763     /**
2764      * Set the height of the y axis.
2765      * 
2766      * @param axisHeight The xAxisHeight to set.
2767      */
2768     public void setXAxisHeight(int axisHeight) {
2769         if (axisHeight != _xAxisHeight) {
2770             int oldVal = _xAxisHeight;
2771             _xAxisHeight = axisHeight;
2772             if (_tbvi != null) {
2773                 _tbvi.repaint();
2774                 _tbvi.firePropertyChange(TimeBarViewerInterface.PROPERTYNAME_XAXISHEIGHT, oldVal, axisHeight);
2775             }
2776         }
2777     }
2778 
2779     /**
2780      * Set the width fo rrendering the hierarchy area.
2781      * 
2782      * @param width width to use
2783      */
2784     public void setHierarchyWidth(int width) {
2785         _hierarchyWidth = width;
2786         if (_tbvi != null) {
2787             _tbvi.repaint();
2788         }
2789     }
2790 
2791     /**
2792      * Retrieve the width used to draw the hierarchy area.
2793      * 
2794      * @return width in pixel
2795      */
2796     public int getHierarchyWidth() {
2797         return _hierarchyWidth;
2798     }
2799 
2800     /**
2801      * Calculate the boundaries of the diagram elements.
2802      * 
2803      * @param cwidth width of the component client area
2804      * @param cheight width of the component client area
2805      */
2806     public void preparePaint(int cwidth, int cheight) {
2807         if (_orientation == Orientation.HORIZONTAL) {
2808             preparePaintHorizontal(cwidth, cheight);
2809         } else {
2810             preparePaintVertical(cwidth, cheight);
2811         }
2812 
2813         // handle a possible set initial display range
2814         if (_initialStartDate != null) {
2815             JaretDate startDate = _initialStartDate;
2816             _initialStartDate = null;
2817             setSecondsDisplayed(_initialSecondsDisplayed, false);
2818             setStartDate(startDate);
2819         }
2820 
2821     }
2822 
2823     /**
2824      * Calculate the boundaries of the diagram elements for the horizontal orientation.
2825      * 
2826      * @param cwidth width of the component client area
2827      * @param cheight width of the component client area
2828      */
2829     public void preparePaintHorizontal(int cwidth, int cheight) {
2830 
2831         int topy = (_timeScalePosition == TimeBarViewerInterface.TIMESCALE_POSITION_BOTTOM || _timeScalePosition == TimeBarViewerInterface.TIMESCALE_POSITION_NONE) ? 0
2832                 : _xAxisHeight;
2833         int height = _timeScalePosition == TimeBarViewerInterface.TIMESCALE_POSITION_NONE ? cheight : cheight
2834                 - _xAxisHeight;
2835         // diagram area itself
2836         _diagramRect = new Rectangle(_hierarchyWidth + _yAxisWidth, topy, cwidth - _yAxisWidth - _hierarchyWidth,
2837                 height);
2838         // y axis
2839         _yAxisRect = new Rectangle(_hierarchyWidth, topy, _yAxisWidth, height);
2840         // hierarchy indicators
2841         _hierarchyRect = new Rectangle(0, topy, _hierarchyWidth, height);
2842 
2843         if (_timeScalePosition != TimeBarViewerInterface.TIMESCALE_POSITION_NONE) {
2844             _xAxisRect = new Rectangle(_diagramRect.x,
2845                     _timeScalePosition == TimeBarViewerInterface.TIMESCALE_POSITION_TOP ? 0 : cheight - _xAxisHeight,
2846                     _diagramRect.width, _xAxisHeight);
2847         } else {
2848             _xAxisRect = new Rectangle(0, 0, 0, 0);
2849         }
2850         // calculate the end date of the drawing area
2851         _endDate = dateForX(cwidth);
2852 
2853         // title rect
2854         _titleRect = new Rectangle(0, _xAxisRect.y, _hierarchyRect.width + _yAxisRect.width, _xAxisRect.height);
2855 
2856         // correct all calculated rectangles by an offset if set
2857         if (_offsetLeft != 0 || _offsetTop != 0) {
2858             _diagramRect.x += _offsetLeft;
2859             _diagramRect.y += _offsetTop;
2860             _yAxisRect.x += _offsetLeft;
2861             _yAxisRect.y += _offsetTop;
2862             _xAxisRect.x += _offsetLeft;
2863             _xAxisRect.y += _offsetTop;
2864             _hierarchyRect.x += _offsetLeft;
2865             _hierarchyRect.y += _offsetTop;
2866             _titleRect.x += _offsetLeft;
2867             _titleRect.y += _offsetTop;
2868         }
2869 
2870         if (_autoScaleRows > 0) {
2871             int rowHeight = _diagramRect.height / _autoScaleRows;
2872             setRowHeight(rowHeight);
2873         }
2874 
2875     }
2876 
2877     /**
2878      * Calculate the boundaries of the diagram elements for the vertical orientation.
2879      * 
2880      * @param cwidth width of the component client area
2881      * @param cheight width of the component client area
2882      */
2883     public void preparePaintVertical(int cwidth, int cheight) {
2884 
2885         int leftX = (_timeScalePosition == TimeBarViewerInterface.TIMESCALE_POSITION_BOTTOM || _timeScalePosition == TimeBarViewerInterface.TIMESCALE_POSITION_NONE) ? 0
2886                 : _xAxisHeight;
2887         int width = _timeScalePosition == TimeBarViewerInterface.TIMESCALE_POSITION_NONE ? cwidth : cwidth
2888                 - _xAxisHeight;
2889 
2890         // diagram area itself
2891         _diagramRect = new Rectangle(leftX, _hierarchyWidth + _yAxisWidth, width, cheight - _yAxisWidth
2892                 - _hierarchyWidth);
2893 
2894         // y axis (x axis in the vertical case)
2895         _yAxisRect = new Rectangle(leftX, _hierarchyWidth, width, _yAxisWidth);
2896         // hierarchy indicators
2897         _hierarchyRect = new Rectangle(leftX, 0, width, _hierarchyWidth);
2898 
2899         // rect for the timescale (yaxis for verticalorientation)
2900         if (_timeScalePosition != TimeBarViewerInterface.TIMESCALE_POSITION_NONE) {
2901             int tsX = _timeScalePosition == TimeBarViewerInterface.TIMESCALE_POSITION_TOP ? 0 : cwidth - _xAxisHeight;
2902             _xAxisRect = new Rectangle(tsX, _diagramRect.y, _xAxisHeight, _diagramRect.height);
2903         } else {
2904             _xAxisRect = new Rectangle(0, 0, 0, 0);
2905         }
2906         // calculate the end date of the drawing area
2907         _endDate = dateForX(cheight);
2908 
2909         // title rect
2910         _titleRect = new Rectangle(_xAxisRect.x, 0, _xAxisRect.width, _hierarchyRect.height + _yAxisRect.height);
2911 
2912         // correct all calculated rectangles by an offset if set
2913         if (_offsetLeft != 0 || _offsetTop != 0) {
2914             _diagramRect.x += _offsetLeft;
2915             _diagramRect.y += _offsetTop;
2916             _yAxisRect.x += _offsetLeft;
2917             _yAxisRect.y += _offsetTop;
2918             _xAxisRect.x += _offsetLeft;
2919             _xAxisRect.y += _offsetTop;
2920             _hierarchyRect.x += _offsetLeft;
2921             _hierarchyRect.y += _offsetTop;
2922             _titleRect.x += _offsetLeft;
2923             _titleRect.y += _offsetTop;
2924         }
2925         if (_autoScaleRows > 0) {
2926             int rowWidth = _diagramRect.width / _autoScaleRows;
2927             setRowHeight(rowWidth);
2928         }
2929 
2930     }
2931 
2932     /**
2933      * @return Returns the diagramRect.
2934      */
2935     public Rectangle getDiagramRect() {
2936         return _diagramRect;
2937     }
2938 
2939     /**
2940      * @return Returns the xAxisRect.
2941      */
2942     public Rectangle getXAxisRect() {
2943         return _xAxisRect;
2944     }
2945 
2946     /**
2947      * @return Returns the yAxisRect.
2948      */
2949     public Rectangle getYAxisRect() {
2950         return _yAxisRect;
2951     }
2952 
2953     /**
2954      * @return the reactangle to draw the hierchy markers in
2955      */
2956     public Rectangle getHierarchyRect() {
2957         return _hierarchyRect;
2958     }
2959 
2960     /**
2961      * Retrieve area for drawing a title.
2962      * 
2963      * @return title area rectangle
2964      */
2965     public Rectangle getTitleRect() {
2966         return _titleRect;
2967     }
2968 
2969     /**
2970      * @return Returns the lastStartDate.
2971      */
2972     public JaretDate getLastStartDate() {
2973         return _lastStartDate;
2974     }
2975 
2976     /**
2977      * @param lastStartDate The lastStartDate to set.
2978      */
2979     public void setLastStartDate(JaretDate lastStartDate) {
2980         _lastStartDate = lastStartDate;
2981     }
2982 
2983     /**
2984      * Retrievs the intervals at the given x coordinate and the given row. The result ist sorted by the length of the
2985      * intervals, shortest first.
2986      * 
2987      * @param row row to investigate
2988      * @param x x coodinate
2989      * @return list of intervals at the given position
2990      */
2991     private List<Interval> getIntervalsSortedAt(TimeBarRow row, int x) {
2992         List<Interval> intervals = getIntervalsAt(row, x);
2993         if (intervals.size() == 0) {
2994             return intervals;
2995         }
2996         // intervals will be sorted by length (shortest first)
2997         // so the shorter intervals will go first for tooltips
2998         Collections.sort(intervals, new Comparator<Interval>() {
2999             public int compare(Interval i1, Interval i2) {
3000                 return i1.getSeconds() - i2.getSeconds();
3001             }
3002 
3003         });
3004         return intervals;
3005     }
3006 
3007     /**
3008      * Retrieves the shortest Interval for a given row and x,y coordinates (including contains check on the component).
3009      * 
3010      * @param row the row to search in
3011      * @param x x coordinate
3012      * @param y y coordinate
3013      * @return the Interval or null if none was found
3014      */
3015     private Interval intervalAt(TimeBarRow row, int x, int y) {
3016         Interval result = null;
3017         // retrieve all intervals in the row for the x coordinate
3018         List<Interval> intervals;
3019         if (_orientation == Orientation.HORIZONTAL) {
3020             intervals = getIntervalsSortedAt(row, x);
3021         } else {
3022             intervals = getIntervalsSortedAt(row, y);
3023         }
3024         if (intervals.size() == 0) {
3025             return null;
3026         }
3027         for (Interval interval : intervals) {
3028             Rectangle intervalRect = getIntervalBounds(row, interval);
3029             boolean overlapping = getTimeBarViewState().getDrawOverlapping(row) ? false : _overlapStrategy
3030                     .getOverlapInfo(row, interval).overlappingCount > 0
3031                     || _useUniformHeight;
3032             if (_tbvi.timeBarContains(interval, intervalRect, x - intervalRect.x, y - intervalRect.y, overlapping)) {
3033                 result = interval;
3034                 // the first interval will get through
3035                 break;
3036             }
3037         }
3038         return result;
3039     }
3040 
3041     // *** Handling of mouse interactions
3042 
3043     /**
3044      * Handle press of a mouse button.
3045      * 
3046      * @param x x coordinate (control)
3047      * @param y y coordinate (control)
3048      * @param isPopupTrigger true if the button is the system's popup trigger
3049      * @param modifierMask modifier mask for keyboard diversification (AWT input event type)
3050      */
3051     public void mousePressed(int x, int y, boolean isPopupTrigger, int modifierMask) {
3052         _lastPressedX = x;
3053         _lastPressedY = y;
3054     	boolean nothingHitInDiagramArea = true;
3055         if (_diagramRect.contains(x, y)) { // selection
3056             // normal selection only if shift is not pressed
3057             if ((modifierMask & InputEvent.SHIFT_DOWN_MASK) == 0) {
3058                 // interval region
3059                 TimeBarRow row = rowForXY(x, y);
3060                 // System.out.println(x + "," + y + " row " + _rowList.indexOf(row));
3061                 Interval interval = intervalAt(row, x, y);
3062                 // System.out.println("Interval " + interval);
3063                 // if the interval is filtered, it can't be selected
3064                 if (interval != null && (_intervalFilter == null || _intervalFilter.isInResult(interval))) {
3065                     // focus handling
3066                     setFocussedInterval(interval);
3067 
3068                     // differentiate between selection and popup
3069                     if ((modifierMask & InputEvent.CTRL_DOWN_MASK) != 0) {
3070                         if (!_selectionModel.isSelected(interval)) {
3071                             _selectionModel.addSelectedInterval(interval);
3072                         } else { // deselect
3073                             _selectionModel.remSelectedInterval(interval);
3074                         }
3075                     } else if (_selectionModel.isEmpty()) {
3076                         _selectionModel.setSelectedInterval(interval);
3077                     }
3078                 } else {
3079 
3080                     List<IIntervalRelation> relations = _tbvi.getRelationsForCoord(x, y);
3081                     if (relations != null && relations.size() > 0) {
3082                         // hit one or more relations
3083                         handleRelationSelection(relations, modifierMask);
3084                     } else {
3085                         // clicked but nothing hit
3086                         // if not done with control -> deselect all
3087                         if (!isPopupTrigger && (modifierMask & InputEvent.CTRL_DOWN_MASK) == 0) {
3088                             // when row selection toggle mode is used only clear interval selection
3089                             if (!_selectionModel.getRowSelectionToggleMode()) {
3090                                 _selectionModel.clearSelection();
3091                             } else {
3092                                 _selectionModel.clearIntervalSelection();
3093                                 _selectionModel.clearRelationSelection();
3094                             }
3095                         }
3096                         // clicked but nothing hit
3097                         // maybe the user starts doing a rectangle selection
3098                         // this may only be done with select multi and
3099                         // rectselectenable. Additionally this won't be possible with
3100                         // the popuptrigger
3101                         if (!isPopupTrigger && _selectionModel.getMultipleSelectionAllowed() && _rectSelectionEnabled
3102                                 && (row == null || getTouchedInterval(row, x, y) == null)) {
3103                             _selectionRect = new Rectangle(x, y, 0, 0);
3104                             fireSelectionRectChanged();
3105                         }
3106                         nothingHitInDiagramArea = true;
3107                     }
3108                 }
3109             } else {
3110                 if (_regionRectEnabled) {
3111                     _regionSelection = new TBRect();
3112                     JaretDate startDate = dateForCoord(x, y);
3113                     _regionSelection.startDate = startDate;
3114                     _regionSelection.endDate = startDate.copy();
3115                     TimeBarRow row = rowForXY(x, y);
3116                     _regionSelection.startRow = row;
3117                     _regionSelection.endRow = row;
3118 
3119                     _regionStartDate = startDate.copy();
3120                     _regionStartRow = row;
3121 
3122                     fireRegionRectChanged();
3123                 }
3124             }
3125         } else if (_lineDraggingAllowed && hierarchyLineHit(x, y)) {
3126             // start drag of hierarchy limiter line
3127             _hierarchyLineDragging = true;
3128         } else if (_lineDraggingAllowed && headerLineHit(x, y)) {
3129             // start drag of header delimiter line
3130             _headerLineDragging = true;
3131         } else if (_rowHeightDraggingAllowed && rowLineHit(x, y)) {
3132             // start drag of row height
3133             _heightDraggedRow = getRowByBottomLine(x, y);
3134         } else if (_yAxisRect.contains(x, y)) {
3135             // area header
3136             TimeBarRow row = rowForXY(x, y);
3137             handleRowSelection(row, modifierMask);
3138         } else if (_hierarchyRect.contains(x, y) && !isPopupTrigger) {
3139             // in the hierarchy area: toggle or select row
3140             TimeBarRow row = rowForXY(x, y);
3141             if (row instanceof TimeBarNode) {
3142                 TimeBarNode node = (TimeBarNode) row;
3143                 if (_tbvi.isInToggleArea(node, x, y)) {
3144                     _hierarchicalViewState.setExpanded(node, !_hierarchicalViewState.isExpanded(node));
3145                 } else if (_tbvi.isInHierarchySelectionArea(node, x, y)) {
3146                     handleRowSelection(row, modifierMask);
3147                 }
3148             }
3149         } else if (_xAxisRect.contains(x, y)) {
3150             // mouse press in x axis area
3151             TimeBarMarker marker = getMarkerForXY(x, y);
3152             if (marker != null && marker.isDraggable()) { // start drag
3153                 _tbvi.setCursor(Cursor.MOVE_CURSOR);
3154                 _draggedMarker = marker;
3155                 _markerDragStart = marker.getDate().copy();
3156                 fireMarkerDragStarted(marker); // inform listeners
3157             }
3158         }
3159         if (_markerDraggingInDiagramArea && _draggedMarker == null && nothingHitInDiagramArea) {
3160             TimeBarMarker marker = getMarkerForXY(x, y);
3161             if (marker != null && marker.isDraggable()) { // start drag
3162                 _tbvi.setCursor(Cursor.MOVE_CURSOR);
3163                 _draggedMarker = marker;
3164                 _markerDragStart = marker.getDate().copy();
3165                 fireMarkerDragStarted(marker); // inform listeners
3166             }
3167         }
3168     }
3169 
3170     /** last selected row. Used for shift-click range selections. */
3171     protected int _lastRowSelectionIndex = -1;
3172 
3173     /**
3174      * Handle row selection based on the modifier.
3175      * 
3176      * @param row selected row
3177      * @param modifierMask Swing modifier mask
3178      */
3179     private void handleRowSelection(TimeBarRow row, int modifierMask) {
3180         int rowIdx = _rowList.indexOf(row);
3181         if ((modifierMask & InputEvent.SHIFT_DOWN_MASK) != 0) {
3182             if (_lastRowSelectionIndex == -1) {
3183                 _selectionModel.addSelectedRow(row);
3184                 _lastRowSelectionIndex = rowIdx;
3185             } else {
3186                 _selectionModel.setSelectedRow(row);
3187                 setRowSelection(_lastRowSelectionIndex, rowIdx);
3188             }
3189         } else {
3190             if ((modifierMask & InputEvent.CTRL_DOWN_MASK) != 0 || _selectionModel.getRowSelectionToggleMode()) {
3191                 if (!_selectionModel.isSelected(row)) {
3192                     _selectionModel.addSelectedRow(row);
3193                     _lastRowSelectionIndex = rowIdx;
3194                 } else { // deselect
3195                     _selectionModel.remSelectedRow(row);
3196                 }
3197             } else if (!_selectionModel.getRowSelectionToggleMode()) {
3198                 _selectionModel.setSelectedRow(row);
3199                 _lastRowSelectionIndex = rowIdx;
3200             }
3201         }
3202     }
3203 
3204     /**
3205      * Handle a relation selection. Always takes the first relation in the list of cliecked relations.
3206      * 
3207      * @param relations cliecked relations
3208      * @param modifierMask modifier mask of the selcting event
3209      */
3210     private void handleRelationSelection(List<IIntervalRelation> relations, int modifierMask) {
3211         IIntervalRelation relation = relations.get(0);
3212         if ((modifierMask & InputEvent.CTRL_DOWN_MASK) != 0) {
3213             if (!_selectionModel.isSelected(relation)) {
3214                 _selectionModel.addSelectedRelation(relation);
3215             } else { // deselect
3216                 _selectionModel.remSelectedRelation(relation);
3217             }
3218         } else {
3219             _selectionModel.setSelectedRelation(relation);
3220         }
3221     }
3222 
3223     /**
3224      * Set all rows in the range as selected.
3225      * 
3226      * @param startIndex start index in the row list
3227      * @param endIndex end index in the row list
3228      */
3229     private void setRowSelection(int startIndex, int endIndex) {
3230         int start = Math.min(startIndex, endIndex);
3231         int end = Math.max(startIndex, endIndex);
3232         for (int i = start; i <= end; i++) {
3233             _selectionModel.addSelectedRow(_rowList.get(i));
3234         }
3235     }
3236 
3237     /**
3238      * Handle release of mouse button.
3239      * 
3240      * @param x x coordinate
3241      * @param y y coordinate
3242      * @param isPopupTrigger true if the button is the systems popup trigger
3243      * @param modifierMask modifier mask for keyboard diversification (AWT input event type)
3244      */
3245     public void mouseReleased(int x, int y, boolean isPopupTrigger, int modifierMask) {
3246         if (_headerLineDragging || _hierarchyLineDragging || _heightDraggedRow != null) {
3247             _headerLineDragging = false;
3248             _hierarchyLineDragging = false;
3249             _heightDraggedRow = null;
3250             _tbvi.setCursor(Cursor.DEFAULT_CURSOR);
3251             _tbvi.repaint();
3252         }
3253         if (_draggedMarker != null) { // stop drag
3254             TimeBarMarker marker = _draggedMarker;
3255             _draggedMarker = null;
3256             _tbvi.setCursor(Cursor.DEFAULT_CURSOR);
3257             // MAYBE possible optimization: can be more specific
3258             _tbvi.repaint();
3259             fireMarkerDragStopped(marker); // inform listeners
3260         }
3261         if (_selectionRect != null) {
3262             // stop selection rectangle
3263             // all selections are updated!
3264             _selectionRect = null;
3265             if (_lastSelRect != null) {
3266                 _tbvi.repaint(_lastSelRect.x, _lastSelRect.y, _lastSelRect.width + 1, _lastSelRect.height + 1);
3267             }
3268             fireSelectionRectClosed();
3269         } else {
3270             if (!isPopupTrigger && (modifierMask & InputEvent.CTRL_DOWN_MASK) == 0 && _diagramRect.contains(x, y)) {
3271                 TimeBarRow row = rowForXY(x, y);
3272                 if (row != null) {
3273                     Interval interval = intervalAt(row, x, y);
3274                     if (interval != null) {
3275                         _selectionModel.setSelectedInterval(interval);
3276                     }
3277                 }
3278             }
3279         }
3280         if (_changingInterval != null) {
3281             // inform listeners about the completion of the drag/change
3282             fireIntervalChanged(_intervalDraggedRow, _changingInterval, _originalInterval.getBegin(), _originalInterval
3283                     .getEnd());
3284             _changingInterval = null;
3285             _tbvi.setCursor(Cursor.DEFAULT_CURSOR);
3286 
3287             if (_dragAllSelectedIntervals && _draggedIntervals != null) {
3288                 for (int i = 0; i < _draggedIntervals.size(); i++) {
3289                     fireIntervalChanged(_draggedIntervalsRows.get(i), _draggedIntervals.get(i), _originalIntervals.get(
3290                             i).getBegin(), _originalIntervals.get(i).getEnd());
3291                 }
3292                 _originalIntervals = null;
3293                 _draggedIntervals = null;
3294                 _draggedIntervalsRows = null;
3295             }
3296 
3297         }
3298 
3299         // popup menues
3300         if (isPopupTrigger) {
3301             TimeBarRow row = rowForXY(x, y);
3302             _ctxCoordinate = new Point(x, y);
3303             _ctxRow = row;
3304             _ctxDate = dateForCoord(x, y);
3305             if (_hierarchyRect.contains(x, y)) {
3306                 _tbvi.displayHierarchyContextMenu(row, x, y);
3307             } else if (_yAxisRect.contains(x, y)) {
3308                 _tbvi.displayHeaderContextMenu(row, x, y);
3309             } else if (_titleRect.contains(x, y)) {
3310                 _tbvi.displayTitleContextMenu(x, y);
3311             } else if (_xAxisRect.contains(x, y)) {
3312                 _tbvi.displayTimeScaleContextMenu(x, y);
3313             } else if (_diagramRect.contains(x, y)) {
3314                 Interval interval = intervalAt(row, x, y);
3315                 // if the interval is filtered, it can't be selected
3316                 if (interval != null && (_intervalFilter == null || _intervalFilter.isInResult(interval))) {
3317                 	// if not selected use the one interval as selection
3318                 	if (!_selectionModel.isSelected(interval)) {
3319                         _selectionModel.setSelectedInterval(interval);
3320                     }_tbvi.displayIntervalContextMenu(interval, x, y);
3321                 } else {
3322                     _tbvi.displayBodyContextMenu(x, y);
3323                 }
3324             }
3325         }
3326 
3327     }
3328 
3329     /**
3330      * Cancel ongoing internal drag.
3331      */
3332     private void cancelDrag() {
3333         if (_draggedMarker != null) {
3334             // dragging a marker
3335             _draggedMarker.setDate(_markerDragStart);
3336             _draggedMarker = null;
3337             _tbvi.setCursor(Cursor.DEFAULT_CURSOR);
3338             _tbvi.repaint();
3339         } else if (_changingInterval != null && _draggedInterval) {
3340             // dragging an interval
3341             _changingInterval.setBegin(_originalInterval.getBegin());
3342             _changingInterval.setEnd(_originalInterval.getEnd());
3343 
3344             // inform listeners about the cancellation
3345             fireIntervalChangeCancelled(_intervalDraggedRow, _changingInterval);
3346 
3347             _changingInterval = null;
3348             _tbvi.setCursor(Cursor.DEFAULT_CURSOR);
3349 
3350             if (_dragAllSelectedIntervals && _draggedIntervals != null) {
3351                 for (int i = 0; i < _draggedIntervals.size(); i++) {
3352                     Interval orig = _originalIntervals.get(i);
3353                     Interval changing = _draggedIntervals.get(i);
3354                     TimeBarRow row = _draggedIntervalsRows.get(i);
3355                     changing.setBegin(orig.getBegin());
3356                     changing.setEnd(orig.getEnd());
3357                     fireIntervalChangeCancelled(row, changing);
3358                 }
3359             }
3360 
3361         } else if (_changingInterval != null && !_draggedInterval) {
3362             // dragging an interval edge
3363             _changingInterval.setBegin(_originalInterval.getBegin());
3364             _changingInterval.setEnd(_originalInterval.getEnd());
3365 
3366             // inform listeners about the cancellation
3367             fireIntervalChangeCancelled(_intervalDraggedRow, _changingInterval);
3368 
3369             _changingInterval = null;
3370             _tbvi.setCursor(Cursor.DEFAULT_CURSOR);
3371         }
3372 
3373     }
3374 
3375     /** coordinate a context menu has been requested to pop up. */
3376     protected Point _ctxCoordinate;
3377     /** row above that a context menu has been requested to pup up. */
3378     protected TimeBarRow _ctxRow;
3379     /** date for the ctx coordinate. */
3380     protected JaretDate _ctxDate;
3381 
3382     /**
3383      * Retrieve the location of a context menu popup request.
3384      * 
3385      * @return the ccordinate
3386      */
3387     public Point getCtxCoordinate() {
3388         return _ctxCoordinate;
3389     }
3390 
3391     /**
3392      * Retrieve the row above that a popup menu request has occurred.
3393      * 
3394      * @return the row or <code>null</code> if none could be determined
3395      */
3396     public TimeBarRow getCtxRow() {
3397         return _ctxRow;
3398     }
3399 
3400     /**
3401      * Retrieve the row and date of the click leading to the activation of a context menu.
3402      * 
3403      * @return Pair containing the row and date of the click position. Might be <code>null</code> if no click has been
3404      * recorded.
3405      */
3406     public Pair<TimeBarRow, JaretDate> getPopUpInformation() {
3407         Pair<TimeBarRow, JaretDate> result = new Pair<TimeBarRow, JaretDate>(_ctxRow, _ctxDate);
3408         return result;
3409     }
3410 
3411     // *** End of MouseListener
3412 
3413     // *** MouseMotionListener
3414     // TODO the state of the ongoing drags and changes could be wrapped more nicely in some state objects
3415     // the differentiation between dragging one or more than one is not really nicely coded
3416 
3417     /** copy of a dragged or resized interval containing the original bounds. */
3418     protected Interval _originalInterval;
3419 
3420     /** list of copies for all intervals selected when a drag started. */
3421     protected List<Interval> _originalIntervals;
3422 
3423     /** list of draged intervals excluding the one on that the drag started. */
3424     protected List<Interval> _draggedIntervals;
3425     /** cache for the row information for the dragged intervals. */
3426     protected List<TimeBarRow> _draggedIntervalsRows;
3427 
3428     /** x ccordinate of the start of an internal drag/resize. */
3429     protected int _startIntervalDragX;
3430 
3431     /** date of drag/resize start. */
3432     protected JaretDate _startIntervalDragDate = null;
3433 
3434     /** last date that has been used in a drag/resize. */
3435     protected JaretDate _lastDragDate;
3436 
3437     /** start date of a marker drag. */
3438     protected JaretDate _markerDragStart;
3439 
3440     /** the row of the dragged/resized interval. */
3441     protected TimeBarRow _intervalDraggedRow;
3442 
3443     /** number of pixels that the viewer has been scrolled during the las drag cycle. */
3444     protected int _scrolledlastDrag;
3445 
3446     /** last difference to a gridsnap position (used when dragging an interval). */
3447     protected double _lastGridSnapDifference = 0;
3448     /** list of lat grid snap differences, used when more than one interval is beeing dragged. */
3449     protected List<Double> _lastGridSnapDifferences;
3450 
3451     protected int _lastPressedX;
3452     protected int _lastPressedY;
3453     /**
3454      * Handle mouse dragging.
3455      * 
3456      * @param x new x coordinate
3457      * @param y new y coordinate
3458      * @param modifierMask modifiers (awt)
3459      */
3460     public void mouseDragged(int x, int y, int modifierMask) {
3461         // limit when out of the diagram rect
3462         Point coord = limitCoord(x, y);
3463         JaretDate xdate = dateForXY(coord.x, coord.y);
3464 
3465         boolean horizontal = _orientation == Orientation.HORIZONTAL;
3466         if (_draggedMarker != null) {
3467             // dragging a marker
3468             _draggedMarker.setDate(xdate);
3469             if (_autoscroll) {
3470                 scrollDateToVisible(xdate);
3471             }
3472         } else if (_changingInterval != null && _draggedInterval) {
3473             dragInterval(_originalInterval, _draggedInterval, _lastDragDate, xdate, x, y, _dragAllSelectedIntervals);
3474         } else if (_changingInterval != null && !_draggedInterval) {
3475             // dragging an interval edge
3476             double diffSeconds = -1.0 * _startIntervalDragDate.diffMilliSeconds(xdate) / 1000.0;
3477             // gridding
3478             diffSeconds = calcGridSnap(diffSeconds, 0, _intervalDraggedRow, _changingInterval, -1);
3479             if (_draggedIntervalEdgeLeft) {
3480                 JaretDate newBegin = _originalInterval.getBegin().copy();
3481                 newBegin.advanceSeconds(diffSeconds);
3482                 // check allowance
3483                 boolean allowed = isNewBeginAllowed(_intervalDraggedRow, _changingInterval, newBegin);
3484                 if (allowed) { // modify if allowed
3485                     _changingInterval.setBegin(newBegin);
3486                     if (_autoscroll) {
3487                         scrollDateToVisible(newBegin);
3488                     }
3489                 }
3490             } else {
3491                 JaretDate newEnd = _originalInterval.getEnd().copy();
3492                 newEnd.advanceSeconds(diffSeconds);
3493                 // check allowance
3494                 boolean allowed = isNewEndAllowed(_intervalDraggedRow, _changingInterval, newEnd);
3495                 if (allowed) { // modify if allowed
3496                     _changingInterval.setEnd(newEnd);
3497                     if (_autoscroll) {
3498                         scrollDateToVisible(newEnd);
3499                     }
3500                 }
3501             }
3502             // inform listeners about the intermediate change
3503             fireIntervalIntermediateChange(_intervalDraggedRow, _changingInterval, _originalInterval.getBegin(),
3504                     _originalInterval.getEnd());
3505         } else if (_selectionRect != null) {
3506             // multiple selection with rectangle selection ongoing
3507             _selectionRect.width = x - _selectionRect.x;
3508             _selectionRect.height = y - _selectionRect.y;
3509             if (_lastSelRect != null) {
3510                 rectRepaint(_lastSelRect);
3511             }
3512             Rectangle curSelRect = normalizeRectangle(_selectionRect);
3513             selectIntervals(curSelRect);
3514             rectRepaint(curSelRect);
3515             fireSelectionRectChanged();
3516         } else if (_regionSelection != null && (modifierMask & InputEvent.SHIFT_DOWN_MASK) != 0) { // check shift
3517             // pressed!
3518             if (_lastRegionSelection != null) {
3519                 _tbvi.repaint(calcRect(_lastRegionSelection));
3520             }
3521             JaretDate curDate = dateForCoord(x, y);
3522             TimeBarRow row = rowForXY(x, y);
3523 
3524             // update time
3525             if (curDate.compareTo(_regionStartDate) > 0) {
3526                 _regionSelection.startDate = _regionStartDate;
3527                 _regionSelection.endDate = curDate;
3528             } else {
3529                 _regionSelection.endDate = _regionStartDate;
3530                 _regionSelection.startDate = curDate;
3531             }
3532 
3533             // update rows
3534             if (row != null) {
3535                 int startRowIdx = _rowList.indexOf(_regionStartRow);
3536                 int curIdx = _rowList.indexOf(row);
3537                 if (startRowIdx <= curIdx) {
3538                     _regionSelection.startRow = _regionStartRow;
3539                     _regionSelection.endRow = row;
3540                 } else {
3541                     _regionSelection.endRow = _regionStartRow;
3542                     _regionSelection.startRow = row;
3543                 }
3544             }
3545             _tbvi.repaint(calcRect(_regionSelection));
3546 
3547             _lastRegionSelection = _regionSelection;
3548 
3549             fireRegionRectChanged();
3550         } else if (_hierarchyLineDragging) {
3551             int value = horizontal ? x : y;
3552             if (value > MIN_DRAG_HIERARCHY_WIDTH) {
3553                 setHierarchyWidth(value);
3554             }
3555         } else if (_headerLineDragging) {
3556             int value = (horizontal ? x : y) - _hierarchyWidth;
3557             if (value > MIN_DRAG_HEADER_WIDTH) {
3558                 setYAxisWidth(value);
3559             }
3560         } else if (_heightDraggedRow != null) {
3561             int beginCoord = yForRow(_heightDraggedRow);
3562             int value = (horizontal ? y : x) - beginCoord;
3563             if (value > MIN_ROW_HEIGHT) {
3564                 _timeBarViewState.setRowHeight(_heightDraggedRow, value);
3565             }
3566         } else {
3567             // no dragging in the moment ... maybe start one
3568             // first check for edge drag
3569         	// use the coordinates from the last press (the curret ones do not reflect the user feedback)
3570             TimeBarRow row = rowForXY(x, y);
3571             if (row != null) {
3572                 Interval interval = getTouchedInterval(row, _lastPressedX, _lastPressedY);
3573                 if (interval != null && isResizingAllowed(row, interval)) {
3574                     Rectangle rect = getIntervalBounds(row, interval);
3575                     int diff = horizontal ? Math.abs(_lastPressedX - rect.x) : Math.abs(_lastPressedY - rect.y);
3576                     if (diff <= _selectionDelta) {
3577                         // left
3578                         _draggedIntervalEdgeLeft = true;
3579                         _tbvi.setCursor(horizontal ? Cursor.E_RESIZE_CURSOR : Cursor.N_RESIZE_CURSOR);
3580                     } else {
3581                         // right
3582                         _draggedIntervalEdgeLeft = false;
3583                         _tbvi.setCursor(horizontal ? Cursor.W_RESIZE_CURSOR : Cursor.S_RESIZE_CURSOR);
3584                     }
3585                     _changingInterval = interval;
3586                     _draggedInterval = false;
3587                     _originalInterval = new IntervalImpl();
3588                     _originalInterval.setBegin(interval.getBegin());
3589                     _originalInterval.setEnd(interval.getEnd());
3590                     _startIntervalDragDate = xdate;
3591                     _intervalDraggedRow = row;
3592                     // inform listeners about the starting edge darg
3593                     fireIntervalChangeStarted(_intervalDraggedRow, _changingInterval);
3594                 }
3595             }
3596             // if not edge drag check for whole drag
3597             if (_changingInterval == null) {
3598                 List<Interval> intervals = getIntervalsAt(x, y);
3599                 if (intervals.size() == 1) {
3600                     Interval interval = intervals.get(0);
3601                     boolean shiftAllowed = isShiftingAllowed(row, interval);
3602 
3603                     // check whether other intervals have to be shifted and copy them
3604                     // check allowance for the shift as well
3605                     // do not include the interval that is the main driver in the drag (that is beeing dragged)
3606                     if (_dragAllSelectedIntervals && shiftAllowed) {
3607                         int size = getSelectionModel().getSelectedIntervals().size();
3608                         _originalIntervals = new ArrayList<Interval>(size);
3609                         _draggedIntervals = new ArrayList<Interval>(size);
3610                         _draggedIntervalsRows = new ArrayList<TimeBarRow>(size);
3611                         _lastGridSnapDifferences = new ArrayList<Double>(size);
3612                         for (int i = 0; i < size; i++) {
3613                             _lastGridSnapDifferences.add(0.0);
3614                         }
3615 
3616                         for (Interval selInterval : getSelectionModel().getSelectedIntervals()) {
3617                             TimeBarRow selIntervalRow = getModel().getRowForInterval(selInterval);
3618                             if (!isShiftingAllowed(selIntervalRow, interval)) {
3619                                 shiftAllowed = false;
3620                                 break;
3621                             }
3622                             if (!selInterval.equals(interval)) {
3623                                 Interval copy = new IntervalImpl();
3624                                 copy.setBegin(selInterval.getBegin().copy());
3625                                 copy.setEnd(selInterval.getEnd().copy());
3626                                 _originalIntervals.add(copy);
3627                                 _draggedIntervals.add(selInterval);
3628                                 _draggedIntervalsRows.add(selIntervalRow);
3629                             }
3630 
3631                         }
3632                     }
3633 
3634                     if (shiftAllowed) {
3635                         _changingInterval = interval;
3636                         _draggedInterval = true;
3637                         _originalInterval = new IntervalImpl();
3638                         _originalInterval.setBegin(interval.getBegin());
3639                         _originalInterval.setEnd(interval.getEnd());
3640                         _startIntervalDragDate = xdate;
3641                         _intervalDraggedRow = row;
3642                         _tbvi.setCursor(Cursor.MOVE_CURSOR);
3643 
3644                         // inform listeners about the starting drag
3645                         fireIntervalChangeStarted(_intervalDraggedRow, _changingInterval);
3646                         if (_draggedIntervals != null && _draggedIntervals.size() > 0) {
3647                             for (int i = 0; i < _draggedIntervals.size(); i++) {
3648                                 fireIntervalChangeStarted(_draggedIntervalsRows.get(i), _draggedIntervals.get(i));
3649                             }
3650                         }
3651                     }
3652                 }
3653             }
3654         }
3655         _lastDragDate = xdate;
3656     }
3657 
3658     /**
3659      * Handle dragging of an interval. If the dragSelected flag is set, all selected intervals will be dragged. The
3660      * leading (i.e. autoscrolling) interval will that that caused the drag first time.
3661      * 
3662      * @param originalInterval original interval
3663      * @param draggedInterval interval that is been dragged
3664      * @param lastDragDate date of the last drag
3665      * @param currentDragDate current date for the cursor position
3666      * @param x x coord of the cursor
3667      * @param y y coord of the cursor
3668      * @param dragSelected if <code>true</code> all selected intervals will be dragged.
3669      */
3670     private void dragInterval(Interval originalInterval, boolean draggedInterval, JaretDate lastDragDate,
3671             JaretDate currentDragDate, int x, int y, boolean dragSelected) {
3672         boolean horizontal = _orientation == Orientation.HORIZONTAL;
3673 
3674         // normal case: drag the delta between the two positions
3675         double deltaSeconds = currentDragDate.diffMilliSeconds(lastDragDate) / 1000.0;
3676         // System.out.println("deltaseconds " + deltaSeconds);
3677         deltaSeconds += _scrolledlastDrag;
3678         // System.out.println("deltaseconds2 " + deltaSeconds);
3679 
3680         // if the cursor is outside the diagram rect, use the autoscroll delta to determine the new
3681         // position
3682         if (horizontal) {
3683             if (x > _diagramRect.x + _diagramRect.width) {
3684                 deltaSeconds = secsForPixelDiff(_autoscrollDelta);
3685             } else if (x < _diagramRect.x) {
3686                 deltaSeconds = -secsForPixelDiff(_autoscrollDelta);
3687             }
3688         } else {
3689             if (y > _diagramRect.y + _diagramRect.height) {
3690                 deltaSeconds = secsForPixelDiff(_autoscrollDelta);
3691             } else if (y < _diagramRect.y) {
3692                 deltaSeconds = -secsForPixelDiff(_autoscrollDelta);
3693             }
3694         }
3695 
3696         double deltaSecondsSnap = calcGridSnap(deltaSeconds, _lastGridSnapDifference, _intervalDraggedRow,
3697                 _changingInterval, -1);
3698 
3699         JaretDate newBegin = _changingInterval.getBegin().copy();
3700         newBegin.advanceSeconds(deltaSecondsSnap);
3701         JaretDate newEnd = _changingInterval.getEnd().copy();
3702         newEnd.advanceSeconds(deltaSecondsSnap);
3703 
3704         List<Double> deltas = new ArrayList<Double>();
3705         // check whether the shift is allowed
3706         boolean allowed = isShiftingAllowed(_intervalDraggedRow, _changingInterval, newBegin);
3707         if (_dragAllSelectedIntervals && allowed && _draggedIntervals != null) {
3708             for (int i = 0; i < _draggedIntervals.size(); i++) {
3709                 deltaSecondsSnap = calcGridSnap(deltaSeconds, _lastGridSnapDifferences.get(i), _draggedIntervalsRows
3710                         .get(i), _draggedIntervals.get(i), i);
3711                 deltas.add(deltaSecondsSnap);
3712                 JaretDate nb = _draggedIntervals.get(i).getBegin().copy().advanceSeconds(deltaSecondsSnap);
3713                 allowed = allowed & isShiftingAllowed(_draggedIntervalsRows.get(i), _draggedIntervals.get(i), nb);
3714             }
3715         }
3716 
3717         if (allowed) {
3718             // always do the setting of the interval bounds in the right order to ensure interval consistency with
3719             // end>begin
3720             if (deltaSecondsSnap > 0) {
3721                 _changingInterval.setEnd(newEnd);
3722                 _changingInterval.setBegin(newBegin);
3723             } else {
3724                 _changingInterval.setBegin(newBegin);
3725                 _changingInterval.setEnd(newEnd);
3726             }
3727             if (_autoscroll && _lastDragDate != null) {
3728                 if (deltaSeconds < 0) {
3729                     int scrolledSeconds = scrollDateToVisible(newBegin);
3730                     _scrolledlastDrag = scrolledSeconds;
3731                 } else {
3732                     int scrolledSeconds = scrollDateToVisible(newEnd);
3733                     _scrolledlastDrag = scrolledSeconds;
3734                 }
3735             }
3736 
3737             if (_dragAllSelectedIntervals) {
3738                 for (int i = 0; i < _draggedIntervals.size(); i++) {
3739                     Interval di = _draggedIntervals.get(i);
3740                     double deltaSnap = deltas.get(i);
3741                     JaretDate nb = di.getBegin().copy().advanceSeconds(deltaSnap);
3742                     JaretDate ne = di.getEnd().copy().advanceSeconds(deltaSnap);
3743                     di.setBegin(nb);
3744                     di.setEnd(ne);
3745                 }
3746             }
3747 
3748             // inform listeners about the intermediate change
3749             fireIntervalIntermediateChange(_intervalDraggedRow, _changingInterval, _originalInterval.getBegin(),
3750                     _originalInterval.getEnd());
3751             if (_dragAllSelectedIntervals) {
3752                 for (int i = 0; i < _draggedIntervals.size(); i++) {
3753                     fireIntervalIntermediateChange(_draggedIntervalsRows.get(i), _draggedIntervals.get(i),
3754                             _originalIntervals.get(i).getBegin(), _originalIntervals.get(i).getEnd());
3755                 }
3756             }
3757         }
3758 
3759     }
3760 
3761     /**
3762      * Calculate a time eqivalent for a pixel delta value. This does not take variable scaling into account.
3763      * 
3764      * @param pixDif delta pixel value
3765      * @return number of seconfs corresponding to the delta
3766      */
3767     private int secsForPixelDiff(int pixDif) {
3768         if (_milliAccuracy) {
3769             int diffSec = (int) ((double) pixDif / _pixelPerSeconds);
3770             return diffSec;
3771         } else {
3772             int diffSec = (int) ((double) pixDif / _pixelPerSeconds);
3773             return diffSec;
3774         }
3775 
3776     }
3777 
3778     /**
3779      * Limits the distance of the cursor position when aoutside the diagram rect. The limit is set by the
3780      * autoscrollDelta value.
3781      * 
3782      * @param x original coord x
3783      * @param y original coord y
3784      * @return Point containing the corrected position
3785      */
3786     private Point limitCoord(int x, int y) {
3787         int limit = _autoscrollDelta;
3788         if (x < _diagramRect.x - limit) {
3789             x = _diagramRect.x - limit;
3790         } else if (x > _diagramRect.x + _diagramRect.width + limit) {
3791             x = _diagramRect.x + _diagramRect.width + limit;
3792         }
3793         if (y < _diagramRect.y - limit) {
3794             y = _diagramRect.y - limit;
3795         } else if (y > _diagramRect.y + _diagramRect.height + limit) {
3796             x = _diagramRect.y + _diagramRect.height + limit;
3797         }
3798 
3799         return new Point(x, y);
3800     }
3801 
3802     /**
3803      * Calculate the difference in seconds to the next grid snap position. The grid snap position is defined by the
3804      * first(!) interval modificator that claims it is applicable (isApplicable). If no interval modificator is
3805      * applicable no grid snap will be applied. The last calculated difference to the actual grid snap is stored in a
3806      * variable. This is not really nice, but is used when dragging an interval.
3807      * 
3808      * @param diffSeconds the measured difference in seconds
3809      * @param additionalDelta an additional value that is taken into account when calculating the snapped value
3810      * @param row row of the interval
3811      * @param interval interval in question
3812      * @param index the index the index to store the difference in (-1 for default)
3813      * @return the difference to use after applying the grid snap
3814      */
3815     private double calcGridSnap(double diffSeconds, double additionalDelta, TimeBarRow row, Interval interval, int index) {
3816         if (_intervalModificators != null && _intervalModificators.size() > 0) {
3817             IntervalModificator modificator = null;
3818             for (IntervalModificator m : _intervalModificators) {
3819                 if (m.isApplicable(row, interval)) {
3820                     modificator = m;
3821                     break;
3822                 }
3823             }
3824             if (modificator != null) {
3825                 double newVal;
3826                 double gridsnap = -1;
3827                 // if the extended interface is available: try
3828                 if (modificator instanceof IIntervalModificator) {
3829                     gridsnap = ((IIntervalModificator) modificator).getSecondGridSnap(row, interval);
3830                 }
3831                 // if no gridsnap available try the generic interface
3832                 if (gridsnap < 0) {
3833                     gridsnap = modificator.getSecondGridSnap();
3834                 }
3835                 if (gridsnap < 0) {
3836                     return diffSeconds;
3837                 }
3838                 diffSeconds += additionalDelta;
3839                 if (Math.abs(diffSeconds % gridsnap) > gridsnap / 2) {
3840                     double off;
3841                     if (diffSeconds < 0) {
3842                         off = -gridsnap;
3843                     } else {
3844                         off = gridsnap;
3845                     }
3846                     newVal = diffSeconds - diffSeconds % gridsnap + off;
3847                 } else {
3848                     newVal = diffSeconds - diffSeconds % gridsnap;
3849                 }
3850                 if (index == -1) {
3851                     _lastGridSnapDifference = diffSeconds - newVal;
3852                 } else {
3853                     _lastGridSnapDifferences.set(index, diffSeconds - newVal);
3854                 }
3855                 return newVal;
3856             }
3857         }
3858         return diffSeconds;
3859 
3860     }
3861 
3862     /**
3863      * Check whether shifting an interval is allowed in general.
3864      * 
3865      * @param row row of the interval
3866      * @param interval interval in question
3867      * @return true if shifting is allowed
3868      */
3869     private boolean isShiftingAllowed(TimeBarRow row, Interval interval) {
3870         if (_intervalModificators == null || _intervalModificators.size() == 0) {
3871             return false;
3872         }
3873         boolean allowed = true;
3874 
3875         for (IntervalModificator modificator : _intervalModificators) {
3876             allowed = allowed
3877                     && (!modificator.isApplicable(row, interval) || modificator.isShiftingAllowed(row, interval));
3878         }
3879         return allowed;
3880     }
3881 
3882     /**
3883      * Check whether a specific shift is allowed for an interval.
3884      * 
3885      * @param row row of the interval
3886      * @param interval interval to be shifted
3887      * @param newBegin new begin date
3888      * @return <code>true</code> if shift is allowed
3889      */
3890     private boolean isShiftingAllowed(TimeBarRow row, Interval interval, JaretDate newBegin) {
3891         if (_intervalModificators == null || _intervalModificators.size() == 0) {
3892             return false;
3893         }
3894         boolean allowed = true;
3895 
3896         for (IntervalModificator modificator : _intervalModificators) {
3897             allowed = allowed
3898                     && (!modificator.isApplicable(row, interval) || modificator.shiftAllowed(row, interval, newBegin));
3899         }
3900         return allowed;
3901     }
3902 
3903     /**
3904      * Check whether resizing is allowed for a given Interval.
3905      * 
3906      * @param row row of the interval
3907      * @param interval interval to check resize allowance for
3908      * @return true if interval is allowed to be resized
3909      */
3910     private boolean isResizingAllowed(TimeBarRow row, Interval interval) {
3911         if (_intervalModificators == null || _intervalModificators.size() == 0) {
3912             return false;
3913         }
3914         boolean result = true;
3915         if (_intervalModificators != null) {
3916             for (IntervalModificator modificator : _intervalModificators) {
3917                 result = result
3918                         && (!modificator.isApplicable(row, interval) || modificator.isSizingAllowed(row, interval));
3919             }
3920         }
3921         return result;
3922     }
3923 
3924     /**
3925      * Check whether a new begin dat efor an interval is allowed to be set.
3926      * 
3927      * @param row row of the interval
3928      * @param interval interal to be changed
3929      * @param newBegin new begin date for the interval
3930      * @return <code>true</code> if allowed
3931      */
3932     private boolean isNewBeginAllowed(TimeBarRow row, Interval interval, JaretDate newBegin) {
3933         boolean allowed = true;
3934         for (IntervalModificator modificator : _intervalModificators) {
3935             allowed = allowed
3936                     && (!modificator.isApplicable(row, interval) || modificator
3937                             .newBeginAllowed(row, interval, newBegin));
3938         }
3939         return allowed;
3940     }
3941 
3942     /**
3943      * Check whether a new end date for an interval is allowed to be set.
3944      * 
3945      * @param row row of the interval
3946      * @param interval interal to be changed
3947      * @param newEnd new end date for the interval
3948      * @return <code>true</code> if allowed
3949      */
3950     private boolean isNewEndAllowed(TimeBarRow row, Interval interval, JaretDate newEnd) {
3951         boolean allowed = true;
3952         for (IntervalModificator modificator : _intervalModificators) {
3953             allowed = allowed
3954                     && (!modificator.isApplicable(row, interval) || modificator.newEndAllowed(row, interval, newEnd));
3955         }
3956         return allowed;
3957     }
3958 
3959     /**
3960      * Repaint the region that has been covered by the bounds of a rectangle.
3961      * 
3962      * @param rect rectangle that defines the regions that need to be repainted (its former bounds)
3963      */
3964     private void rectRepaint(Rectangle rect) {
3965         _tbvi.repaint(rect.x, rect.y, rect.width, 1);
3966         _tbvi.repaint(rect.x, rect.y + rect.height, rect.width, 1);
3967         _tbvi.repaint(rect.x, rect.y, 1, rect.height);
3968         _tbvi.repaint(rect.x + rect.width, rect.y, 1, rect.height);
3969     }
3970 
3971     /**
3972      * Handle simple mouse movements. This mainly means: change he cursor for selective areas.
3973      * 
3974      * @param x x coordinate
3975      * @param y y coordinate
3976      */
3977     public void mouseMoved(int x, int y) {
3978         boolean horizontal = _orientation == Orientation.HORIZONTAL;
3979         boolean nothingHitInDiagramArea = true;
3980         if (_lineDraggingAllowed && (hierarchyLineHit(x, y) || headerLineHit(x, y))) {
3981             _tbvi.setCursor(Cursor.HAND_CURSOR);
3982             nothingHitInDiagramArea = false;
3983         } else if (_rowHeightDraggingAllowed && rowLineHit(x, y)) {
3984             _tbvi.setCursor(Cursor.HAND_CURSOR);
3985             nothingHitInDiagramArea = false;
3986         } else if (_diagramRect != null && _diagramRect.contains(x, y)) {
3987             // in the diagram area check for interval bounds and change cursor
3988             // if an interval modificator
3989             // is set and resizing is allowed
3990             TimeBarRow row = rowForXY(x, y);
3991             if (row != null) {
3992                 Interval interval = getTouchedInterval(row, x, y);
3993                 if (interval != null && isResizingAllowed(row, interval)) {
3994                     JaretDate d = dateForXY(x, y);
3995                     long eastDiff = Math.abs(d.diffMilliSeconds(interval.getBegin()));
3996                     long westDiff = Math.abs(d.diffMilliSeconds(interval.getEnd()));
3997                     if (eastDiff < westDiff) {
3998                         _tbvi.setCursor(horizontal ? Cursor.E_RESIZE_CURSOR : Cursor.N_RESIZE_CURSOR);
3999                     } else {
4000                         _tbvi.setCursor(horizontal ? Cursor.W_RESIZE_CURSOR : Cursor.S_RESIZE_CURSOR);
4001                     }
4002                     nothingHitInDiagramArea = false;
4003                 } else {
4004                     _tbvi.setCursor(Cursor.DEFAULT_CURSOR);
4005                 }
4006             }
4007         } else {
4008             _tbvi.setCursor(Cursor.DEFAULT_CURSOR);
4009         }
4010         // if the mouse is in the axis area and hits a marker change the
4011         // cursor (or if dragging in the area is allowed)
4012         if (nothingHitInDiagramArea
4013                 && ((_xAxisRect != null && _xAxisRect.contains(x, y)) || _markerDraggingInDiagramArea)) {
4014             TimeBarMarker marker = getMarkerForXY(x, y);
4015             if (marker != null) {
4016                 _tbvi.setCursor(Cursor.HAND_CURSOR);
4017             } else {
4018                 _tbvi.setCursor(Cursor.DEFAULT_CURSOR);
4019             }
4020         }
4021 
4022     }
4023 
4024     /**
4025      * Check whether the hierarchy delimiting line is hit by the given location.
4026      * 
4027      * @param x x coordinate
4028      * @param y y coordinate
4029      * @return true if the location is above or in the range of the selection delta
4030      */
4031     private boolean hierarchyLineHit(int x, int y) {
4032         if (_orientation == Orientation.HORIZONTAL) {
4033             return (_hierarchyWidth > 0 && Math.abs(_hierarchyWidth - x) < _selectionDelta);
4034         } else {
4035             return (_hierarchyWidth > 0 && Math.abs(_hierarchyWidth - y) < _selectionDelta);
4036         }
4037     }
4038 
4039     /**
4040      * Check whether a row delimiter line is hit by a coordinate.
4041      * 
4042      * @param x x coord
4043      * @param y y coord
4044      * @return <code>true</code> if a row line is hit
4045      */
4046     public boolean rowLineHit(int x, int y) {
4047         if (_rowList.size() > 0 && (_yAxisRect.contains(x, y) || _hierarchyRect.contains(x, y))) {
4048             int coord;
4049             int max;
4050             if (_orientation == Orientation.HORIZONTAL) {
4051                 coord = y;
4052                 coord -= _yAxisRect.y;
4053                 max = _yAxisRect.height;
4054             } else {
4055                 coord = x;
4056                 coord -= _yAxisRect.x;
4057                 max = _yAxisRect.width;
4058             }
4059             TimeBarRow row = getRow(_firstRow);
4060             int idx = _firstRow;
4061             int endCoord = _timeBarViewState.getRowHeight(row) - _firstRowPixelOffset;
4062 
4063             while (endCoord < max && idx < _rowList.size()) {
4064                 if (Math.abs(endCoord - coord) < _selectionDelta) {
4065                     return true;
4066                 }
4067                 idx++;
4068                 if (idx > _rowList.size() - 1) {
4069                     break;
4070                 }
4071                 row = getRow(idx);
4072                 endCoord += _timeBarViewState.getRowHeight(row);
4073             }
4074         }
4075         return false;
4076     }
4077 
4078     /**
4079      * Retrieve the row identified by it's bottom border.
4080      * 
4081      * @param x x coordinate in the control
4082      * @param y y coordinate in the control
4083      * @return row or <code>null</code>
4084      */
4085     private TimeBarRow getRowByBottomLine(int x, int y) {
4086         if (_diagramRect.contains(x, y) || _yAxisRect.contains(x, y) || _hierarchyRect.contains(x, y)) {
4087             int coord;
4088             int max;
4089             if (_orientation == Orientation.HORIZONTAL) {
4090                 coord = y;
4091                 coord -= _diagramRect.y;
4092                 max = _diagramRect.height;
4093             } else {
4094                 coord = x;
4095                 coord -= _diagramRect.x;
4096                 max = _diagramRect.width;
4097             }
4098             TimeBarRow row = getRow(_firstRow);
4099             int idx = _firstRow;
4100             int endCoord = _timeBarViewState.getRowHeight(row) - _firstRowPixelOffset;
4101 
4102             while (endCoord < max) {
4103                 if (Math.abs(endCoord - coord) < _selectionDelta) {
4104                     return row;
4105                 }
4106                 idx++;
4107                 row = getRow(idx);
4108                 endCoord += _timeBarViewState.getRowHeight(row);
4109             }
4110         }
4111         return null;
4112     }
4113 
4114     /**
4115      * Check whether the header delimiting line is hit by the given location.
4116      * 
4117      * @param x x coordinate
4118      * @param y y coordinate
4119      * @return true if the location is above or in the range of the selection delta
4120      */
4121     private boolean headerLineHit(int x, int y) {
4122         if (_orientation == Orientation.HORIZONTAL) {
4123             return (_yAxisWidth > 0 && Math.abs(_hierarchyWidth + _yAxisWidth - x) < _selectionDelta);
4124         } else {
4125             return (_yAxisWidth > 0 && Math.abs(_hierarchyWidth + _yAxisWidth - y) < _selectionDelta);
4126         }
4127     }
4128 
4129     /**
4130      * Normalizes a rectangle: x,y will be the upper left corner.
4131      * 
4132      * @param rect rectangle to normalize
4133      * @return normalized recangle
4134      */
4135     private Rectangle normalizeRectangle(Rectangle rect) {
4136         int x = Math.min(rect.x, rect.x + rect.width);
4137         int y = Math.min(rect.y, rect.y + rect.height);
4138         int width = Math.abs(rect.width);
4139         int height = Math.abs(rect.height);
4140         return new Rectangle(x, y, width, height);
4141     }
4142 
4143     /**
4144      * Ensures that all intervals in the given rectangle are selected. The intervals have to be either complete in the
4145      * rectangle (complete area) or if the renderer sets the containinng rectangle this rectangle has to be inside the
4146      * selecting rectangle
4147      * 
4148      * @param curSelRect selction rectangle
4149      */
4150     public void selectIntervals(Rectangle curSelRect) {
4151         // get the intervals in question
4152         List<Interval> intervals = getIntervals(curSelRect);
4153         // now ensure they are in the selection of intervals
4154         // & limit the selection to the rect selection
4155         for (Interval interval : intervals) {
4156             if (!_selectionModel.getSelectedIntervals().contains(interval)) {
4157                 _selectionModel.addSelectedInterval(interval);
4158             }
4159         }
4160         List<Interval> selection = new ArrayList<Interval>();
4161         selection.addAll(_selectionModel.getSelectedIntervals());
4162 
4163         for (Interval interval : selection) {
4164             if (!intervals.contains(interval)) {
4165                 _selectionModel.remSelectedInterval(interval);
4166             }
4167         }
4168     }
4169 
4170     /**
4171      * Retrieve all intervals inside a selection rectangle.
4172      * 
4173      * @param curSelRect the marking rectangle.
4174      * @return a list of intervals inside the rectangle
4175      */
4176     public List<Interval> getIntervals(Rectangle curSelRect) {
4177         List<Interval> result = new ArrayList<Interval>();
4178         boolean horizontal = _orientation == Orientation.HORIZONTAL;
4179         // first get the rows
4180         List<TimeBarRow> rows = horizontal ? getRows(curSelRect.y, curSelRect.y + curSelRect.height) : getRows(
4181                 curSelRect.x, curSelRect.x + curSelRect.width);
4182         // calculate the dates
4183         JaretDate begin = horizontal ? dateForCoord(curSelRect.x) : dateForCoord(curSelRect.y);
4184         JaretDate end = horizontal ? dateForCoord(curSelRect.x + curSelRect.width) : dateForCoord(curSelRect.y
4185                 + curSelRect.height);
4186         // go through all intervals and check whether their represenation is
4187         // really inside the rect
4188         for (TimeBarRow row : rows) {
4189             List<Interval> intervals = row.getIntervals(begin, end);
4190             for (Interval interval : intervals) {
4191                 boolean overlapping = getTimeBarViewState().getDrawOverlapping(row) ? false : _overlapStrategy
4192                         .getOverlapInfo(row, interval).overlappingCount > 0;
4193                 Rectangle intervalRect = getIntervalBounds(row, interval);
4194                 // get the containing rect or - if not set by the renderer -
4195                 // use the component bounds
4196                 Rectangle containingRect = _tbvi.timeBarContainingRect(interval, intervalRect, overlapping);
4197                 if (containingRect == null) {
4198                     containingRect = intervalRect;
4199                 }
4200                 // now check whether the rect is contained
4201                 if (curSelRect.contains(containingRect)) {
4202                     result.add(interval);
4203                 }
4204             }
4205         }
4206 
4207         return result;
4208     }
4209 
4210     /**
4211      * Retrieve the interval that has a bound near to the given x coordinate in a given row. If more than one interval
4212      * might be hit, the exact coordinates are checked. if the location checked is in beetween two intervals in reach,
4213      * the first interval will be returned.
4214      * 
4215      * @param row row in question
4216      * @param x x coordinate to be checked for intervals
4217      * @param y y coordinate
4218      * @return nearest interval with bound inside the area around x or <code>null</code>
4219      */
4220     private Interval getTouchedInterval(TimeBarRow row, int x, int y) {
4221         // check only intervals currently displayed
4222         List<Interval> intervals = row.getIntervals(getStartDate(), getEndDate());
4223         List<Interval> candidates = new ArrayList<Interval>(5);
4224         List<Rectangle> candidateRects = new ArrayList<Rectangle>(5);
4225         for (Interval interval : intervals) {
4226             Rectangle intervalRect = getIntervalBounds(row, interval);
4227             if (_orientation == Orientation.HORIZONTAL) {
4228                 if (y >= intervalRect.y && y <= intervalRect.y + intervalRect.height) {
4229                     if (Math.abs(x - intervalRect.x) <= _selectionDelta) {
4230                         candidates.add(interval);
4231                         candidateRects.add(intervalRect);
4232                     } else if (Math.abs(intervalRect.x + intervalRect.width - x) <= _selectionDelta) {
4233                         candidates.add(interval);
4234                         candidateRects.add(intervalRect);
4235                     }
4236                 }
4237             } else {
4238                 if (x >= intervalRect.x && x <= intervalRect.x + intervalRect.width) {
4239                     if (Math.abs(y - intervalRect.y) <= _selectionDelta) {
4240                         candidates.add(interval);
4241                         candidateRects.add(intervalRect);
4242                     } else if (Math.abs(intervalRect.y + intervalRect.height - y) <= _selectionDelta) {
4243                         candidates.add(interval);
4244                         candidateRects.add(intervalRect);
4245                     }
4246                 }
4247             }
4248         }
4249         // check candidates
4250         if (candidates.size() == 0) {
4251             return null;
4252         }
4253         if (candidates.size() == 1) {
4254             return candidates.get(0);
4255         }
4256 
4257         for (int i = 0; i < candidates.size(); i++) {
4258             Interval interval = candidates.get(i);
4259             Rectangle rect = candidateRects.get(i);
4260             if (rect.contains(x, y)) {
4261                 return interval;
4262             }
4263         }
4264         // there might be the case that no inteval is a direct hit ...
4265         // then just use the first one
4266         return candidates.get(0);
4267 
4268     }
4269 
4270     /**
4271      * Retrieve the rows between two coordinates (either y or x depending on the orientation).
4272      * 
4273      * @param c1 c1
4274      * @param c2 c2
4275      * @return list of rows
4276      */
4277     private List<TimeBarRow> getRows(int c1, int c2) {
4278         List<TimeBarRow> result = new ArrayList<TimeBarRow>();
4279         // boolean horizontal = _orientation == Orientation.HORIZONTAL;
4280         for (int r = _firstRow; r <= _firstRow + getRowsDisplayed() && r < getRowCount(); r++) {
4281             // int startC = (r - _firstRow) * _rowHeight + (horizontal ? _diagramRect.y : _diagramRect.x)
4282             // - _firstRowPixelOffset;
4283             // int endC = startC + _rowHeight;
4284             int startC = yForRow(getRow(r));
4285             int endC = startC + _timeBarViewState.getRowHeight(getRow(r));
4286             if ((startC >= c1 && startC <= c2) || (endC >= c1 && endC <= c2) || (c1 >= startC && c1 <= endC)) {
4287                 result.add(getRow(r));
4288             }
4289         }
4290         return result;
4291     }
4292 
4293     /**
4294      * @return Returns the drawRowGrid.
4295      */
4296     public boolean getDrawRowGrid() {
4297         return _drawRowGrid;
4298     }
4299 
4300     /**
4301      * @param drawRowGrid The drawRowGrid to set.
4302      */
4303     public void setDrawRowGrid(boolean drawRowGrid) {
4304         _drawRowGrid = drawRowGrid;
4305         if (_tbvi != null) {
4306             _tbvi.repaint();
4307         }
4308     }
4309 
4310     /**
4311      * Highlight a row by giving the display y coordinate.
4312      * 
4313      * @param y y coordinate
4314      */
4315     public void highlightRow(int y) {
4316         TimeBarRow row = rowForY(y);
4317         highlightRow(row);
4318     }
4319 
4320     /**
4321      * Highlight a row.
4322      * 
4323      * @param row the row to be highlighted
4324      */
4325     public void highlightRow(TimeBarRow row) {
4326         if (row != _highlightedRow) {
4327             TimeBarRow oldRow = _highlightedRow;
4328             _highlightedRow = row;
4329             _tbvi.repaint(getRowBounds(_highlightedRow));
4330             if (oldRow != null) {
4331                 // repaint the row highlighted before
4332                 _tbvi.repaint(getRowBounds(oldRow));
4333             }
4334         }
4335     }
4336 
4337     /**
4338      * Dehighlight a highlighted row.
4339      * 
4340      */
4341     public void deHighlightRow() {
4342         if (_highlightedRow != null) {
4343             TimeBarRow row = _highlightedRow;
4344             _highlightedRow = null;
4345             _tbvi.repaint(getRowBounds(row));
4346         }
4347     }
4348 
4349     /**
4350      * Retrieve the highlighted row if prsent.
4351      * 
4352      * @return the highlilghted row or <code>null</code> if no row is highlighted
4353      */
4354     public TimeBarRow getHighlightedRow() {
4355         return _highlightedRow;
4356     }
4357 
4358     /**
4359      * Retrieve the marker currently dragged.
4360      * 
4361      * @return the currently dragged marker or <code>null</code> if no marker is beeing dragged
4362      */
4363     public TimeBarMarker getDraggedMarker() {
4364         return _draggedMarker;
4365     }
4366 
4367     /**
4368      * Retrieve the selection rect.
4369      * 
4370      * @return the current selection rect if present or <code>null</code> if none is present
4371      */
4372     public Rectangle getSelectionRect() {
4373         return _selectionRect;
4374     }
4375 
4376     /**
4377      * Set the last selection rectangle.
4378      * 
4379      * @param rect rectangle
4380      */
4381     public void setLastSelRect(Rectangle rect) {
4382         _lastSelRect = rect;
4383     }
4384 
4385     /**
4386      * Retrieve the last selection rectangle.
4387      * 
4388      * @return rect last selection rect if any or <code>null</code>
4389      */
4390     public Rectangle getLastSelRect() {
4391         return _lastSelRect;
4392     }
4393 
4394     /**
4395      * Retrieve the tooltip text for a given location.
4396      * 
4397      * @param x x coordinate in the component
4398      * @param y y coordinate in the component
4399      * @return the tooltip text or null if there is no text
4400      */
4401     public String getToolTipText(int x, int y) {
4402         // check for Marker
4403         TimeBarMarker marker = getMarkerForXY(x, y);
4404         if (marker != null) {
4405             return marker.getDescription();
4406         }
4407 
4408         // retrieve the row
4409         if (_diagramRect.contains(x, y)) {
4410             TimeBarRow row = rowForXY(x, y);
4411             if (row != null) {
4412                 // retrieve all intervals in the row for the x coordinate
4413                 String tooltip = null;
4414                 List<Interval> intervals = getIntervalsAt(row, x, y);
4415                 
4416                 
4417                 
4418                 // hack for Pierre-Jean (has to be done in a better way sometime) TODO
4419 //                int range = 10; // 10 pixel
4420 //                if (intervals.size()==0) {
4421 //                    Set<Interval> set = new HashSet<Interval>();
4422 //                    for (int xxx=x-range;xxx<=x+range;xxx++) {
4423 //                        set.addAll(getIntervalsAt(row, xxx, y));
4424 //                    }
4425 //                    intervals = new ArrayList<Interval>();
4426 //                    intervals.addAll(set);
4427 //                }
4428                 // END hack for Pierre-Jean
4429                 
4430                 
4431                 
4432                 // TODO diagram tooltip
4433                 
4434                 // no intervals? Tooltip of the diagram itself
4435                 if (intervals.size() == 0) {
4436                     // may be over a relation
4437                     return _tbvi.getRelationTooltip(x, y);
4438                 }
4439 
4440                 Interval interval;
4441                 if (intervals.size() == 1) {
4442                     interval = intervals.get(0);
4443                 } else {
4444                     interval = _intervalSelectionStrategy.selectInterval(intervals);
4445                 }
4446                 Rectangle intervalRect = getIntervalBounds(row, interval);
4447                 boolean overlapping = getTimeBarViewState().getDrawOverlapping(row) ? false : _overlapStrategy
4448                         .getOverlapInfo(row, interval).overlappingCount > 0;
4449                 if (_tbvi.timeBarContains(interval, intervalRect, x - intervalRect.x, y - intervalRect.y, overlapping)) {
4450                     tooltip = _tbvi.getIntervalToolTipText(interval, intervalRect, x - intervalRect.x, y
4451                             - intervalRect.y);
4452                 }
4453                 return tooltip;
4454             }
4455         } else if (_hierarchyRect.contains(x, y)) {
4456             // hierarchy
4457             TimeBarRow row = rowForXY(x, y);
4458             if (row instanceof TimeBarNode) {
4459                 return _tbvi.getHierarchyToolTipText((TimeBarNode) row, x, y);
4460             } else {
4461                 // row is not a node ...
4462                 return null;
4463             }
4464         } else if (_yAxisRect.contains(x, y)) {
4465             // header area
4466             return _tbvi.getHeaderToolTipText(rowForXY(x, y), x, y);
4467         } else if (_xAxisRect.contains(x, y)) {
4468             // time scale
4469             return _tbvi.getTimeScaleToolTipText(x, y);
4470         }
4471         return null;
4472     }
4473 
4474     /**
4475      * Calculate the rectangle for drawing the header of a given row.
4476      * 
4477      * @param row row to calculae the header recct for
4478      * @return the rectangle for drawing the the header or <code>null</code> if the header is not visible.
4479      */
4480     public Rectangle getHeaderRect(TimeBarRow row) {
4481         if (!isRowDisplayed(row)) {
4482             return null;
4483         } else {
4484             if (_orientation.equals(TimeBarViewerInterface.Orientation.HORIZONTAL)) {
4485                 int y = yForRow(row);
4486                 Rectangle rect = new Rectangle(_yAxisRect.x, y, _yAxisWidth, _timeBarViewState.getRowHeight(row));
4487                 return rect;
4488             } else {
4489                 int x = yForRow(row);
4490                 Rectangle rect = new Rectangle(x, _yAxisRect.y, _timeBarViewState.getRowHeight(row), _yAxisWidth);
4491                 return rect;
4492             }
4493         }
4494     }
4495 
4496     /**
4497      * Calculate the rectangle for drawing the hierachy marker of a given row.
4498      * 
4499      * @param row row to calculate the hierarchy rect for
4500      * @return the rectangle for drawing the the hierarchy marker or <code>null</code> if the row is not visible.
4501      */
4502     public Rectangle getHierarchyRect(TimeBarRow row) {
4503         if (!isRowDisplayed(row)) {
4504             return null;
4505         } else {
4506             int y = yForRow(row);
4507             Rectangle rect = new Rectangle(_hierarchyRect.x, y, _hierarchyWidth, _timeBarViewState.getRowHeight(row));
4508             return rect;
4509         }
4510     }
4511 
4512     /**
4513      * Produce a simple string representation.
4514      * 
4515      * @return simpe string representation
4516      */
4517     public String toString() {
4518         StringBuffer buf = new StringBuffer();
4519         buf.append("TimeBarViewerDelegate[");
4520         buf.append("Name:" + _name + ",");
4521         buf.append("StartDate:" + _startDate.toDisplayString() + ",");
4522         buf.append("MinDate:" + _minDate.toDisplayString() + ",");
4523         buf.append("MaxDate:" + _maxDate.toDisplayString() + ",");
4524 
4525         buf.append("]");
4526         return buf.toString();
4527     }
4528 
4529     /**
4530      * Set the name of the viewer/component for display or debugging purposes.
4531      * 
4532      * @param name name
4533      */
4534     public void setName(String name) {
4535         _name = name;
4536     }
4537 
4538     /**
4539      * Get the name of the viewer/component.
4540      * 
4541      * @return the name or <code>null</code>
4542      */
4543     public String getName() {
4544         return _name;
4545     }
4546 
4547     /**
4548      * Set the autoscroll behaviour. If autoscroll is anabled, drag and select by selection rect will autoscroll the
4549      * viewer.
4550      * 
4551      * @param enableAutoscroll true for enabling autoscroll
4552      */
4553     public void setAutoscrollEnabled(boolean enableAutoscroll) {
4554         _autoscroll = enableAutoscroll;
4555     }
4556 
4557     /**
4558      * Get the autoscroll behaviour.
4559      * 
4560      * @return true if autoscroll is enabled.
4561      */
4562     public boolean isAutoscrollEnabled() {
4563         return _autoscroll;
4564     }
4565 
4566     /**
4567      * Scroll a date into the visible area of the viewer.
4568      * 
4569      * @param date date to be shown.
4570      * @return the number of seconds the start date have been modified
4571      */
4572     public int scrollDateToVisible(JaretDate date) {
4573         JaretDate enddate = getStartDate().copy();
4574         date = date.copy();
4575         JaretDate oldStartDate = getStartDate().copy();
4576         if (_milliAccuracy) {
4577             enddate.advanceMillis(getMilliSecondsDisplayed());
4578             if (date.compareTo(getStartDate()) < 0) {
4579                 setStartDate(date);
4580             } else if (date.compareTo(enddate) > 0) {
4581                 date.advanceMillis(-getMilliSecondsDisplayed());
4582                 setStartDate(date);
4583             }
4584         } else {
4585             enddate.advanceSeconds(getSecondsDisplayed());
4586             if (date.compareTo(getStartDate()) < 0) {
4587                 setStartDate(date);
4588             } else if (date.compareTo(enddate) > 0) {
4589                 date.advanceSeconds(-getSecondsDisplayed());
4590                 setStartDate(date);
4591             }
4592         }
4593         return oldStartDate.diffSeconds(getStartDate());
4594     }
4595 
4596     /**
4597      * Make sure the specified row is visible.
4598      * 
4599      * @param row TimeBarRow to be in the visible area.
4600      */
4601     public void scrollRowToVisible(TimeBarRow row) {
4602         if (!isFiltered(row)) {
4603             int ridx = _rowList.indexOf(row);
4604             if (ridx == -1) {
4605                 return;
4606             }
4607             if (ridx < _firstRow) {
4608                 setFirstRow(ridx);
4609             } else if (ridx >= _firstRow + getRowsDisplayed()) {
4610                 setLastRow(ridx);
4611             } else if (getTimeBarViewState().getUseVariableRowHeights() && ridx >= _firstRow + getRowsDisplayed() - 1) {
4612                 setLastRow(ridx);
4613             }
4614         }
4615     }
4616 
4617     /**
4618      * Make sure the specified interval is in the visibe area of the viewer. If the interval does not fit in the visible
4619      * area, the beginning of the interval will be displayed.
4620      * 
4621      * @param row TimeBarRow of the interval
4622      * @param interval inteval.
4623      */
4624     public void scrollIntervalToVisible(TimeBarRow row, Interval interval) {
4625         // MAYBE change, since next call may result in two consequent redraws
4626         scrollRowToVisible(row);
4627         if (_milliAccuracy) {
4628             if (interval.getBegin().compareTo(_startDate) < 0) {
4629                 scrollDateToVisible(interval.getBegin());
4630             } else if (interval.getEnd().diffMilliSeconds(interval.getBegin()) > getMilliSecondsDisplayed()) {
4631                 setStartDate(interval.getBegin());
4632             } else {
4633                 scrollDateToVisible(interval.getEnd());
4634             }
4635         } else {
4636             if (interval.getBegin().compareTo(_startDate) < 0) {
4637                 scrollDateToVisible(interval.getBegin());
4638             } else if (interval.getEnd().diffSeconds(interval.getBegin()) > getSecondsDisplayed()) {
4639                 setStartDate(interval.getBegin());
4640             } else {
4641                 scrollDateToVisible(interval.getEnd());
4642             }
4643         }
4644     }
4645 
4646     /**
4647      * Check whether any part of an interval is visible.
4648      * 
4649      * @param row row
4650      * @param interval interval
4651      * @return true if any part of the interval is rendered.
4652      */
4653     public boolean isIntervalVisible(TimeBarRow row, Interval interval) {
4654         if (!isRowDisplayed(row)) {
4655             return false;
4656         }
4657 
4658         if (interval.getBegin().compareTo(_startDate) < 0 && interval.getEnd().compareTo(getEndDate()) >= 0) {
4659             // interval is partly shown in the middle
4660             return true;
4661         } else if (interval.getBegin().compareTo(getEndDate()) > 0) {
4662             // interval past the displayed area
4663             return false;
4664         } else if (interval.getEnd().compareTo(_startDate) < 0) {
4665             // interval before the displayed area
4666             return false;
4667         }
4668         return true;
4669     }
4670 
4671     /**
4672      * Make sure the specified interval is in the visibe area of the viewer. If the interval does not fit in the visible
4673      * area, the beginning of the interval will be displayed.
4674      * 
4675      * @param interval inteval.
4676      */
4677     public void scrollIntervalToVisible(Interval interval) {
4678         TimeBarRow row = _model.getRowForInterval(interval);
4679         if (row != null) {
4680             scrollIntervalToVisible(row, interval);
4681         }
4682     }
4683 
4684     /**
4685      * Scroll an interval to specified position (by ration) in the vieable area of the chart. The beginning of the
4686      * interval will be positioned according to the ratio given.
4687      * 
4688      * @param interval interval to scroll to
4689      * @param horizontalRatio horizontal ratio (0 to 1.0 = left to right)
4690      * @param verticalRatio vertical ratio (0 to 1.0 = top to bottom)
4691      */
4692     public void scrollIntervalToVisible(Interval interval, double horizontalRatio, double verticalRatio) {
4693         if (horizontalRatio < 0.0 || horizontalRatio > 1.0 || verticalRatio < 0.0 || verticalRatio > 1.0) {
4694             throw new IllegalArgumentException("ratios have to be in the range from 0.0 to 1.0");
4695         }
4696         TimeBarRow row = _model.getRowForInterval(interval);
4697         if (row != null) {
4698             scrollIntervalToVisible(row, interval, horizontalRatio, verticalRatio);
4699         }
4700     }
4701 
4702     /**
4703      * Scroll an interval to specified position (by ration) in the vieable area of the chart. The beginning of the
4704      * interval will be positioned according to the ratio given.
4705      * 
4706      * @param interval interval to scroll to
4707      * @param row row of the interval
4708      * @param horizontalRatio horizontal ratio (0 to 1.0 = left to right)
4709      * @param verticalRatio vertical ratio (0 to 1.0 = top to bottom)
4710      */
4711     public void scrollIntervalToVisible(TimeBarRow row, Interval interval, double horizontalRatio, double verticalRatio) {
4712         if (horizontalRatio < 0.0 || horizontalRatio > 1.0 || verticalRatio < 0.0 || verticalRatio > 1.0) {
4713             throw new IllegalArgumentException("ratios have to be in the range from 0.0 to 1.0");
4714         }
4715         // calculate date for ratio
4716         int secondsDisplayed = getSecondsDisplayed();
4717         double secondsFromStart = (double) secondsDisplayed * horizontalRatio;
4718         JaretDate startDate = interval.getBegin().copy().backSeconds(secondsFromStart);
4719 
4720         // calculate row to be the first row
4721         int pos = (int) (getDiagramRect().height * verticalRatio);
4722         int destRowIdx = _rowList.indexOf(row);
4723 
4724         // loop to find the new first row
4725         // no distinction between var/fixed row heights -> just try it
4726         int togo = pos;
4727         int idx;
4728         if (togo > 0) {
4729             idx = destRowIdx - 1;
4730         } else {
4731             idx = destRowIdx;
4732         }
4733         int rowHeight = 0;
4734         while (togo > 0 && idx >= 0) {
4735             rowHeight = getTimeBarViewState().getRowHeight(_rowList.get(idx));
4736             togo -= rowHeight;
4737             idx--;
4738         }
4739 
4740         if (idx < 0) {
4741             setFirstRow(0);
4742         } else {
4743             setFirstRow(idx, rowHeight - togo);
4744         }
4745         setStartDate(startDate);
4746 
4747     }
4748 
4749     /**
4750      * Add an interval modificator.
4751      * 
4752      * @param intervalModificator modificator to add
4753      */
4754     public synchronized void addIntervalModificator(IntervalModificator intervalModificator) {
4755         if (_intervalModificators == null) {
4756             _intervalModificators = new Vector<IntervalModificator>();
4757         }
4758         _intervalModificators.add(intervalModificator);
4759     }
4760 
4761     /**
4762      * Remove an interval modificator.
4763      * 
4764      * @param intervalModificator modificator to remove
4765      */
4766     public void remIntervalModificator(IntervalModificator intervalModificator) {
4767         if (_intervalModificators != null) {
4768             _intervalModificators.remove(intervalModificator);
4769         }
4770     }
4771 
4772     /**
4773      * Retrieve the timebarviewstate.
4774      * 
4775      * @return the timebar viewstate
4776      */
4777     public ITimeBarViewState getTimeBarViewState() {
4778         return _timeBarViewState;
4779     }
4780 
4781     /**
4782      * Retrieve the hierarchical viewstate.
4783      * 
4784      * @return the hierarchical viewstate or <code>null</code> if no hierarchical model is used
4785      */
4786     public HierarchicalViewState getHierarchicalViewState() {
4787         return _hierarchicalViewState;
4788     }
4789 
4790     /**
4791      * Set the hierarchical viewstate. The hierarchical viewstate will only be used together with a hierarchical model.
4792      * 
4793      * @param hvs hierachical viewstate to use
4794      */
4795     public void setHierarchicalViewState(HierarchicalViewState hvs) {
4796         _hierarchicalViewState = hvs;
4797     }
4798 
4799     /**
4800      * Set the title string to be displayed.
4801      * 
4802      * @param title title to be displayed
4803      */
4804     public void setTitle(String title) {
4805         _title = title;
4806         if (_tbvi != null) {
4807             _tbvi.repaint();
4808         }
4809     }
4810 
4811     /**
4812      * Retrieve the title of the viewer.
4813      * 
4814      * @return the title
4815      */
4816     public String getTitle() {
4817         return _title;
4818     }
4819 
4820     /**
4821      * @return true for overlap drawing mode.
4822      */
4823     public boolean isDrawOverlapping() {
4824         return _drawOverlapping;
4825     }
4826 
4827     /**
4828      * Set the drawing mode concerning overlapping intervals.
4829      * 
4830      * @param drawOverlapping if set to true all intervals will be painted overlapping each other if they do overlap.
4831      * False will reduce the space for rendering, stacking the intervals (default).
4832      */
4833     public void setDrawOverlapping(boolean drawOverlapping) {
4834         _drawOverlapping = drawOverlapping;
4835         if (_tbvi != null) {
4836             _tbvi.repaint();
4837         }
4838     }
4839 
4840     /**
4841      * Retrieve tha maximal count of overlapping intervals in a row.
4842      * 
4843      * @param row row to check
4844      * @return count of maximum overlapping intervals in the row
4845      */
4846     public int getMaxOverlapCount(TimeBarRow row) {
4847         if (getTimeBarViewState().getDrawOverlapping(row)) {
4848             return 1;
4849         } else {
4850             return _overlapStrategy.getMaxOverlapCount(row);
4851         }
4852     }
4853 
4854     /**
4855      * Applies the interval filter if set.
4856      * 
4857      * @param intervals list of intervals to be filtered
4858      * @return list of intervals that pass the filter
4859      */
4860     public List<Interval> filterIntervals(List<Interval> intervals) {
4861         if (_intervalFilter == null) {
4862             return intervals;
4863         }
4864         ArrayList<Interval> result = new ArrayList<Interval>();
4865         for (Interval interval : intervals) {
4866             if (_intervalFilter.isInResult(interval)) {
4867                 result.add(interval);
4868             }
4869         }
4870 
4871         return result;
4872     }
4873 
4874     // ********** Propchangelistener
4875     /**
4876      * {@inheritDoc}
4877      */
4878     public void propertyChange(PropertyChangeEvent evt) {
4879         if (evt.getSource().equals(_rowFilter) || evt.getSource().equals(_rowSorter)) {
4880             // on changes of filter or sorter do a rowlist refresh and inform
4881             // the viewer if present
4882             updateRowList();
4883             if (_tbvi != null) {
4884                 _tbvi.repaint();
4885             }
4886         } else if (evt.getSource() instanceof TimeBarIntervalFilter) {
4887             if (_tbvi != null) {
4888                 _overlapStrategy.clearCachedData();
4889                 _tbvi.repaint();
4890             }
4891         }
4892     }
4893 
4894     // ********** End propchange listener
4895 
4896     // ************* focus handling
4897     /**
4898      * Retrieve the focusef interval.
4899      * 
4900      * @return the currently focussed interval or null if none is in focus
4901      */
4902     public Interval getFocussedInterval() {
4903         return _focussedInterval;
4904     }
4905 
4906     /**
4907      * @return the currently focussed row or null if none is in focus
4908      */
4909     public TimeBarRow getFocussedRow() {
4910         return _focussedRow;
4911     }
4912 
4913     /**
4914      * Check whether an interval is focussed.
4915      * 
4916      * @param interval interval to check
4917      * @return true if focussed
4918      */
4919     public boolean isFocussed(Interval interval) {
4920         if (_focussedInterval == null) {
4921             return false;
4922         }
4923         return _focussedInterval.equals(interval);
4924     }
4925 
4926     /**
4927      * Set the new focussed interval.
4928      * 
4929      * @param interval new focussed interval
4930      */
4931     public void setFocussedInterval(Interval interval) {
4932         setFocussedInterval(null, interval);
4933     }
4934 
4935     /**
4936      * Set the new focussed interval. Ths method should be used, if the row of the interval is known.
4937      * 
4938      * @param row row of the interval. May be <code>null</code> if the row of the interval is unknown.
4939      * @param interval interval to be focussed.
4940      */
4941     public void setFocussedInterval(TimeBarRow row, Interval interval) {
4942         if (_focussedInterval == null && interval == null) {
4943             return;
4944         }
4945         if (_focussedInterval != null && _focussedInterval.equals(interval)) {
4946             return;
4947         }
4948         if (interval != null && isFiltered(interval)) {
4949             // can not focus filtered interval
4950             return;
4951         }
4952         if (interval != null && row == null) {
4953             _focussedRow = _model.getRowForInterval(interval);
4954             if (_focussedRow == null || isFiltered(_focussedRow)) {
4955                 return;
4956             }
4957         } else {
4958             _focussedRow = row;
4959         }
4960         // real change
4961         _focussedInterval = interval;
4962 
4963         if (_focussedInterval != null && _scrollOnFocus) {
4964             if (!isIntervalVisible(_focussedRow, _focussedInterval)) {
4965                 scrollIntervalToVisible(_focussedRow, _focussedInterval);
4966             }
4967         }
4968         _tbvi.repaint(); // MAYBE optimize with exact redraw
4969         fireFocussedIntervalChange(_focussedRow, _focussedInterval);
4970     }
4971 
4972     /**
4973      * Move the focus to the right.
4974      */
4975     public void moveFocusRight() {
4976         if (_focussedRow != null && _focussedInterval != null) {
4977             int idx = _focussedRow.getIntervals().indexOf(_focussedInterval);
4978             if (idx + 1 < _focussedRow.getIntervals().size()) {
4979                 setFocussedInterval(_focussedRow.getIntervals().get(idx + 1));
4980             }
4981         }
4982     }
4983 
4984     /**
4985      * Move the focus to the left.
4986      */
4987     public void moveFocusLeft() {
4988         if (_focussedRow != null && _focussedInterval != null) {
4989             int idx = _focussedRow.getIntervals().indexOf(_focussedInterval);
4990             if (idx - 1 >= 0) {
4991                 setFocussedInterval(_focussedRow.getIntervals().get(idx - 1));
4992             }
4993         }
4994     }
4995 
4996     /**
4997      * Move the focus up.
4998      */
4999     public void moveFocusUp() {
5000         if (_focussedRow != null && _focussedInterval != null) {
5001             int ridx = _rowList.indexOf(_focussedRow);
5002             if (ridx > 0) {
5003                 TimeBarRow focussedRow = _rowList.get(ridx - 1);
5004                 List<Interval> intervals = focussedRow.getIntervals(_focussedInterval.getBegin(), _focussedInterval
5005                         .getEnd());
5006                 if (intervals != null && intervals.size() > 0) {
5007                     setFocussedInterval(focussedRow, intervals.get(0));
5008                 } else {
5009                     Interval interval = getNearestInterval(focussedRow, _focussedInterval);
5010                     if (interval != null) {
5011                         setFocussedInterval(focussedRow, interval);
5012                     }
5013                 }
5014             }
5015         }
5016     }
5017 
5018     /**
5019      * Move the focus down.
5020      */
5021     public void moveFocusDown() {
5022         if (_focussedRow != null && _focussedInterval != null) {
5023             int ridx = _rowList.indexOf(_focussedRow);
5024             if (ridx + 1 < _rowList.size()) {
5025                 TimeBarRow focussedRow = _rowList.get(ridx + 1);
5026                 List<Interval> intervals = focussedRow.getIntervals(_focussedInterval.getBegin(), _focussedInterval
5027                         .getEnd());
5028                 if (intervals != null && intervals.size() > 0) {
5029                     setFocussedInterval(focussedRow, intervals.get(0));
5030                 } else {
5031                     Interval interval = getNearestInterval(focussedRow, _focussedInterval);
5032                     if (interval != null) {
5033                         setFocussedInterval(focussedRow, interval);
5034                     }
5035                 }
5036             }
5037         }
5038     }
5039 
5040     /**
5041      * Retrieve the nearest interval in a row relative to the middle of a given interval.
5042      * 
5043      * @param row row to search in
5044      * @param interval the iterval the nearest interval is searched for (using the middle of the interval)
5045      * @return the nearest intervals relative to the middle if the given interval or <code>null</code> if no interval is
5046      * in the row.
5047      */
5048     private Interval getNearestInterval(TimeBarRow row, Interval interval) {
5049         Interval result = null;
5050         long deltaSec = -1;
5051         JaretDate d = new JaretDate(interval.getBegin());
5052         d.advanceSeconds(interval.getSeconds() / 2.0);
5053         for (Interval i : row.getIntervals()) {
5054             int delta = Math.abs(i.getBegin().diffSeconds(d));
5055             if (delta < deltaSec || deltaSec == -1) {
5056                 result = i;
5057                 deltaSec = delta;
5058             }
5059             delta = Math.abs(i.getEnd().diffSeconds(d));
5060             if (delta < deltaSec || deltaSec == -1) {
5061                 result = i;
5062                 deltaSec = delta;
5063             }
5064         }
5065         return result;
5066     }
5067 
5068     /**
5069      * Add a listener to be informed when the focus changes.
5070      * 
5071      * @param listener listener to be added.
5072      */
5073     public synchronized void addFocussedIntervalListener(FocussedIntervalListener listener) {
5074         if (_focussedIntervalListeners == null) {
5075             _focussedIntervalListeners = new Vector<FocussedIntervalListener>(2);
5076         }
5077         _focussedIntervalListeners.add(listener);
5078     }
5079 
5080     /**
5081      * Remove a focussedIntervalListener.
5082      * 
5083      * @param listener listener to be removed from the listener list.
5084      */
5085     public synchronized void remFocussedIntervalListener(FocussedIntervalListener listener) {
5086         if (_focussedIntervalListeners != null) {
5087             _focussedIntervalListeners.remove(listener);
5088         }
5089     }
5090 
5091     /**
5092      * Inform listeners about a focus change.
5093      * 
5094      * @param newFocussedRow new (currently focussed row)
5095      * @param newFocussedInterval new (currently) focussed interval
5096      */
5097     protected synchronized void fireFocussedIntervalChange(TimeBarRow newFocussedRow, Interval newFocussedInterval) {
5098         if (_focussedIntervalListeners != null) {
5099             for (FocussedIntervalListener listener : _focussedIntervalListeners) {
5100                 listener.focussedIntervalChanged(_tbvi, newFocussedRow, newFocussedInterval);
5101             }
5102         }
5103     }
5104 
5105     /**
5106      * Add a listener to be informed when the selection rect changes.
5107      * 
5108      * @param listener listener to be added.
5109      */
5110     public synchronized void addSelectionRectListener(ISelectionRectListener listener) {
5111         if (_selectionRectListeners == null) {
5112             _selectionRectListeners = new Vector<ISelectionRectListener>(2);
5113         }
5114         _selectionRectListeners.add(listener);
5115     }
5116 
5117     /**
5118      * Remove a selection rect listener.
5119      * 
5120      * @param listener listener to be removed from the listener list.
5121      */
5122     public synchronized void remSelectionRectListener(ISelectionRectListener listener) {
5123         if (_selectionRectListeners != null) {
5124             _selectionRectListeners.remove(listener);
5125         }
5126     }
5127 
5128     /**
5129      * Inform selection rect listeners about a change of the selection rect.
5130      */
5131     protected void fireSelectionRectChanged() {
5132         if (_selectionRectListeners != null) {
5133             for (ISelectionRectListener listener : _selectionRectListeners) {
5134                 boolean horizontal = _orientation == Orientation.HORIZONTAL;
5135                 Rectangle curSelRect = _selectionRect;
5136                 // first get the rows
5137                 List<TimeBarRow> rows = horizontal ? getRows(curSelRect.y, curSelRect.y + curSelRect.height) : getRows(
5138                         curSelRect.x, curSelRect.x + curSelRect.width);
5139                 // calculate the dates
5140                 JaretDate begin = horizontal ? dateForCoord(curSelRect.x) : dateForCoord(curSelRect.y);
5141                 JaretDate end = horizontal ? dateForCoord(curSelRect.x + curSelRect.width) : dateForCoord(curSelRect.y
5142                         + curSelRect.height);
5143                 // inform the listeners
5144                 listener.selectionRectChanged(this, begin, end, rows);
5145             }
5146         }
5147     }
5148 
5149     /**
5150      * Inform selectionRectListeners about the end of the selection rect.
5151      */
5152     protected void fireSelectionRectClosed() {
5153         if (_selectionRectListeners != null) {
5154             for (ISelectionRectListener listener : _selectionRectListeners) {
5155                 listener.selectionRectClosed(this);
5156             }
5157         }
5158     }
5159 
5160     /**
5161      * Inform selection rect listeners about a change of the region rect.
5162      */
5163     protected void fireRegionRectChanged() {
5164         if (_selectionRectListeners != null) {
5165             for (ISelectionRectListener listener : _selectionRectListeners) {
5166                 // inform the listeners
5167                 listener.regionRectChanged(this, _regionSelection);
5168             }
5169         }
5170     }
5171 
5172     /**
5173      * Inform selectionRectListeners about the end of the region rect.
5174      */
5175     protected void fireRegionRectClosed() {
5176         if (_selectionRectListeners != null) {
5177             for (ISelectionRectListener listener : _selectionRectListeners) {
5178                 listener.regionRectClosed(this);
5179             }
5180         }
5181     }
5182 
5183     // ************* end focus handling
5184 
5185     /**
5186      * Handle key events from the time bar viewers.
5187      * 
5188      * @param keyCode keyCode (Swing)
5189      * @param modifierMask (Swing)
5190      */
5191     public void handleKeyPressed(int keyCode, int modifierMask) {
5192         boolean horizontal = _orientation.equals(Orientation.HORIZONTAL);
5193         if (modifierMask == 0) {
5194             switch (keyCode) {
5195             case KeyEvent.VK_RIGHT:
5196                 if (horizontal) {
5197                     moveFocusRight();
5198                 } else {
5199                     moveFocusDown();
5200                 }
5201                 break;
5202             case KeyEvent.VK_LEFT:
5203                 if (horizontal) {
5204                     moveFocusLeft();
5205                 } else {
5206                     moveFocusUp();
5207                 }
5208                 break;
5209             case KeyEvent.VK_UP:
5210                 if (horizontal) {
5211                     moveFocusUp();
5212                 } else {
5213                     moveFocusLeft();
5214                 }
5215                 break;
5216             case KeyEvent.VK_DOWN:
5217                 if (horizontal) {
5218                     moveFocusDown();
5219                 } else {
5220                     moveFocusRight();
5221                 }
5222                 break;
5223             case KeyEvent.VK_SPACE:
5224                 selectFocussedInterval(false);
5225                 break;
5226             case KeyEvent.VK_ESCAPE:
5227                 cancelDrag();
5228                 break;
5229 
5230             default:
5231                 // do nothing
5232                 break;
5233             }
5234         } else if ((modifierMask & InputEvent.SHIFT_DOWN_MASK) != 0) {
5235             switch (keyCode) {
5236             case KeyEvent.VK_RIGHT:
5237                 growRight(_focussedRow, _focussedInterval, _keyboardChangeDelta);
5238                 break;
5239             case KeyEvent.VK_LEFT:
5240                 growLeft(_focussedRow, _focussedInterval, _keyboardChangeDelta);
5241                 break;
5242             default:
5243                 // do nothing
5244                 break;
5245             }
5246         } else if ((modifierMask & InputEvent.ALT_DOWN_MASK) != 0) {
5247             switch (keyCode) {
5248             case KeyEvent.VK_RIGHT:
5249                 growLeft(_focussedRow, _focussedInterval, -_keyboardChangeDelta);
5250                 break;
5251             case KeyEvent.VK_LEFT:
5252                 growRight(_focussedRow, _focussedInterval, -_keyboardChangeDelta);
5253                 break;
5254             default:
5255                 // do nothing
5256                 break;
5257             }
5258         } else if ((modifierMask & InputEvent.CTRL_DOWN_MASK) != 0) {
5259             switch (keyCode) {
5260             case KeyEvent.VK_RIGHT:
5261                 moveInterval(_focussedRow, _focussedInterval, _keyboardChangeDelta);
5262                 break;
5263             case KeyEvent.VK_LEFT:
5264                 moveInterval(_focussedRow, _focussedInterval, -_keyboardChangeDelta);
5265                 break;
5266             case KeyEvent.VK_SPACE:
5267                 selectFocussedInterval(true);
5268                 break;
5269             case KeyEvent.VK_ESCAPE:
5270                 cancelDrag();
5271                 break;
5272             default:
5273                 // do nothing
5274                 break;
5275             }
5276         }
5277     }
5278 
5279     /**
5280      * Select (add to selection) the curently focussed interval.
5281      * 
5282      * @param add if true add the interval to selection, if false selection will be replaced
5283      */
5284     private void selectFocussedInterval(boolean add) {
5285         if (_focussedInterval != null) {
5286             if (!add) {
5287                 _selectionModel.setSelectedInterval(_focussedInterval);
5288             } else {
5289                 if (!_selectionModel.isSelected(_focussedInterval)) {
5290                     _selectionModel.addSelectedInterval(_focussedInterval);
5291                 } else { // deselect
5292                     _selectionModel.remSelectedInterval(_focussedInterval);
5293                 }
5294             }
5295         }
5296     }
5297 
5298     /**
5299      * Move an interval.
5300      * 
5301      * @param row row of the interval
5302      * @param interval the interval to move
5303      * @param deltaSeconds seconds delta
5304      */
5305     private void moveInterval(TimeBarRow row, Interval interval, int deltaSeconds) {
5306         if (interval == null) {
5307             return;
5308         }
5309         JaretDate newBegin = interval.getBegin().copy().advanceSeconds(deltaSeconds);
5310         boolean allowed = isShiftingAllowed(row, interval, newBegin);
5311         if (allowed) {
5312             JaretDate oldBegin = interval.getBegin();
5313             JaretDate oldEnd = interval.getEnd();
5314 
5315             interval.setBegin(newBegin);
5316             interval.setEnd(interval.getEnd().advanceSeconds(deltaSeconds));
5317 
5318             // inform the timebar change listener
5319             fireIntervalChangeStarted(row, interval);
5320             fireIntervalChanged(row, interval, oldBegin, oldEnd);
5321         }
5322 
5323     }
5324 
5325     /**
5326      * Alter the duration of an interval by a delta of seconds altering the end of the interval.
5327      * 
5328      * @param row row of the interval
5329      * @param interval interval to alter
5330      * @param deltaSeconds delta in seconds
5331      */
5332     private void growLeft(TimeBarRow row, Interval interval, int deltaSeconds) {
5333         if (interval == null) {
5334             return;
5335         }
5336         JaretDate newBegin = interval.getBegin().copy().backSeconds(deltaSeconds);
5337         boolean allowed = isNewBeginAllowed(row, interval, newBegin);
5338         if (allowed) {
5339             JaretDate oldBegin = interval.getBegin();
5340             interval.setBegin(newBegin);
5341 
5342             // inform the timebar change listener
5343             fireIntervalChangeStarted(row, interval);
5344             fireIntervalChanged(row, interval, oldBegin, interval.getEnd());
5345         }
5346     }
5347 
5348     /**
5349      * Alter the duration of an interval by a delta of seconds altering the begin of the interval.
5350      * 
5351      * @param row row of the interval
5352      * @param interval interval to alter
5353      * @param deltaSeconds delta in seconds
5354      */
5355     private void growRight(TimeBarRow row, Interval interval, int deltaSeconds) {
5356         if (interval == null) {
5357             return;
5358         }
5359         JaretDate newEnd = interval.getEnd().copy().advanceSeconds(deltaSeconds);
5360         boolean allowed = isNewEndAllowed(row, interval, newEnd);
5361         if (allowed) {
5362             JaretDate oldEnd = interval.getEnd();
5363             interval.setEnd(newEnd);
5364 
5365             // inform the timebar change listener
5366             fireIntervalChangeStarted(row, interval);
5367             fireIntervalChanged(row, interval, interval.getBegin(), oldEnd);
5368         }
5369     }
5370 
5371     /**
5372      * Retrieve the keyboardChangeDelta currently used.
5373      * 
5374      * @return the keyboardChangeDelta in seconds
5375      */
5376     public int getKeyboardChangeDelta() {
5377         return _keyboardChangeDelta;
5378     }
5379 
5380     /**
5381      * Set the delta for resizing and moving via keyboard.
5382      * 
5383      * @param keyboardChangeDelta the keyboardChangeDelta in seconds to set
5384      */
5385     public void setKeyboardChangeDelta(int keyboardChangeDelta) {
5386         _keyboardChangeDelta = keyboardChangeDelta;
5387     }
5388 
5389     /**
5390      * Retrieve the state of the variable xscale state. If true a list of intervals contlrols different pps values for
5391      * different intervals on the axis.
5392      * 
5393      * @return true if a varying pps value is used
5394      */
5395     public boolean hasVariableXScale() {
5396         return _variableXScale;
5397     }
5398 
5399     /**
5400      * Set the state for the variable xscale.
5401      * 
5402      * @param state true if a variable scale should be used.
5403      */
5404     public void setVariableXScale(boolean state) {
5405         if (state != _variableXScale) {
5406             _variableXScale = state;
5407             if (state) {
5408                 setOptimizeScrolling(false); // optimized scrolling will not
5409                 // work together with a variable
5410                 // xscale
5411                 _xScalePPSIntervalRow = new DefaultTimeBarNode(new DefaultRowHeader("PPSROW"));
5412                 if (_repaintingRowListener == null) {
5413                     _repaintingRowListener = new TimeBarRowListener() {
5414                         // TimeBarRow listener that repaints every change
5415                         // and recalculates the break pps values
5416                         public void elementAdded(TimeBarRow row, Interval element) {
5417                             updateTimeScaleBreaks();
5418                             _tbvi.repaint();
5419                         }
5420 
5421                         public void elementChanged(TimeBarRow row, Interval element) {
5422                             updateTimeScaleBreaks();
5423                             _tbvi.repaint();
5424                         }
5425 
5426                         public void elementRemoved(TimeBarRow row, Interval element) {
5427                             _tbvi.repaint();
5428                         }
5429 
5430                         public void headerChanged(TimeBarRow row, TimeBarRowHeader newHeader) {
5431                             _tbvi.repaint();
5432                         }
5433 
5434                         public void rowDataChanged(TimeBarRow row) {
5435                             updateTimeScaleBreaks();
5436                             _tbvi.repaint();
5437                         }
5438                     };
5439                 }
5440                 _xScalePPSIntervalRow.addTimeBarRowListener(_repaintingRowListener);
5441             } else {
5442                 if (_xScalePPSIntervalRow != null && _repaintingRowListener != null) {
5443                     _xScalePPSIntervalRow.remTimeBarRowListener(_repaintingRowListener);
5444                 }
5445                 _xScalePPSIntervalRow = null;
5446             }
5447             if (_tbvi != null) {
5448                 _tbvi.repaint();
5449             }
5450             updateScrollBars();
5451         }
5452     }
5453 
5454     /**
5455      * Retrieve the row that hold intervals (PpsIntervals) defining the pps value for different intervals.
5456      * 
5457      * @return the row or <code>null</code> if no variable xscale has been defined.
5458      */
5459     public TimeBarNode getPpsRow() {
5460         return _xScalePPSIntervalRow;
5461     }
5462 
5463     /**
5464      * Retrieve the selection delta used to determine whether a marker or interval edge is clicked/dragged.
5465      * 
5466      * @return max distance for detection
5467      */
5468     public int getSelectionDelta() {
5469         return _selectionDelta;
5470     }
5471 
5472     /**
5473      * Set the selection delta used to determine whether a marker or interval edge is clicked/dragged.
5474      * 
5475      * @param selectionDelta max distance for detection
5476      */
5477     public void setSelectionDelta(int selectionDelta) {
5478         _selectionDelta = selectionDelta;
5479     }
5480 
5481     /**
5482      * Check whether it is allowed to drag the limiting lines of the hierarhy ara and the header (yaxis) area.
5483      * 
5484      * @return true if dragging is allowed.
5485      */
5486     public boolean isLineDraggingAllowed() {
5487         return _lineDraggingAllowed;
5488     }
5489 
5490     /**
5491      * Set the allowance for line dragging of the limiting lines for hierarchy and header(yaxis) areas.
5492      * 
5493      * @param lineDraggingAllowed true for enabling the drag possibility
5494      */
5495     public void setLineDraggingAllowed(boolean lineDraggingAllowed) {
5496         _lineDraggingAllowed = lineDraggingAllowed;
5497     }
5498 
5499     /**
5500      * Check whether th delegate is setup for millisecond accuracy. This will only have an impact on the x scroll bar.
5501      * 
5502      * @return true if ms accuracy ist set
5503      */
5504     public boolean isMilliAccuracy() {
5505         return _milliAccuracy;
5506     }
5507 
5508     /**
5509      * Set the delegates status concerning millisecond accuracy. If set to true the x scroll bar will operate in
5510      * milliseconds.
5511      * 
5512      * @param milliAccuracy true to use ms accuracy
5513      */
5514     public void setMilliAccuracy(boolean milliAccuracy) {
5515         _milliAccuracy = milliAccuracy;
5516     }
5517 
5518     /**
5519      * Set margins to the left and top to be included in geometry calculations. This is introduced for supporting
5520      * printing (margins) but might be useful for insets if necessary.
5521      * 
5522      * @param marginLeft left margin (pixel)
5523      * @param marginTop top margin (pixel)
5524      */
5525     public void setDrawingOffset(int marginLeft, int marginTop) {
5526         _offsetLeft = marginLeft;
5527         _offsetTop = marginTop;
5528     }
5529 
5530     /**
5531      * Get whether optimzed scrollnig is used.
5532      * 
5533      * @return true if optimized scrolling is used
5534      */
5535     public boolean getOptimizeScrolling() {
5536         return _optimizeScrolling;
5537     }
5538 
5539     /**
5540      * Set whether optimized scrolling should be used (not allowed togeter with a variable xscale).
5541      * 
5542      * @param optimizeScrolling true for optimized scrolling
5543      */
5544     public void setOptimizeScrolling(boolean optimizeScrolling) {
5545         if (_variableXScale && optimizeScrolling) {
5546             throw new RuntimeException("Optimized scrolling can not be used together with a variable xscale");
5547         }
5548         _optimizeScrolling = optimizeScrolling;
5549     }
5550 
5551     /**
5552      * Retrieve the orientation of the viewer.
5553      * 
5554      * @return the orientation of the viewer
5555      */
5556     public Orientation getOrientation() {
5557         return _orientation;
5558     }
5559 
5560     /**
5561      * Set the orientation of the viewer.
5562      * 
5563      * @param orientation the new orientation for the viewer
5564      */
5565     public void setOrientation(Orientation orientation) {
5566         if (_orientation != orientation) {
5567             _orientation = orientation;
5568             updateScrollBars();
5569             if (_tbvi != null) {
5570                 _tbvi.repaint();
5571             }
5572         }
5573     }
5574 
5575     /**
5576      * Get the number of rows (columns) that the viewer scales itself to.
5577      * 
5578      * @return number of rows to display or -1 if no scale has been set
5579      */
5580     public int getAutoScaleRows() {
5581         return _autoScaleRows;
5582     }
5583 
5584     /**
5585      * Set a number of rows (columns) to be displayed by the viewer. The row height will always be changed to math the
5586      * number of rows to display.
5587      * 
5588      * @param rows the number of rows or -1 for no special scaling (default)
5589      */
5590     public void setAutoScaleRows(int rows) {
5591         if (_autoScaleRows != rows) {
5592             _autoScaleRows = rows;
5593             if (_tbvi != null) {
5594                 _tbvi.repaint();
5595             }
5596         }
5597     }
5598 
5599     /**
5600      * Add a listener to be informed about interval changes.
5601      * 
5602      * @param listener listener
5603      */
5604     public void addTimeBarChangeListener(ITimeBarChangeListener listener) {
5605         if (!_timeBarChangeListeners.contains(listener)) {
5606             _timeBarChangeListeners.add(listener);
5607         }
5608     }
5609 
5610     /**
5611      * Remove a timebar change listener.
5612      * 
5613      * @param listener listener to remove
5614      */
5615     public void removeTimeBarChangeListener(ITimeBarChangeListener listener) {
5616         _timeBarChangeListeners.remove(listener);
5617     }
5618 
5619     /**
5620      * Inform time bar change listeners about a beginning interval change.
5621      * 
5622      * @param row row involved
5623      * @param interval interval to be changed
5624      */
5625     protected void fireIntervalChangeStarted(TimeBarRow row, Interval interval) {
5626         for (ITimeBarChangeListener listener : _timeBarChangeListeners) {
5627             listener.intervalChangeStarted(row, interval);
5628         }
5629     }
5630 
5631     /**
5632      * Inform time bar change listeners about an intermediate interval change.
5633      * 
5634      * @param row row involved
5635      * @param interval interval changing
5636      * @param oldBegin begin when then change started
5637      * @param oldEnd end when the change started
5638      */
5639     protected void fireIntervalIntermediateChange(TimeBarRow row, Interval interval, JaretDate oldBegin,
5640             JaretDate oldEnd) {
5641         for (ITimeBarChangeListener listener : _timeBarChangeListeners) {
5642             listener.intervalIntermediateChange(row, interval, oldBegin, oldEnd);
5643         }
5644 
5645     }
5646 
5647     /**
5648      * Inform time bar change listeners about a finished interval change.
5649      * 
5650      * @param row row involved
5651      * @param interval interval that has been changed
5652      * @param oldBegin begin when then change started
5653      * @param oldEnd end when the change started
5654      */
5655     protected void fireIntervalChanged(TimeBarRow row, Interval interval, JaretDate oldBegin, JaretDate oldEnd) {
5656         for (ITimeBarChangeListener listener : _timeBarChangeListeners) {
5657             listener.intervalChanged(row, interval, oldBegin, oldEnd);
5658         }
5659 
5660     }
5661 
5662     /**
5663      * Inform time bar change listeners about a cancelled interval change.
5664      * 
5665      * @param row row involved
5666      * @param interval interval that's change has been cancelled
5667      */
5668     protected void fireIntervalChangeCancelled(TimeBarRow row, Interval interval) {
5669         for (ITimeBarChangeListener listener : _timeBarChangeListeners) {
5670             listener.intervalChangeCancelled(row, interval);
5671         }
5672     }
5673 
5674     /**
5675      * Informa listener about a started marker drag.
5676      * 
5677      * @param marker marker that is beeing dragged
5678      */
5679     protected void fireMarkerDragStarted(TimeBarMarker marker) {
5680         for (ITimeBarChangeListener listener : _timeBarChangeListeners) {
5681             listener.markerDragStarted(marker);
5682         }
5683     }
5684 
5685     /**
5686      * Inform listeners about stopping of a marker drag.
5687      * 
5688      * @param marker marker that has been dragged
5689      */
5690     protected void fireMarkerDragStopped(TimeBarMarker marker) {
5691         for (ITimeBarChangeListener listener : _timeBarChangeListeners) {
5692             listener.markerDragStopped(marker);
5693         }
5694     }
5695 
5696     /**
5697      * Check whether row height dragging is allowed.
5698      * 
5699      * @return true if row height dragging is enabled
5700      */
5701     public boolean isRowHeightDraggingAllowed() {
5702         return _rowHeightDraggingAllowed;
5703     }
5704 
5705     /**
5706      * Set whether row height dragging should be allowed.
5707      * 
5708      * @param rowHeightDraggingAllowed true for allowance
5709      */
5710     public void setRowHeightDraggingAllowed(boolean rowHeightDraggingAllowed) {
5711         if (_rowHeightDraggingAllowed != rowHeightDraggingAllowed) {
5712             _rowHeightDraggingAllowed = rowHeightDraggingAllowed;
5713             if (_tbvi != null) {
5714                 _tbvi.firePropertyChangeX(TimeBarViewerInterface.PROPERTYNAME_ROWHEIGHTDRAGGINGALLOWED,
5715                         !rowHeightDraggingAllowed, rowHeightDraggingAllowed);
5716             }
5717         }
5718     }
5719 
5720     /**
5721      * Check whether a given point is in the row axis area (hierarchy or header).
5722      * 
5723      * @param x x coordinate
5724      * @param y y coordinate
5725      * @return <code>true</code> if the point is in either hierarchy or header area
5726      */
5727     public boolean isInRowAxis(int x, int y) {
5728         return _hierarchyRect.contains(x, y) || _yAxisRect.contains(x, y);
5729     }
5730 
5731     /**
5732      * Check whether a given point is in the main diagram area.
5733      * 
5734      * @param x x coordinate
5735      * @param y y coordinate
5736      * @return <code>true</code> if the point is in the diagram rectangle
5737      */
5738     public boolean isInDiagram(int x, int y) {
5739         return _diagramRect.contains(x, y);
5740     }
5741 
5742     /**
5743      * Retrieve the strategy for filtering when painting (see {@link #setStrictClipTimeCheck(boolean)}).
5744      * 
5745      * @return <code>true</code> if strict checking is enabled
5746      */
5747     public boolean getStrictClipTimeCheck() {
5748         return _strictClipTimeCheck;
5749     }
5750 
5751     /**
5752      * Set the optimization strategy for interval filetering when painting.
5753      * 
5754      * @param strictClipTimeCheck If set to true, intervals are filtered strictly by their interval bounds, disallowing
5755      * rendering beyond the bounding box calculated by the interval bounds. Defaults to false resulting in filtering by
5756      * the preferred drawing area.
5757      */
5758     public void setStrictClipTimeCheck(boolean strictClipTimeCheck) {
5759         _strictClipTimeCheck = strictClipTimeCheck;
5760     }
5761 
5762     /**
5763      * Retrieve the time to be additionally considered (looking back) when deciding which intervals are to be painted.
5764      * 
5765      * @return time in mnutes
5766      */
5767     public int getScrollLookBackMinutes() {
5768         return _scrollLookBackMinutes;
5769     }
5770 
5771     /**
5772      * Set the additional time to be considered when deciding whether to draw an interval looking back.
5773      * 
5774      * @param scrollLookBackMinutes time in minutes
5775      */
5776     public void setScrollLookBackMinutes(int scrollLookBackMinutes) {
5777         _scrollLookBackMinutes = scrollLookBackMinutes;
5778     }
5779 
5780     /**
5781      * Retrieve the time to be additionally considered (looking forward) when deciding which intervals are to be
5782      * painted.
5783      * 
5784      * @return time in mnutes
5785      */
5786     public int getScrollLookForwardMinutes() {
5787         return _scrollLookForwardMinutes;
5788     }
5789 
5790     /**
5791      * Set the additional time to be considered when deciding whether to draw an interval looking forward.
5792      * 
5793      * @param scrollLookForwardMinutes time in minutes
5794      */
5795     public void setScrollLookForwardMinutes(int scrollLookForwardMinutes) {
5796         _scrollLookForwardMinutes = scrollLookForwardMinutes;
5797     }
5798 
5799     /**
5800      * Retrieve the used strategy for determing overlap information.
5801      * 
5802      * @return the overlap strategy
5803      */
5804     public IOverlapStrategy getOverlapStrategy() {
5805         return _overlapStrategy;
5806     }
5807 
5808     /**
5809      * Set the strategy to be used for calculating overlap information.
5810      * 
5811      * @param overlapStrategy the strytegy to be used. May not be <code>null</code>.
5812      */
5813     public void setOverlapStrategy(IOverlapStrategy overlapStrategy) {
5814         if (overlapStrategy == null) {
5815             throw new IllegalArgumentException("Strategy may not be null");
5816         }
5817         IOverlapStrategy oldStrategy = _overlapStrategy;
5818         _overlapStrategy = overlapStrategy;
5819         oldStrategy.dispose(); // tell the old strategy it is no longer used
5820     }
5821 
5822     /**
5823      * Retrieve the currently set autoscroll delta.
5824      * 
5825      * @return the autoscroll delat in pixel
5826      */
5827     public int getAutoscrollDelta() {
5828         return _autoscrollDelta;
5829     }
5830 
5831     /**
5832      * Set the autoscroll delta. This value will be used to deteremine the autoscroll deltas when the mouse pointer is
5833      * not in the diagram rectangle. It is specified in pixel so it is always relative to the timescale. The value will
5834      * also be used to limit the maximum delta when resizing an interval (edge dragging) with the cursor outside the
5835      * diagram rectangle.
5836      * 
5837      * @param autoscrollDelta delta in pixel
5838      */
5839     public void setAutoscrollDelta(int autoscrollDelta) {
5840         _autoscrollDelta = autoscrollDelta;
5841     }
5842 
5843     /**
5844      * If <code>true</code> all selected intervals will be dragged together with the interval on that the drag happened.
5845      * 
5846      * @return the state of the flag
5847      */
5848     public boolean getDragAllSelectedIntervals() {
5849         return _dragAllSelectedIntervals;
5850     }
5851 
5852     /**
5853      * If set to <code>true</code> all selected intervals are dragged when an interval is dragged. The default is false.
5854      * 
5855      * @param dragAllSelectedIntervals <code>true</code> to drag all selcted intervals
5856      */
5857     public void setDragAllSelectedIntervals(boolean dragAllSelectedIntervals) {
5858         _dragAllSelectedIntervals = dragAllSelectedIntervals;
5859     }
5860 
5861     /**
5862      * Retrieve the state of the scroll to focus flag.
5863      * 
5864      * @return <code>true</code> if the viewer should scroll to the focussed interval
5865      */
5866     public boolean getScrollOnFocus() {
5867         return _scrollOnFocus;
5868     }
5869 
5870     /**
5871      * If set to true the viewer will scroll to the begin of an interval if it's focussed.
5872      * 
5873      * @param scrollOnFocus <code>true</code> for scrolling to the focussed interval
5874      */
5875     public void setScrollOnFocus(boolean scrollOnFocus) {
5876         _scrollOnFocus = scrollOnFocus;
5877     }
5878 
5879     /**
5880      * Retrieve whether the root node is shown when using a hierachical model.
5881      * 
5882      * @return <code>true</code> if the root is not shown
5883      */
5884     public boolean getHideRoot() {
5885         return _hideRoot;
5886     }
5887 
5888     /**
5889      * Set whether the root node should be shown when using a hierachical model.
5890      * 
5891      * @param hideRoot <code>true</code> if the root node should be hidden
5892      */
5893     public void setHideRoot(boolean hideRoot) {
5894         _hideRoot = hideRoot;
5895         if (_model instanceof StdHierarchicalTimeBarModel) {
5896             ((StdHierarchicalTimeBarModel) _model).setHideRoot(hideRoot);
5897         }
5898     }
5899 
5900     /** if true markers can be dragged in the diagram area. */
5901     protected boolean _markerDraggingInDiagramArea = false;
5902 
5903     /**
5904      * Retrieve whether marker dragging in the diagram area is activated.
5905      * 
5906      * @return <code>true</code> if marker dragging in the diagram area is allowed
5907      */
5908     public boolean getMarkerDraggingInDiagramArea() {
5909         return _markerDraggingInDiagramArea;
5910     }
5911 
5912     /**
5913      * Set whether marker dragging is allowed in the diagram area (If intervals are modificable the marker will only be
5914      * grabbed when no other operation is applicable).
5915      * 
5916      * @param allowed <code>true</code> for allowing marker drag in the diagram area.
5917      */
5918     public void setMarkerDraggingInDiagramArea(boolean allowed) {
5919         _markerDraggingInDiagramArea = allowed;
5920     }
5921 
5922     /**
5923      * Check whether a marker is beeing dragged.
5924      * 
5925      * @return <code>true</code> if a marker drag is in progress
5926      */
5927     public boolean isMarkerDraggingInProgress() {
5928         return _draggedMarker != null;
5929     }
5930 
5931     /**
5932      * Retrieve the interval selection strategy used.
5933      * 
5934      * @return the strategy
5935      */
5936     public IIntervalSelectionStrategy getIntervalSelectionStrategy() {
5937         return _intervalSelectionStrategy;
5938     }
5939 
5940     /**
5941      * Set the interval selection strategy.
5942      * 
5943      * @param intervalSelectionStrategy the interval selection strategy to use.
5944      */
5945     public void setIntervalSelectionStrategy(IIntervalSelectionStrategy intervalSelectionStrategy) {
5946         _intervalSelectionStrategy = intervalSelectionStrategy;
5947     }
5948 
5949     /**
5950      * Calculate the rectangle for screen representation for a tbrect (time and rows).
5951      * 
5952      * @param tbrect tbrect to be painted
5953      * @return rectangle in screen coordinates
5954      */
5955     public Rectangle calcRect(TBRect tbrect) {
5956         Rectangle result = new Rectangle();
5957         if (_orientation == Orientation.HORIZONTAL) {
5958             result.x = xForDate(tbrect.startDate);
5959             result.width = xForDate(tbrect.endDate) - result.x;
5960             result.y = getRowBounds(tbrect.startRow).y;
5961             result.height = getRowBounds(tbrect.endRow).y + getTimeBarViewState().getRowHeight(tbrect.endRow)
5962                     - result.y;
5963         } else {
5964             result.y = xForDate(tbrect.startDate);
5965             result.height = xForDate(tbrect.endDate) - result.x;
5966             result.x = getRowBounds(tbrect.startRow).y;
5967             result.width = getRowBounds(tbrect.endRow).y + getTimeBarViewState().getRowHeight(tbrect.endRow) - result.y;
5968         }
5969         return result;
5970     }
5971 
5972     /**
5973      * Retrieve the selected region.
5974      * 
5975      * @return the selected region or <code>null</code>
5976      */
5977     public TBRect getRegionRect() {
5978         return _regionSelection;
5979     }
5980 
5981     /**
5982      * Remove the selction of a region if existent.
5983      */
5984     public void clearRegionRect() {
5985         if (_regionSelection != null) {
5986             _regionSelection = null;
5987             _lastRegionSelection = null;
5988             _tbvi.repaint();
5989             fireRegionRectClosed();
5990         }
5991     }
5992 
5993     /**
5994      * Enable/Disable region selections.
5995      * 
5996      * @param enabled <code>true</code> enabling
5997      */
5998     public void setRegionRectEnable(boolean enabled) {
5999         _regionRectEnabled = enabled;
6000         clearRegionRect();
6001     }
6002 
6003     /**
6004      * Retrieve whether region selections are enabled.
6005      * 
6006      * @return true if region selections are enabled
6007      */
6008     public boolean getRegionRectEnable() {
6009         return _regionRectEnabled;
6010     }
6011 
6012     /**
6013      * Set whether all intervals in a non overlapping drawn ro should use the same height/width.
6014      * 
6015      * @param useUniformHeight <code>true</code> for uniform heights/widths
6016      */
6017     public void setUseUniformHeight(boolean useUniformHeight) {
6018         if (useUniformHeight != _useUniformHeight) {
6019             _useUniformHeight = useUniformHeight;
6020             _tbvi.repaint();
6021         }
6022     }
6023 
6024     /**
6025      * Retrieve whether uniform height is use for all intervals in a row.
6026      * 
6027      * @return <code>true</code> if all intervals in a non overlapping drawn row should use the same height/width
6028      */
6029     public boolean getUseUniformHeight() {
6030         return _useUniformHeight;
6031     }
6032 
6033     /**
6034      * Handle a component resize and scroll down if an empty space would be shown in the newly exposed screen estate.
6035      */
6036     public void componentResized() {
6037         if (_diagramRect.width != 0) { // do not do any scrolling if the component has not been painted before
6038             preparePaint(_tbvi.getWidth(), _tbvi.getHeight());
6039 
6040             int numRows = _rowList.size();
6041             if (numRows > 0) {
6042                 TimeBarRow row = _rowList.get(_rowList.size() - 1);
6043                 int absStartY = getAbsPosForRow(_firstRow) + _firstRowPixelOffset;
6044                 int absLastY = getAbsPosForRow(_rowList.size() - 1);
6045 
6046                 if ((absLastY + _timeBarViewState.getRowHeight(row)) - absStartY < _diagramRect.height) {
6047                     setLastRow(row);
6048                 }
6049             }
6050         }
6051         updateScrollBars();
6052     }
6053 
6054 
6055 }