View Javadoc

1   /*
2    *  File: JaretTable.java 
3    *  Copyright (c) 2004-2007  Peter Kliem (Peter.Kliem@jaret.de)
4    *  A commercial license is available, see http://www.jaret.de.
5    *
6    * All rights reserved. This program and the accompanying materials
7    * are made available under the terms of the Common Public License v1.0
8    * which accompanies this distribution, and is available at
9    * http://www.eclipse.org/legal/cpl-v10.html
10   */
11  package de.jaret.util.ui.table;
12  
13  import java.beans.PropertyChangeEvent;
14  import java.beans.PropertyChangeListener;
15  import java.beans.PropertyChangeSupport;
16  import java.lang.reflect.Constructor;
17  import java.util.ArrayList;
18  import java.util.Collection;
19  import java.util.Collections;
20  import java.util.Comparator;
21  import java.util.Date;
22  import java.util.HashMap;
23  import java.util.HashSet;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Set;
27  import java.util.Vector;
28  
29  import org.eclipse.swt.SWT;
30  import org.eclipse.swt.events.DisposeEvent;
31  import org.eclipse.swt.events.DisposeListener;
32  import org.eclipse.swt.events.KeyEvent;
33  import org.eclipse.swt.events.KeyListener;
34  import org.eclipse.swt.events.MouseEvent;
35  import org.eclipse.swt.events.MouseListener;
36  import org.eclipse.swt.events.MouseMoveListener;
37  import org.eclipse.swt.events.MouseTrackListener;
38  import org.eclipse.swt.events.PaintEvent;
39  import org.eclipse.swt.events.PaintListener;
40  import org.eclipse.swt.events.SelectionAdapter;
41  import org.eclipse.swt.events.SelectionEvent;
42  import org.eclipse.swt.graphics.Color;
43  import org.eclipse.swt.graphics.GC;
44  import org.eclipse.swt.graphics.Image;
45  import org.eclipse.swt.graphics.Point;
46  import org.eclipse.swt.graphics.Rectangle;
47  import org.eclipse.swt.widgets.Canvas;
48  import org.eclipse.swt.widgets.Composite;
49  import org.eclipse.swt.widgets.Control;
50  import org.eclipse.swt.widgets.Display;
51  import org.eclipse.swt.widgets.Event;
52  import org.eclipse.swt.widgets.Listener;
53  import org.eclipse.swt.widgets.Menu;
54  import org.eclipse.swt.widgets.ScrollBar;
55  import org.eclipse.swt.widgets.Shell;
56  
57  import de.jaret.util.date.JaretDate;
58  import de.jaret.util.misc.PropertyObservable;
59  import de.jaret.util.misc.PropertyObservableBase;
60  import de.jaret.util.ui.table.editor.BooleanCellEditor;
61  import de.jaret.util.ui.table.editor.DateCellEditor;
62  import de.jaret.util.ui.table.editor.DoubleCellEditor;
63  import de.jaret.util.ui.table.editor.EnumComboEditor;
64  import de.jaret.util.ui.table.editor.ICellEditor;
65  import de.jaret.util.ui.table.editor.IntegerCellEditor;
66  import de.jaret.util.ui.table.editor.TextCellEditor;
67  import de.jaret.util.ui.table.filter.DefaultAutoFilter;
68  import de.jaret.util.ui.table.filter.IAutoFilter;
69  import de.jaret.util.ui.table.filter.IRowFilter;
70  import de.jaret.util.ui.table.model.DefaultHierarchicalTableViewState;
71  import de.jaret.util.ui.table.model.DefaultTableViewState;
72  import de.jaret.util.ui.table.model.IColumn;
73  import de.jaret.util.ui.table.model.IHierarchicalJaretTableModel;
74  import de.jaret.util.ui.table.model.IHierarchicalTableViewState;
75  import de.jaret.util.ui.table.model.IJaretTableCell;
76  import de.jaret.util.ui.table.model.IJaretTableModel;
77  import de.jaret.util.ui.table.model.IJaretTableModelListener;
78  import de.jaret.util.ui.table.model.IJaretTableSelection;
79  import de.jaret.util.ui.table.model.IJaretTableSelectionModel;
80  import de.jaret.util.ui.table.model.IJaretTableSelectionModelListener;
81  import de.jaret.util.ui.table.model.IRow;
82  import de.jaret.util.ui.table.model.IRowSorter;
83  import de.jaret.util.ui.table.model.ITableFocusListener;
84  import de.jaret.util.ui.table.model.ITableNode;
85  import de.jaret.util.ui.table.model.ITableViewState;
86  import de.jaret.util.ui.table.model.ITableViewStateListener;
87  import de.jaret.util.ui.table.model.JaretTableCellImpl;
88  import de.jaret.util.ui.table.model.JaretTableSelectionModelImpl;
89  import de.jaret.util.ui.table.model.StdHierarchicalTableModel;
90  import de.jaret.util.ui.table.model.ITableViewState.RowHeightMode;
91  import de.jaret.util.ui.table.renderer.BooleanCellRenderer;
92  import de.jaret.util.ui.table.renderer.DateCellRenderer;
93  import de.jaret.util.ui.table.renderer.DefaultTableHeaderRenderer;
94  import de.jaret.util.ui.table.renderer.DoubleCellRenderer;
95  import de.jaret.util.ui.table.renderer.ICellRenderer;
96  import de.jaret.util.ui.table.renderer.ICellStyle;
97  import de.jaret.util.ui.table.renderer.IHierarchyRenderer;
98  import de.jaret.util.ui.table.renderer.ITableHeaderRenderer;
99  import de.jaret.util.ui.table.renderer.ImageCellRenderer;
100 import de.jaret.util.ui.table.renderer.TableHierarchyRenderer;
101 import de.jaret.util.ui.table.renderer.TextCellRenderer;
102 import de.jaret.util.ui.table.strategies.DefaultCCPStrategy;
103 import de.jaret.util.ui.table.strategies.DefaultFillDragStrategy;
104 import de.jaret.util.ui.table.strategies.ICCPStrategy;
105 import de.jaret.util.ui.table.strategies.IFillDragStrategy;
106 
107 /***
108  * Custom drawn table widget for the SWT Toolkit. Always consider using the native table widget!
109  * <p>
110  * The JaretTable features:
111  * </p>
112  * <ul>
113  * <li>Flexible rendering of all elements</li>
114  * <li>CellEditor support</li>
115  * <li>Flat (table) and hierarchical (tree) support</li>
116  * <li>Separation between data model and viewstate</li>
117  * <li>row filtering and sorting without model modification</li>
118  * <li>simple auto filter</li>
119  * <li>drag fill support </li>
120  * <li></li>
121  * <li></li>
122  * </ul>
123  * 
124  * Keyboard controls <table>
125  * <tr>
126  * <td><b>Key</b></td>
127  * <td><b>Function</b></td>
128  * </tr>
129  * <tr>
130  * <td>Shift+Click</td>
131  * <td>Move focus</td>
132  * </tr>
133  * <tr>
134  * <td>Arrows</td>
135  * <td>Move focus</td>
136  * </tr>
137  * <tr>
138  * <td>Shift-Arrow</td>
139  * <td>Select Current cell, shift focus and select newly focussed cell and the rectangle of cells between first and
140  * current cell</td>
141  * </tr>
142  * </table>
143  * 
144  * @author Peter Kliem
145  * @version $Id: JaretTable.java 1076 2010-12-05 13:34:42Z kliem $
146  */
147 public class JaretTable extends Canvas implements ITableViewStateListener, IJaretTableModelListener,
148         IJaretTableSelectionModelListener, PropertyChangeListener, PropertyObservable {
149     /*** true for measuring paint time. */
150     private static final boolean DEBUGPAINTTIME = false;
151 
152     /*** pixel for row resize/selection if no fixed rows are present. */
153     private static final int SELDELTA = 4;
154 
155     /*** size of the marker for fill dragging. */
156     private static final int FILLDRAGMARKSIZE = 4;
157 
158     /*** default height for the header. */
159     private static final int DEFAULTHEADERHEIGHT = 16;
160 
161     /*** default value for the minimal header height. */
162     private static final int DEFAULTMINHEADERHEIGHT = 10;
163 
164     /*** button that is the popuptrigger. */
165     private static final int POPUPTRIGGER = 3;
166 
167     /*** name of the bound property. */
168     public static final String PROPERTYNAME_HEADERHEIGHT = "HeaderHeight";
169     /*** name of the bound property. */
170     public static final String PROPERTYNAME_FIRSTROWIDX = "FirstRowIdx";
171     /*** name of the bound property. */
172     public static final String PROPERTYNAME_FIRSTROWPIXELOFFSET = "FirstRowPixelOffset";
173     /*** name of the bound property. */
174     public static final String PROPERTYNAME_ROWSORTER = "RowSorter";
175     /*** name of the bound property. */
176     public static final String PROPERTYNAME_ROWFILTER = "RowFilter";
177     /*** Pseudo propertyname on which property change is fired whenever the sorting changes. */
178     public static final String PROPERTYNAME_SORTING = "Sorting";
179     /*** Pseudo propertyname on which property change is fired whenever the filtering changes. */
180     public static final String PROPERTYNAME_FILTERING = "Filtering";
181     /*** name of the bound property. */
182     public static final String PROPERTYNAME_AUTOFILTERENABLE = "AutoFilterEnable";
183 
184     // scroll positions of the main table area
185     /*** Index of the first row displayed (may be only a half display). */
186     protected int _firstRowIdx = 0;
187     /*** Pixel offset of the display of the first row. */
188     protected int _firstRowPixelOffset = 0;
189     /*** Index of the first row displayed. */
190     protected int _firstColIdx = 0;
191     /*** pixel offset of the firs displayed column. */
192     protected int _firstColPixelOffset = 0;
193 
194     /*** number of fixed columns. */
195     protected int _fixedColumns = 0;
196     /*** number of fixed rows. */
197     protected int _fixedRows = 0;
198 
199     /*** cell renderer map for columns. */
200     protected Map<IColumn, ICellRenderer> _colCellRendererMap = new HashMap<IColumn, ICellRenderer>();
201 
202     /*** cell renderer map for classes. */
203     protected Map<Class<?>, ICellRenderer> _colClassRendererMap = new HashMap<Class<?>, ICellRenderer>();
204 
205     /*** cell editor map for columns. */
206     protected Map<IColumn, ICellEditor> _colCellEditorMap = new HashMap<IColumn, ICellEditor>();
207 
208     /*** cell editor map for classes. */
209     protected Map<Class<?>, ICellEditor> _colClassEditorMap = new HashMap<Class<?>, ICellEditor>();
210 
211     /*** configuration: support fill dragging. */
212     protected boolean _supportFillDragging = true;
213 
214     /*** fill drag strategy. * */
215     protected IFillDragStrategy _fillDragStrategy = new DefaultFillDragStrategy();
216 
217     /*** Strategy for handling cut copy paste. */
218     protected ICCPStrategy _ccpStrategy = new DefaultCCPStrategy();
219 
220     /*** table model. */
221     protected IJaretTableModel _model;
222 
223     /*** hierarchical table model if used. */
224     protected IHierarchicalJaretTableModel _hierarchicalModel;
225 
226     /*** table viewstate. */
227     protected ITableViewState _tvs = new DefaultTableViewState();
228 
229     /*** List of rows actually diplayed (filtered and ordered). */
230     protected List<IRow> _rows = new ArrayList<IRow>();
231 
232     /*** row filter. */
233     protected IRowFilter _rowFilter;
234 
235     /*** row sorter. */
236     protected IRowSorter _rowSorter;
237 
238     /*** List of columns actually displayed. */
239     protected List<IColumn> _cols = new ArrayList<IColumn>();
240 
241     /*** Rectangle the headers are painted in. */
242     protected Rectangle _headerRect;
243 
244     /*** height of the headers. */
245     protected int _headerHeight = DEFAULTHEADERHEIGHT;
246 
247     /*** minimal height for the header. */
248     protected int _minHeaderHeight = DEFAULTMINHEADERHEIGHT;
249 
250     /*** if true headers will be drawn. */
251     protected boolean _drawHeader = true;
252 
253     /*** rectangle the main table is drawn into (withou fixedcolRect and without fixedRowRect!). */
254     protected Rectangle _tableRect;
255 
256     /*** Renderer used to render the headers. */
257     protected ITableHeaderRenderer _headerRenderer = new DefaultTableHeaderRenderer();
258 
259     /*** Rectangle in which the fixed columns will be painted. */
260     protected Rectangle _fixedColRect;
261 
262     /*** Rectangle in which the fixed rows will be painted. */
263     protected Rectangle _fixedRowRect;
264 
265     /*** cache for the drag marker location. */
266     protected Rectangle _dragMarkerRect;
267 
268     /*** Rectangle the autofilter elements (combos) are placed. */
269     protected Rectangle _autoFilterRect;
270 
271     /*** if true autofilters are enabled and present. */
272     protected boolean _autoFilterEnabled = false;
273 
274     /*** Instance of the interbal RowFilter that makes up the autofilter. */
275     protected AutoFilter _autoFilter = new AutoFilter(this);
276 
277     /*** map containing the actual instantiated autofilters for the different columns. */
278     protected Map<IColumn, IAutoFilter> _autoFilterMap = new HashMap<IColumn, IAutoFilter>();
279 
280     /*** map containing the autofilter classes to use for dedicated content classes. */
281     protected Map<Class<?>, Class<? extends IAutoFilter>> _autoFilterClassMap = new HashMap<Class<?>, Class<? extends IAutoFilter>>();
282 
283     /*** map containing teh autofiletr classes to use for specified columns. */
284     protected Map<IColumn, Class<? extends IAutoFilter>> _autoFilterColumnMap = new HashMap<IColumn, Class<? extends IAutoFilter>>();
285 
286     /*** if true header resizing is allowed. */
287     private boolean _headerResizeAllowed = true;
288 
289     /*** if true row resizes are allowed. */
290     private boolean _rowResizeAllowed = true;
291 
292     /*** if true column resizes are allowed. */
293     private boolean _columnResizeAllowed = true;
294 
295     /***
296      * If true, resizing is only allowed in header and fixed columns (for rows) and the leftmost SELDELTA pixels of
297      * eachrow.
298      */
299     protected boolean _resizeRestriction = false;
300 
301     /*** if true fixed rows will not be affected by sorting operations. */
302     protected boolean _excludeFixedRowsFromSorting = true;
303 
304     /*** global flag for allowing sorting of the table. */
305     protected boolean _allowSorting = true;
306 
307     // focus control
308     /*** row of the focussed cell or null. */
309     protected IRow _focussedRow = null;
310 
311     /*** column of the focussed cell or null. */
312     protected IColumn _focussedColumn = null;
313 
314     /*** Listz of listeners interested in changes of the focussed cell. */
315     protected List<ITableFocusListener> _tableFocusListeners;
316 
317     /*** selection model used by the table. */
318     protected IJaretTableSelectionModel _selectionModel = new JaretTableSelectionModelImpl();
319 
320     // editing
321     /*** cell editor used to edit a cell. will be nun null when editiing. */
322     protected ICellEditor _editor; // if != null editing in progress
323 
324     /*** control of the editor. */
325     protected Control _editorControl = null;
326 
327     /*** row that is edited. */
328     protected IRow _editorRow;
329 
330     /*** context menu used on table headers. */
331     protected Menu _headerContextMenu;
332 
333     /*** context menu used for rows. */
334     protected Menu _rowContextMenu;
335 
336     /*** Delegate to handle property change listener support. */
337     protected PropertyChangeSupport _propertyChangeSupport;
338 
339     /*** row information cache. */
340     protected List<RowInfo> _rowInfoCache = null;
341     /*** column information cache. */
342     protected List<ColInfo> _colInfoCache = new ArrayList<ColInfo>();
343 
344     /***
345      * Simple struct for storing row information.
346      * 
347      * @author Peter Kliem
348      * @version $Id: JaretTable.java 1076 2010-12-05 13:34:42Z kliem $
349      */
350     public class RowInfo {
351         /*** beginning y coordinate. */
352         public int y;
353         /*** row reference. */
354         public IRow row;
355         /*** height of the row. */
356         public int height;
357         /*** fixed row flag. */
358         public boolean fixed = false;
359 
360         /***
361          * Construct a row info instance.
362          * 
363          * @param rowIn row reference
364          * @param yIn begin y
365          * @param heightIn height of the row
366          * @param fixedIn true if the row is a fixed row
367          */
368         public RowInfo(IRow rowIn, int yIn, int heightIn, boolean fixedIn) {
369             this.row = rowIn;
370             this.y = yIn;
371             this.height = heightIn;
372             this.fixed = fixedIn;
373         }
374     }
375 
376     /***
377      * Simple struct for storing column information.
378      * 
379      * @author Peter Kliem
380      * @version $Id: JaretTable.java 1076 2010-12-05 13:34:42Z kliem $
381      */
382     public class ColInfo {
383         /*** begin x coordinate. */
384         public int x;
385 
386         /*** column reference. */
387         public IColumn column;
388 
389         /*** actual width of the column. */
390         public int width;
391 
392         /***
393          * Construct a col info instance.
394          * 
395          * @param columnIn col ref
396          * @param xIn begin x
397          * @param widthIn width in pixel
398          */
399         public ColInfo(IColumn columnIn, int xIn, int widthIn) {
400             this.column = columnIn;
401             this.x = xIn;
402             this.width = widthIn;
403         }
404     }
405 
406     /***
407      * Constructor for a new JaretTable widget.
408      * 
409      * @param parent parent composite
410      * @param style style bits (use HSCROLL, VSCROLL)
411      */
412     public JaretTable(Composite parent, int style) {
413         // no background painting
414         super(parent, style | SWT.NO_REDRAW_RESIZE | SWT.NO_BACKGROUND | SWT.DOUBLE_BUFFERED);
415 
416         addPaintListener(new PaintListener() {
417             public void paintControl(PaintEvent event) {
418                 onPaint(event);
419             }
420         });
421 
422         addMouseListener(new MouseListener() {
423 
424             public void mouseDoubleClick(MouseEvent me) {
425                 mouseDouble(me.x, me.y);
426             }
427 
428             public void mouseDown(MouseEvent me) {
429                 forceFocus();
430                 mousePressed(me.x, me.y, me.button == POPUPTRIGGER, me.stateMask);
431             }
432 
433             public void mouseUp(MouseEvent me) {
434                 mouseReleased(me.x, me.y, me.button == POPUPTRIGGER);
435             }
436         });
437 
438         addMouseMoveListener(new MouseMoveListener() {
439             public void mouseMove(MouseEvent me) {
440                 if ((me.stateMask & SWT.BUTTON1) != 0) {
441                     mouseDragged(me.x, me.y, me.stateMask);
442                 } else {
443                     mouseMoved(me.x, me.y, me.stateMask);
444                 }
445             }
446         });
447 
448         addMouseTrackListener(new MouseTrackListener() {
449             public void mouseEnter(MouseEvent arg0) {
450             }
451 
452             public void mouseExit(MouseEvent arg0) {
453                 // on exit set standard cursor
454                 if (Display.getCurrent().getActiveShell() != null) {
455                     Display.getCurrent().getActiveShell().setCursor(
456                             Display.getCurrent().getSystemCursor(SWT.CURSOR_ARROW));
457                 }
458             }
459 
460             public void mouseHover(MouseEvent me) {
461                 setToolTipText(getToolTipText(me.x, me.y));
462             }
463         });
464 
465         addDisposeListener(new DisposeListener() {
466             public void widgetDisposed(DisposeEvent event) {
467                 onDispose(event);
468             }
469         });
470 
471         // key listener for keyboard control
472         addKeyListener(new KeyListener() {
473             public void keyPressed(KeyEvent event) {
474                 handleKeyPressed(event);
475             }
476 
477             public void keyReleased(KeyEvent arg0) {
478             }
479         });
480 
481         Listener listener = new Listener() {
482             public void handleEvent(Event event) {
483                 switch (event.type) {
484                 case SWT.Resize:
485                     updateScrollBars();
486                     break;
487                 default:
488                     // do nothing
489                     break;
490                 }
491             }
492         };
493         addListener(SWT.Resize, listener);
494 
495         ScrollBar verticalBar = getVerticalBar();
496         if (verticalBar != null) {
497             verticalBar.addSelectionListener(new SelectionAdapter() {
498                 public void widgetSelected(SelectionEvent event) {
499                     handleVerticalScroll(event);
500                 }
501             });
502         }
503 
504         ScrollBar horizontalBar = getHorizontalBar();
505         if (horizontalBar != null) {
506             horizontalBar.addSelectionListener(new SelectionAdapter() {
507                 public void widgetSelected(SelectionEvent event) {
508                     handleHorizontalScroll(event);
509                 }
510             });
511         }
512 
513         _tableRect = getClientArea();
514         _fixedColRect = getClientArea();
515 
516         // updateYScrollBar();
517 
518         // register default cell renderers, editors and autofilters
519         registerDefaultRenderers();
520         registerDefaultEditors();
521         registerDefaultAutofilters();
522 
523         // register with the viewstate
524         _tvs.addTableViewStateListener(this);
525 
526         // register with the selection model
527         _selectionModel.addTableSelectionModelListener(this);
528 
529         setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WHITE));
530 
531         _propertyChangeSupport = new PropertyChangeSupport(this);
532     }
533 
534     /***
535      * Dispose whatever there is to dispose (renderers and so on).
536      * 
537      * @param event dispose event
538      */
539     private void onDispose(DisposeEvent event) {
540         // header renderer
541         if (_headerRenderer != null) {
542             _headerRenderer.dispose();
543         }
544         // cell renderers
545         for (ICellRenderer renderer : _colCellRendererMap.values()) {
546             renderer.dispose();
547         }
548         for (ICellRenderer renderer : _colClassRendererMap.values()) {
549             renderer.dispose();
550         }
551         // cell editors
552         for (ICellEditor editor : _colCellEditorMap.values()) {
553             editor.dispose();
554         }
555         for (ICellEditor editor : _colClassEditorMap.values()) {
556             editor.dispose();
557         }
558         // autofilters
559         for (IAutoFilter autoFilter : _autoFilterMap.values()) {
560             autoFilter.dispose();
561         }
562         if (_rowSorter != null) {
563             _rowSorter.removePropertyChangeListener(this);
564         }
565         if (_rowFilter != null) {
566             _rowFilter.removePropertyChangeListener(this);
567         }
568         if (_ccpStrategy != null) {
569             _ccpStrategy.dispose();
570         }
571     }
572 
573     /***
574      * Register all default renderers.
575      * 
576      */
577     private void registerDefaultRenderers() {
578         ICellRenderer cellRenderer = new TextCellRenderer();
579         registerCellRenderer(void.class, cellRenderer);
580         registerCellRenderer(String.class, cellRenderer);
581         registerCellRenderer(Image.class, new ImageCellRenderer());
582         cellRenderer = new BooleanCellRenderer();
583         registerCellRenderer(Boolean.class, cellRenderer);
584         registerCellRenderer(Boolean.TYPE, cellRenderer);
585         cellRenderer = new DateCellRenderer();
586         registerCellRenderer(Date.class, cellRenderer);
587         registerCellRenderer(JaretDate.class, cellRenderer);
588         cellRenderer = new DoubleCellRenderer();
589         registerCellRenderer(Double.class, cellRenderer);
590         registerCellRenderer(Double.TYPE, cellRenderer);
591     }
592 
593     /***
594      * Register all default editors.
595      * 
596      */
597     private void registerDefaultEditors() {
598         registerCellEditor(String.class, new TextCellEditor(true));
599         registerCellEditor(Boolean.class, new BooleanCellEditor(true));
600         registerCellEditor(Date.class, new DateCellEditor());
601         registerCellEditor(JaretDate.class, new DateCellEditor());
602         registerCellEditor(Enum.class, new EnumComboEditor());
603         registerCellEditor(Integer.class, new IntegerCellEditor());
604         registerCellEditor(Integer.TYPE, new IntegerCellEditor());
605         registerCellEditor(Double.class, new DoubleCellEditor());
606         registerCellEditor(Double.TYPE, new DoubleCellEditor());
607     }
608 
609     /***
610      * Regsiter the default autofilters.
611      */
612     private void registerDefaultAutofilters() {
613         registerAutoFilterForClass(String.class, DefaultAutoFilter.class);
614     }
615 
616     /***
617      * Register a cell renderer for rendering objects of class clazz.
618      * 
619      * @param clazz class the renderer should be applied for
620      * @param cellRenderer renderer to use for clazz
621      */
622     public void registerCellRenderer(Class<?> clazz, ICellRenderer cellRenderer) {
623         _colClassRendererMap.put(clazz, cellRenderer);
624     }
625 
626     /***
627      * Register a cell renderer for a column.
628      * 
629      * @param column column the renderer should be used on
630      * @param cellRenderer renderer to use
631      */
632     public void registerCellRenderer(IColumn column, ICellRenderer cellRenderer) {
633         _colCellRendererMap.put(column, cellRenderer);
634     }
635 
636     /***
637      * Retrieve the cell renderer for a cell.
638      * 
639      * @param row row row of the cell
640      * @param column column column of the cell
641      * @return cell renderer
642      */
643     protected ICellRenderer getCellRenderer(IRow row, IColumn column) {
644         // first check column specific renderers
645         ICellRenderer renderer = null;
646         renderer = _colCellRendererMap.get(column);
647         if (renderer == null) {
648             // try class map
649             Object value = column.getValue(row);
650             if (value != null) {
651                 renderer = getCellRendererFromMap(value.getClass());
652             }
653             if (renderer == null) {
654                 // nothing? -> default
655                 renderer = _colClassRendererMap.get(void.class);
656             }
657         }
658         return renderer;
659     }
660 
661     /***
662      * Register a cell editor for objects of class clazz.
663      * 
664      * @param clazz class of objeects the editor should be used for
665      * @param cellEditor editor to use
666      */
667     public void registerCellEditor(Class<?> clazz, ICellEditor cellEditor) {
668         _colClassEditorMap.put(clazz, cellEditor);
669     }
670 
671     /***
672      * Register a cell editor for a column.
673      * 
674      * @param column column the editor should be used for
675      * @param cellEditor editor to use
676      */
677     public void registerCellEditor(IColumn column, ICellEditor cellEditor) {
678         _colCellEditorMap.put(column, cellEditor);
679     }
680 
681     /***
682      * Retrieve the cell editor for a cell.
683      * 
684      * @param row row of the cell
685      * @param column col of the cell
686      * @return cell editor or <code>null</code> if no editor can be found
687      */
688     private ICellEditor getCellEditor(IRow row, IColumn column) {
689         // first check column specific renderers
690         ICellEditor editor = null;
691         editor = _colCellEditorMap.get(column);
692         if (editor == null) {
693             // try class map
694             Object value = column.getValue(row);
695             if (value != null) {
696                 editor = getCellEditorFromMap(value.getClass());
697             } else if (column.getContentClass(row) != null) {
698                 editor = getCellEditorFromMap(column.getContentClass(row));
699             } else if (column.getContentClass() != null) {
700                 editor = getCellEditorFromMap(column.getContentClass());
701             }
702         }
703         return editor;
704     }
705 
706     /***
707      * Retrieve a cell editor for a given class. Checks all interfaces and all superclasses.
708      * 
709      * @param clazz class in queston
710      * @return editor or null
711      */
712     private ICellEditor getCellEditorFromMap(Class<?> clazz) {
713         ICellEditor result = null;
714         result = _colClassEditorMap.get(clazz);
715         if (result != null) {
716             return result;
717         }
718 
719         Class<?>[] interfaces = clazz.getInterfaces();
720         for (Class<?> c : interfaces) {
721             result = _colClassEditorMap.get(c);
722             if (result != null) {
723                 return result;
724             }
725         }
726 
727         Class<?> sc = clazz.getSuperclass();
728 
729         while (sc != null) {
730             result = _colClassEditorMap.get(sc);
731             if (result != null) {
732                 return result;
733             }
734             // interfaces of the superclass
735             Class<?>[] scinterfaces = sc.getInterfaces();
736             for (Class<?> c : scinterfaces) {
737                 result = _colClassEditorMap.get(c);
738                 if (result != null) {
739                     return result;
740                 }
741             }
742             sc = sc.getSuperclass();
743         }
744 
745         return result;
746     }
747 
748     /***
749      * Retrieve a cell renderer for a given class. Checks all interfaces and all superclasses.
750      * 
751      * @param clazz class in queston
752      * @return renderer or null
753      */
754     private ICellRenderer getCellRendererFromMap(Class<?> clazz) {
755         ICellRenderer result = null;
756         result = _colClassRendererMap.get(clazz);
757         if (result != null) {
758             return result;
759         }
760 
761         Class<?>[] interfaces = clazz.getInterfaces();
762         for (Class<?> c : interfaces) {
763             result = _colClassRendererMap.get(c);
764             if (result != null) {
765                 return result;
766             }
767         }
768 
769         Class<?> sc = clazz.getSuperclass();
770 
771         while (sc != null) {
772             result = _colClassRendererMap.get(sc);
773             if (result != null) {
774                 return result;
775             }
776             // interfaces of the superclass
777             Class<?>[] scinterfaces = sc.getInterfaces();
778             for (Class<?> c : scinterfaces) {
779                 result = _colClassRendererMap.get(c);
780                 if (result != null) {
781                     return result;
782                 }
783             }
784             sc = sc.getSuperclass();
785         }
786 
787         return result;
788     }
789 
790     /***
791      * Register an autofilter implementing class to be used on columns that announce a specific content class.
792      * 
793      * @param clazz content clazz thet triggers the use of the filter
794      * @param autoFilterClass class implementing the IAutoFilter interface that will be used
795      */
796     public void registerAutoFilterForClass(Class<?> clazz, Class<? extends IAutoFilter> autoFilterClass) {
797         _autoFilterClassMap.put(clazz, autoFilterClass);
798     }
799 
800     /***
801      * Regsiter an autofilter implementing class for use with a specific column.
802      * 
803      * @param column column
804      * @param autoFilterClass class of autofilter that will be used
805      */
806     public void registerAutoFilterForColumn(IColumn column, Class<? extends IAutoFilter> autoFilterClass) {
807         _autoFilterColumnMap.put(column, autoFilterClass);
808     }
809 
810     /***
811      * Get the autofiletr class to be used on a column.
812      * 
813      * @param column column
814      * @return class or <code>null</code> if none could be determined
815      */
816     protected Class<? extends IAutoFilter> getAutoFilterClass(IColumn column) {
817         Class<? extends IAutoFilter> result = _autoFilterColumnMap.get(column);
818         if (result != null) {
819             return result;
820         }
821         Class<?> contentClass = column.getContentClass();
822         if (contentClass != null) {
823             result = getAutoFilterClassForClass(contentClass);
824         }
825         // nothing found so long -> use default (String) filter
826         if (result == null) {
827             result = _autoFilterClassMap.get(String.class);
828         }
829         return result;
830     }
831 
832     /***
833      * Retrieve autofilter class for a given content class. Takes interfaces and superclasses into account.
834      * 
835      * @param clazz content class
836      * @return class to be used or <code>null</code> if none could be determined
837      */
838     private Class<? extends IAutoFilter> getAutoFilterClassForClass(Class<?> clazz) {
839         Class<? extends IAutoFilter> result = null;
840         result = _autoFilterClassMap.get(clazz);
841         if (result != null) {
842             return result;
843         }
844 
845         Class<?>[] interfaces = clazz.getInterfaces();
846         for (Class<?> c : interfaces) {
847             result = _autoFilterClassMap.get(c);
848             if (result != null) {
849                 return result;
850             }
851         }
852 
853         Class<?> sc = clazz.getSuperclass();
854 
855         while (sc != null) {
856             result = _autoFilterClassMap.get(sc);
857             if (result != null) {
858                 return result;
859             }
860             // interfaces of the superclass
861             Class<?>[] scinterfaces = sc.getInterfaces();
862             for (Class<?> c : scinterfaces) {
863                 result = _autoFilterClassMap.get(c);
864                 if (result != null) {
865                     return result;
866                 }
867             }
868             sc = sc.getSuperclass();
869         }
870 
871         return result;
872     }
873 
874     // ////// mouse handling
875     /*** currently dragged row (dragging height). */
876     protected RowInfo _heightDraggedRowInfo = null;
877 
878     /*** currently dragged (width) column or null. */
879     protected ColInfo _widthDraggedColumn = null;
880 
881     /*** true if the header height is beeing dragged. */
882     protected boolean _headerDragged = false;
883 
884     /***
885      * Handle mouse double click.
886      * 
887      * @param x x coordinate
888      * @param y y coordinate
889      */
890     private void mouseDouble(int x, int y) {
891         if (_tableRect.contains(x, y) || (_fixedColumns > 0 && _fixedColRect.contains(x, y))
892                 || (_fixedRows > 0 && _fixedRowRect.contains(x, y))) {
893             setFocus(x, y);
894             startEditing(rowForY(y), colForX(x), (char) 0);
895         }
896     }
897 
898     /***
899      * Handle mouse pressed.
900      * 
901      * @param x x coordinate
902      * @param y y coordinate
903      * @param popuptrigger true if the button pressed was the popup trigger
904      * @param stateMask statemask from the event
905      */
906     private void mousePressed(int x, int y, boolean popuptrigger, int stateMask) {
907         // check for location over drag marker and start fill drag if necessary
908         if (_dragMarkerRect != null && _dragMarkerRect.contains(x, y)) {
909             _isFillDrag = true;
910             _firstFillDragSelect = _selectedIdxRectangle;
911             return;
912         }
913 
914         IRow row = rowByBottomBorder(y);
915         if (row != null
916                 && _rowResizeAllowed
917                 && (_tvs.getRowHeigthMode(row) == RowHeightMode.VARIABLE || _tvs.getRowHeigthMode(row) == RowHeightMode.OPTANDVAR)
918                 && (!_resizeRestriction || Math.abs(x - _tableRect.x) <= SELDELTA || (_fixedColRect != null && _fixedColRect
919                         .contains(x, y)))) {
920             _heightDraggedRowInfo = getRowInfo(row);
921             return;
922         } else {
923             IColumn col = colByRightBorder(x);
924             if (col != null && _columnResizeAllowed && _tvs.columnResizingAllowed(col)
925                     && (!_resizeRestriction || _headerRect == null || _headerRect.contains(x, y))) {
926                 _widthDraggedColumn = getColInfo(col);
927                 return;
928             }
929         }
930         // check header drag
931         if (_headerResizeAllowed && Math.abs(_headerRect.y + _headerRect.height - y) <= SELDELTA) {
932             _headerDragged = true;
933             return;
934         }
935         // handle mouse press for selection
936         boolean doSelect = true;
937         // check focus set
938         if (_tableRect.contains(x, y) || (_fixedColumns > 0 && _fixedColRect.contains(x, y))
939                 || (_fixedRows > 0 && _fixedRowRect.contains(x, y))) {
940             setFocus(x, y);
941             doSelect = !handleEditorSingleClick(x, y);
942         }
943         // check hierarchy
944         if (_tableRect.contains(x, y) || (_fixedColumns > 0 && _fixedColRect.contains(x, y))
945                 || (_fixedRows > 0 && _fixedRowRect.contains(x, y))) {
946             IRow xrow = rowForY(y);
947             IColumn xcol = colForX(x);
948             if (xrow != null && xcol != null && isHierarchyColumn(xrow, xcol)) {
949                 Rectangle rect = getCellBounds(xrow, xcol);
950                 IHierarchyRenderer hrenderer = (IHierarchyRenderer) getCellRenderer(xrow, xcol);
951                 if (hrenderer.isInActiveArea(xrow, rect, x, y)) {
952                     toggleExpanded(xrow);
953                 }
954             }
955         }
956         // check header sorting clicks
957         IColumn xcol = colForX(x);
958         if (_allowSorting && _headerRect.contains(x, y)
959                 && _headerRenderer.isSortingClick(getHeaderDrawingArea(xcol), xcol, x, y)) {
960             _tvs.setSorting(xcol);
961         } else if (doSelect) {
962             // selection can be intercepted by editor clicks
963             handleSelection(x, y, stateMask, false);
964         }
965     }
966 
967     /***
968      * Toggle the expanded state of a row.
969      * 
970      * @param row row to toggle
971      */
972     private void toggleExpanded(IRow row) {
973         IHierarchicalTableViewState hvs = (IHierarchicalTableViewState) _tvs;
974         hvs.setExpanded((ITableNode) row, !hvs.isExpanded((ITableNode) row));
975     }
976 
977     /***
978      * Determine whether the column is the hierrarchy column. This is accomplished by looking at the cell renderer
979      * class.
980      * 
981      * @param row row
982      * @param col column
983      * @return true if hte adressed cell is part of the hierarchy column
984      */
985     public boolean isHierarchyColumn(IRow row, IColumn col) {
986         if (row == null || col == null) {
987             return false;
988         }
989         return getCellRenderer(row, col) instanceof TableHierarchyRenderer;
990     }
991 
992     /***
993      * Check whether a column is the hierarchy column.
994      * 
995      * @param column column to check
996      * @return <code>true</code> if the column is the hierarchy column
997      */
998     public boolean isHierarchyColumn(IColumn column) {
999     	if (column == null) {
1000     		return false;
1001     	}
1002     	return isHierarchyColumn(_rows.get(0), column);
1003     }
1004     
1005     /***
1006      * Retrieve the rectangle in which the header of a column is drawn.
1007      * 
1008      * @param col column
1009      * @return drawing rectangle for the header
1010      */
1011     private Rectangle getHeaderDrawingArea(IColumn col) {
1012         int x = getColInfo(col).x;
1013         Rectangle r = new Rectangle(x, _tableRect.y, _tvs.getColumnWidth(col), _tableRect.height);
1014         return r;
1015     }
1016 
1017     /***
1018      * Check whether a click is a row selection.
1019      * 
1020      * @param x x coordinate
1021      * @param y y coordinate
1022      * @return true for click on the left margin of the table or in the fixed column area
1023      */
1024     private boolean isRowSelection(int x, int y) {
1025         return (Math.abs(x - _tableRect.x) <= SELDELTA || (_fixedColRect != null && _fixedColRect.contains(x, y)));
1026     }
1027 
1028     /***
1029      * Check whether a click is a column selection.
1030      * 
1031      * @param x x coordinate
1032      * @param y y coordinate
1033      * @return true for coordinate in header or fixed rows
1034      */
1035     private boolean isColumnSelection(int x, int y) {
1036         return (_headerRect != null && _headerRect.contains(x, y))
1037                 || (_fixedRowRect != null && _fixedRowRect.contains(x, y));
1038     }
1039 
1040     protected int _firstCellSelectX = -1;
1041     protected int _firstCellSelectY = -1;
1042     protected int _lastCellSelectX = -1;
1043     protected int _lastCellSelectY = -1;
1044 
1045     /***
1046      * marker flag for drag operation: fill drag.
1047      */
1048     protected boolean _isFillDrag = false;
1049 
1050     /*** first col selected in drag. */
1051     protected int _firstColSelectIdx = -1;
1052     /*** last col selected in drag or as standard col selection. */
1053     protected int _lastColSelectIdx = -1;
1054     int _lastKeyColSelectIdx = -1;
1055     int _firstKeyColSelectIdx = -1;
1056 
1057     /*** first row selected in drag. */
1058     protected int _firstRowSelectIdx = -1;
1059     /*** last row selected in drag. */
1060     protected int _lastRowSelectIdx = -1;
1061     int _lastKeyRowSelectIdx = -1;
1062     int _firstKeyRowSelectIdx = -1;
1063 
1064     /*** last cell idx selected by shift-arrow. */
1065     protected Point _lastKeySelect = null;
1066     /*** first cell idx selected by shift-arrow. */
1067     protected Point _firstKeySelect = null;
1068 
1069     /*** enum for the selection type (intern). */
1070     private enum SelectType {
1071         NONE, CELL, COLUMN, ROW
1072     };
1073 
1074     /*** index rectangle of selected cells whenn a fill drag starts. */
1075     Rectangle _firstFillDragSelect = null;
1076     /*** true if the fill drag is horizontal (fill along the x axis), false for vertical fill drag. */
1077     private boolean _horizontalFillDrag;
1078 
1079     /*** type of the last selection, used for handling keyboard selection. */
1080     protected SelectType _lastSelectType = SelectType.NONE;
1081 
1082     /***
1083      * Handle selection operations.
1084      * 
1085      * @param x x
1086      * @param y y
1087      * @param stateMask key state mask
1088      * @param dragging true when dragging
1089      */
1090     private void handleSelection(int x, int y, int stateMask, boolean dragging) {
1091         // a mouse select always ends a shift-arrow select
1092         _lastKeySelect = null;
1093         _firstKeySelect = null;
1094         _firstKeyColSelectIdx = -1;
1095         _lastKeyColSelectIdx = -1;
1096         _firstKeyRowSelectIdx = -1;
1097         _lastKeyRowSelectIdx = -1;
1098 
1099         IRow row = rowForY(y);
1100         int rowIdx = row != null ? _rows.indexOf(row) : -1;
1101         IColumn col = colForX(x);
1102         int colIdx = getColumnIdx(col);
1103 
1104         // check fill dragging
1105         if (dragging && _isFillDrag) {
1106             if (_selectionModel.isCellSelectionAllowed() && _tableRect.contains(x, y)) {
1107                 if (col != null && row != null) {
1108                     if (_firstCellSelectX == -1) {
1109                         _firstCellSelectX = colIdx;
1110                         _firstCellSelectY = rowIdx;
1111                     }
1112                     if (Math.abs(_firstCellSelectX - colIdx) > Math.abs(_firstCellSelectY - rowIdx)) {
1113                         rowIdx = _firstCellSelectY;
1114                         row = rowForIdx(rowIdx);
1115                         _horizontalFillDrag = false;
1116                     } else {
1117                         colIdx = _firstCellSelectX;
1118                         col = colForIdx(colIdx);
1119                         _horizontalFillDrag = true;
1120                     }
1121                     ensureSelectionContainsRegion(_firstFillDragSelect, colIdx, rowIdx, _lastCellSelectX,
1122                             _lastCellSelectY);
1123                     // ensureSelectionContainsRegion(_firstCellSelectX, _firstCellSelectY, colIdx, rowIdx,
1124                     // _lastCellSelectX, _lastCellSelectY);
1125                     _lastCellSelectX = colIdx;
1126                     _lastCellSelectY = rowIdx;
1127                     // a newly selected cell will always be the focussed cell (causes scrolling this cell to be
1128                     // completely visible)
1129                     setFocus(row, col);
1130                 }
1131             }
1132 
1133             return;
1134         }
1135 
1136         // check row selection
1137         if (row != null && _selectionModel.isFullRowSelectionAllowed()
1138                 && (isRowSelection(x, y) || _selectionModel.isOnlyRowSelectionAllowed() || _firstRowSelectIdx != -1)) {
1139             if (_firstRowSelectIdx == -1) {
1140                 _firstRowSelectIdx = rowIdx;
1141             }
1142             if ((stateMask & SWT.CONTROL) != 0) {
1143                 if (!_selectionModel.getSelection().getSelectedRows().contains(row)) {
1144                     _selectionModel.addSelectedRow(row);
1145                 } else {
1146                     _selectionModel.remSelectedRow(row);
1147                 }
1148                 _lastSelectType = SelectType.ROW;
1149             } else if (dragging) {
1150                 ensureSelectionContainsRowRegion(_firstRowSelectIdx, rowIdx, _lastRowSelectIdx);
1151                 _lastRowSelectIdx = rowIdx;
1152                 _lastSelectType = SelectType.ROW;
1153             } else {
1154                 _selectionModel.clearSelection();
1155                 _selectionModel.addSelectedRow(row);
1156                 _lastSelectType = SelectType.ROW;
1157             }
1158             _lastRowSelectIdx = rowIdx;
1159             return;
1160         }
1161         // check column selection
1162         if (_selectionModel.isFullColumnSelectionAllowed() && (isColumnSelection(x, y) || _firstColSelectIdx != -1)) {
1163             if (_firstColSelectIdx == -1) {
1164                 _firstColSelectIdx = colIdx;
1165             }
1166             if ((stateMask & SWT.CONTROL) != 0) {
1167                 if (!_selectionModel.getSelection().getSelectedColumns().contains(col)) {
1168                     _selectionModel.addSelectedColumn(col);
1169                 } else {
1170                     _selectionModel.remSelectedColumn(col);
1171                 }
1172                 _lastSelectType = SelectType.COLUMN;
1173             } else if (dragging) {
1174                 ensureSelectionContainsColRegion(_firstColSelectIdx, colIdx, _lastColSelectIdx);
1175                 _lastColSelectIdx = colIdx;
1176                 _lastSelectType = SelectType.COLUMN;
1177             } else {
1178                 _selectionModel.clearSelection();
1179                 _selectionModel.addSelectedColumn(col);
1180                 _lastSelectType = SelectType.COLUMN;
1181             }
1182             _lastColSelectIdx = colIdx;
1183             return;
1184         }
1185         // check cell selection
1186         if (_selectionModel.isCellSelectionAllowed() && _tableRect.contains(x, y)) {
1187             if (col != null && row != null) {
1188                 IJaretTableCell cell = new JaretTableCellImpl(row, col);
1189                 if (_firstCellSelectX == -1) {
1190                     _firstCellSelectX = colIdx;
1191                     _firstCellSelectY = rowIdx;
1192                 }
1193                 if ((stateMask & SWT.CONTROL) != 0) {
1194                     if (!_selectionModel.getSelection().getSelectedCells().contains(cell)) {
1195                         _selectionModel.addSelectedCell(cell);
1196                     } else {
1197                         _selectionModel.remSelectedCell(cell);
1198                     }
1199                     _lastSelectType = SelectType.CELL;
1200                 } else if (dragging) {
1201                     ensureSelectionContainsRegion(_firstCellSelectX, _firstCellSelectY, colIdx, rowIdx,
1202                             _lastCellSelectX, _lastCellSelectY);
1203                     _lastCellSelectX = colIdx;
1204                     _lastCellSelectY = rowIdx;
1205                     _lastSelectType = SelectType.CELL;
1206                 } else {
1207                     _selectionModel.clearSelection();
1208                     _selectionModel.addSelectedCell(cell);
1209                     _lastSelectType = SelectType.CELL;
1210                 }
1211                 // a newly selected cell will always be the focussed cell (causes scrolling this cell to be completely
1212                 // visible)
1213                 setFocus(row, col);
1214             }
1215         }
1216 
1217     }
1218 
1219     /***
1220      * Ensures that the selection contains the rows from firstIdx to rowIdx. If the range firstIdx to lastIdx is larger
1221      * than the region the other rows will be removed from the selection.
1222      * 
1223      * @param firstRowSelectIdx first selected row index
1224      * @param rowIdx current selected row index
1225      * @param lastRowSelectIdx may be -1 for no last selection
1226      */
1227     private void ensureSelectionContainsRowRegion(int firstRowSelectIdx, int rowIdx, int lastRowSelectIdx) {
1228         int first = Math.min(firstRowSelectIdx, rowIdx);
1229         int end = Math.max(firstRowSelectIdx, rowIdx);
1230         for (int i = first; i <= end; i++) {
1231             IRow row = rowForIdx(i);
1232             if (!_selectionModel.getSelection().getSelectedRows().contains(row)) {
1233                 _selectionModel.addSelectedRow(row);
1234             }
1235         }
1236         if (lastRowSelectIdx != -1) {
1237             int f = Math.min(firstRowSelectIdx, lastRowSelectIdx);
1238             int e = Math.max(firstRowSelectIdx, lastRowSelectIdx);
1239             for (int i = f; i <= e; i++) {
1240                 if (i < first || i > end) {
1241                     IRow row = rowForIdx(i);
1242                     _selectionModel.remSelectedRow(row);
1243                 }
1244             }
1245         }
1246     }
1247 
1248     /***
1249      * Ensures that the selection contains the columns from firstIdx to colIdx. If the range firstIdx to lastIdx is
1250      * larger than the region the other columns will be removed from the selection.
1251      * 
1252      * @param firstColIdx first selected column index
1253      * @param colIdx current selected column index
1254      * @param lastColSelectIdx may be -1 for no last selection
1255      */
1256     private void ensureSelectionContainsColRegion(int firstColIdx, int colIdx, int lastColSelectIdx) {
1257         int first = Math.min(firstColIdx, colIdx);
1258         int end = Math.max(firstColIdx, colIdx);
1259         for (int i = first; i <= end; i++) {
1260             IColumn col = colForIdx(i);
1261             if (!_selectionModel.getSelection().getSelectedColumns().contains(col)) {
1262                 _selectionModel.addSelectedColumn(col);
1263             }
1264         }
1265         if (lastColSelectIdx != -1) {
1266             int f = Math.min(firstColIdx, lastColSelectIdx);
1267             int e = Math.max(firstColIdx, lastColSelectIdx);
1268             for (int i = f; i <= e; i++) {
1269                 if (i < first || i > end) {
1270                     IColumn col = colForIdx(i);
1271                     _selectionModel.remSelectedColumn(col);
1272                 }
1273             }
1274         }
1275     }
1276 
1277     /***
1278      * Ensures the selection contains the cells in the rectangle given by first*, *Idx. If the rectangle given by
1279      * first*, last* is larger than the other rectangle is is ensured that the additional cells are not in the
1280      * selection.
1281      * 
1282      * @param firstCellSelectX begin x index of selected cell rectangle
1283      * @param firstCellSelectY begin y index of selected cell rectangle
1284      * @param colIdx end x index of selected cell rectangle
1285      * @param rowIdx end y index of selected cell rectangle
1286      * @param lastCellSelectX may be -1 for no last selection
1287      * @param lastCellSelectY may be -1 for no last selection
1288      */
1289     private void ensureSelectionContainsRegion(int firstCellSelectX, int firstCellSelectY, int colIdx, int rowIdx,
1290             int lastCellSelectX, int lastCellSelectY) {
1291         int firstx = Math.min(firstCellSelectX, colIdx);
1292         int endx = Math.max(firstCellSelectX, colIdx);
1293         int firsty = Math.min(firstCellSelectY, rowIdx);
1294         int endy = Math.max(firstCellSelectY, rowIdx);
1295 
1296         for (int x = firstx; x <= endx; x++) {
1297             for (int y = firsty; y <= endy; y++) {
1298                 IJaretTableCell cell = new JaretTableCellImpl(rowForIdx(y), colForIdx(x));
1299                 if (!_selectionModel.getSelection().getSelectedCells().contains(cell)) {
1300                     _selectionModel.addSelectedCell(cell);
1301                 }
1302             }
1303         }
1304 
1305         // last sel rect
1306         if (lastCellSelectX != -1) {
1307             int lfx = Math.min(firstCellSelectX, lastCellSelectX);
1308             int lex = Math.max(firstCellSelectX, lastCellSelectX);
1309             int lfy = Math.min(firstCellSelectY, lastCellSelectY);
1310             int ley = Math.max(firstCellSelectY, lastCellSelectY);
1311 
1312             for (int x = lfx; x <= lex; x++) {
1313                 for (int y = lfy; y <= ley; y++) {
1314                     if (!(x >= firstx && x <= endx && y >= firsty && y <= endy)) {
1315                         IJaretTableCell cell = new JaretTableCellImpl(rowForIdx(y), colForIdx(x));
1316                         _selectionModel.remSelectedCell(cell);
1317                     }
1318                 }
1319             }
1320         }
1321 
1322     }
1323 
1324     /***
1325      * Ensures the selection contains the cells in the rectangle given by firstIdxRect, *Idx. If the rectangle given by
1326      * first*, last* is larger than the other rectangle is is ensured that the additional cells are not in the
1327      * selection.
1328      * 
1329      * @param firstIdxRect rectangle containing the indizes of the originating rect
1330      * @param colIdx new end x index for the selected rectangle
1331      * @param rowIdx new end y index for teh selecetd rectangle
1332      * @param lastCellSelectX may be -1 for no last selection
1333      * @param lastCellSelectY may be -1 for no last selection
1334      */
1335     private void ensureSelectionContainsRegion(Rectangle firstIdxRect, int colIdx, int rowIdx, int lastCellSelectX,
1336             int lastCellSelectY) {
1337 
1338         int firstx = Math.min(firstIdxRect.x, colIdx);
1339         int endx = Math.max(firstIdxRect.x + firstIdxRect.width - 1, colIdx);
1340         int firsty = Math.min(firstIdxRect.y, rowIdx);
1341         int endy = Math.max(firstIdxRect.y + firstIdxRect.height - 1, rowIdx);
1342 
1343         for (int x = firstx; x <= endx; x++) {
1344             for (int y = firsty; y <= endy; y++) {
1345                 IJaretTableCell cell = new JaretTableCellImpl(rowForIdx(y), colForIdx(x));
1346                 if (!_selectionModel.getSelection().getSelectedCells().contains(cell)) {
1347                     _selectionModel.addSelectedCell(cell);
1348                 }
1349             }
1350         }
1351 
1352         // last sel rect
1353         if (lastCellSelectX != -1 && lastCellSelectY != -1) {
1354             int lfx = Math.min(firstIdxRect.x, lastCellSelectX);
1355             int lex = Math.max(firstIdxRect.x + firstIdxRect.width - 1, lastCellSelectX);
1356             int lfy = Math.min(firstIdxRect.y, lastCellSelectY);
1357             int ley = Math.max(firstIdxRect.y + firstIdxRect.height - 1, lastCellSelectY);
1358 
1359             for (int x = lfx; x <= lex; x++) {
1360                 for (int y = lfy; y <= ley; y++) {
1361                     if (!(x >= firstx && x <= endx && y >= firsty && y <= endy)) {
1362                         IJaretTableCell cell = new JaretTableCellImpl(rowForIdx(y), colForIdx(x));
1363                         _selectionModel.remSelectedCell(cell);
1364                     }
1365                 }
1366             }
1367         }
1368 
1369     }
1370 
1371     /***
1372      * Handle drag of mouse.
1373      * 
1374      * @param x x coordinate of pointer
1375      * @param y y coordinate of pointer
1376      * @param stateMask keyStatemask
1377      */
1378     private void mouseDragged(int x, int y, int stateMask) {
1379         if (_isFillDrag) {
1380             handleSelection(x, y, stateMask, true);
1381         } else if (_heightDraggedRowInfo != null) {
1382             int newHeight = y - _heightDraggedRowInfo.y;
1383             if (newHeight < _tvs.getMinimalRowHeight()) {
1384                 newHeight = _tvs.getMinimalRowHeight();
1385             }
1386             _tvs.setRowHeight(_heightDraggedRowInfo.row, newHeight);
1387             // setting the row heigth on an OPTVAR ror converts this to variable
1388             if (_tvs.getRowHeigthMode(_heightDraggedRowInfo.row) == RowHeightMode.OPTANDVAR) {
1389                 _tvs.setRowHeightMode(_heightDraggedRowInfo.row, RowHeightMode.VARIABLE);
1390             }
1391 
1392         } else if (_widthDraggedColumn != null) {
1393             int newWidth = x - _widthDraggedColumn.x;
1394             if (newWidth < _tvs.getMinimalColWidth()) {
1395                 newWidth = _tvs.getMinimalColWidth();
1396             }
1397             if (_tvs.getColumnWidth(_widthDraggedColumn.column) != newWidth) {
1398                 _tvs.setColumnWidth(_widthDraggedColumn.column, newWidth);
1399             }
1400         } else if (_headerDragged) {
1401             int newHeight = y - _headerRect.y;
1402             if (newHeight < _minHeaderHeight) {
1403                 newHeight = _minHeaderHeight;
1404             }
1405             setHeaderHeight(newHeight);
1406         } else {
1407             handleSelection(x, y, stateMask, true);
1408         }
1409     }
1410 
1411     /***
1412      * Handle mouse move. This is mostly: modifying the appearance of the cursor.
1413      * 
1414      * @param x x coordinate of pointer
1415      * @param y y coordinate of pointer
1416      * @param stateMask keyStatemask
1417      */
1418     private void mouseMoved(int x, int y, int stateMask) {
1419         Display display = Display.getCurrent();
1420         Shell activeShell = display != null ? display.getActiveShell() : null;
1421 
1422         // check for location over drag marker
1423         if (_dragMarkerRect != null && _dragMarkerRect.contains(x, y)) {
1424             if (activeShell != null) {
1425                 // MAYBE other cursor for differentiation?
1426                 activeShell.setCursor(display.getSystemCursor(SWT.CURSOR_SIZEALL));
1427             }
1428             return;
1429         }
1430 
1431         // check for location over lower border of row
1432         IRow row = rowByBottomBorder(y);
1433         if (row != null
1434                 && _rowResizeAllowed
1435                 && (_tvs.getRowHeigthMode(row) == RowHeightMode.VARIABLE || _tvs.getRowHeigthMode(row) == RowHeightMode.OPTANDVAR)
1436                 && (!_resizeRestriction || Math.abs(x - _tableRect.x) <= SELDELTA || (_fixedColRect != null && _fixedColRect
1437                         .contains(x, y)))) {
1438             if (activeShell != null) {
1439                 activeShell.setCursor(display.getSystemCursor(SWT.CURSOR_SIZENS));
1440             }
1441             return;
1442         } else {
1443             IColumn col = colByRightBorder(x);
1444             if (col != null && _columnResizeAllowed && _tvs.columnResizingAllowed(col)
1445                     && (!_resizeRestriction || _headerRect == null || _headerRect.contains(x, y))) {
1446                 if (activeShell != null) {
1447                     activeShell.setCursor(display.getSystemCursor(SWT.CURSOR_SIZEW));
1448                 }
1449                 return;
1450             }
1451         }
1452         // check header drag symboling
1453         if (_headerRect != null && _headerResizeAllowed && Math.abs(_headerRect.y + _headerRect.height - y) <= SELDELTA) {
1454             if (activeShell != null) {
1455                 activeShell.setCursor(display.getSystemCursor(SWT.CURSOR_SIZENS));
1456             }
1457             return;
1458         }
1459 
1460         if (Display.getCurrent().getActiveShell() != null) {
1461             Display.getCurrent().getActiveShell().setCursor(Display.getCurrent().getSystemCursor(SWT.CURSOR_ARROW));
1462         }
1463 
1464     }
1465 
1466     /***
1467      * Handle the release of a mouse button.
1468      * 
1469      * @param x x coordinate
1470      * @param y y coordinate
1471      * @param popUpTrigger true if the buttonm is the popup trigger
1472      */
1473     private void mouseReleased(int x, int y, boolean popUpTrigger) {
1474 
1475         if (_isFillDrag) {
1476             handleFill();
1477         }
1478 
1479         _heightDraggedRowInfo = null;
1480         _widthDraggedColumn = null;
1481         _headerDragged = false;
1482 
1483         _firstCellSelectX = -1;
1484         _firstCellSelectY = -1;
1485         _lastCellSelectX = -1;
1486         _lastCellSelectY = -1;
1487 
1488         _firstColSelectIdx = -1;
1489         // _lastColSelectIdx = -1;
1490         _firstRowSelectIdx = -1;
1491         // _lastRowSelectIDx = -1;
1492 
1493         _isFillDrag = false;
1494 
1495         if (_headerRect.contains(x, y) && popUpTrigger) {
1496             displayHeaderContextMenu(x, y);
1497         } else if (popUpTrigger && isRowSelection(x, y)) {
1498             displayRowContextMenu(x, y);
1499         }
1500 
1501     }
1502 
1503     /***
1504      * Handle the end of a fill drag.
1505      * 
1506      */
1507     private void handleFill() {
1508         if (_fillDragStrategy != null) {
1509             if (_horizontalFillDrag) {
1510                 // horizontal base rect
1511                 for (int i = _firstFillDragSelect.x; i < _firstFillDragSelect.x + _firstFillDragSelect.width; i++) {
1512                     IJaretTableCell firstCell = getCellForIdx(i, _firstFillDragSelect.y);
1513                     List<IJaretTableCell> cells = getSelectedCellsVertical(i);
1514                     cells.remove(firstCell);
1515                     _fillDragStrategy.doFill(this, firstCell, cells);
1516                 }
1517             } else {
1518                 // vertical base rect
1519                 for (int i = _firstFillDragSelect.y; i < _firstFillDragSelect.y + _firstFillDragSelect.height; i++) {
1520                     IJaretTableCell firstCell = getCellForIdx(_firstFillDragSelect.x, i);
1521                     List<IJaretTableCell> cells = getSelectedCellsHorizontal(i);
1522                     cells.remove(firstCell);
1523                     _fillDragStrategy.doFill(this, firstCell, cells);
1524                 }
1525             }
1526         }
1527     }
1528 
1529     /***
1530      * Get all cells that are selected at the x idx given.
1531      * 
1532      * @param x x idx
1533      * @return list of selecetd cells with x idx == x
1534      */
1535     private List<IJaretTableCell> getSelectedCellsVertical(int x) {
1536         List<IJaretTableCell> cells = new ArrayList<IJaretTableCell>();
1537         List<IJaretTableCell> s = getSelectionModel().getSelection().getSelectedCells();
1538         for (IJaretTableCell cell : s) {
1539             Point p = getCellDisplayIdx(cell);
1540             if (p.x == x) {
1541                 cells.add(cell);
1542             }
1543         }
1544         return cells;
1545     }
1546 
1547     /***
1548      * Get all cells that are selected at the y idx given.
1549      * 
1550      * @param y y idx
1551      * @return list of selecetd cells at idx y == y
1552      */
1553     private List<IJaretTableCell> getSelectedCellsHorizontal(int y) {
1554         List<IJaretTableCell> cells = new ArrayList<IJaretTableCell>();
1555         List<IJaretTableCell> s = getSelectionModel().getSelection().getSelectedCells();
1556         for (IJaretTableCell cell : s) {
1557             Point p = getCellDisplayIdx(cell);
1558             if (p.y == y) {
1559                 cells.add(cell);
1560             }
1561         }
1562         return cells;
1563     }
1564 
1565     /***
1566      * Supply tooltip text for a position in the table.
1567      * 
1568      * @param x x coordinate
1569      * @param y y coordinate
1570      * @return tooltip text or <code>null</code>
1571      */
1572     private String getToolTipText(int x, int y) {
1573         IJaretTableCell cell = getCell(x, y);
1574         if (cell != null) {
1575             Rectangle bounds = getCellBounds(cell);
1576             ICellRenderer renderer = getCellRenderer(cell.getRow(), cell.getColumn());
1577             if (renderer != null) {
1578                 String tt = renderer.getTooltip(this, bounds, cell.getRow(), cell.getColumn(), x, y);
1579                 if (tt != null) {
1580                     return tt;
1581                 }
1582             }
1583         }
1584         return null;
1585     }
1586 
1587     // /////// end mouse handling
1588 
1589     // // keyboard handling
1590     /***
1591      * Handle any key presses.
1592      * 
1593      * @param event key event
1594      */
1595     private void handleKeyPressed(KeyEvent event) {
1596         if ((event.stateMask & SWT.SHIFT) != 0 && Character.isISOControl(event.character)) {
1597             switch (event.keyCode) {
1598             case SWT.ARROW_RIGHT:
1599                 selectRight();
1600                 break;
1601             case SWT.ARROW_LEFT:
1602                 selectLeft();
1603                 break;
1604             case SWT.ARROW_DOWN:
1605                 selectDown();
1606                 break;
1607             case SWT.ARROW_UP:
1608                 selectUp();
1609                 break;
1610 
1611             default:
1612                 // do nothing
1613                 break;
1614             }
1615         } else if ((event.stateMask & SWT.CONTROL) != 0 && Character.isISOControl(event.character)) {
1616             // TODO keybindings hard coded is ok for now
1617             // System.out.println("keycode "+event.keyCode);
1618             switch (event.keyCode) {
1619             case 'c':
1620                 copy();
1621                 break;
1622             case 'x':
1623                 cut();
1624                 break;
1625             case 'v':
1626                 paste();
1627                 break;
1628             case 'a':
1629                 selectAll();
1630                 break;
1631 
1632             default:
1633                 // do nothing
1634                 break;
1635             }
1636 
1637         } else {
1638             _lastKeySelect = null;
1639             _firstKeySelect = null;
1640 
1641             switch (event.keyCode) {
1642             case SWT.ARROW_RIGHT:
1643                 focusRight();
1644                 break;
1645             case SWT.ARROW_LEFT:
1646                 focusLeft();
1647                 break;
1648             case SWT.ARROW_DOWN:
1649                 focusDown();
1650                 break;
1651             case SWT.ARROW_UP:
1652                 focusUp();
1653                 break;
1654             case SWT.TAB:
1655                 focusRight();
1656                 break;
1657             case SWT.F2:
1658                 startEditing(_focussedRow, _focussedColumn, (char) 0);
1659                 break;
1660 
1661             default:
1662                 if (event.character == ' ' && isHierarchyColumn(_focussedRow, _focussedColumn)) {
1663                     toggleExpanded(_focussedRow);
1664                 } else if (!Character.isISOControl(event.character)) {
1665                     startEditing(event.character);
1666                 }
1667                 // do nothing
1668                 break;
1669             }
1670         }
1671 
1672     }
1673 
1674     /***
1675      * Enlarge selection to the right.
1676      */
1677     private void selectRight() {
1678         IJaretTableCell cell = getFocussedCell();
1679         if (_lastSelectType == SelectType.CELL && cell != null) {
1680             int cx = getColumnIdx(cell.getColumn());
1681             int cy = getRowIdx(cell.getRow());
1682             if (_lastKeySelect == null) {
1683                 _lastKeySelect = new Point(-1, -1);
1684             }
1685             if (_firstKeySelect == null) {
1686                 _firstKeySelect = new Point(cx, cy);
1687                 _selectionModel.clearSelection();
1688             }
1689 
1690             focusRight();
1691             cell = getFocussedCell();
1692             cx = getColumnIdx(cell.getColumn());
1693             cy = getRowIdx(cell.getRow());
1694 
1695             ensureSelectionContainsRegion(_firstKeySelect.x, _firstKeySelect.y, cx, cy, _lastKeySelect.x,
1696                     _lastKeySelect.y);
1697 
1698             _lastSelectType = SelectType.CELL;
1699             _lastKeySelect = new Point(cx, cy);
1700         } else if (_lastSelectType == SelectType.COLUMN && (_lastColSelectIdx != -1 || _lastKeyColSelectIdx != -1)) {
1701             if (_firstKeyColSelectIdx == -1) {
1702                 _firstKeyColSelectIdx = _lastColSelectIdx;
1703             }
1704             int colIdx = _lastKeyColSelectIdx != -1 ? _lastKeyColSelectIdx + 1 : _firstKeyColSelectIdx + 1;
1705             if (colIdx > _cols.size() - 1) {
1706                 colIdx = _cols.size() - 1;
1707             }
1708             ensureSelectionContainsColRegion(_firstKeyColSelectIdx, colIdx, _lastKeyColSelectIdx);
1709             _lastKeyColSelectIdx = colIdx;
1710         }
1711     }
1712 
1713     /***
1714      * Enlarge selection to the left.
1715      */
1716     private void selectLeft() {
1717         IJaretTableCell cell = getFocussedCell();
1718         if (_lastSelectType == SelectType.CELL && cell != null) {
1719             if (cell != null) {
1720                 int cx = getColumnIdx(cell.getColumn());
1721                 int cy = getRowIdx(cell.getRow());
1722                 if (_lastKeySelect == null) {
1723                     _lastKeySelect = new Point(-1, -1);
1724                 }
1725                 if (_firstKeySelect == null) {
1726                     _firstKeySelect = new Point(cx, cy);
1727                     _selectionModel.clearSelection();
1728                 }
1729 
1730                 focusLeft();
1731 
1732                 cell = getFocussedCell();
1733                 cx = getColumnIdx(cell.getColumn());
1734                 cy = getRowIdx(cell.getRow());
1735 
1736                 ensureSelectionContainsRegion(_firstKeySelect.x, _firstKeySelect.y, cx, cy, _lastKeySelect.x,
1737                         _lastKeySelect.y);
1738                 _lastSelectType = SelectType.CELL;
1739                 _lastKeySelect = new Point(cx, cy);
1740             }
1741         } else if (_lastSelectType == SelectType.COLUMN && (_lastColSelectIdx != -1 || _lastKeyColSelectIdx != -1)) {
1742             if (_firstKeyColSelectIdx == -1) {
1743                 _firstKeyColSelectIdx = _lastColSelectIdx;
1744             }
1745             int colIdx = _lastKeyColSelectIdx != -1 ? _lastKeyColSelectIdx - 1 : _firstKeyColSelectIdx - 1;
1746             if (colIdx < 0) {
1747                 colIdx = 0;
1748             }
1749             ensureSelectionContainsColRegion(_firstKeyColSelectIdx, colIdx, _lastKeyColSelectIdx);
1750             _lastKeyColSelectIdx = colIdx;
1751         }
1752     }
1753 
1754     /***
1755      * Enlarge selection downwards.
1756      */
1757     private void selectDown() {
1758         IJaretTableCell cell = getFocussedCell();
1759         if (_lastSelectType == SelectType.CELL && cell != null) {
1760             if (cell != null) {
1761                 int cx = getColumnIdx(cell.getColumn());
1762                 int cy = getRowIdx(cell.getRow());
1763                 if (_lastKeySelect == null) {
1764                     _lastKeySelect = new Point(-1, -1);
1765                 }
1766                 if (_firstKeySelect == null) {
1767                     _firstKeySelect = new Point(cx, cy);
1768                     _selectionModel.clearSelection();
1769                 }
1770 
1771                 focusDown();
1772 
1773                 cell = getFocussedCell();
1774                 cx = getColumnIdx(cell.getColumn());
1775                 cy = getRowIdx(cell.getRow());
1776 
1777                 ensureSelectionContainsRegion(_firstKeySelect.x, _firstKeySelect.y, cx, cy, _lastKeySelect.x,
1778                         _lastKeySelect.y);
1779 
1780                 _lastSelectType = SelectType.CELL;
1781                 _lastKeySelect = new Point(cx, cy);
1782             }
1783         } else if (_lastSelectType == SelectType.ROW && (_lastRowSelectIdx != -1 || _lastKeyRowSelectIdx != -1)) {
1784             if (_firstKeyRowSelectIdx == -1) {
1785                 _firstKeyRowSelectIdx = _lastRowSelectIdx;
1786             }
1787             int rowIdx = _lastKeyRowSelectIdx != -1 ? _lastKeyRowSelectIdx + 1 : _firstKeyRowSelectIdx + 1;
1788             if (rowIdx > _rows.size() - 1) {
1789                 rowIdx = _rows.size() - 1;
1790             }
1791             ensureSelectionContainsRowRegion(_firstKeyRowSelectIdx, rowIdx, _lastKeyRowSelectIdx);
1792             _lastKeyRowSelectIdx = rowIdx;
1793         }
1794 
1795     }
1796 
1797     /***
1798      * Enlarge selection upwards.
1799      */
1800     private void selectUp() {
1801         IJaretTableCell cell = getFocussedCell();
1802         if (_lastSelectType == SelectType.CELL && cell != null) {
1803             if (cell != null) {
1804                 int cx = getColumnIdx(cell.getColumn());
1805                 int cy = getRowIdx(cell.getRow());
1806                 if (_lastKeySelect == null) {
1807                     _lastKeySelect = new Point(-1, -1);
1808                 }
1809                 if (_firstKeySelect == null) {
1810                     _firstKeySelect = new Point(cx, cy);
1811                     _selectionModel.clearSelection();
1812                 }
1813 
1814                 focusUp();
1815 
1816                 cell = getFocussedCell();
1817                 cx = getColumnIdx(cell.getColumn());
1818                 cy = getRowIdx(cell.getRow());
1819 
1820                 ensureSelectionContainsRegion(_firstKeySelect.x, _firstKeySelect.y, cx, cy, _lastKeySelect.x,
1821                         _lastKeySelect.y);
1822 
1823                 _lastSelectType = SelectType.CELL;
1824                 _lastKeySelect = new Point(cx, cy);
1825             }
1826         } else if (_lastSelectType == SelectType.ROW && (_lastRowSelectIdx != -1 || _lastKeyRowSelectIdx != -1)) {
1827             if (_firstKeyRowSelectIdx == -1) {
1828                 _firstKeyRowSelectIdx = _lastRowSelectIdx;
1829             }
1830             int rowIdx = _lastKeyRowSelectIdx != -1 ? _lastKeyRowSelectIdx - 1 : _firstKeyRowSelectIdx - 1;
1831             if (rowIdx < 0) {
1832                 rowIdx = 0;
1833             }
1834             ensureSelectionContainsRowRegion(_firstKeyRowSelectIdx, rowIdx, _lastKeyRowSelectIdx);
1835             _lastKeyRowSelectIdx = rowIdx;
1836         }
1837     }
1838 
1839     /***
1840      * Retrieve the currently focussed cell.
1841      * 
1842      * @return the focussed cell or <code>null</code> if no cell is focussed
1843      */
1844     public IJaretTableCell getFocussedCell() {
1845         if (_focussedColumn != null && _focussedRow != null) {
1846             return new JaretTableCellImpl(_focussedRow, _focussedColumn);
1847         }
1848         return null;
1849     }
1850 
1851     /***
1852      * Retrieve the indizes of the currently focussed cell (idx in the filtered, sorted or whatever table).
1853      * 
1854      * @return Point x = column, y = row or <code>null</code> if no cell is focussed
1855      */
1856     public Point getFocussedCellIdx() {
1857         if (_focussedColumn != null && _focussedRow != null) {
1858             return new Point(getColumnIdx(_focussedColumn), getRowIdx(_focussedRow));
1859         }
1860         return null;
1861     }
1862 
1863     /***
1864      * Retrieve the display coordinates for a table cell.
1865      * 
1866      * @param cell cell to get he coordinates for
1867      * @return Point x = colIdx, y = rowIdx
1868      */
1869     public Point getCellDisplayIdx(IJaretTableCell cell) {
1870         return new Point(getColumnIdx(cell.getColumn()), getRowIdx(cell.getRow()));
1871     }
1872 
1873     /***
1874      * Convenience method for setting a value at a displayed position in the table. NOTE: this method does call the the
1875      * set method of the model directly, so be aware that the model may protest by throwing a runtime exception or just
1876      * ignore the new value.
1877      * 
1878      * @param colIdx column index
1879      * @param rowIdx row index
1880      * @param value value to set
1881      */
1882     public void setValue(int colIdx, int rowIdx, Object value) {
1883         IColumn col = getColumn(colIdx);
1884         IRow row = getRow(rowIdx);
1885         col.setValue(row, value);
1886     }
1887 
1888     /***
1889      * {@inheritDoc} will get call to transfer focus to the table. The mthod will focus the left/uppermost cell
1890      * displayed. If no rows and columns are present no cell will get the focus.
1891      */
1892     public boolean setFocus() {
1893         super.setFocus();
1894         if (_focussedRow == null && _rows != null && _rows.size() > 0 && _cols.size() > 0) {
1895             setFocus(_rows.get(_firstRowIdx), _cols.get(_firstColIdx));
1896         }
1897         return true;
1898     }
1899 
1900     /***
1901      * Ensures there is a focussed cell and uses the cell at 0,0 if no cell is focussed.
1902      * 
1903      */
1904     private void ensureFocus() {
1905         if (_focussedRow == null) {
1906             _focussedRow = _rows.get(0);
1907         }
1908         if (_focussedColumn == null) {
1909             _focussedColumn = _cols.get(0);
1910         }
1911     }
1912 
1913     /***
1914      * Move the focus left.
1915      */
1916     public void focusLeft() {
1917         ensureFocus();
1918         int idx = _cols.indexOf(_focussedColumn);
1919         if (idx > 0) {
1920             setFocus(_focussedRow, _cols.get(idx - 1));
1921         }
1922     }
1923 
1924     /***
1925      * Move the focus right.
1926      */
1927     public void focusRight() {
1928         ensureFocus();
1929         int idx = _cols.indexOf(_focussedColumn);
1930         if (idx < _cols.size() - 1) {
1931             setFocus(_focussedRow, _cols.get(idx + 1));
1932         }
1933     }
1934 
1935     /***
1936      * Move the focus up.
1937      */
1938     public void focusUp() {
1939         ensureFocus();
1940         int idx = _rows.indexOf(_focussedRow);
1941         if (idx > 0) {
1942             setFocus(_rows.get(idx - 1), _focussedColumn);
1943         }
1944     }
1945 
1946     /***
1947      * Move the focus down.
1948      */
1949     public void focusDown() {
1950         ensureFocus();
1951         int idx = _rows.indexOf(_focussedRow);
1952         if (idx < _rows.size() - 1) {
1953             setFocus(_rows.get(idx + 1), _focussedColumn);
1954         }
1955     }
1956 
1957     /***
1958      * Set the focussed cell by coordinates.
1959      * 
1960      * @param x x coordinate
1961      * @param y y coordinate
1962      */
1963     private void setFocus(int x, int y) {
1964         IRow row = rowForY(y);
1965         IColumn col = colForX(x);
1966         if (col != null && row != null) {
1967             setFocus(row, col);
1968         }
1969     }
1970 
1971     /***
1972      * Check whether editing of a cell is in progress.
1973      * 
1974      * @return true when editing a cell
1975      */
1976     public boolean isEditing() {
1977         return _editor != null;
1978     }
1979 
1980     /***
1981      * Handle a single mouseclick by passing it to the cell editor if present.
1982      * 
1983      * @param x x coordinate of the click
1984      * @param y y coordinate of the click
1985      * @return true if the editor handled the click
1986      */
1987     private boolean handleEditorSingleClick(int x, int y) {
1988         IRow row = rowForY(y);
1989         IColumn col = colForX(x);
1990         if (col != null && row != null) {
1991             ICellEditor editor = getCellEditor(row, col);
1992             if (editor != null) {
1993                 Rectangle area = getCellBounds(row, col);
1994                 return editor.handleClick(this, row, col, area, x, y);
1995             }
1996         }
1997         return false;
1998     }
1999 
2000     /***
2001      * Start editing after a keystroke on a cell.
2002      * 
2003      * @param typedKey the typed key
2004      */
2005     private void startEditing(char typedKey) {
2006         if (_focussedRow != null && _focussedColumn != null) {
2007             startEditing(_focussedRow, _focussedColumn, typedKey);
2008         }
2009     }
2010 
2011     /***
2012      * Start editing of a specified cell if it is editable.
2013      * 
2014      * @param row row
2015      * @param col column
2016      * @param typedKey key typed
2017      */
2018     public void startEditing(IRow row, IColumn col, char typedKey) {
2019         if (isEditing()) {
2020             stopEditing(true);
2021         }
2022         if (!_model.isEditable(row, col)) {
2023             return;
2024         }
2025         clearSelection();
2026         if (row != null && col != null) {
2027             _editor = getCellEditor(row, col);
2028             if (_editor != null) {
2029                 _editorControl = _editor.getEditorControl(this, row, col, typedKey);
2030                 if (_editorControl != null) {
2031                     Rectangle bounds = getCellBounds(row, col);
2032                     // TODO borderwidth
2033                     bounds.x += 1;
2034                     bounds.width -= 1;
2035                     bounds.y += 1;
2036                     if (_editor.getPreferredHeight() == -1 || _editor.getPreferredHeight() < bounds.height) {
2037                         bounds.height -= 1;
2038                     } else {
2039                         bounds.height = _editor.getPreferredHeight();
2040                     }
2041                     _editorControl.setBounds(bounds);
2042                     _editorControl.setVisible(true);
2043                     _editorControl.forceFocus();
2044                 }
2045                 _editorRow = row;
2046             } else {
2047                 // System.out.println("no cell editor found!");
2048             }
2049         }
2050         if (_editorControl == null) {
2051             stopEditing(true);
2052         }
2053     }
2054 
2055     /***
2056      * Clear the selection.
2057      */
2058     private void clearSelection() {
2059         _selectionModel.clearSelection();
2060     }
2061 
2062     /***
2063      * Stop editing if in progress.
2064      * 
2065      * @param storeValue if true the value of the editor is stored.
2066      */
2067     public void stopEditing(boolean storeValue) {
2068         if (isEditing()) {
2069             _editor.stopEditing(storeValue);
2070             if (storeValue && (_tvs.getRowHeigthMode(_editorRow) == ITableViewState.RowHeightMode.OPTIMAL)
2071                     || _tvs.getRowHeigthMode(_editorRow) == ITableViewState.RowHeightMode.OPTANDVAR) {
2072                 optimizeHeight(_editorRow);
2073             }
2074             _editorRow = null;
2075             _editor = null;
2076         }
2077     }
2078 
2079     /***
2080      * Set the focussed cell.
2081      * 
2082      * @param row row
2083      * @param col column
2084      */
2085     private void setFocus(IRow row, IColumn col) {
2086         if (_focussedRow != row || _focussedColumn != col) {
2087             IRow oldRow = _focussedRow;
2088             IColumn oldCol = _focussedColumn;
2089 
2090             _focussedRow = row;
2091             _focussedColumn = col;
2092 
2093             if (isCompleteVisible(_focussedRow, _focussedColumn)) {
2094                 redraw(_focussedRow, _focussedColumn);
2095                 if (oldRow != null && oldCol != null) {
2096                     redraw(oldRow, oldCol);
2097                 }
2098             } else {
2099                 scrollToVisible(_focussedRow, _focussedColumn); // includes redrawing
2100             }
2101             fireTableFocusChanged(row, col);
2102         }
2103     }
2104 
2105     /***
2106      * Calculate the preferred height of a row. Only visibl columns are taken into account.
2107      * 
2108      * @param gc Graphics context
2109      * @param row row to calculate the height for
2110      * @return preferred height or -1 if no preferred height can be determined
2111      */
2112     private int getPreferredRowHeight(GC gc, IRow row) {
2113         int result = -1;
2114         for (IColumn column : _cols) {
2115             if (_tvs.getColumnVisible(column)) {
2116                 ICellRenderer renderer = getCellRenderer(row, column);
2117                 ICellStyle cellStyle = _tvs.getCellStyle(row, column);
2118                 int ph = renderer.getPreferredHeight(gc, cellStyle, _tvs.getColumnWidth(column), row, column);
2119                 if (ph > result) {
2120                     result = ph;
2121                 }
2122             }
2123         }
2124         if (result < _tvs.getMinimalRowHeight()) {
2125             result = _tvs.getMinimalRowHeight();
2126         }
2127         return result;
2128     }
2129 
2130     /*** list of rows that will be optimized before the next drawing using the gc at hand. */
2131     protected Collection<IRow> _rowsToOptimize = Collections.synchronizedCollection(new HashSet<IRow>());
2132 
2133     /***
2134      * Register a row for height optimization in the next redrwa (redraw triggered by this method).
2135      * 
2136      * @param row row to optimize height for
2137      */
2138     public void optimizeHeight(IRow row) {
2139         _rowsToOptimize.add(row);
2140         syncedRedraw();
2141     }
2142 
2143     /***
2144      * Remove a row from the list to optimize.
2145      * 
2146      * @param row row to remove from the list
2147      */
2148     private void doNotOptimizeHeight(IRow row) {
2149         _rowsToOptimize.remove(row);
2150     }
2151 
2152     /***
2153      * Register a list of rows for heigt optimization.
2154      * 
2155      * @param rows list of rows to optimize
2156      */
2157     public void optimizeHeight(List<IRow> rows) {
2158         _rowsToOptimize.addAll(rows);
2159         syncedRedraw();
2160     }
2161 
2162     /***
2163      * Calculates and sets the row height for all rows waiting to be optimized.
2164      * 
2165      * @param gc GC for calculation of the heights
2166      */
2167     private void doRowHeightOptimization(GC gc) {
2168         for (IRow row : _rowsToOptimize) {
2169             int h = getPreferredRowHeight(gc, row);
2170             if (h != -1) {
2171                 _tvs.setRowHeight(row, h);
2172             }
2173         }
2174         _rowsToOptimize.clear();
2175     }
2176 
2177     /***
2178      * Scroll the addressed cell so, that is it completely visible.
2179      * 
2180      * @param row row of the cell
2181      * @param column column of the cell
2182      */
2183     public void scrollToVisible(IRow row, IColumn column) {
2184         // first decide: above the visible area or below?
2185         int rIdx = _rows.indexOf(row);
2186         int cellY = getAbsBeginYForRowIdx(rIdx) - getFixedRowsHeight();
2187         int shownY = getAbsBeginYForRowIdx(_firstRowIdx) + _firstRowPixelOffset - getFixedRowsHeight();
2188         if (cellY < shownY) {
2189             if (getVerticalBar() != null) {
2190                 getVerticalBar().setSelection(cellY);
2191             }
2192         } else {
2193             int cellHeight = _tvs.getRowHeight(row);
2194             if (getVerticalBar() != null) {
2195                 getVerticalBar().setSelection(cellY + cellHeight - _tableRect.height);
2196             }
2197         }
2198         // handleVerticalScroll(null);
2199         // now left/right
2200         int cIdx = _cols.indexOf(column);
2201         int cellX = getAbsBeginXForColIdx(cIdx) - getFixedColumnsWidth();
2202         int shownX = getAbsBeginXForColIdx(_firstColIdx) - getFixedColumnsWidth();
2203         if (cellX < shownX) {
2204             if (getHorizontalBar() != null) {
2205                 getHorizontalBar().setSelection(cellX);
2206             }
2207         } else {
2208             int cellWidth = _tvs.getColumnWidth(column);
2209             if (getHorizontalBar() != null) {
2210                 getHorizontalBar().setSelection(cellX + cellWidth - _tableRect.width);
2211             }
2212         }
2213         updateScrollBars();
2214         redraw();
2215     }
2216 
2217     /***
2218      * Return true, if the adressed cell is completely (i.e. not clipped) visible.
2219      * 
2220      * @param row row of the cell
2221      * @param column column of the cell
2222      * @return true if the cell is completely visible
2223      */
2224     private boolean isCompleteVisible(IRow row, IColumn column) {
2225         RowInfo rInfo = getRowInfo(row);
2226         if (rInfo == null) {
2227             return false;
2228         }
2229         ColInfo cInfo = getColInfo(column);
2230         if (cInfo == null) {
2231             return false;
2232         }
2233         Rectangle b = getCellBounds(rInfo, cInfo);
2234         if (!(_tableRect.contains(b.x, b.y) && _tableRect.contains(b.x + b.width, b.y + b.height))) {
2235             if (_fixedColumns == 0 && _fixedRows == 0) {
2236                 return false;
2237             } else {
2238                 // may be in a fixed area
2239                 if (_fixedColumns > 0 && _fixedColRect.contains(b.x, b.y)
2240                         && _fixedColRect.contains(b.x + b.width, b.y + b.height)) {
2241                     return true;
2242                 }
2243                 if (_fixedRows > 0 && _fixedRowRect.contains(b.x, b.y)
2244                         && _fixedRowRect.contains(b.x + b.width, b.y + b.height)) {
2245                     return true;
2246                 }
2247                 return false;
2248             }
2249         }
2250         return true;
2251     }
2252 
2253     /***
2254      * Check whether a row is currently displayed.
2255      * 
2256      * @param row row to check
2257      * @return true if the row is displayed.
2258      */
2259     public boolean isDisplayed(IRow row) {
2260         RowInfo rInfo = getRowInfo(row);
2261         return rInfo != null;
2262     }
2263 
2264     /***
2265      * Check whether a column is currently displayed.
2266      * 
2267      * @param column column to check
2268      * @return true if the column is displayed.
2269      */
2270     public boolean isDisplayed(IColumn column) {
2271         ColInfo cInfo = getColInfo(column);
2272         return cInfo != null;
2273     }
2274 
2275     /***
2276      * Retrieve row by y coordinate of the bottom border.
2277      * 
2278      * @param y y coordinate
2279      * @return a row identified by the bottom delimiter or <code>null</code>
2280      */
2281     private IRow rowByBottomBorder(int y) {
2282         for (RowInfo info : getRowInfos()) {
2283             int by = info.y + info.height;
2284             if (Math.abs(by - y) <= SELDELTA) {
2285                 return info.row;
2286             }
2287         }
2288         return null;
2289     }
2290 
2291     /***
2292      * Retrieve the row info for a row.
2293      * 
2294      * @param row row to get the info for
2295      * @return info or <code>null</code>
2296      */
2297     protected RowInfo getRowInfo(IRow row) {
2298         for (RowInfo info : getRowInfos()) {
2299             if (info.row.equals(row)) {
2300                 return info;
2301             }
2302         }
2303         return null;
2304     }
2305 
2306     /***
2307      * Retrieve a column by its right border.
2308      * 
2309      * @param x x coordinate
2310      * @return the column or <code>null</code>
2311      */
2312     private IColumn colByRightBorder(int x) {
2313         if (_colInfoCache != null) {
2314             for (ColInfo info : _colInfoCache) {
2315                 int bx = info.x + info.width;
2316                 if (Math.abs(bx - x) <= SELDELTA) {
2317                     return info.column;
2318                 }
2319             }
2320         }
2321         return null;
2322     }
2323 
2324     /***
2325      * Retrieve cached col info for a row.
2326      * 
2327      * @param col column to get the info for
2328      * @return colInfo or <code>null</code>
2329      */
2330     private ColInfo getColInfo(IColumn col) {
2331         if (_colInfoCache != null) {
2332             for (ColInfo info : _colInfoCache) {
2333                 if (info.column.equals(col)) {
2334                     return info;
2335                 }
2336             }
2337         }
2338         return null;
2339     }
2340 
2341     /***
2342      * Get the bounding rectangle for a cell.
2343      * 
2344      * @param row row of the cell
2345      * @param column column of the cell
2346      * @return the bounding rectangle or <code>null</code> if the cell is not visible
2347      */
2348     public Rectangle getCellBounds(IRow row, IColumn column) {
2349         RowInfo rInfo = getRowInfo(row);
2350         ColInfo cInfo = getColInfo(column);
2351         if (rInfo == null || cInfo == null) {
2352             return null;
2353         }
2354         return getCellBounds(rInfo, cInfo);
2355     }
2356 
2357     /***
2358      * Get the bounding rectangle for a cell.
2359      * 
2360      * @param rInfo row info for the row
2361      * @param cInfo column info for the row
2362      * @return the bounding rectangle
2363      */
2364     private Rectangle getCellBounds(RowInfo rInfo, ColInfo cInfo) {
2365         return new Rectangle(cInfo.x, rInfo.y, cInfo.width, rInfo.height);
2366     }
2367 
2368     /***
2369      * Retrieve the bounds for a cell.
2370      * 
2371      * @param cell cell
2372      * @return cell bounds or null if the cell is not displayed
2373      */
2374     private Rectangle getCellBounds(IJaretTableCell cell) {
2375         return getCellBounds(cell.getRow(), cell.getColumn());
2376     }
2377 
2378     /***
2379      * Get the bounding rect for a row.
2380      * 
2381      * @param row row
2382      * @return bounding rect or <code>null</code> if the row is not visible
2383      */
2384     private Rectangle getRowBounds(IRow row) {
2385         RowInfo rInfo = getRowInfo(row);
2386         return rInfo == null ? null : getRowBounds(rInfo);
2387     }
2388 
2389     /***
2390      * Get bounding rect for a row by the rowinfo.
2391      * 
2392      * @param info row info
2393      * @return bounding rect
2394      */
2395     private Rectangle getRowBounds(RowInfo info) {
2396         int bx = _fixedColumns == 0 ? _tableRect.x : _fixedColRect.x;
2397         int width = _fixedColumns == 0 ? _tableRect.width : _fixedColRect.width + _tableRect.width;
2398         return new Rectangle(bx, info.y, width, info.height);
2399     }
2400 
2401     /***
2402      * Get the bounding rect for a column.
2403      * 
2404      * @param column column
2405      * @return bounding rect or <code>null</code> if the column is not visible
2406      */
2407     public Rectangle getColumnBounds(IColumn column) {
2408         ColInfo cInfo = getColInfo(column);
2409 
2410         return cInfo != null ? getColumnBounds(cInfo) : null;
2411     }
2412 
2413     /***
2414      * Get the bounds for a column.
2415      * 
2416      * @param info column info
2417      * @return bounding rectangle
2418      */
2419     private Rectangle getColumnBounds(ColInfo info) {
2420         int by = _fixedRows == 0 ? _tableRect.y : _fixedRowRect.y;
2421         int height = _fixedRows == 0 ? _tableRect.height : _fixedRowRect.height + _tableRect.height;
2422         return new Rectangle(info.x, by, info.width, height);
2423     }
2424 
2425     /***
2426      * Redraw a cell. Method can be called from any thread.
2427      * 
2428      * @param row row of the cell
2429      * @param column column of the cell
2430      */
2431     private void redraw(IRow row, IColumn column) {
2432         Rectangle r = getCellBounds(row, column);
2433         if (r != null) {
2434             syncedRedraw(r.x, r.y, r.width, r.height);
2435         }
2436     }
2437 
2438     /***
2439      * Redraw a row. Method can be called from any thread.
2440      * 
2441      * @param row row to be painted
2442      */
2443     private void redraw(IRow row) {
2444         Rectangle r = getRowBounds(row);
2445         if (r != null) {
2446             syncedRedraw(r.x, r.y, r.width, r.height);
2447         }
2448     }
2449 
2450     /***
2451      * Redraw a column. Method can be called from any thread.
2452      * 
2453      * @param column column to be repainted
2454      */
2455     private void redraw(IColumn column) {
2456         Rectangle r = getColumnBounds(column);
2457         if (r != null) {
2458             syncedRedraw(r.x, r.y, r.width, r.height);
2459         }
2460     }
2461 
2462     /***
2463      * Redraw a region. This method can be called from any thread.
2464      * 
2465      * @param x x coordinate
2466      * @param y y coordinate
2467      * @param width width of the region
2468      * @param height height of the region
2469      */
2470     private void syncedRedraw(final int x, final int y, final int width, final int height) {
2471         Runnable r = new Runnable() {
2472             public void run() {
2473                 if (!isDisposed()) {
2474                     redraw(x, y, width, height, true);
2475                 }
2476             }
2477         };
2478         Display.getCurrent().syncExec(r);
2479     }
2480 
2481     /***
2482      * Redraw complete area. Safe to call from any thread.
2483      * 
2484      */
2485     private void syncedRedraw() {
2486         Runnable r = new Runnable() {
2487             public void run() {
2488                 if (!isDisposed()) {
2489                     redraw();
2490                 }
2491             }
2492         };
2493         Display.getCurrent().syncExec(r);
2494     }
2495 
2496     // / end redra methods
2497 
2498     /***
2499      * Update the scrollbars.
2500      */
2501     protected void updateScrollBars() {
2502         updateYScrollBar();
2503         updateXScrollBar();
2504     }
2505 
2506     /***
2507      * Update the horiontal scrollbar.
2508      */
2509     private void updateXScrollBar() {
2510         if (Display.getCurrent() != null) {
2511             Display.getCurrent().syncExec(new Runnable() {
2512                 public void run() {
2513                     ScrollBar scroll = getHorizontalBar();
2514                     // scroll may be null
2515                     if (scroll != null) {
2516                         _oldHorizontalScroll = -1; // make sure no optimization will be applied
2517                         scroll.setMinimum(0);
2518                         scroll.setMaximum(getTotalWidth() - getFixedColumnsWidth());
2519                         scroll.setThumb(getWidth() - getFixedColumnsWidth());
2520                         scroll.setIncrement(50); // increment for arrows
2521                         scroll.setPageIncrement(getWidth()); // page increment areas
2522                     }
2523                 }
2524             });
2525         }
2526     }
2527 
2528     /*** last horizontal scroll value for scroll optimization. */
2529     private int _oldHorizontalScroll = -1;
2530     /*** last vertical scroll value for scroll optimization. */
2531     private int _oldVerticalScroll = -1;
2532 
2533     /*** if true use optimized scrolling. */
2534     private boolean _optimizeScrolling = true;
2535 
2536     /***
2537      * Handle a change of the horizontal scrollbar.
2538      * 
2539      * @param event SelectionEvent
2540      */
2541     private void handleHorizontalScroll(SelectionEvent event) {
2542         int value = getHorizontalBar().getSelection() + getFixedColumnsWidth();
2543         int colIdx = getColIdxForAbsX(value);
2544         int offset = value - getAbsBeginXForColIdx(colIdx);
2545         int diff = _oldHorizontalScroll - value;
2546 
2547         if (Math.abs(diff) > _tableRect.width / 2 || _oldHorizontalScroll == -1 || !_optimizeScrolling) {
2548             _firstColIdx = colIdx;
2549             _firstColPixelOffset = offset;
2550             redraw();
2551         } else {
2552             if (diff > 0) {
2553                 scroll(_tableRect.x + diff, 0, _tableRect.x, 0, _tableRect.width - diff, getHeight(), false);
2554             } else {
2555                 diff = -diff;
2556                 scroll(_tableRect.x, 0, _tableRect.x + diff, 0, _tableRect.width - diff, getHeight(), false);
2557             }
2558             _firstColIdx = colIdx;
2559             _firstColPixelOffset = offset;
2560         }
2561         _oldHorizontalScroll = value;
2562     }
2563 
2564     /***
2565      * Update the vertical scrollbar if present.
2566      */
2567     public void updateYScrollBar() {
2568         if (Display.getCurrent() != null) {
2569             Display.getCurrent().syncExec(new Runnable() {
2570                 public void run() {
2571                     ScrollBar scroll = getVerticalBar();
2572                     // scroll may be null
2573                     if (scroll != null) {
2574                         _oldVerticalScroll = -1; // guarantee a clean repaint
2575                         scroll.setMinimum(0);
2576                         scroll.setMaximum(getTotalHeight() - getFixedRowsHeight());
2577                         int height = getHeight();
2578                         if (_tableRect != null) {
2579                             height = _tableRect.height;
2580                         }
2581                         scroll.setThumb(height); // - getFixedRowsHeight() - getHeaderHeight());
2582                         scroll.setIncrement(50); // increment for arrows
2583                         scroll.setPageIncrement(getHeight()); // page increment areas
2584                         scroll.setSelection(getAbsBeginYForRowIdx(_firstRowIdx) + _firstRowPixelOffset
2585                                 + getFixedRowsHeight());
2586                     }
2587                 }
2588             });
2589         }
2590     }
2591 
2592     /***
2593      * Handle a selection on the vertical scroll bar (a vertical scroll).
2594      * 
2595      * @param event selection
2596      */
2597     private void handleVerticalScroll(SelectionEvent event) {
2598         int value = getVerticalBar().getSelection() + getFixedRowsHeight();
2599         int rowidx = getRowIdxForAbsY(value);
2600         int offset = value - getAbsBeginYForRowIdx(rowidx);
2601         int oldFirstIdx = _firstRowIdx;
2602         int oldPixelOffset = _firstRowPixelOffset;
2603 
2604         int diff = _oldVerticalScroll - value;
2605 
2606         if (Math.abs(diff) > _tableRect.height / 2 || _oldVerticalScroll == -1 || !_optimizeScrolling) {
2607             update();
2608             _firstRowIdx = rowidx;
2609             _firstRowPixelOffset = offset;
2610             _rowInfoCache = null; // kill the cache
2611             redraw();
2612         } else {
2613             Rectangle completeArea = new Rectangle(_fixedColRect.x, _tableRect.y, _fixedColRect.width
2614                     + _tableRect.width, _tableRect.height);
2615 
2616             if (diff > 0) {
2617                 scroll(0, completeArea.y + diff, 0, completeArea.y, getWidth(), completeArea.height - diff, false);
2618             } else {
2619                 diff = -diff;
2620                 scroll(0, completeArea.y, 0, completeArea.y + diff, getWidth(), completeArea.height - diff, false);
2621             }
2622             _firstRowIdx = rowidx;
2623             _firstRowPixelOffset = offset;
2624             _rowInfoCache = null; // kill the cache
2625         }
2626         _oldVerticalScroll = value;
2627         firePropertyChange(PROPERTYNAME_FIRSTROWIDX, oldFirstIdx, _firstRowIdx);
2628         firePropertyChange(PROPERTYNAME_FIRSTROWPIXELOFFSET, oldPixelOffset, _firstRowPixelOffset);
2629     }
2630 
2631     /***
2632      * Get the absolute begin x for a column.
2633      * 
2634      * @param colIdx index of the column (in the displayed columns)
2635      * @return the absolute x coordinate
2636      */
2637     public int getAbsBeginXForColIdx(int colIdx) {
2638         int x = 0;
2639         for (int idx = 0; idx < colIdx; idx++) {
2640             IColumn col = _cols.get(idx);
2641             int colWidth = _tvs.getColumnWidth(col);
2642             x += colWidth;
2643         }
2644         return x;
2645     }
2646 
2647     /***
2648      * Get the absolute begin x for a column.
2649      * 
2650      * @param column the column
2651      * @return the absolute x coordinate
2652      */
2653     public int getAbsBeginXForColumn(IColumn column) {
2654         return getAbsBeginXForColIdx(_cols.indexOf(column));
2655     }
2656 
2657     /***
2658      * Retrieve the beginning x coordinate for a column.
2659      * 
2660      * @param column column
2661      * @return beginning x coordinate for drawing that column
2662      */
2663     private int xForCol(IColumn column) {
2664         int x = getAbsBeginXForColIdx(_cols.indexOf(column));
2665         int begin = getAbsBeginXForColIdx(_firstColIdx) + _firstColPixelOffset;
2666         return x - begin + _fixedColRect.width;
2667     }
2668 
2669     /***
2670      * Return the (internal) index of the column corresponding to the given x coordinate value (absolute value taking
2671      * all visible columns into account).
2672      * 
2673      * @param absX absolute x coordinate
2674      * @return the column index in the internal list of columns (or -1 if none could be determined)
2675      */
2676     public int getColIdxForAbsX(int absX) {
2677         int idx = 0;
2678         int x = 0;
2679         while (x <= absX && idx < _cols.size()) {
2680             IColumn col = _cols.get(idx);
2681             int colWidth = _tvs.getColumnWidth(col);
2682             x += colWidth;
2683             idx++;
2684         }
2685         return idx < _cols.size() ? idx - 1 : -1;
2686     }
2687 
2688     /***
2689      * Return the column for an absolute x coordinate.
2690      * 
2691      * @param absX absolute x coordinate
2692      * @return the column for the coordinate
2693      */
2694     public IColumn getColumnForAbsX(int absX) {
2695         return _cols.get(getColIdxForAbsX(absX));
2696     }
2697 
2698     /***
2699      * Return the absolute y coordinate for the given row (given by index).
2700      * 
2701      * @param rowidx index of the row
2702      * @return absolute y coordinate (of all rows) for the row
2703      */
2704     private int getAbsBeginYForRowIdx(int rowidx) {
2705         int y = 0;
2706         for (int idx = 0; idx < rowidx; idx++) {
2707             IRow row = _rows.get(idx);
2708             int rowHeight = _tvs.getRowHeight(row);
2709             y += rowHeight;
2710         }
2711         return y;
2712     }
2713 
2714     /***
2715      * Get row index for an absolute y coordinate (thought on the full height table with all rows).
2716      * 
2717      * @TODO optimize
2718      * @param absY absolute y position (thought on the full table height)
2719      * @return index of the corresponding row
2720      */
2721     public int getRowIdxForAbsY(int absY) {
2722         int idx = 0;
2723         int y = 0;
2724         while (y <= absY) {
2725             IRow row = _rows.get(idx);
2726             int rowHeight = _tvs.getRowHeight(row);
2727             y += rowHeight;
2728             idx++;
2729         }
2730         return idx - 1;
2731     }
2732 
2733     /***
2734      * Retrie internal index of gicen row.
2735      * 
2736      * @param row row
2737      * @return internal index or -1 if the row is not in the internal list
2738      */
2739     private int getRowIdx(IRow row) {
2740         return row != null ? _rows.indexOf(row) : -1;
2741     }
2742 
2743     /***
2744      * Retrieve the row corresponding to a specified y coordinate.
2745      * 
2746      * @param y y
2747      * @return row for that y ycoordinate or <code>null</code> if no row could be determined.
2748      */
2749     public IRow rowForY(int y) {
2750         if ((y < _tableRect.y || y > _tableRect.y + _tableRect.height) && _fixedRows == 0) {
2751             return null;
2752         }
2753 
2754         for (RowInfo rInfo : getRowInfos()) {
2755             if (y >= rInfo.y && y < rInfo.y + rInfo.height) {
2756                 return rInfo.row;
2757             }
2758         }
2759         return null;
2760     }
2761 
2762     /***
2763      * Retrive the list of currently availbale RowInfo etries.
2764      * 
2765      * @return list of rowinfos
2766      */
2767     private List<RowInfo> getRowInfos() {
2768         if (_rowInfoCache == null) {
2769             fillRowInfoCache();
2770         }
2771         return _rowInfoCache;
2772     }
2773 
2774     /***
2775      * Fill the cache of row infos for the current viewconfiguration.
2776      */
2777     private void fillRowInfoCache() {
2778         if (_rowInfoCache != null) {
2779             return;
2780         }
2781         _rowInfoCache = new ArrayList<RowInfo>();
2782 
2783         // fixed row area
2784         int y = 0;
2785         if (_fixedRows > 0) {
2786             y = _fixedRowRect.y;
2787             for (int rIdx = 0; rIdx < _fixedRows; rIdx++) {
2788                 IRow row = _rows.get(rIdx);
2789                 int rHeight = _tvs.getRowHeight(row);
2790                 _rowInfoCache.add(new RowInfo(row, y, rHeight, true));
2791                 y += rHeight;
2792             }
2793 
2794         }
2795         // normal table area
2796         int rIdx = _firstRowIdx;
2797         y = _tableRect.y - _firstRowPixelOffset;
2798 
2799         while (y < getHeight() && rIdx < _rows.size()) {
2800             IRow row = _rows.get(rIdx);
2801             int rHeight = _tvs.getRowHeight(row);
2802             _rowInfoCache.add(new RowInfo(row, y, rHeight, false));
2803             y += rHeight;
2804             rIdx++;
2805         }
2806     }
2807 
2808     /***
2809      * Retrieve a row from the internal list of rows.
2810      * 
2811      * @param idx index in the internal list
2812      * @return the row for idx
2813      */
2814     private IRow rowForIdx(int idx) {
2815         return _rows.get(idx);
2816     }
2817 
2818     /***
2819      * Retrieve the column corresponding to a x coordinate.
2820      * 
2821      * @param x x
2822      * @return the corresponding column or <code>null</code> if none could be determined
2823      */
2824     public IColumn colForX(int x) {
2825         if ((x < _tableRect.x || x > _tableRect.x + _tableRect.width) && _fixedColumns == 0) {
2826             return null;
2827         }
2828         for (ColInfo cInfo : _colInfoCache) {
2829             if (x >= cInfo.x && x < cInfo.x + cInfo.width) {
2830                 return cInfo.column;
2831             }
2832         }
2833         return null;
2834     }
2835 
2836     /***
2837      * Get the column for a given index.
2838      * 
2839      * @param idx index
2840      * @return tghe column
2841      */
2842     private IColumn colForIdx(int idx) {
2843         return _cols.get(idx);
2844     }
2845 
2846     /***
2847      * Get the index of a given column.
2848      * 
2849      * @param column column
2850      * @return the index of the column or -1 if no index could be given
2851      */
2852     private int getColumnIdx(IColumn column) {
2853         return column != null ? _cols.indexOf(column) : -1;
2854     }
2855 
2856     /***
2857      * Retrieve TableXCell for given pixel coordinates.
2858      * 
2859      * @param x pixel coordinate x
2860      * @param y pixel coordinate y
2861      * @return table cel if found or <code>null</code> if no cell can be found
2862      */
2863     public IJaretTableCell getCell(int x, int y) {
2864         if (_tableRect.contains(x, y)) {
2865             IRow row = rowForY(y);
2866             IColumn col = colForX(x);
2867             if (row == null || col == null) {
2868                 return null;
2869             }
2870             return new JaretTableCellImpl(row, col);
2871         }
2872         return null;
2873     }
2874 
2875     /***
2876      * Retrieve a table cell for given index coordinates.
2877      * 
2878      * @param colIdx column index (X)
2879      * @param rowIdx row index (Y)
2880      * @return table cell
2881      */
2882     public IJaretTableCell getCellForIdx(int colIdx, int rowIdx) {
2883         return new JaretTableCellImpl(rowForIdx(rowIdx), colForIdx(colIdx));
2884     }
2885 
2886     /***
2887      * Set a table model to be displayed by the jaret table.
2888      * 
2889      * @param model the table model to be displayed.
2890      */
2891     public void setTableModel(IJaretTableModel model) {
2892         if (_model != null) {
2893             _model.removeJaretTableModelListener(this);
2894         }
2895         _model = model;
2896         _model.addJaretTableModelListener(this);
2897 
2898         _hierarchicalModel = null;
2899 
2900         // update the sorted columnlist
2901         List<IColumn> cList = new ArrayList<IColumn>();
2902         for (int i = 0; i < model.getColumnCount(); i++) {
2903             cList.add(model.getColumn(i));
2904         }
2905         _tvs.setSortedColumns(cList);
2906 
2907         updateColumnList();
2908         registerRowsForOptimization();
2909         updateRowList();
2910 
2911         updateYScrollBar();
2912         updateXScrollBar();
2913         redraw();
2914     }
2915 
2916     /***
2917      * Set a hierarchical table model. This will internally create a StdHierrahicalTableModel that is a normal
2918      * TbaleModel including only the expanded rows.
2919      * 
2920      * @param hmodel hierarchical model to display
2921      */
2922     public void setTableModel(IHierarchicalJaretTableModel hmodel) {
2923         if (_model != null) {
2924             _model.removeJaretTableModelListener(this);
2925         }
2926         if (_tvs != null) {
2927             _tvs.removeTableViewStateListener(this);
2928         }
2929         _tvs = new DefaultHierarchicalTableViewState();
2930         _tvs.addTableViewStateListener(this);
2931         _model = new StdHierarchicalTableModel(hmodel, (IHierarchicalTableViewState) _tvs);
2932         _model.addJaretTableModelListener(this);
2933 
2934         _hierarchicalModel = hmodel;
2935 
2936         updateColumnList();
2937         registerRowsForOptimization();
2938         updateRowList();
2939         updateColumnList();
2940         updateYScrollBar();
2941         updateXScrollBar();
2942         redraw();
2943     }
2944 
2945     /***
2946      * Retrieve a hierarchical model if set.
2947      * 
2948      * @return hierarchical model or <code>null</code>
2949      */
2950     public IHierarchicalJaretTableModel getHierarchicalModel() {
2951         return _hierarchicalModel;
2952     }
2953 
2954     /***
2955      * Retrieve the displayed table model.
2956      * 
2957      * @return the table model
2958      */
2959     public IJaretTableModel getTableModel() {
2960         return _model;
2961     }
2962 
2963     /***
2964      * Add a column to the underlying table model. Model has to be set for that operation or an IllegalStateException
2965      * will be thrown.
2966      * 
2967      * @param column column to be added
2968      */
2969     public void addColumn(IColumn column) {
2970         if (_model != null) {
2971             _model.addColumn(column);
2972         } else {
2973             throw new IllegalStateException("model has to be set for the operation.");
2974         }
2975     }
2976 
2977     /***
2978      * Registers all rows in the model for optimization that have a mode indicating optimal height.
2979      * 
2980      */
2981     private void registerRowsForOptimization() {
2982         if (_model != null) {
2983             for (int i = 0; i < _model.getRowCount(); i++) {
2984                 IRow row = _model.getRow(i);
2985                 if (_tvs.getRowHeigthMode(row) == ITableViewState.RowHeightMode.OPTANDVAR
2986                         || _tvs.getRowHeigthMode(row) == ITableViewState.RowHeightMode.OPTIMAL) {
2987                     optimizeHeight(row);
2988                 }
2989             }
2990         }
2991     }
2992 
2993     /***
2994      * Update the internal rowlist according to filter and sorter.
2995      * 
2996      */
2997     private void updateRowList() {
2998         _rows = new ArrayList<IRow>();
2999         if (_model != null) {
3000             for (int i = 0; i < _model.getRowCount(); i++) {
3001                 IRow row = _model.getRow(i);
3002                 if (i < _fixedRows) {
3003                     // fixed rows are exluded from filtering
3004                     _rows.add(row);
3005                 } else if (_rowFilter == null || (_rowFilter != null && _rowFilter.isInResult(row))) {
3006                     if (!_autoFilterEnabled || _autoFilter.isInResult(row)) {
3007                         if (!_rows.contains(row)) {
3008                             _rows.add(row);
3009                         }
3010                     }
3011                 }
3012             }
3013         }
3014 
3015         // sort either by column sort order or by a set row sorter
3016         RowComparator comparator = new RowComparator();
3017         IRowSorter rs = null;
3018         if (comparator.canSort()) {
3019             rs = comparator;
3020         } else {
3021             rs = _rowSorter;
3022         }
3023         if (rs != null) {
3024             List<IRow> fixedRows = new ArrayList<IRow>();
3025             // the exclusion of the fixed rows may be solved more elegant ...
3026             if (_excludeFixedRowsFromSorting) {
3027                 for (int i = 0; i < _fixedRows; i++) {
3028                     fixedRows.add(_rows.remove(0));
3029                 }
3030             }
3031             Collections.sort(_rows, rs);
3032             if (_excludeFixedRowsFromSorting) {
3033                 for (int i = fixedRows.size() - 1; i >= 0; i--) {
3034                     _rows.add(0, fixedRows.get(i));
3035                 }
3036             }
3037         }
3038         // to be sure no one misses this
3039         updateYScrollBar();
3040     }
3041 
3042     /***
3043      * Get the index of the given row in the internal, fileterd list of rows.
3044      * 
3045      * @param row row to retrieve the index for
3046      * @return index of the row or -1 if the row is not in filtered list of rows
3047      */
3048     public int getInternalRowIndex(IRow row) {
3049         return _rows.indexOf(row);
3050     }
3051 
3052     /***
3053      * Comparator based on the sorting settings of the columns.
3054      * 
3055      * @author Peter Kliem
3056      * @version $Id: JaretTable.java 1076 2010-12-05 13:34:42Z kliem $
3057      */
3058     public class RowComparator extends PropertyObservableBase implements IRowSorter {
3059         /*** arary of Row comparators (IColumns are Comparators for rows!). */
3060         private List<Comparator<IRow>> _comparators = new ArrayList<Comparator<IRow>>();
3061 
3062         /***
3063          * Construct it. Initializes itself with the columns.
3064          */
3065         public RowComparator() {
3066             IColumn[] arr = new IColumn[_cols.size()];
3067             int max = 0;
3068             for (IColumn col : _cols) {
3069                 int sortP = _tvs.getColumnSortingPosition(col);
3070                 if (sortP > 0) {
3071                     arr[sortP] = col;
3072                     if (sortP > max) {
3073                         max = sortP;
3074                     }
3075                 }
3076             }
3077             for (int i = 1; i <= max; i++) {
3078                 _comparators.add(arr[i]);
3079             }
3080 
3081         }
3082 
3083         /***
3084          * Check whether the comparator is able to sort.
3085          * 
3086          * @return true if comparators are present
3087          */
3088         public boolean canSort() {
3089             return _comparators.size() > 0;
3090         }
3091 
3092         /***
3093          * {@inheritDoc}
3094          */
3095         public int compare(IRow r1, IRow r2) {
3096             for (Comparator<IRow> comp : _comparators) {
3097                 int res = comp.compare(r1, r2);
3098                 res = _tvs.getColumnSortingDirection((IColumn) comp) ? res : -res;
3099                 if (res != 0) {
3100                     return res;
3101                 }
3102             }
3103             return 0;
3104         }
3105 
3106     }
3107 
3108     /***
3109      * Update the internal column list. Should be called whenever a column changes visibility or the column order has
3110      * been changed.
3111      * 
3112      */
3113     public void updateColumnList() {
3114         _cols = new ArrayList<IColumn>();
3115         // these are columns to take into account
3116         for (int i = 0; i < _model.getColumnCount(); i++) {
3117             if (i < _tvs.getSortedColumns().size()) {
3118                 IColumn col = _tvs.getSortedColumns().get(i);
3119                 if (_tvs.getColumnVisible(col)) {
3120                     _cols.add(col);
3121                 }
3122             }
3123         }
3124         // if not all columns have been in the sorted columns - add the other columns
3125         for (int i = 0; i < _model.getColumnCount(); i++) {
3126             IColumn col = _model.getColumn(i);
3127             if (!_cols.contains(col) && _tvs.getColumnVisible(col)) {
3128                 _cols.add(col);
3129             }
3130         }
3131     }
3132 
3133     /***
3134      * Handling of the paint event -> do the painting.
3135      * 
3136      * @param event PaintEvent
3137      */
3138     private void onPaint(PaintEvent event) {
3139         if (event.width == 0 || event.height == 0) {
3140             return;
3141         }
3142 // System.out.println("Paint event "+event);
3143         long time = System.currentTimeMillis();
3144         // kill the cache
3145         _rowInfoCache = null;
3146         GC gc = event.gc; // gc for painting
3147 
3148         // do rowheight optimizations for registered rows
3149         doRowHeightOptimization(gc);
3150 
3151         // do the actual painting
3152         paint(gc, getWidth(), getHeight());
3153         if (DEBUGPAINTTIME) {
3154             System.out.println("time " + (System.currentTimeMillis() - time) + " ms");
3155         }
3156     }
3157 
3158     /***
3159      * Calculate the layout of the table area rectangles.
3160      * 
3161      * @param width width of the table
3162      * @param height height of the table
3163      */
3164     private void preparePaint(int width, int height) {
3165         if (_drawHeader) {
3166             _headerRect = new Rectangle(0, 0, width, _headerHeight);
3167         } else {
3168             _headerRect = new Rectangle(0, 0, 0, 0);
3169         }
3170 
3171         if (_autoFilterEnabled) {
3172             // preferred height of the autofilters
3173             int autoFilterHeight = getPreferredAutoFilterHeight();
3174             _autoFilterRect = new Rectangle(0, _headerRect.y + _headerRect.height, _headerRect.width, autoFilterHeight);
3175             _tableRect = new Rectangle(0, _autoFilterRect.y + _autoFilterRect.height, width, height
3176                     - _autoFilterRect.height);
3177         } else {
3178             _tableRect = new Rectangle(0, _headerRect.y + _headerRect.height, width, height - _headerRect.height);
3179         }
3180 
3181         // do we have fixed cols? correct other rects and calc fixed col rect
3182         if (_fixedColumns > 0) {
3183             int fWidth = getFixedColumnsWidth();
3184             _headerRect.x = _headerRect.x + fWidth;
3185             _headerRect.width = _headerRect.width - fWidth;
3186 
3187             if (_autoFilterEnabled) {
3188                 _autoFilterRect.x = _headerRect.x;
3189                 _autoFilterRect.width = _headerRect.width;
3190             }
3191 
3192             _tableRect.x = _headerRect.x;
3193             _tableRect.width = _headerRect.width;
3194 
3195             _fixedColRect = new Rectangle(0, _tableRect.y, fWidth, _tableRect.height);
3196 
3197         } else {
3198             _fixedColRect = new Rectangle(_tableRect.x, _tableRect.y, 0, _tableRect.height);
3199         }
3200 
3201         // do we have fixed rows? correct other rects nd setup the fixed row rect
3202         if (_fixedRows > 0) {
3203             int fHeight = getFixedRowsHeight();
3204             if (_autoFilterEnabled) {
3205                 _fixedRowRect = new Rectangle(0, _autoFilterRect.y + _autoFilterRect.height, width,
3206                         getFixedRowsHeight());
3207             } else {
3208                 _fixedRowRect = new Rectangle(0, _headerRect.y + _headerRect.height, width, getFixedRowsHeight());
3209             }
3210 
3211             _tableRect.y = _tableRect.y + fHeight;
3212             _tableRect.height = _tableRect.height - fHeight;
3213             if (_fixedColumns > 0) {
3214                 _fixedColRect.y = _tableRect.y;
3215                 _fixedColRect.height = _tableRect.height;
3216             }
3217         } else {
3218             // ensure fixed Row rect is available
3219             _fixedRowRect = new Rectangle(_tableRect.x, _tableRect.y, _tableRect.width, 0);
3220         }
3221 
3222     }
3223 
3224     /***
3225      * Retrieve the maximum preferred height of the autofilter controls.
3226      * 
3227      * @return preferred height for the autofilters
3228      */
3229     private int getPreferredAutoFilterHeight() {
3230         int result = 0;
3231         for (IAutoFilter af : _autoFilterMap.values()) {
3232             int height = af.getControl().computeSize(SWT.DEFAULT, SWT.DEFAULT).y;
3233             if (height > result) {
3234                 result = height;
3235             }
3236         }
3237         return result;
3238     }
3239 
3240     /***
3241      * The main paint method.
3242      * 
3243      * @param gc GC
3244      * @param width width of the control
3245      * @param height height of the control
3246      */
3247     public void paint(GC gc, int width, int height) {
3248         preparePaint(width, height);
3249 
3250         // clear bg
3251         Color bg = gc.getBackground();
3252         gc.setBackground(getBackground());
3253         gc.fillRectangle(gc.getClipping());
3254         gc.setBackground(bg);
3255 
3256         drawHeader(gc, width, height);
3257         drawTableArea(gc, width, height);
3258         // set the bounds for the output filters
3259         setUpAutoFilter(gc);
3260 
3261         // additional rendering
3262         if (_isFillDrag) {
3263             drawFillDragBorder(gc);
3264         }
3265 
3266     }
3267 
3268     /***
3269      * Setup the autofilter components.
3270      * 
3271      * @param gc GC
3272      */
3273     private void setUpAutoFilter(GC gc) {
3274         if (_autoFilterEnabled) {
3275             for (IColumn column : _cols) {
3276                 IAutoFilter af = _autoFilterMap.get(column);
3277                 ColInfo cInfo = getColInfo(column);
3278                 if (af != null && cInfo == null) {
3279                     af.getControl().setVisible(false);
3280                 } else {
3281                     if (af != null) {
3282                         af.getControl().setVisible(true);
3283                         af.getControl().setBounds(cInfo.x, _autoFilterRect.y, cInfo.width, _autoFilterRect.height);
3284                     }
3285                 }
3286             }
3287         } else {
3288             for (IColumn column : _cols) {
3289                 IAutoFilter af = _autoFilterMap.get(column);
3290                 if (af != null) {
3291                     af.getControl().setVisible(false);
3292                 }
3293             }
3294         }
3295     }
3296 
3297     /***
3298      * Draw the table header.
3299      * 
3300      * @param gc gc
3301      * @param width width of the table
3302      * @param height height of the table
3303      */
3304     private void drawHeader(GC gc, int width, int height) {
3305         if (_headerRenderer != null && _drawHeader) {
3306             // draw headers for fixed columns
3307             for (int cIdx = 0; cIdx < _fixedColumns; cIdx++) {
3308                 int x = getAbsBeginXForColIdx(cIdx);
3309                 IColumn col = _cols.get(cIdx);
3310                 int colwidth = _tvs.getColumnWidth(col);
3311                 // fixed cols may render wherever they want if they disable clipping
3312                 if (!_headerRenderer.disableClipping()) {
3313                     gc.setClipping(x, _headerRect.y, colwidth, _headerRect.height);
3314                 }
3315                 // column may indicate that no header should be painted
3316                 if (col.displayHeader()) {
3317                     drawHeader(gc, x, colwidth, col);
3318                 }
3319             }
3320 
3321             // draw headers table area
3322             int x = -_firstColPixelOffset;
3323             x += _tableRect.x;
3324             int cIdx = _firstColIdx;
3325             while (x < getWidth() && cIdx < _cols.size()) {
3326                 IColumn col = _cols.get(cIdx);
3327                 int colwidth = _tvs.getColumnWidth(col);
3328                 int xx = x > _headerRect.x ? x : _headerRect.x;
3329                 int clipWidth = x > _headerRect.x ? colwidth : colwidth - _firstColPixelOffset;
3330                 if (!_headerRenderer.disableClipping()) {
3331                     gc.setClipping(xx, _headerRect.y, clipWidth, _headerRect.height);
3332                 } else if (_fixedColumns > 0) {
3333                     // if fixed columns are present the header renderer of ordinary columns may not interfere with the
3334                     // fixed region
3335                     gc.setClipping(xx, _headerRect.y, _tableRect.width - xx, _headerRect.height);
3336                 }
3337 
3338                 // column may indicate that no header should be painted
3339                 if (col.displayHeader()) {
3340                     drawHeader(gc, x, colwidth, col);
3341                 }
3342 
3343                 x += colwidth;
3344                 cIdx++;
3345             }
3346         }
3347 
3348     }
3349 
3350     /***
3351      * Render the header for one column.
3352      * 
3353      * @param gc gc
3354      * @param x starting x
3355      * @param colwidth width
3356      * @param col column
3357      */
3358     private void drawHeader(GC gc, int x, int colwidth, IColumn col) {
3359         Rectangle area = new Rectangle(x, _headerRect.y, colwidth, _headerRect.height);
3360         int sortingPos = _tvs.getColumnSortingPosition(col);
3361         boolean sortingDir = _tvs.getColumnSortingDirection(col);
3362         _headerRenderer.draw(gc, area, col, sortingPos, sortingDir, false);
3363     }
3364 
3365     /***
3366      * Convenience method to check whether a certain cell is selected.
3367      * 
3368      * @param row row of the cell
3369      * @param column column of the cell
3370      * @return true if the cell is selected (by itself, a row of a column selection)
3371      */
3372     public boolean isSelected(IRow row, IColumn column) {
3373         if (_selectionModel.getSelection().getSelectedRows().contains(row)) {
3374             return true;
3375         }
3376         if (_selectionModel.getSelection().getSelectedColumns().contains(column)) {
3377             return true;
3378         }
3379         IJaretTableCell cell = new JaretTableCellImpl(row, column);
3380         if (_selectionModel.getSelection().getSelectedCells().contains(cell)) {
3381             return true;
3382         }
3383         return false;
3384     }
3385 
3386     /***
3387      * Draw the main table area including fixed rows and columns.
3388      * 
3389      * @param gc gc
3390      * @param width width of the area
3391      * @param height height of the area
3392      */
3393     private void drawTableArea(GC gc, int width, int height) {
3394         _colInfoCache.clear();
3395         boolean colCacheFilled = false;
3396 
3397         Rectangle clipSave = gc.getClipping();
3398 
3399         // iterate over all rows in the row info cache
3400         for (RowInfo rowInfo : getRowInfos()) {
3401             int y = rowInfo.y;
3402             IRow row = rowInfo.row;
3403             int rHeight = _tvs.getRowHeight(row);
3404 
3405             int yclip = y;
3406             if (rowInfo.fixed && yclip < _fixedRowRect.y) {
3407                 yclip = _fixedRowRect.y;
3408             } else if (!rowInfo.fixed && yclip < _tableRect.y) {
3409                 yclip = _tableRect.y;
3410             }
3411 
3412             int x = 0;
3413             // fixed columns
3414             if (_fixedColumns > 0) {
3415                 x = _fixedColRect.x;
3416                 for (int cIdx = 0; cIdx < _fixedColumns; cIdx++) {
3417                     IColumn col = _cols.get(cIdx);
3418                     int colwidth = _tvs.getColumnWidth(col);
3419                     if (!colCacheFilled) {
3420                         _colInfoCache.add(new ColInfo(col, x, colwidth));
3421                     }
3422                     // clipping is extended by 1 for border drawing
3423                     gc.setClipping(x, yclip, colwidth + 1, rHeight + 1);
3424                     Rectangle area = new Rectangle(x, y, colwidth, rHeight);
3425 
3426                     drawCell(gc, area, row, col);
3427 
3428                     x += colwidth;
3429                 }
3430             }
3431 
3432             // columns in normal table area
3433             x = _tableRect.x - _firstColPixelOffset;
3434             int cIdx = _firstColIdx;
3435             while (x < getWidth() && cIdx < _cols.size()) {
3436                 IColumn col = _cols.get(cIdx);
3437                 int colwidth = _tvs.getColumnWidth(col);
3438                 if (!colCacheFilled) {
3439                     _colInfoCache.add(new ColInfo(col, x, colwidth));
3440                 }
3441                 int xx = x > _tableRect.x ? x : _tableRect.x;
3442                 int clipWidth = x > _tableRect.x ? colwidth : colwidth - _firstColPixelOffset;
3443                 // clipping is extended by 1 for border drawing
3444                 gc.setClipping(xx, yclip, clipWidth + 1, rHeight + 1);
3445                 Rectangle area = new Rectangle(x, y, colwidth, rHeight);
3446 
3447                 drawCell(gc, area, row, col);
3448 
3449                 x += colwidth;
3450                 cIdx++;
3451             }
3452             colCacheFilled = true;
3453         }
3454 
3455         // TODO this is a workaround for the autofilter to be rendered correctly if the
3456         // filter result is no rows
3457         if (!colCacheFilled) {
3458             // fixed cols
3459             int x = 0;
3460             if (_fixedColumns > 0) {
3461                 for (int i = 0; i < _fixedColumns; i++) {
3462                     IColumn col = _cols.get(i);
3463                     int colwidth = _tvs.getColumnWidth(col);
3464                     _colInfoCache.add(new ColInfo(col, x, colwidth));
3465                     x += colwidth;
3466                 }
3467             }
3468 
3469             x = -_firstColPixelOffset;
3470             x += _tableRect.x;
3471             int cIdx = _firstColIdx;
3472             while (x < getWidth() && cIdx < _cols.size()) {
3473                 IColumn col = _cols.get(cIdx);
3474                 int colwidth = _tvs.getColumnWidth(col);
3475                 _colInfoCache.add(new ColInfo(col, x, colwidth));
3476                 x += colwidth;
3477                 cIdx++;
3478             }
3479 
3480         }
3481 
3482         // draw extra lines to separate fixed areas
3483         if (_fixedColRect != null && _fixedColumns > 0) {
3484             int maxY = _fixedColRect.y + _fixedColRect.height - 1;
3485             if (_rows != null && _rows.size() > 0) {
3486                 IRow lastRow = _rows.get(_rows.size() - 1);
3487                 Rectangle bounds = getRowBounds(lastRow);
3488                 if (bounds != null) {
3489                     maxY = bounds.y + bounds.height;
3490                 }
3491             }
3492             gc.setClipping(new Rectangle(0, 0, width, height));
3493             int fx = _fixedColRect.x + _fixedColRect.width - 1;
3494             gc.drawLine(fx, _fixedRowRect.y, fx, maxY);
3495             gc.setClipping(clipSave);
3496         }
3497 
3498         if (_fixedRowRect != null && _fixedRows > 0) {
3499             int maxX = _fixedRowRect.x + _fixedRowRect.width - 1;
3500             if (_cols != null && _cols.size() > 0) {
3501                 IColumn lastCol = _cols.get(_cols.size() - 1);
3502                 int mx = xForCol(lastCol) + _tvs.getColumnWidth(lastCol);
3503                 maxX = mx;
3504             }
3505             gc.setClipping(new Rectangle(0, 0, width, height));
3506             int fy = _fixedRowRect.y + _fixedRowRect.height - 1;
3507             gc.drawLine(_fixedRowRect.x, fy, maxX, fy);
3508             gc.setClipping(clipSave);
3509         }
3510 
3511     }
3512 
3513     /***
3514      * Draw a single cell. Drawing is accomplished by the associated cell renderer. However the mark for fill dragging
3515      * is drawn by this method.
3516      * 
3517      * @param gc gc
3518      * @param area drawing area the cell takes up
3519      * @param row row of the cell
3520      * @param col olumn of the cell
3521      */
3522     private void drawCell(GC gc, Rectangle area, IRow row, IColumn col) {
3523         ICellStyle bc = _tvs.getCellStyle(row, col);
3524         ICellRenderer cellRenderer = getCellRenderer(row, col);
3525 
3526         boolean hasFocus = false;
3527         if (_focussedRow == row && _focussedColumn == col) { // == is appropriate: these are really the same objects!
3528             hasFocus = true;
3529         }
3530         boolean isSelected = isSelected(row, col);
3531         cellRenderer.draw(gc, this, bc, area, row, col, hasFocus, isSelected, false);
3532         if (_supportFillDragging && isSelected && isDragMarkerCell(row, col)) {
3533             drawFillDragMark(gc, area);
3534         }
3535     }
3536 
3537     /*** if a rectangular area is selected, this holds the rectangle ofth eindizes. */
3538     private Rectangle _selectedIdxRectangle = null;
3539 
3540     /***
3541      * Retrieve the index rectangle of selected cells.
3542      * 
3543      * @return rectangel made from the indizes of the selected cells if a rectangular area is selected (all cells)
3544      */
3545     private Rectangle getSelectedRectangle() {
3546         IJaretTableSelection selection = getSelectionModel().getSelection();
3547         if (!selection.isEmpty() && _selectedIdxRectangle == null) {
3548             Set<IJaretTableCell> cells = selection.getAllSelectedCells(getTableModel());
3549             int minx = -1;
3550             int maxx = -1;
3551             int miny = -1;
3552             int maxy = -1;
3553             // line is the outer map
3554             Map<Integer, Map<Integer, IJaretTableCell>> cellMap = new HashMap<Integer, Map<Integer, IJaretTableCell>>();
3555             for (IJaretTableCell cell : cells) {
3556                 Point p = getCellDisplayIdx(cell);
3557                 Map<Integer, IJaretTableCell> lineMap = cellMap.get(p.y);
3558                 if (lineMap == null) {
3559                     lineMap = new HashMap<Integer, IJaretTableCell>();
3560                     cellMap.put(p.y, lineMap);
3561                 }
3562                 if (miny == -1 || p.y < miny) {
3563                     miny = p.y;
3564                 }
3565                 if (maxy == -1 || p.y > maxy) {
3566                     maxy = p.y;
3567                 }
3568                 lineMap.put(p.x, cell);
3569                 if (minx == -1 || p.x < minx) {
3570                     minx = p.x;
3571                 }
3572                 if (maxx == -1 || p.x > maxx) {
3573                     maxx = p.x;
3574                 }
3575             }
3576             // check if all cells are selected
3577             boolean everythingSelected = true;
3578             for (int y = miny; y <= maxy && everythingSelected; y++) {
3579                 Map<Integer, IJaretTableCell> lineMap = cellMap.get(y);
3580                 if (lineMap != null) {
3581                     for (int x = minx; x <= maxx; x++) {
3582                         IJaretTableCell cell = lineMap.get(x);
3583                         if (cell == null) {
3584                             everythingSelected = false;
3585                             break;
3586                         }
3587                     }
3588                 } else {
3589                     everythingSelected = false;
3590                     break;
3591                 }
3592             }
3593             if (everythingSelected) {
3594                 _selectedIdxRectangle = new Rectangle(minx, miny, maxx - minx + 1, maxy - miny + 1);
3595             } else {
3596                 _selectedIdxRectangle = null;
3597             }
3598         }
3599         return _selectedIdxRectangle;
3600     }
3601 
3602     /***
3603      * Check whether a cell is the cell that should currently be marked with the drag fill marker.
3604      * 
3605      * @param row row of the cell
3606      * @param col column of the cell
3607      * @return true if it is the cell carrying the drag mark
3608      */
3609     private boolean isDragMarkerCell(IRow row, IColumn col) {
3610         Rectangle selIdxRect = getSelectedRectangle();
3611         if (selIdxRect != null) {
3612             if (selIdxRect.width == 1 || selIdxRect.height == 1) {
3613                 int x = getColumnIdx(col);
3614                 int y = getRowIdx(row);
3615                 if (x == selIdxRect.x + selIdxRect.width - 1 && y == selIdxRect.y + selIdxRect.height - 1) {
3616                     return true;
3617                 }
3618             }
3619         }
3620 
3621         return false;
3622     }
3623 
3624     /***
3625      * Draws the fill drag mark.
3626      * 
3627      * @param gc GC
3628      * @param area drawing area of the cell carrying the marker
3629      */
3630     private void drawFillDragMark(GC gc, Rectangle area) {
3631         Color bg = gc.getBackground();
3632         gc.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_BLACK));
3633         _dragMarkerRect = new Rectangle(area.x + area.width - FILLDRAGMARKSIZE,
3634                 area.y + area.height - FILLDRAGMARKSIZE, FILLDRAGMARKSIZE, FILLDRAGMARKSIZE);
3635         gc.fillRectangle(_dragMarkerRect);
3636         gc.setBackground(bg);
3637     }
3638 
3639     /***
3640      * Draws a thicker border around the fill drag area.
3641      * 
3642      * @param gc GC
3643      */
3644     private void drawFillDragBorder(GC gc) {
3645         // TODO
3646         // if (_firstCellSelectX != -1) {
3647         // IRow row = rowForIdx(_firstCellSelectX);
3648         // IColumn column = colForIdx(_firstCellSelectY);
3649         // Rectangle firstCellBounds = getCellBounds(row, column);
3650         // if (firstCellBounds == null) {
3651         // firstCellBounds = new Rectangle(-1, -1, -1, -1);
3652         // if (getRowBounds(row) != null) {
3653         // firstCellBounds = getRowBounds(row);
3654         // } else if (getColumnBounds(column)!=null) {
3655         // firstCellBounds = getColumnBounds(column);
3656         // } else
3657         // }
3658         // }
3659     }
3660 
3661     /***
3662      * Set the header drawing height.
3663      * 
3664      * @param newHeight height in pixel.
3665      */
3666     public void setHeaderHeight(int newHeight) {
3667         if (newHeight != _headerHeight) {
3668             int oldVal = _headerHeight;
3669             _headerHeight = newHeight;
3670             redraw();
3671             firePropertyChange(PROPERTYNAME_HEADERHEIGHT, oldVal, _headerHeight);
3672         }
3673     }
3674 
3675     /***
3676      * Retrieve the header height.
3677      * 
3678      * @return header height (pixel)
3679      */
3680     public int getHeaderHeight() {
3681         return _headerHeight;
3682     }
3683 
3684     /***
3685      * Total height of all possibly displayed rows (filter applied!).
3686      * 
3687      * @return sum of all rowheigths
3688      */
3689     public int getTotalHeight() {
3690         if (_rows != null) {
3691             int h = 0;
3692             for (IRow row : _rows) {
3693                 h += _tvs.getRowHeight(row);
3694             }
3695             return h;
3696         } else {
3697             return 0;
3698         }
3699     }
3700 
3701     /***
3702      * Total height of the first n rows.
3703      * 
3704      * @param numRows number of rows to sum up the heights of
3705      * @return sum of the first first n rowheights
3706      */
3707     public int getTotalHeight(int numRows) {
3708         if (_rows != null) {
3709             int h = 0;
3710             for (int i = 0; i < numRows; i++) {
3711                 IRow row = _rows.get(i);
3712                 h += _tvs.getRowHeight(row);
3713             }
3714             return h;
3715         } else {
3716             return 0;
3717         }
3718     }
3719 
3720     /***
3721      * Retrieve total width of all possibly displayed columns.
3722      * 
3723      * @return sum of colwidhts
3724      */
3725     public int getTotalWidth() {
3726         if (_cols != null) {
3727             int width = 0;
3728             for (IColumn col : _cols) {
3729                 width += _tvs.getColumnWidth(col);
3730             }
3731             return width;
3732         } else {
3733             return 0;
3734         }
3735     }
3736 
3737     /***
3738      * Retrieve total width of the first n columns.
3739      * 
3740      * @param n number of colums to take into account
3741      * @return sum of the first n column withs
3742      */
3743     public int getTotalWidth(int n) {
3744         if (_cols != null) {
3745             int width = 0;
3746             for (int i = 0; i < n; i++) {
3747                 IColumn col = _cols.get(i);
3748                 width += _tvs.getColumnWidth(col);
3749             }
3750             return width;
3751         } else {
3752             return 0;
3753         }
3754     }
3755 
3756     /***
3757      * Calculate the width of all fixed columns.
3758      * 
3759      * @return the sum of the individual widths of the fixed columns
3760      */
3761     private int getFixedColumnsWidth() {
3762         int w = 0;
3763         for (int i = 0; i < _fixedColumns; i++) {
3764             w += _tvs.getColumnWidth(_cols.get(i));
3765         }
3766         return w;
3767     }
3768 
3769     /***
3770      * Calculate the height of all fixed rows.
3771      * 
3772      * @return sum of the individual heights of the fixed rows
3773      */
3774     private int getFixedRowsHeight() {
3775         int h = 0;
3776         for (int i = 0; i < _fixedRows; i++) {
3777             h += _tvs.getRowHeight(_rows.get(i));
3778         }
3779         return h;
3780     }
3781 
3782     /***
3783      * Retrieve the width of the control.
3784      * 
3785      * @return width in pixel
3786      */
3787     public int getWidth() {
3788         return getClientArea().width;
3789     }
3790 
3791     /***
3792      * Retrieve the height of the control.
3793      * 
3794      * @return height in pixel
3795      */
3796     public int getHeight() {
3797         return getClientArea().height;
3798     }
3799 
3800     /***
3801      * Retrieve the table viewstate.
3802      * 
3803      * @return the tvs.
3804      */
3805     public ITableViewState getTableViewState() {
3806         return _tvs;
3807     }
3808 
3809     /***
3810      * Set a TableViewState.
3811      * 
3812      * @param tvs The tvs to set.
3813      */
3814     public void setTableViewState(ITableViewState tvs) {
3815         if (_tvs != null) {
3816             _tvs.removeTableViewStateListener(this);
3817         }
3818         _tvs = tvs;
3819         _tvs.addTableViewStateListener(this);
3820     }
3821 
3822     // ///////// TableViewStateListener
3823     /***
3824      * {@inheritDoc}
3825      */
3826     public void rowHeightChanged(IRow row, int newHeight) {
3827         Rectangle r = getRowBounds(row);
3828         if (r != null) {
3829             int height = getHeight() - r.y;
3830             redraw(r.x, r.y, r.width, height, true);
3831             _rowInfoCache = null;
3832         }
3833         updateYScrollBar();
3834     }
3835 
3836     /***
3837      * {@inheritDoc}
3838      */
3839     public void rowHeightModeChanged(IRow row, RowHeightMode newHeightMode) {
3840         if (isDisplayed(row)) {
3841             if (newHeightMode == RowHeightMode.OPTANDVAR || newHeightMode == RowHeightMode.OPTIMAL) {
3842                 optimizeHeight(row);
3843             }
3844             redraw();
3845         }
3846         // tweak: if the default height mode qualifies the row for height optimization it will be optimized since it is
3847         // registered
3848         // so it has to be removed if the mode changes before drawing
3849         if (newHeightMode != RowHeightMode.OPTANDVAR && newHeightMode != RowHeightMode.OPTIMAL) {
3850             doNotOptimizeHeight(row);
3851         }
3852     }
3853 
3854     /***
3855      * {@inheritDoc}
3856      */
3857     public void columnWidthChanged(IColumn column, int newWidth) {
3858         registerRowsForOptimization(); // row heights may change for opt/optvar
3859         Rectangle r = getColumnBounds(column);
3860         if (_headerRect != null) {
3861             int y = _drawHeader ? _headerRect.y : r.y;
3862             int width = getWidth() - r.x;
3863             int height = _drawHeader ? _headerRect.height + r.height : r.height;
3864             if (_autoFilterEnabled) {
3865                 height += _autoFilterRect.height;
3866             }
3867             redraw(r.x, y, width, height, true);
3868         }
3869         updateXScrollBar();
3870     }
3871 
3872     /***
3873      * {@inheritDoc}
3874      */
3875     public void columnWidthsChanged() {
3876         registerRowsForOptimization(); // row heights may change for opt/optvar
3877         redraw();
3878         updateXScrollBar();
3879     }
3880 
3881     // private boolean isColumnResizePossible(IColumn column, int oldWidth, int newWidth) {
3882     // if (_tvs.getColumnResizeMode() == TableViewState.ColumnResizeMode.NONE) {
3883     // return true;
3884     // }
3885     // int delta = newWidth - oldWidth;
3886     // if (_tvs.getColumnResizeMode() == TableViewState.ColumnResizeMode.SUBSEQUENT) {
3887     // int idx = _cols.indexOf(column);
3888     // if (idx > _cols.size()-1) {
3889     // return false;
3890     // }
3891     // IColumn subsequent = _cols.get(idx+1);
3892     // if (_tvs.getColumnWidth(subsequent) - delta > _tvs.getMinimalColWidth()) {
3893     // return true;
3894     // }
3895     // return false;
3896     // }
3897     // return false;
3898     // }
3899 
3900     /***
3901      * {@inheritDoc}
3902      */
3903     public void columnVisibilityChanged(IColumn column, boolean visible) {
3904         updateColumnList();
3905         updateXScrollBar();
3906         redraw();
3907     }
3908 
3909     /***
3910      * {@inheritDoc}
3911      */
3912     public void sortingChanged() {
3913         updateRowList();
3914         redraw();
3915         // fire the general sorting change
3916         firePropertyChange(PROPERTYNAME_SORTING, null, "x");
3917     }
3918 
3919     /***
3920      * {@inheritDoc}
3921      */
3922     public void columnOrderChanged() {
3923         updateColumnList();
3924         redraw();
3925     }
3926 
3927     /***
3928      * {@inheritDoc}
3929      */
3930     public void cellStyleChanged(IRow row, IColumn column, ICellStyle style) {
3931         if (column == null) {
3932             redraw(row);
3933         } else if (row == null) {
3934             redraw(column);
3935         } else {
3936             redraw(row, column);
3937         }
3938     }
3939 
3940     // //// End tableviewstatelistener
3941 
3942     /***
3943      * Set the enabled state for the autofilter.
3944      * 
3945      * @param enable true for enabling the autofilter
3946      */
3947     public void setAutoFilterEnable(boolean enable) {
3948         if (_autoFilterEnabled != enable) {
3949             _autoFilterEnabled = enable;
3950             if (enable) {
3951                 updateAutoFilter();
3952             }
3953             redraw();
3954             updateYScrollBar();
3955             preparePaint(getWidth(), getHeight());
3956             firePropertyChange(PROPERTYNAME_AUTOFILTERENABLE, !enable, enable);
3957         }
3958     }
3959 
3960     /***
3961      * Retrieve the autofilter state.
3962      * 
3963      * @return true for anabled autofilter
3964      */
3965     public boolean getAutoFilterEnable() {
3966         return _autoFilterEnabled;
3967     }
3968 
3969     /***
3970      * Create and/or update autofilters.
3971      * 
3972      */
3973     private void updateAutoFilter() {
3974         if (_autoFilterEnabled) {
3975             // check combining autofilter
3976             if (_autoFilter == null) {
3977                 _autoFilter = new AutoFilter(this);
3978             }
3979             // create autofilter instances and controls if necessary
3980             for (IColumn column : _cols) {
3981                 if (_autoFilterMap.get(column) == null) {
3982                     IAutoFilter af = createAutoFilter(column);
3983                     if (af != null) {
3984                         af.addPropertyChangeListener(_autoFilter);
3985                         _autoFilterMap.put(column, af);
3986                     }
3987                 }
3988             }
3989 
3990             // update the filters and register them with the combining internal autofilter row filter
3991             for (IColumn column : _cols) {
3992                 IAutoFilter af = _autoFilterMap.get(column);
3993                 if (af != null) { // might be null in case of errors
3994                     af.update();
3995                 }
3996             }
3997 
3998         }
3999     }
4000 
4001     /***
4002      * Instantiate an autofilter instance for the given column.
4003      * 
4004      * @param column column
4005      * @return instantiated autofilter or <code>null</code> if any error occurs during instantiation
4006      */
4007     private IAutoFilter createAutoFilter(IColumn column) {
4008         Class<? extends IAutoFilter> clazz = getAutoFilterClass(column);
4009         if (clazz == null) {
4010             return null;
4011         }
4012         IAutoFilter result = null;
4013         try {
4014             Constructor<? extends IAutoFilter> constructor = clazz.getConstructor();
4015             result = constructor.newInstance();
4016         } catch (Exception e) {
4017             e.printStackTrace(); // TODO
4018             return null;
4019         }
4020 
4021         result.setTable(this);
4022         result.setColumn(column);
4023         return result;
4024     }
4025 
4026     /***
4027      * Retrieve the state of header drawing.
4028      * 
4029      * @return true when headers are drawn.
4030      */
4031     public boolean getDrawHeader() {
4032         return _drawHeader;
4033     }
4034 
4035     /***
4036      * If set to true, the header row will be drawn.
4037      * 
4038      * @param drawHeader true: draw the header
4039      */
4040     public void setDrawHeader(boolean drawHeader) {
4041         if (_drawHeader != drawHeader) {
4042             _drawHeader = drawHeader;
4043             redraw();
4044         }
4045     }
4046 
4047     /***
4048      * @return Returns the headerRenderer.
4049      */
4050     public ITableHeaderRenderer getHeaderRenderer() {
4051         return _headerRenderer;
4052     }
4053 
4054     /***
4055      * Set a header renderer.
4056      * 
4057      * @param headerRenderer The headerRenderer to set.
4058      */
4059     public void setHeaderRenderer(ITableHeaderRenderer headerRenderer) {
4060         _headerRenderer = headerRenderer;
4061         redraw();
4062     }
4063 
4064     /***
4065      * @return Returns the columnResizeAllowed.
4066      */
4067     public boolean isColumnResizeAllowed() {
4068         return _columnResizeAllowed;
4069     }
4070 
4071     /***
4072      * Set whether column resizing is allowed.
4073      * 
4074      * @param columnResizeAllowed true for allowing col resizing.
4075      */
4076     public void setColumnResizeAllowed(boolean columnResizeAllowed) {
4077         _columnResizeAllowed = columnResizeAllowed;
4078     }
4079 
4080     /***
4081      * @return Returns the headerResizeAllowed.
4082      */
4083     public boolean isHeaderResizeAllowed() {
4084         return _headerResizeAllowed;
4085     }
4086 
4087     /***
4088      * @param headerResizeAllowed The headerResizeAllowed to set.
4089      */
4090     public void setHeaderResizeAllowed(boolean headerResizeAllowed) {
4091         _headerResizeAllowed = headerResizeAllowed;
4092     }
4093 
4094     /***
4095      * @return Returns the rowResizeAllowed.
4096      */
4097     public boolean isRowResizeAllowed() {
4098         return _rowResizeAllowed;
4099     }
4100 
4101     /***
4102      * @param rowResizeAllowed The rowResizeAllowed to set.
4103      */
4104     public void setRowResizeAllowed(boolean rowResizeAllowed) {
4105         _rowResizeAllowed = rowResizeAllowed;
4106     }
4107 
4108     /***
4109      * Set the first row displayed.
4110      * 
4111      * @param idx index of the first row to be displayed.
4112      * @param pixeloffset the pixeloffset of the first row
4113      */
4114     public void setFirstRow(int idx, int pixeloffset) {
4115         if (_firstRowIdx != idx || _firstRowPixelOffset != pixeloffset) {
4116             int oldFirstIdx = _firstRowIdx;
4117             int oldPixelOffset = _firstRowPixelOffset;
4118 
4119             _firstRowIdx = idx;
4120             _firstRowPixelOffset = pixeloffset;
4121             _rowInfoCache = null; // kill the cache
4122             updateYScrollBar();
4123             redraw();
4124             firePropertyChange(PROPERTYNAME_FIRSTROWIDX, oldFirstIdx, idx);
4125             firePropertyChange(PROPERTYNAME_FIRSTROWPIXELOFFSET, oldPixelOffset, pixeloffset);
4126         }
4127     }
4128 
4129     /***
4130      * Internal row filter pooling the results of the autofilters.
4131      * 
4132      * @author Peter Kliem
4133      * @version $Id: JaretTable.java 1076 2010-12-05 13:34:42Z kliem $
4134      */
4135     private class AutoFilter extends PropertyObservableBase implements IRowFilter, PropertyChangeListener {
4136         /*** the table instance of the filter. */
4137         private JaretTable _table;
4138 
4139         public AutoFilter(JaretTable table) {
4140             _table = table;
4141         }
4142 
4143         /***
4144          * {@inheritDoc}
4145          */
4146         public boolean isInResult(IRow row) {
4147             boolean result = true;
4148             for (IColumn column : _cols) {
4149                 IAutoFilter af = _autoFilterMap.get(column);
4150                 if (af != null) {
4151                     result = result && af.isInResult(row);
4152                 }
4153                 if (!result) {
4154                     break;
4155                 }
4156             }
4157             return result;
4158         }
4159 
4160         /***
4161          * {@inheritDoc} Whenever a filter signals change update everything.
4162          */
4163         public void propertyChange(PropertyChangeEvent evt) {
4164             updateRowList();
4165             setFirstRow(_fixedRows, 0);
4166             redraw();
4167             // genarl filtering change signalling
4168             _table.firePropertyChange(PROPERTYNAME_FILTERING, null, "x");
4169         }
4170     }
4171 
4172     /***
4173      * If a header context is present, display it at x,y.
4174      * 
4175      * @param x x coordinate
4176      * @param y y coordinate
4177      */
4178     public void displayHeaderContextMenu(int x, int y) {
4179         if (_headerContextMenu != null) {
4180             dispContextMenu(_headerContextMenu, x, y);
4181         }
4182     }
4183 
4184     /***
4185      * If a row context is present, display it at x,y.
4186      * 
4187      * @param x x coordinate
4188      * @param y y coordinate
4189      */
4190     public void displayRowContextMenu(int x, int y) {
4191         if (_rowContextMenu != null) {
4192             dispContextMenu(_rowContextMenu, x, y);
4193         }
4194     }
4195 
4196     // todo move to utils
4197     private void dispContextMenu(Menu contextMenu, int x, int y) {
4198         Shell shell = Display.getCurrent().getActiveShell();
4199         Point coords = Display.getCurrent().map(this, shell, x, y);
4200         contextMenu.setLocation(coords.x + shell.getLocation().x, coords.y + shell.getLocation().y);
4201         contextMenu.setVisible(true);
4202     }
4203 
4204     /***
4205      * Set a context menu to be displayed on the header area.
4206      * 
4207      * @param headerCtxMenu menu to display on the header or <code>null</code> to disable
4208      */
4209     public void setHeaderContextMenu(Menu headerCtxMenu) {
4210         _headerContextMenu = headerCtxMenu;
4211     }
4212 
4213     /***
4214      * Retrieve a context menu that has been set on the header.
4215      * 
4216      * @return context menu or <code>null</code>
4217      */
4218     public Menu getHeaderContextMenu() {
4219         return _headerContextMenu;
4220     }
4221 
4222     /***
4223      * Set a context menu to be displayed on rows.
4224      * 
4225      * @param rowCtxMenu context menu or <code>null</code> to disable
4226      */
4227     public void setRowContextMenu(Menu rowCtxMenu) {
4228         _rowContextMenu = rowCtxMenu;
4229     }
4230 
4231     /***
4232      * Retrieve a context menu that has been set for the rows.
4233      * 
4234      * @return context menu or <code>null</code>
4235      */
4236     public Menu getRowContextMenu() {
4237         return _rowContextMenu;
4238     }
4239 
4240     /***
4241      * @return Returns the fixedColumns.
4242      */
4243     public int getFixedColumns() {
4244         return _fixedColumns;
4245     }
4246 
4247     /***
4248      * Set the numerb of fixed columns. Fixed columns are excluded from vertial scrolling. Row resizing can be
4249      * restricted to the area of the fixed columns.
4250      * 
4251      * @param fixedColumns The fixedColumns to set.
4252      */
4253     public void setFixedColumns(int fixedColumns) {
4254         if (_fixedColumns != fixedColumns) {
4255             _fixedColumns = fixedColumns;
4256             _firstColIdx = fixedColumns;
4257             _firstColPixelOffset = 0;
4258             redraw();
4259         }
4260     }
4261 
4262     /***
4263      * @return Returns the fixedRows.
4264      */
4265     public int getFixedRows() {
4266         return _fixedRows;
4267     }
4268 
4269     /***
4270      * Set the number of rows to be fixed (excluded from scrolling and autofiltering; optionally from sorting).
4271      * 
4272      * @param fixedRows The fixedRows to set.
4273      */
4274     public void setFixedRows(int fixedRows) {
4275         if (_fixedRows != fixedRows) {
4276             _fixedRows = fixedRows;
4277             _firstRowIdx = fixedRows;
4278             _firstRowPixelOffset = 0;
4279             _rowInfoCache = null;
4280             redraw();
4281         }
4282     }
4283 
4284     // /////////// table model listener
4285 
4286     /***
4287      * {@inheritDoc}
4288      */
4289     public void rowChanged(IRow row) {
4290         if (_tvs.getRowHeigthMode(row) == ITableViewState.RowHeightMode.OPTIMAL
4291                 || _tvs.getRowHeigthMode(row) == ITableViewState.RowHeightMode.OPTANDVAR) {
4292             optimizeHeight(row);
4293         }
4294         redraw(row);
4295     }
4296 
4297     /***
4298      * {@inheritDoc}
4299      */
4300     public void rowRemoved(IRow row) {
4301         // MAYBE could be further optimized
4302         updateRowList();
4303         if (isDisplayed(row)) {
4304             syncedRedraw();
4305         }
4306         updateYScrollBar();
4307     }
4308 
4309     /***
4310      * {@inheritDoc}
4311      */
4312     public void rowAdded(int idx, IRow row) {
4313         updateRowList();
4314         syncedRedraw();
4315         updateYScrollBar();
4316     }
4317 
4318     /***
4319      * {@inheritDoc}
4320      */
4321     public void columnAdded(int idx, IColumn column) {
4322         _tvs.getSortedColumns().add(column);
4323         updateColumnList();
4324         syncedRedraw();
4325         updateXScrollBar();
4326     }
4327 
4328     /***
4329      * {@inheritDoc}
4330      */
4331     public void columnRemoved(IColumn column) {
4332         // MAYBE optimize
4333         _tvs.getSortedColumns().remove(column);
4334         updateColumnList();
4335         if (isDisplayed(column)) {
4336             syncedRedraw();
4337         }
4338         updateXScrollBar();
4339     }
4340 
4341     /***
4342      * {@inheritDoc}
4343      */
4344     public void columnChanged(IColumn column) {
4345         redraw(column);
4346     }
4347 
4348     /***
4349      * {@inheritDoc}
4350      */
4351     public void cellChanged(IRow row, IColumn column) {
4352         redraw(row, column);
4353     }
4354 
4355     /***
4356      * {@inheritDoc}
4357      */
4358     public void tableDataChanged() {
4359         // TODO optimze row heights
4360         updateRowList();
4361         syncedRedraw();
4362     }
4363 
4364     // end table model listener
4365 
4366     /***
4367      * Retrieve the flag controlling whether fixed rows are excluded from sorting.
4368      * 
4369      * @return if true fixed rows will not be affected by sorting operations
4370      */
4371     public boolean getExcludeFixedRowsFromSorting() {
4372         return _excludeFixedRowsFromSorting;
4373     }
4374 
4375     /***
4376      * If set to true, fixed rows are exluded from sorting.
4377      * 
4378      * @param excludeFixedRowsFromSorting true for exclude fixed rows from sorting.
4379      */
4380     public void setExcludeFixedRowsFromSorting(boolean excludeFixedRowsFromSorting) {
4381         _excludeFixedRowsFromSorting = excludeFixedRowsFromSorting;
4382     }
4383 
4384     /***
4385      * Get the state of the resize restriction flag. If true, resizing is only allowed in header and fixed columns (for
4386      * rows) and the leftmost SELDELTA pixels of eachrow.
4387      * 
4388      * @return Returns the resizeRestriction.
4389      */
4390     public boolean getResizeRestriction() {
4391         return _resizeRestriction;
4392     }
4393 
4394     /***
4395      * If set to true resizing of columns will only be allowed in the header area. Row resizing will be allowed on fixed
4396      * columns and on the first SEL_DELTA pixels of the leftmost column when restricted.
4397      * 
4398      * @param resizeRestriction The resizeRestriction to set.
4399      */
4400     public void setResizeRestriction(boolean resizeRestriction) {
4401         _resizeRestriction = resizeRestriction;
4402     }
4403 
4404     // //////// selection listener
4405 
4406     /***
4407      * {@inheritDoc}
4408      */
4409     public void rowSelectionAdded(IRow row) {
4410         _selectedIdxRectangle = null;
4411         redraw(row);
4412     }
4413 
4414     /***
4415      * {@inheritDoc}
4416      */
4417     public void rowSelectionRemoved(IRow row) {
4418         _selectedIdxRectangle = null;
4419         redraw(row);
4420     }
4421 
4422     /***
4423      * {@inheritDoc}
4424      */
4425     public void cellSelectionAdded(IJaretTableCell cell) {
4426         _selectedIdxRectangle = null;
4427         redraw(cell.getRow(), cell.getColumn());
4428     }
4429 
4430     /***
4431      * {@inheritDoc}
4432      */
4433     public void cellSelectionRemoved(IJaretTableCell cell) {
4434         _selectedIdxRectangle = null;
4435         redraw(cell.getRow(), cell.getColumn());
4436     }
4437 
4438     /***
4439      * {@inheritDoc}
4440      */
4441     public void columnSelectionAdded(IColumn column) {
4442         _selectedIdxRectangle = null;
4443         redraw(column);
4444     }
4445 
4446     /***
4447      * {@inheritDoc}
4448      */
4449     public void columnSelectionRemoved(IColumn column) {
4450         _selectedIdxRectangle = null;
4451         redraw(column);
4452     }
4453 
4454     // end selection listener
4455 
4456     /***
4457      * Retrieve the selectionmodel used by the table.
4458      * 
4459      * @return the selection model
4460      */
4461     public IJaretTableSelectionModel getSelectionModel() {
4462         return _selectionModel;
4463     }
4464 
4465     /***
4466      * Set the selection model to be used by the table.
4467      * 
4468      * @param jts the selection model to be used (usually the default implementation)
4469      */
4470     public void setSelectionModel(IJaretTableSelectionModel jts) {
4471         if (_selectionModel != null) {
4472             _selectionModel.removeTableSelectionModelListener(this);
4473         }
4474         _selectionModel = jts;
4475         _selectionModel.addTableSelectionModelListener(this);
4476     }
4477 
4478     /***
4479      * Current number of displayed columns.
4480      * 
4481      * @return number of displayed columns
4482      */
4483     public int getColumnCount() {
4484         return _cols.size();
4485     }
4486 
4487     /***
4488      * Retrieve column by the display idx.
4489      * 
4490      * @param idx display idx
4491      * @return column
4492      */
4493     public IColumn getColumn(int idx) {
4494         return _cols.get(idx);
4495     }
4496 
4497     /***
4498      * Convenience method to retrieve a column by it's id from the model.
4499      * 
4500      * @param id id of the column
4501      * @return column or <code>null</code>
4502      */
4503     public IColumn getColumn(String id) {
4504         for (int i = 0; i < _model.getColumnCount(); i++) {
4505             if (_model.getColumn(i).getId().equals(id)) {
4506                 return _model.getColumn(i);
4507             }
4508         }
4509         return null;
4510     }
4511 
4512     /***
4513      * Get the number of displayed rows (after filtering!).
4514      * 
4515      * @return number of displayed rows
4516      */
4517     public int getRowCount() {
4518         return _rows.size();
4519     }
4520 
4521     /***
4522      * Get a row by the display idx.
4523      * 
4524      * @param idx index in the list of displayed rows.
4525      * @return row
4526      */
4527     public IRow getRow(int idx) {
4528         return _rows.get(idx);
4529     }
4530 
4531     /***
4532      * @return Returns the rowFilter.
4533      */
4534     public IRowFilter getRowFilter() {
4535         return _rowFilter;
4536     }
4537 
4538     /***
4539      * Set a row filter on the table.
4540      * 
4541      * @param rowFilter The rowFilter to set.
4542      */
4543     public void setRowFilter(IRowFilter rowFilter) {
4544         IRowFilter oldVal = _rowFilter;
4545         if (_rowFilter != null) {
4546             _rowFilter.removePropertyChangeListener(this);
4547         }
4548         _rowFilter = rowFilter;
4549         if (_rowFilter != null) {
4550             _rowFilter.addPropertyChangeListener(this);
4551         }
4552         updateRowList();
4553         updateAutoFilter(); // update autofilter (just in case it is enabled)
4554         redraw();
4555         firePropertyChange(PROPERTYNAME_ROWFILTER, oldVal, _rowFilter);
4556         // general change of filtering
4557         firePropertyChange(PROPERTYNAME_FILTERING, null, "x");
4558     }
4559 
4560     /***
4561      * @return Returns the rowSorter.
4562      */
4563     public IRowSorter getRowSorter() {
4564         return _rowSorter;
4565     }
4566 
4567     /***
4568      * Set a row sorter. A row sorter will be overruled by sorting setup on columns.
4569      * 
4570      * @param rowSorter The rowSorter to set.
4571      */
4572     public void setRowSorter(IRowSorter rowSorter) {
4573         IRowSorter oldValue = _rowSorter;
4574         if (_rowSorter != null) {
4575             _rowSorter.removePropertyChangeListener(this);
4576         }
4577         _rowSorter = rowSorter;
4578         if (_rowSorter != null) {
4579             _rowSorter.addPropertyChangeListener(this);
4580         }
4581         updateRowList();
4582         redraw();
4583         // fire the change for the sorter object
4584         firePropertyChange(PROPERTYNAME_ROWSORTER, oldValue, _rowSorter);
4585         // fire the general sorting change
4586         firePropertyChange(PROPERTYNAME_SORTING, null, "x");
4587     }
4588 
4589     /***
4590      * Add a listener to listen for focus changes in the table (focussed cell).
4591      * 
4592      * @param tfl listener
4593      */
4594     public synchronized void addTableFocusListener(ITableFocusListener tfl) {
4595         if (_tableFocusListeners == null) {
4596             _tableFocusListeners = new Vector<ITableFocusListener>();
4597         }
4598         _tableFocusListeners.add(tfl);
4599     }
4600 
4601     /***
4602      * Remove a registered listener.
4603      * 
4604      * @param tfl listener
4605      */
4606     public synchronized void remTableFocusListener(ITableFocusListener tfl) {
4607         if (_tableFocusListeners != null) {
4608             _tableFocusListeners.remove(tfl);
4609         }
4610     }
4611 
4612     /***
4613      * Inform focus listeners about a change of the focussed cell.
4614      * 
4615      * @param row row of the focussed cell
4616      * @param column column of the focussed cell
4617      */
4618     private void fireTableFocusChanged(IRow row, IColumn column) {
4619         if (_tableFocusListeners != null) {
4620             for (ITableFocusListener listener : _tableFocusListeners) {
4621                 listener.tableFocusChanged(this, row, column);
4622             }
4623         }
4624     }
4625 
4626     // ************ property change listener
4627     /***
4628      * {@inheritDoc} The table eitselflistens for prop changes of the rowSorter and the rowFilter.
4629      */
4630     public void propertyChange(PropertyChangeEvent evt) {
4631         if (evt.getSource().equals(_rowSorter)) {
4632             updateRowList();
4633             updateAutoFilter(); // update autofilter (just in case it is enabled)
4634             redraw();
4635             firePropertyChange(PROPERTYNAME_SORTING, null, "x");
4636         } else if (evt.getSource().equals(_rowFilter)) {
4637             updateRowList();
4638             updateAutoFilter(); // update autofilter (just in case it is enabled)
4639             redraw();
4640             firePropertyChange(PROPERTYNAME_FILTERING, null, "x");
4641         }
4642 
4643     }
4644 
4645     // ************ property change listener
4646 
4647     /***
4648      * Retrieve the used startegy when performing a fill drag.
4649      * 
4650      * @return the fillDragStrategy
4651      */
4652     public IFillDragStrategy getFillDragStrategy() {
4653         return _fillDragStrategy;
4654     }
4655 
4656     /***
4657      * Set the strategy used when perfoming a fill drag.
4658      * 
4659      * @param fillDragStrategy the fillDragStrategy to set. Must be non null.
4660      */
4661     public void setFillDragStrategy(IFillDragStrategy fillDragStrategy) {
4662         if (fillDragStrategy == null) {
4663             throw new IllegalArgumentException("FillDragStrategy must not be NULL");
4664         }
4665         _fillDragStrategy = fillDragStrategy;
4666     }
4667 
4668     /***
4669      * Retrieve whether fill dragging is activated.
4670      * 
4671      * @return the supportFillDragging
4672      */
4673     public boolean isSupportFillDragging() {
4674         return _supportFillDragging;
4675     }
4676 
4677     /***
4678      * Set fill drag activation.
4679      * 
4680      * @param supportFillDragging the supportFillDragging to set
4681      */
4682     public void setSupportFillDragging(boolean supportFillDragging) {
4683         _supportFillDragging = supportFillDragging;
4684         _dragMarkerRect = null;
4685         redraw();
4686     }
4687 
4688     /***
4689      * @return the iccpStrategy
4690      */
4691     public ICCPStrategy getCcpStrategy() {
4692         return _ccpStrategy;
4693     }
4694 
4695     /***
4696      * Set the strategy to perform cut, copy, paste operations. Setting the strategy to <code>null</code> causes
4697      * deactivation of ccp.
4698      * 
4699      * @param ccpStrategy the iccpStrategy to set or <code>null</code> to deactivat ccp
4700      */
4701     public void setCcpStrategy(ICCPStrategy ccpStrategy) {
4702         _ccpStrategy = ccpStrategy;
4703     }
4704 
4705     /***
4706      * Do a cut operation. Implementation is supplied by the CCPStrategy.
4707      * 
4708      */
4709     public void cut() {
4710         if (_ccpStrategy != null) {
4711             _ccpStrategy.cut(this);
4712         }
4713     }
4714 
4715     /***
4716      * Do a copy operation. Implementation is supplied by the CCPStrategy.
4717      * 
4718      */
4719     public void copy() {
4720         if (_ccpStrategy != null) {
4721             _ccpStrategy.copy(this);
4722         }
4723     }
4724 
4725     /***
4726      * Do a paste operation. Implementation is supplied by the CCPStrategy.
4727      * 
4728      */
4729     public void paste() {
4730         if (_ccpStrategy != null) {
4731             _ccpStrategy.paste(this);
4732         }
4733     }
4734 
4735     /***
4736      * Select all cells by selectiong all displayed (not filtered) columns.
4737      * 
4738      */
4739     public void selectAll() {
4740         getSelectionModel().clearSelection();
4741         for (IColumn col : _cols) {
4742             getSelectionModel().addSelectedColumn(col);
4743         }
4744     }
4745 
4746     /***
4747      * Retrieve whether scroll opotimizations are active.
4748      * 
4749      * @return true if scrolling is done optimized
4750      */
4751     public boolean getOptimizeScrolling() {
4752         return _optimizeScrolling;
4753     }
4754 
4755     /***
4756      * Set whether to use optimized scrolling by copying content. Defaults to false (since it causes trouble when
4757      * running on Linux or OSX).
4758      * 
4759      * @param optimizeScrolling true for optimizing
4760      */
4761     public void setOptimizeScrolling(boolean optimizeScrolling) {
4762         _optimizeScrolling = optimizeScrolling;
4763     }
4764 
4765     /***
4766      * Retrieve the height used to render the autofilters.
4767      * 
4768      * @return height of the autofilter rectangle
4769      */
4770     public int getAutoFilterHeight() {
4771         return _autoFilterEnabled ? _autoFilterRect.height : 0;
4772     }
4773 
4774     /***
4775      * {@inheritDoc}
4776      */
4777     public void addPropertyChangeListener(PropertyChangeListener listener) {
4778         _propertyChangeSupport.addPropertyChangeListener(listener);
4779     }
4780 
4781     /***
4782      * {@inheritDoc}
4783      */
4784     public void removePropertyChangeListener(PropertyChangeListener listener) {
4785         _propertyChangeSupport.removePropertyChangeListener(listener);
4786     }
4787 
4788     /***
4789      * {@inheritDoc}
4790      */
4791     public void firePropertyChange(String propName, Object oldVal, Object newVal) {
4792         if (_propertyChangeSupport != null) {
4793             _propertyChangeSupport.firePropertyChange(propName, oldVal, newVal);
4794         }
4795     }
4796 
4797     /***
4798      * Retrieve the pixel offset the first row is scrolled.
4799      * 
4800      * @return pixel ofset of the first row
4801      */
4802     public int getFirstRowPixelOffset() {
4803         return _firstRowPixelOffset;
4804     }
4805 
4806     /***
4807      * Retrive the index of the first row displayed in the scrolled area of the table.
4808      * 
4809      * @return index of the first row displayed
4810      */
4811     public int getFirstRowIdx() {
4812         return _firstRowIdx;
4813     }
4814 
4815     /***
4816      * Check whether sorting the table is allowed.
4817      * 
4818      * @return true if sorting is allowed
4819      */
4820     public boolean getAllowSorting() {
4821         return _allowSorting;
4822     }
4823 
4824     /***
4825      * Set the global allowance for sorting. This defaults to true.
4826      * 
4827      * @param allowSorting true to allow sorting
4828      */
4829     public void setAllowSorting(boolean allowSorting) {
4830         _allowSorting = allowSorting;
4831     }
4832 
4833     /***
4834      * Get access to the internal row list. This is for special purposes (like synchronizing models) only. Use with
4835      * care!
4836      * 
4837      * @return the internal list of rows
4838      */
4839     public List<IRow> getInternalRowList() {
4840         return _rows;
4841     }
4842 
4843 }