1 // Written in the D programming language.
2 
3 /**
4 
5 This module contains implementation of grid widgets
6 
7 
8 GridWidgetBase - abstract grid widget
9 
10 StringGridWidget - grid of strings
11 
12 
13 Synopsis:
14 
15 ----
16 import dlangui.widgets.grid;
17 
18 StringGridWidget grid = new StringGridWidget("GRID1");
19 grid.layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT);
20 grid.showColHeaders = true;
21 grid.showRowHeaders = true;
22 grid.resize(30, 50);
23 grid.fixedCols = 3;
24 grid.fixedRows = 2;
25 //grid.rowSelect = true; // testing full row selection
26 grid.selectCell(4, 6, false);
27 // create sample grid content
28 for (int y = 0; y < grid.rows; y++) {
29     for (int x = 0; x < grid.cols; x++) {
30         grid.setCellText(x, y, "cell("d ~ to!dstring(x + 1) ~ ","d ~ to!dstring(y + 1) ~ ")"d);
31     }
32     grid.setRowTitle(y, to!dstring(y + 1));
33 }
34 for (int x = 0; x < grid.cols; x++) {
35     int col = x + 1;
36     dstring res;
37     int n1 = col / 26;
38     int n2 = col % 26;
39     if (n1)
40         res ~= n1 + 'A';
41     res ~= n2 + 'A';
42     grid.setColTitle(x, res);
43 }
44 grid.autoFit();
45 
46 
47 ----
48 
49 Copyright: Vadim Lopatin, 2014
50 License:   Boost License 1.0
51 Authors:   Vadim Lopatin, coolreader.org@gmail.com
52 */
53 module dlangui.widgets.grid;
54 
55 import dlangui.core.config;
56 import dlangui.widgets.widget;
57 import dlangui.widgets.controls;
58 import dlangui.widgets.scroll;
59 import dlangui.widgets.menu;
60 import std.conv;
61 import std.container.rbtree;
62 import std.algorithm : equal, min;
63 
64 /// cellPopupMenu signal handler interface
65 interface CellPopupMenuHandler {
66     MenuItem getCellPopupMenu(GridWidgetBase source, int col, int row);
67 }
68 
69 /**
70  * Data provider for GridWidget.
71  */
72 interface GridAdapter {
73     /// number of columns
74     @property int cols();
75     /// number of rows
76     @property int rows();
77     /// returns widget to draw cell at (col, row)
78     Widget cellWidget(int col, int row);
79     /// returns row header widget, null if no header
80     Widget rowHeader(int row);
81     /// returns column header widget, null if no header
82     Widget colHeader(int col);
83 }
84 
85 /**
86  */
87 class StringGridAdapter : GridAdapter {
88     protected int _cols;
89     protected int _rows;
90     protected dstring[][] _data;
91     protected dstring[] _rowTitles;
92     protected dstring[] _colTitles;
93     /// number of columns
94     @property int cols() { return _cols; }
95     /// number of columns
96     @property void cols(int v) { resize(v, _rows); }
97     /// number of rows
98     @property int rows() { return _rows; }
99     /// number of columns
100     @property void rows(int v) { resize(_cols, v); }
101 
102     /// returns row header title
103     dstring rowTitle(int row) {
104         return _rowTitles[row];
105     }
106     /// set row header title
107     StringGridAdapter setRowTitle(int row, dstring title) {
108         _rowTitles[row] = title;
109         return this;
110     }
111     /// returns row header title
112     dstring colTitle(int col) {
113         return _colTitles[col];
114     }
115     /// set col header title
116     StringGridAdapter setColTitle(int col, dstring title) {
117         _colTitles[col] = title;
118         return this;
119     }
120     /// get cell text
121     dstring cellText(int col, int row) {
122         return _data[row][col];
123     }
124     /// set cell text
125     StringGridAdapter setCellText(int col, int row, dstring text) {
126         _data[row][col] = text;
127         return this;
128     }
129     /// set new size
130     void resize(int cols, int rows) {
131         if (cols == _cols && rows == _rows)
132             return;
133         _cols = cols;
134         _rows = rows;
135         _data.length = _rows;
136         for (int y = 0; y < _rows; y++)
137             _data[y].length = _cols;
138         _colTitles.length = _cols;
139         _rowTitles.length = _rows;
140     }
141     /// returns widget to draw cell at (col, row)
142     Widget cellWidget(int col, int row) { return null; }
143     /// returns row header widget, null if no header
144     Widget rowHeader(int row) { return null; }
145     /// returns column header widget, null if no header
146     Widget colHeader(int col) { return null; }
147 }
148 
149 /// grid control action codes
150 enum GridActions : int {
151     /// no action
152     None = 0,
153     /// move selection up
154     Up = 2000,
155     /// expend selection up
156     SelectUp,
157     /// move selection down
158     Down,
159     /// expend selection down
160     SelectDown,
161     /// move selection left
162     Left,
163     /// expend selection left
164     SelectLeft,
165     /// move selection right
166     Right,
167     /// expend selection right
168     SelectRight,
169 
170     /// scroll up, w/o changing selection
171     ScrollUp,
172     /// scroll down, w/o changing selection
173     ScrollDown,
174     /// scroll left, w/o changing selection
175     ScrollLeft,
176     /// scroll right, w/o changing selection
177     ScrollRight,
178 
179     /// scroll up, w/o changing selection
180     ScrollPageUp,
181     /// scroll down, w/o changing selection
182     ScrollPageDown,
183     /// scroll left, w/o changing selection
184     ScrollPageLeft,
185     /// scroll right, w/o changing selection
186     ScrollPageRight,
187 
188     /// move cursor one page up
189     PageUp,
190     /// move cursor one page up with selection
191     SelectPageUp,
192     /// move cursor one page down
193     PageDown,
194     /// move cursor one page down with selection
195     SelectPageDown,
196     /// move cursor to the beginning of page
197     PageBegin,
198     /// move cursor to the beginning of page with selection
199     SelectPageBegin,
200     /// move cursor to the end of page
201     PageEnd,
202     /// move cursor to the end of page with selection
203     SelectPageEnd,
204     /// move cursor to the beginning of line
205     LineBegin,
206     /// move cursor to the beginning of line with selection
207     SelectLineBegin,
208     /// move cursor to the end of line
209     LineEnd,
210     /// move cursor to the end of line with selection
211     SelectLineEnd,
212     /// move cursor to the beginning of document
213     DocumentBegin,
214     /// move cursor to the beginning of document with selection
215     SelectDocumentBegin,
216     /// move cursor to the end of document
217     DocumentEnd,
218     /// move cursor to the end of document with selection
219     SelectDocumentEnd,
220     /// select all entries without moving the cursor
221     SelectAll,
222     /// Enter key pressed on cell
223     ActivateCell,
224 }
225 
226 /// Adapter for custom drawing of some cells in grid widgets
227 interface CustomGridCellAdapter {
228     /// return true for custom drawn cell
229     bool isCustomCell(int col, int row);
230     /// return cell size
231     Point measureCell(int col, int row);
232     /// draw data cell content
233     void drawCell(DrawBuf buf, Rect rc, int col, int row);
234 }
235 
236 interface GridModelAdapter {
237     @property int fixedCols();
238     @property int fixedRows();
239     @property void fixedCols(int value);
240     @property void fixedRows(int value);
241 }
242 
243 /// Callback for handling of cell selection
244 interface CellSelectedHandler {
245     void onCellSelected(GridWidgetBase source, int col, int row);
246 }
247 
248 /// Callback for handling of cell double click or Enter key press
249 interface CellActivatedHandler {
250     void onCellActivated(GridWidgetBase source, int col, int row);
251 }
252 
253 /// Callback for handling of view scroll (top left visible cell change)
254 interface ViewScrolledHandler {
255     void onViewScrolled(GridWidgetBase source, int col, int row);
256 }
257 
258 /// Callback for handling of grid header cell click
259 interface HeaderCellClickHandler {
260     void onHeaderCellClick(GridWidgetBase source, int col, int row);
261 }
262 
263 /// Abstract grid widget
264 class GridWidgetBase : ScrollWidgetBase, GridModelAdapter, MenuItemActionHandler {
265     /// Callback to handle selection change
266     Listener!CellSelectedHandler cellSelected;
267 
268     /// Callback to handle cell double click
269     Listener!CellActivatedHandler cellActivated;
270 
271     /// Callback for handling of view scroll (top left visible cell change)
272     Listener!ViewScrolledHandler viewScrolled;
273 
274     /// Callback for handling header cell click
275     Listener!HeaderCellClickHandler headerCellClicked;
276 
277     protected CustomGridCellAdapter _customCellAdapter;
278 
279     /// Get adapter to override drawing of some particular cells
280     @property CustomGridCellAdapter customCellAdapter() { return _customCellAdapter; }
281     /// Set adapter to override drawing of some particular cells
282     @property GridWidgetBase customCellAdapter(CustomGridCellAdapter adapter) { _customCellAdapter = adapter; return this; }
283 
284     protected GridModelAdapter _gridModelAdapter;
285     /// Get adapter to hold grid model data
286     @property GridModelAdapter gridModelAdapter() { return _gridModelAdapter; }
287     /// Set adapter to hold grid model data
288     @property GridWidgetBase gridModelAdapter(GridModelAdapter adapter) { _gridModelAdapter = adapter; return this; }
289 
290     protected bool _smoothHScroll = true;
291     /// Get smooth horizontal scroll flag - when true - scrolling by pixels, when false - by cells
292     @property bool smoothHScroll() { return _smoothHScroll; }
293     /// Get smooth horizontal scroll flag - when true - scrolling by pixels, when false - by cells
294     @property GridWidgetBase smoothHScroll(bool flgSmoothScroll) {
295         if (_smoothHScroll != flgSmoothScroll) {
296             _smoothHScroll = flgSmoothScroll;
297             // TODO: snap to grid if necessary
298             updateScrollBars();
299         }
300         return this;
301     }
302 
303     protected bool _smoothVScroll = true;
304     /// Get smooth vertical scroll flag - when true - scrolling by pixels, when false - by cells
305     @property bool smoothVScroll() { return _smoothVScroll; }
306     /// Get smooth vertical scroll flag - when true - scrolling by pixels, when false - by cells
307     @property GridWidgetBase smoothVScroll(bool flgSmoothScroll) {
308         if (_smoothVScroll != flgSmoothScroll) {
309             _smoothVScroll = flgSmoothScroll;
310             // TODO: snap to grid if necessary
311             updateScrollBars();
312         }
313         return this;
314     }
315 
316     /// column count (including header columns and fixed columns)
317     protected int _cols;
318     /// row count (including header rows and fixed rows)
319     protected int _rows;
320     /// column widths
321     protected int[] _colWidths;
322     /// total width from first column to right of this
323     protected int[] _colCumulativeWidths;
324     /// row heights
325     protected int[] _rowHeights;
326     /// total height from first row to bottom of this
327     protected int[] _rowCumulativeHeights;
328     /// when true, shows col headers row
329     protected bool _showColHeaders;
330     /// when true, shows row headers column
331     protected bool _showRowHeaders;
332     /// number of header rows (e.g. like col name A, B, C... in excel; 0 for no header row)
333     protected int _headerRows;
334     /// number of header columns (e.g. like row number in excel; 0 for no header column)
335     protected int _headerCols;
336     /// number of fixed (non-scrollable) columns
337     protected int _fixedCols;
338     /// number of fixed (non-scrollable) rows
339     protected int _fixedRows;
340 
341     /// scroll X offset in pixels
342     protected int _scrollX;
343     /// scroll Y offset in pixels
344     protected int _scrollY;
345 
346     /// selected cells when multiselect is enabled
347     protected RedBlackTree!Point _selection;
348     /// selected cell column
349     protected int _col;
350     /// selected cell row
351     protected int _row;
352     /// when true, allows multi cell selection
353     protected bool _multiSelect;
354     private Point _lastSelectedCell;
355     /// when true, allows to select only whole row
356     protected bool _rowSelect;
357     /// default column width - for newly added columns
358     protected int _defColumnWidth;
359     /// default row height - for newly added rows
360     protected int _defRowHeight;
361 
362     // properties
363 
364     /// selected cells when multiselect is enabled
365     @property RedBlackTree!Point selection() { return _selection; }
366     /// selected column
367     @property int col() { return _col - _headerCols; }
368     /// selected row
369     @property int row() { return _row - _headerRows; }
370     /// column count
371     @property int cols() { return _cols - _headerCols; }
372     /// set column count
373     @property GridWidgetBase cols(int c) { resize(c, rows); return this; }
374     /// row count
375     @property int rows() { return _rows - _headerRows; }
376     /// set row count
377     @property GridWidgetBase rows(int r) { resize(cols, r); return this; }
378 
379     protected bool _allowColResizing = true;
380     /// get col resizing flag; when true, allow resizing of column with mouse
381     @property bool allowColResizing() { return _allowColResizing; }
382     /// set col resizing flag; when true, allow resizing of column with mouse
383     @property GridWidgetBase allowColResizing(bool flgAllowColResizing) { _allowColResizing = flgAllowColResizing; return this; }
384 
385     protected uint _selectionColor = 0x804040FF;
386     protected uint _selectionColorRowSelect = 0xC0A0B0FF;
387     protected uint _fixedCellBackgroundColor = 0xC0E0E0E0;
388     protected uint _fixedCellBorderColor = 0xC0C0C0C0;
389     protected uint _cellBorderColor = 0xC0C0C0C0;
390     protected uint _cellHeaderBorderColor = 0xC0202020;
391     protected uint _cellHeaderBackgroundColor = 0xC0909090;
392     protected uint _cellHeaderSelectedBackgroundColor = 0x80FFC040;
393     protected DrawableRef _cellHeaderBackgroundDrawable;
394     protected DrawableRef _cellHeaderSelectedBackgroundDrawable;
395     protected DrawableRef _cellRowHeaderBackgroundDrawable;
396     protected DrawableRef _cellRowHeaderSelectedBackgroundDrawable;
397 
398     /// row header column count
399     @property int headerCols() { return _headerCols; }
400     @property GridWidgetBase headerCols(int c) {
401         _headerCols = c;
402         invalidate();
403         return this;
404     }
405     /// col header row count
406     @property int headerRows() { return _headerRows; }
407     @property GridWidgetBase headerRows(int r) {
408         _headerRows = r;
409         invalidate();
410         return this;
411     }
412 
413     /// fixed (non-scrollable) data column count
414     @property int fixedCols() { return _gridModelAdapter is null ? _fixedCols : _gridModelAdapter.fixedCols; }
415     @property void fixedCols(int c) {
416         if (_gridModelAdapter is null)
417             _fixedCols = c;
418         else
419             _gridModelAdapter.fixedCols = c;
420         invalidate();
421     }
422     /// fixed (non-scrollable) data row count
423     @property int fixedRows() { return _gridModelAdapter is null ? _fixedRows : _gridModelAdapter.fixedCols; }
424     @property void fixedRows(int r) {
425         if (_gridModelAdapter is null)
426             _fixedRows = r;
427         else
428             _gridModelAdapter.fixedCols = r;
429         invalidate();
430     }
431 
432     /// default column width - for newly added columns
433     @property int defColumnWidth() {
434         return _defColumnWidth;
435     }
436     @property GridWidgetBase defColumnWidth(int v) {
437         _defColumnWidth = v;
438         _changedSize = true;
439         return this;
440     }
441     /// default row height - for newly added rows
442     @property int defRowHeight() {
443         return _defRowHeight;
444     }
445     @property GridWidgetBase defRowHeight(int v) {
446         _defRowHeight = v;
447         _changedSize = true;
448         return this;
449     }
450 
451     /// when true, allows multi cell selection
452     @property bool multiSelect() {
453         return _multiSelect;
454     }
455     @property GridWidgetBase multiSelect(bool flg) {
456         _multiSelect = flg;
457         if (!_multiSelect) {
458             _selection.clear();
459             _selection.insert(Point(_col - _headerCols, _row - _headerRows));
460         }
461         return this;
462     }
463 
464     /// when true, allows only select the whole row
465     @property bool rowSelect() {
466         return _rowSelect;
467     }
468     @property GridWidgetBase rowSelect(bool flg) {
469         _rowSelect = flg;
470         if (_rowSelect) {
471             _selection.clear();
472             _selection.insert(Point(_col - _headerCols, _row - _headerRows));
473         }
474         invalidate();
475         return this;
476     }
477 
478     /// set bool property value, for ML loaders
479     mixin(generatePropertySettersMethodOverride("setBoolProperty", "bool",
480           "showColHeaders", "showColHeaders", "rowSelect", "smoothHScroll", "smoothVScroll", "allowColResizing"));
481 
482     /// set int property value, for ML loaders
483     mixin(generatePropertySettersMethodOverride("setIntProperty", "int",
484           "headerCols", "headerRows", "fixedCols", "fixedRows", "cols", "rows", "defColumnWidth", "defRowHeight"));
485 
486     /// flag to enable column headers
487     @property bool showColHeaders() {
488         return _showColHeaders;
489     }
490 
491     @property GridWidgetBase showColHeaders(bool show) {
492         if (_showColHeaders != show) {
493             _showColHeaders = show;
494             for (int i = 0; i < _headerRows; i++)
495                 autoFitRowHeight(i);
496             invalidate();
497             _changedSize = true;
498         }
499         return this;
500     }
501 
502     /// flag to enable row headers
503     @property bool showRowHeaders() {
504         return _showRowHeaders;
505     }
506 
507     @property GridWidgetBase showRowHeaders(bool show) {
508         if (_showRowHeaders != show) {
509             _showRowHeaders = show;
510             for (int i = 0; i < _headerCols; i++)
511                 autoFitColumnWidth(i);
512             _changedSize = true;
513             invalidate();
514         }
515         return this;
516     }
517 
518     protected bool _changedSize = true;
519     /// recalculate colCumulativeWidths, rowCumulativeHeights after resizes
520     protected void updateCumulativeSizes() {
521         if (!_changedSize)
522             return;
523         _changedSize = false;
524         _colCumulativeWidths.length = _colWidths.length;
525         _rowCumulativeHeights.length = _rowHeights.length;
526         for (int i = 0; i < _colCumulativeWidths.length; i++) {
527             if (i == 0)
528                 _colCumulativeWidths[i] = _colWidths[i];
529             else
530                 _colCumulativeWidths[i] = _colWidths[i] + _colCumulativeWidths[i - 1];
531         }
532         for (int i = 0; i < _rowCumulativeHeights.length; i++) {
533             if (i == 0)
534                 _rowCumulativeHeights[i] = _rowHeights[i];
535             else
536                 _rowCumulativeHeights[i] = _rowHeights[i] + _rowCumulativeHeights[i - 1];
537         }
538     }
539 
540     /// set new size
541     void resize(int c, int r) {
542         if (c == cols && r == rows)
543             return;
544         _changedSize = true;
545         _colWidths.length = c + _headerCols;
546         for (int i = _cols; i < c + _headerCols; i++) {
547             _colWidths[i] = _defColumnWidth;
548         }
549         _rowHeights.length = r + _headerRows;
550         for (int i = _rows; i < r + _headerRows; i++) {
551             _rowHeights[i] = _defRowHeight;
552         }
553         _cols = c + _headerCols;
554         _rows = r + _headerRows;
555         updateCumulativeSizes();
556     }
557 
558     /// count of non-scrollable columns (header + fixed)
559     @property int nonScrollCols() { return _headerCols + fixedCols; }
560     /// count of non-scrollable rows (header + fixed)
561     @property int nonScrollRows() { return _headerRows + fixedRows; }
562     /// return all (fixed + scrollable) cells size in pixels
563     @property Point fullAreaPixels() {
564         if (_changedSize) updateCumulativeSizes();
565         return Point(_cols ? _colCumulativeWidths[_cols - 1] : 0, _rows ? _rowCumulativeHeights[_rows - 1] : 0);
566     }
567     /// non-scrollable area size in pixels
568     @property Point nonScrollAreaPixels() {
569         if (_changedSize) updateCumulativeSizes();
570         int nscols = nonScrollCols;
571         int nsrows = nonScrollRows;
572         return Point(nscols ? _colCumulativeWidths[nscols - 1] : 0, nsrows ? _rowCumulativeHeights[nsrows - 1] : 0);
573     }
574     /// scrollable area size in pixels
575     @property Point scrollAreaPixels() {
576         return fullAreaPixels - nonScrollAreaPixels;
577     }
578     /// get cell rectangle (relative to client area) not counting scroll position
579     Rect cellRectNoScroll(int x, int y) {
580         if (_changedSize) updateCumulativeSizes();
581         if (x < 0 || y < 0 || x >= _cols || y >= _rows)
582             return Rect(0,0,0,0);
583         return Rect(x ? _colCumulativeWidths[x - 1] : 0, y ? _rowCumulativeHeights[y - 1] : 0,
584                 _colCumulativeWidths[x], _rowCumulativeHeights[y]);
585     }
586     /// get cell rectangle moved by scroll pixels (may overlap non-scroll cols!)
587     Rect cellRectScroll(int x, int y) {
588         Rect rc = cellRectNoScroll(x, y);
589         int nscols = nonScrollCols;
590         int nsrows = nonScrollRows;
591         if (x >= nscols) {
592             rc.left -= _scrollX;
593             rc.right -= _scrollX;
594         }
595         if (y >= nsrows) {
596             rc.top -= _scrollY;
597             rc.bottom -= _scrollY;
598         }
599         return rc;
600     }
601     /// returns true if column is inside client area and not overlapped outside scroll area
602     bool colVisible(int x) {
603         if (_changedSize) updateCumulativeSizes();
604         if (x < 0 || x >= _cols)
605             return false;
606         if (x == 0)
607             return true;
608         int nscols = nonScrollCols;
609         if (x < nscols) {
610             // non-scrollable
611             return _colCumulativeWidths[x - 1] < _clientRect.width;
612         } else {
613             // scrollable
614             int start = _colCumulativeWidths[x - 1] - _scrollX;
615             int end = _colCumulativeWidths[x] - _scrollX;
616             if (start >= _clientRect.width)
617                 return false; // at right
618             if (end <= (nscols ? _colCumulativeWidths[nscols - 1] : 0))
619                 return false; // at left
620             return true; // visible
621         }
622     }
623     /// returns true if row is inside client area and not overlapped outside scroll area
624     bool rowVisible(int y) {
625         if (y < 0 || y >= _rows)
626             return false;
627         if (_changedSize) updateCumulativeSizes();
628         if (y == 0)
629             return true; // first row always visible
630         int nsrows = nonScrollRows;
631         if (y < nsrows) {
632             // non-scrollable
633             return _rowCumulativeHeights[y - 1] < _clientRect.height;
634         } else {
635             // scrollable
636             int start = _rowCumulativeHeights[y - 1] - _scrollY;
637             int end = _rowCumulativeHeights[y] - _scrollY;
638             if (start >= _clientRect.height)
639                 return false; // at right
640             if (end <= (nsrows ? _rowCumulativeHeights[nsrows - 1] : 0))
641                 return false; // at left
642             return true; // visible
643         }
644     }
645 
646     void setColWidth(int x, int w) {
647         _colWidths[x] = w;
648         _changedSize = true;
649     }
650 
651     void setRowHeight(int y, int w) {
652         _rowHeights[y] = w;
653         _changedSize = true;
654     }
655 
656     /// get column width, 0 is header column
657     int colWidth(int col) {
658         if (col < 0 || col >= _colWidths.length)
659             return 0;
660         return _colWidths[col];
661     }
662 
663     /// get row height, 0 is header row
664     int rowHeight(int row) {
665         if (row < 0 || row >= _rowHeights.length)
666             return 0;
667         return _rowHeights[row];
668     }
669 
670     /// returns cell rectangle relative to client area; row 0 is col headers row; col 0 is row headers column
671     Rect cellRect(int x, int y) {
672         return cellRectScroll(x, y);
673     }
674 
675     /// converts client rect relative coordinates to cell coordinates
676     bool pointToCell(int x, int y, ref int col, ref int row, ref Rect cellRect) {
677         if (_changedSize) updateCumulativeSizes();
678         int nscols = nonScrollCols;
679         int nsrows = nonScrollRows;
680         Point ns = nonScrollAreaPixels;
681         col = colByAbsoluteX(x < ns.x ? x : x + _scrollX);
682         row = rowByAbsoluteY(y < ns.y ? y : y + _scrollY);
683         cellRect = cellRectScroll(col, row);
684         return cellRect.isPointInside(x, y);
685     }
686 
687     /// update scrollbar positions
688     override protected void updateScrollBars() {
689         if (_changedSize) updateCumulativeSizes();
690         calcScrollableAreaPos();
691         correctScrollPos();
692         super.updateScrollBars();
693     }
694 
695     /// search for index of position inside cumulative sizes array
696     protected static int findPosIndex(int[] cumulativeSizes, int pos) {
697         // binary search
698         if (pos < 0 || !cumulativeSizes.length)
699             return 0;
700         int a = 0; // inclusive lower bound
701         int b = cast(int)cumulativeSizes.length; // exclusive upper bound
702         if (pos >= cumulativeSizes[$ - 1])
703             return b - 1;
704         int * w = cumulativeSizes.ptr;
705         for(;;) {
706             if (a + 1 >= b)
707                 return a; // single point
708             // middle point
709             // always inside range
710             int c = (a + b) >> 1;
711             int start = c ? w[c - 1] : 0;
712             int end = w[c];
713             if (pos < start) {
714                 // left
715                 b = c;
716             } else if (pos >= end) {
717                 // right
718                 a = c + 1;
719             } else {
720                 // found
721                 return c;
722             }
723         }
724     }
725 
726     /// column by X, ignoring scroll position
727     protected int colByAbsoluteX(int x) {
728         if (_changedSize) updateCumulativeSizes();
729         return findPosIndex(_colCumulativeWidths, x);
730     }
731 
732     /// row by Y, ignoring scroll position
733     protected int rowByAbsoluteY(int y) {
734         if (_changedSize) updateCumulativeSizes();
735         return findPosIndex(_rowCumulativeHeights, y);
736     }
737 
738     /// returns first fully visible column in scroll area
739     protected int scrollCol() {
740         if (_changedSize) updateCumulativeSizes();
741         int x = nonScrollAreaPixels.x + _scrollX;
742         int col = colByAbsoluteX(x);
743         int start = col ? _colCumulativeWidths[col - 1] : 0;
744         int end = _colCumulativeWidths[col];
745         if (x <= start)
746             return col;
747         // align to next col
748         return colByAbsoluteX(end);
749     }
750 
751     /// returns last fully visible column in scroll area
752     protected int lastScrollCol() {
753         if (_changedSize) updateCumulativeSizes();
754         int x = nonScrollAreaPixels.x + _scrollX + _visibleScrollableArea.width - 1;
755         int col = colByAbsoluteX(x);
756         int start = col ? _colCumulativeWidths[col - 1] : 0;
757         int end = _colCumulativeWidths[col];
758         if (x >= end - 1) // fully visible
759             return col;
760         if (col > nonScrollCols && col > scrollCol)
761             col--;
762         return col;
763     }
764 
765     /// returns first fully visible row in scroll area
766     protected int scrollRow() {
767         if (_changedSize) updateCumulativeSizes();
768         int y = nonScrollAreaPixels.y + _scrollY;
769         int row = rowByAbsoluteY(y);
770         int start = row ? _rowCumulativeHeights[row - 1] : 0;
771         int end = _rowCumulativeHeights[row];
772         if (y <= start)
773             return row;
774         // align to next col
775         return rowByAbsoluteY(end);
776     }
777 
778     /// returns last fully visible row in scroll area
779     protected int lastScrollRow() {
780         if (_changedSize) updateCumulativeSizes();
781         int y = nonScrollAreaPixels.y + _scrollY + _visibleScrollableArea.height - 1;
782         int row = rowByAbsoluteY(y);
783         int start = row ? _rowCumulativeHeights[row - 1] : 0;
784         int end = _rowCumulativeHeights[row];
785         if (y >= end - 1) // fully visible
786             return row;
787         if (row > nonScrollRows && row > scrollRow)
788             row--;
789         return row;
790     }
791 
792     /// move scroll position horizontally by dx, and vertically by dy; returns true if scrolled
793     bool scrollBy(int dx, int dy) {
794         if (_changedSize) updateCumulativeSizes();
795         int col = scrollCol + dx;
796         int row = scrollRow + dy;
797         if (col >= _cols)
798             col = _cols - 1;
799         if (row >= _rows)
800             row = _rows - 1;
801         if (col < nonScrollCols)
802             col = nonScrollCols;
803         if (row < nonScrollCols)
804             row = nonScrollRows;
805         Rect rc = cellRectNoScroll(col, row);
806         Point ns = nonScrollAreaPixels;
807         return scrollTo(rc.left - ns.x, rc.top - ns.y);
808     }
809 
810     /// override to support modification of client rect after change, e.g. apply offset
811     override protected void handleClientRectLayout(ref Rect rc) {
812         //correctScrollPos();
813     }
814 
815     // ensure scroll position is inside min/max area
816     protected void correctScrollPos() {
817         if (_changedSize) updateCumulativeSizes();
818         int maxscrollx = _fullScrollableArea.width - _visibleScrollableArea.width;
819         int maxscrolly = _fullScrollableArea.height - _visibleScrollableArea.height;
820         if (_scrollX < 0)
821             _scrollX = 0;
822         if (_scrollY < 0)
823             _scrollY = 0;
824         if (_scrollX > maxscrollx)
825             _scrollX = maxscrollx;
826         if (_scrollY > maxscrolly)
827             _scrollY = maxscrolly;
828     }
829 
830     /// set scroll position to show specified cell as top left in scrollable area; col or row -1 value means no change
831     bool scrollTo(int x, int y, GridWidgetBase source = null, bool doNotify = true) {
832         if (_changedSize) updateCumulativeSizes();
833         int oldx = _scrollX;
834         int oldy = _scrollY;
835         _scrollX = x;
836         _scrollY = y;
837         correctScrollPos();
838         updateScrollBars();
839         invalidate();
840         bool changed = oldx != _scrollX || oldy != _scrollY;
841         if (doNotify && changed && viewScrolled.assigned) {
842             if (source is null)
843                 source = this;
844             viewScrolled(source, x, y);
845         }
846         return changed;
847     }
848 
849     /// process horizontal scrollbar event
850     override bool onHScroll(ScrollEvent event) {
851         if (event.action == ScrollAction.SliderMoved || event.action == ScrollAction.SliderReleased) {
852             scrollTo(event.position, _scrollY);
853         } else if (event.action == ScrollAction.PageUp) {
854             dispatchAction(new Action(GridActions.ScrollPageLeft));
855         } else if (event.action == ScrollAction.PageDown) {
856             dispatchAction(new Action(GridActions.ScrollPageRight));
857         } else if (event.action == ScrollAction.LineUp) {
858             dispatchAction(new Action(GridActions.ScrollLeft));
859         } else if (event.action == ScrollAction.LineDown) {
860             dispatchAction(new Action(GridActions.ScrollRight));
861         }
862         return true;
863     }
864 
865     /// process vertical scrollbar event
866     override bool onVScroll(ScrollEvent event) {
867         if (event.action == ScrollAction.SliderMoved || event.action == ScrollAction.SliderReleased) {
868             scrollTo(_scrollX, event.position);
869         } else if (event.action == ScrollAction.PageUp) {
870             dispatchAction(new Action(GridActions.ScrollPageUp));
871         } else if (event.action == ScrollAction.PageDown) {
872             dispatchAction(new Action(GridActions.ScrollPageDown));
873         } else if (event.action == ScrollAction.LineUp) {
874             dispatchAction(new Action(GridActions.ScrollUp));
875         } else if (event.action == ScrollAction.LineDown) {
876             dispatchAction(new Action(GridActions.ScrollDown));
877         }
878         return true;
879     }
880 
881     /// ensure that cell is visible (scroll if necessary)
882     void makeCellVisible(int col, int row) {
883         if (_changedSize) updateCumulativeSizes();
884         bool scrolled = false;
885         int newx = _scrollX;
886         int newy = _scrollY;
887         Rect rc = cellRectNoScroll(col, row);
888         Rect visibleRc = _visibleScrollableArea;
889         if (col >= nonScrollCols) {
890             // can scroll X
891             if (rc.left < visibleRc.left) {
892                 // scroll left
893                 newx += rc.left - visibleRc.left;
894             } else if (rc.right > visibleRc.right) {
895                 // scroll right
896                 newx += rc.right - visibleRc.right;
897             }
898         }
899         if (row >= nonScrollRows) {
900             // can scroll Y
901             if (rc.top < visibleRc.top) {
902                 // scroll left
903                 newy += rc.top - visibleRc.top;
904             } else if (rc.bottom > visibleRc.bottom) {
905                 // scroll right
906                 newy += rc.bottom - visibleRc.bottom;
907             }
908         }
909         if (newy < 0)
910             newy = 0;
911         if (newx < 0)
912             newx = 0;
913         if (newx != _scrollX || newy != _scrollY) {
914             scrollTo(newx, newy);
915         }
916     }
917 
918 	bool multiSelectCell(int col, int row, bool expandExisting = false) {
919         if (_col == col && _row == row && !expandExisting)
920             return false; // same position
921         if (col < _headerCols || row < _headerRows || col >= _cols || row >= _rows)
922             return false; // out of range
923         if (_changedSize) updateCumulativeSizes();
924         _lastSelectedCell.x = col;
925         _lastSelectedCell.y = row;
926         if (_rowSelect) col = _headerCols;
927         if (expandExisting) {
928             _selection.clear();
929             int startX = _col - _headerCols;
930             int startY = _row - headerRows;
931             int endX = col - _headerCols;
932             int endY = row - headerRows;
933             if (_rowSelect) startX = 0;
934             if (startX > endX) {
935                 startX = endX;
936                 endX = _col - _headerCols;
937             }
938             if (startY > endY) {
939                 startY = endY;
940                 endY = _row - _headerRows;
941             }
942             for (int x = startX; x <= endX; ++x) {
943                 for (int y = startY; y <= endY; ++y) {
944                     _selection.insert(Point(x, y));
945                 }
946             }
947         } else {
948             _selection.insert(Point(col - _headerCols, row - _headerRows));
949             _col = col;
950             _row = row;
951         }
952         invalidate();
953         calcScrollableAreaPos();
954         makeCellVisible(_lastSelectedCell.x, _lastSelectedCell.y);
955         return true;
956 	}
957 
958     /// move selection to specified cell
959     bool selectCell(int col, int row, bool makeVisible = true, GridWidgetBase source = null, bool needNotification = true) {
960         if (source is null)
961             source = this;
962         _selection.clear();
963         if (_col == col && _row == row)
964             return false; // same position
965         if (col < _headerCols || row < _headerRows || col >= _cols || row >= _rows)
966             return false; // out of range
967         if (_changedSize) updateCumulativeSizes();
968         _col = col;
969         _row = row;
970         _lastSelectedCell = Point(col, row);
971         if (_rowSelect) {
972             _selection.insert(Point(0, row - _headerRows));
973         } else {
974             _selection.insert(Point(col - _headerCols, row - _headerRows));
975         }
976         invalidate();
977         calcScrollableAreaPos();
978         if (makeVisible)
979             makeCellVisible(_col, _row);
980         if (needNotification && cellSelected.assigned)
981             cellSelected(source, _col - _headerCols, _row - _headerRows);
982         return true;
983     }
984 
985     /// Select cell and call onCellActivated handler
986     bool activateCell(int col, int row) {
987         if (_changedSize) updateCumulativeSizes();
988         if (_col != col || _row != row) {
989             selectCell(col, row, true);
990         }
991         if (cellActivated.assigned)
992             cellActivated(this, this.col, this.row);
993         return true;
994     }
995 
996     /// cell popup menu
997     Signal!CellPopupMenuHandler cellPopupMenu;
998     /// popup menu item action
999     Signal!MenuItemActionHandler menuItemAction;
1000 
1001     protected MenuItem getCellPopupMenu(int col, int row) {
1002         if (cellPopupMenu.assigned)
1003             return cellPopupMenu(this, col, row);
1004         return null;
1005     }
1006 
1007     /// handle popup menu action
1008     protected override bool onMenuItemAction(const Action action) {
1009         if (menuItemAction.assigned)
1010             return menuItemAction(action);
1011         return false;
1012     }
1013 
1014     /// returns true if widget can show popup menu (e.g. by mouse right click at point x,y)
1015     override bool canShowPopupMenu(int x, int y) {
1016         int col, row;
1017         Rect rc;
1018         x -= _clientRect.left;
1019         y -= _clientRect.top;
1020         pointToCell(x, y, col, row, rc);
1021         MenuItem item = getCellPopupMenu(col - _headerCols, row - _headerRows);
1022         if (!item)
1023             return false;
1024         return true;
1025     }
1026 
1027     /// shows popup menu at (x,y)
1028     override void showPopupMenu(int xx, int yy) {
1029         int col, row;
1030         Rect rc;
1031         int x = xx - _clientRect.left;
1032         int y = yy - _clientRect.top;
1033         pointToCell(x, y, col, row, rc);
1034         MenuItem menu = getCellPopupMenu(col - _headerCols, row - _headerRows);
1035         if (menu) {
1036             import dlangui.widgets.popup;
1037             menu.updateActionState(this);
1038             PopupMenu popupMenu = new PopupMenu(menu);
1039             popupMenu.menuItemAction = this;
1040             PopupWidget popup = window.showPopup(popupMenu, this, PopupAlign.Point | PopupAlign.Right, xx, yy);
1041             popup.flags = PopupFlags.CloseOnClickOutside;
1042         }
1043     }
1044 
1045     /// returns mouse cursor type for widget
1046     override uint getCursorType(int x, int y) {
1047         if (_allowColResizing) {
1048             if (_colResizingIndex >= 0) // resizing in progress
1049                 return CursorType.SizeWE;
1050             int col = isColumnResizingPoint(x, y);
1051             if (col >= 0)
1052                 return CursorType.SizeWE;
1053         }
1054         return CursorType.Arrow;
1055     }
1056 
1057     protected int _colResizingIndex = -1;
1058     protected int _colResizingStartX = -1;
1059     protected int _colResizingStartWidth = -1;
1060 
1061     protected void startColResize(int col, int x) {
1062         _colResizingIndex = col;
1063         _colResizingStartX = x;
1064         _colResizingStartWidth = _colWidths[col];
1065     }
1066     protected void processColResize(int x) {
1067         if (_colResizingIndex < 0 || _colResizingIndex >= _cols)
1068             return;
1069         int newWidth = _colResizingStartWidth + x - _colResizingStartX;
1070         if (newWidth < 0)
1071             newWidth = 0;
1072         _colWidths[_colResizingIndex] = newWidth;
1073         _changedSize = true;
1074         updateCumulativeSizes();
1075         updateScrollBars();
1076         invalidate();
1077     }
1078     protected void endColResize() {
1079         _colResizingIndex = -1;
1080     }
1081 
1082     /// return column index to resize if point is in column resize area in header row, -1 if outside resize area
1083     int isColumnResizingPoint(int x, int y) {
1084         if (_changedSize) updateCumulativeSizes();
1085         x -= _clientRect.left;
1086         y -= _clientRect.top;
1087         if (!_headerRows)
1088             return -1; // no header rows
1089         if (y >= _rowCumulativeHeights[_headerRows - 1])
1090             return -1; // not in header row
1091         // point is somewhere in header row
1092         int resizeRange = BACKEND_GUI ? 4.pointsToPixels : 1;
1093         if (x >= nonScrollAreaPixels.x)
1094             x += _scrollX;
1095         int col = colByAbsoluteX(x);
1096         int start = col > 0 ? _colCumulativeWidths[col - 1] : 0;
1097         int end = (col < _cols ? _colCumulativeWidths[col] : _colCumulativeWidths[$ - 1]) - 1;
1098         //Log.d("column range ", start, "..", end, " x=", x);
1099         if (x >= end - resizeRange / 2)
1100             return col; // resize this column
1101         if (x <= start + resizeRange / 2)
1102             return col - 1; // resize previous column
1103         return -1;
1104     }
1105 
1106     /// handle mouse wheel events
1107     override bool onMouseEvent(MouseEvent event) {
1108         if (visibility != Visibility.Visible)
1109             return false;
1110         int c, r; // col, row
1111         Rect rc;
1112         bool cellFound = false;
1113         bool normalCell = false;
1114         bool insideHeaderRow = false;
1115         bool insideHeaderCol = false;
1116         if (_colResizingIndex >= 0) {
1117             if (event.action == MouseAction.Move) {
1118                 // column resize is active
1119                 processColResize(event.x);
1120                 return true;
1121             }
1122             if (event.action == MouseAction.ButtonUp || event.action == MouseAction.Cancel) {
1123                 // stop column resizing
1124                 if (event.action == MouseAction.ButtonUp)
1125                     processColResize(event.x);
1126                 endColResize();
1127                 return true;
1128             }
1129         }
1130         // convert coordinates
1131         if (event.action == MouseAction.ButtonUp || event.action == MouseAction.ButtonDown || event.action == MouseAction.Move) {
1132             int x = event.x;
1133             int y = event.y;
1134             x -= _clientRect.left;
1135             y -= _clientRect.top;
1136             if (_headerRows)
1137                 insideHeaderRow = y < _rowCumulativeHeights[_headerRows - 1];
1138             if (_headerCols)
1139                 insideHeaderCol = y < _colCumulativeWidths[_headerCols - 1];
1140             cellFound = pointToCell(x, y, c, r, rc);
1141             normalCell = c >= _headerCols && r >= _headerRows;
1142         }
1143         if (event.action == MouseAction.ButtonDown && event.button == MouseButton.Left) {
1144             if (canFocus && !focused)
1145                 setFocus();
1146             int resizeCol = isColumnResizingPoint(event.x, event.y);
1147             if (resizeCol >= 0) {
1148                 // start column resizing
1149                 startColResize(resizeCol, event.x);
1150                 event.track(this);
1151                 return true;
1152             }
1153             if (cellFound && normalCell) {
1154                 if (c == _col && r == _row && event.doubleClick) {
1155                     activateCell(c, r);
1156                 } else if (_multiSelect && (event.flags & (MouseFlag.Shift | MouseFlag.Control)) != 0) {
1157                     multiSelectCell(c, r, (event.flags & MouseFlag.Shift) != 0);
1158                 } else {
1159                     selectCell(c, r);
1160                 }
1161             }
1162             return true;
1163         }
1164         if (event.action == MouseAction.ButtonUp && event.button == MouseButton.Left) {
1165             if (cellFound && !normalCell) {
1166                 if (headerCellClicked.assigned) {
1167                     headerCellClicked(this, c, r);
1168                 }
1169             }
1170         }
1171         if (event.action == MouseAction.Move && (event.flags & MouseFlag.LButton)) {
1172             // TODO: selection
1173             if (cellFound && normalCell) {
1174                 if (_multiSelect) {
1175                     multiSelectCell(c, r, true);
1176                 } else {
1177                     selectCell(c, r);
1178                 }
1179             }
1180             return true;
1181         }
1182         if (event.action == MouseAction.Wheel) {
1183             if (event.flags & MouseFlag.Shift)
1184                 scrollBy(-cast(int)event.wheelDelta, 0);
1185             else
1186                 scrollBy(0, -cast(int)event.wheelDelta);
1187             return true;
1188         }
1189         return super.onMouseEvent(event);
1190     }
1191 
1192     protected bool _fullColumnOnLeft = true;
1193     /// Extends scroll area to show full column at left when scrolled to rightmost column
1194     @property bool fullColumnOnLeft() { return _fullColumnOnLeft; }
1195     /// Extends scroll area to show full column at left when scrolled to rightmost column
1196     @property GridWidgetBase fullColumnOnLeft(bool newFullColumnOnLeft) {
1197         if (_fullColumnOnLeft != newFullColumnOnLeft) {
1198             _fullColumnOnLeft = newFullColumnOnLeft;
1199             updateScrollBars();
1200         }
1201         return this;
1202     }
1203 
1204     protected bool _fullRowOnTop = true;
1205     /// Extends scroll area to show full row at top when scrolled to end row
1206     @property bool fullRowOnTop() { return _fullColumnOnLeft; }
1207     /// Extends scroll area to show full row at top when scrolled to end row
1208     @property GridWidgetBase fullRowOnTop(bool newFullRowOnTop) {
1209         if (_fullRowOnTop != newFullRowOnTop) {
1210             _fullRowOnTop = newFullRowOnTop;
1211             updateScrollBars();
1212         }
1213         return this;
1214     }
1215 
1216     /// calculate scrollable area info
1217     protected void calcScrollableAreaPos() {
1218         // don't calc if client rect was not set
1219         if (_clientRect.size.x == 0 && _clientRect.size.y == 0) {
1220             _scrollX = 0;
1221             _scrollY = 0;
1222             return;
1223         }
1224 
1225         if (_scrollX < 0)
1226             _scrollX = 0;
1227         if (_scrollY < 0)
1228             _scrollY = 0;
1229         // calculate _fullScrollableArea, _visibleScrollableArea relative to clientRect
1230         Point nonscrollPixels = nonScrollAreaPixels;
1231         Point scrollPixels = scrollAreaPixels;
1232         Point fullPixels = fullAreaPixels;
1233         Point clientPixels = _clientRect.size;
1234         Point scrollableClient = clientPixels - nonscrollPixels;
1235         if (scrollableClient.x < 0)
1236             scrollableClient.x = 0;
1237         if (scrollableClient.y < 0)
1238             scrollableClient.y = 0;
1239         _fullScrollableArea = Rect(nonscrollPixels.x, nonscrollPixels.y, fullPixels.x, fullPixels.y);
1240         if (_fullScrollableArea.right < clientPixels.x)
1241             _fullScrollableArea.right = clientPixels.x;
1242         if (_fullScrollableArea.bottom < clientPixels.y)
1243             _fullScrollableArea.bottom = clientPixels.y;
1244 
1245         // extending scroll area if necessary
1246         if (_fullRowOnTop || _fullColumnOnLeft) {
1247             int maxscrollx = _fullScrollableArea.right - scrollableClient.x;
1248             int col = colByAbsoluteX(maxscrollx);
1249             int maxscrolly = _fullScrollableArea.bottom - scrollableClient.y;
1250             int row = rowByAbsoluteY(maxscrolly);
1251             Rect rc = cellRectNoScroll(col, row);
1252 
1253             // extend scroll area to show full column at left when scrolled to rightmost column
1254             if (_fullColumnOnLeft && maxscrollx >= nonscrollPixels.x && rc.left < maxscrollx)
1255                 _fullScrollableArea.right += rc.right - maxscrollx;
1256 
1257 
1258             // extend scroll area to show full row at top when scrolled to end row
1259             if (_fullRowOnTop && maxscrolly >= nonscrollPixels.y && rc.top < maxscrolly)
1260                 _fullScrollableArea.bottom += rc.bottom - maxscrolly;
1261         }
1262 
1263         // scrollable area
1264         Point scrollableClientAreaSize = scrollableClient; // size left for scrollable area
1265         int scrollx = nonscrollPixels.x + _scrollX;
1266         int scrolly = nonscrollPixels.y + _scrollY;
1267         _visibleScrollableArea = Rect(scrollx, scrolly, scrollx + scrollableClientAreaSize.x, scrolly + scrollableClientAreaSize.y);
1268     }
1269 
1270     protected int _maxScrollCol;
1271     protected int _maxScrollRow;
1272 
1273     override protected bool handleAction(const Action a) {
1274         calcScrollableAreaPos();
1275         int actionId = a.id;
1276         if (_rowSelect) {
1277             switch(actionId) with(GridActions)
1278             {
1279                 case Left:
1280                     actionId = GridActions.ScrollLeft;
1281                     break;
1282                 case Right:
1283                     actionId = GridActions.ScrollRight;
1284                     break;
1285                 //case LineBegin:
1286                 //    actionId = GridActions.ScrollPageLeft;
1287                 //    break;
1288                 //case LineEnd:
1289                 //    actionId = GridActions.ScrollPageRight;
1290                 //    break;
1291                 default:
1292                     break;
1293             }
1294         }
1295 
1296         int sc = scrollCol; // first fully visible column in scroll area
1297         int sr = scrollRow; // first fully visible row in scroll area
1298         switch (actionId) with(GridActions)
1299         {
1300             case ActivateCell:
1301                 if (cellActivated.assigned) {
1302                     cellActivated(this, col, row);
1303                     return true;
1304                 }
1305                 return false;
1306             case ScrollLeft:
1307                 scrollBy(-1, 0);
1308                 return true;
1309             case Left:
1310                 selectCell(_col - 1, _row);
1311                 return true;
1312             case SelectLeft:
1313                 if (_multiSelect) {
1314                     multiSelectCell(_lastSelectedCell.x - 1, _lastSelectedCell.y, true);
1315                 } else {
1316                     selectCell(_col - 1, _row);
1317                 }
1318                 return true;
1319             case ScrollRight:
1320                 scrollBy(1, 0);
1321                 return true;
1322             case Right:
1323                 selectCell(_col + 1, _row);
1324                 return true;
1325             case SelectRight:
1326                 if (_multiSelect) {
1327                     multiSelectCell(_lastSelectedCell.x + 1, _lastSelectedCell.y, true);
1328                 } else {
1329                     selectCell(_col + 1, _row);
1330                 }
1331                 return true;
1332             case ScrollUp:
1333                 scrollBy(0, -1);
1334                 return true;
1335             case Up:
1336                 selectCell(_col, _row - 1);
1337                 return true;
1338             case SelectUp:
1339                 if (_multiSelect) {
1340                     multiSelectCell(_lastSelectedCell.x, _lastSelectedCell.y - 1, true);
1341                 } else {
1342                     selectCell(_col, _row - 1);
1343                 }
1344                 return true;
1345             case ScrollDown:
1346                 if (lastScrollRow < _rows - 1)
1347                     scrollBy(0, 1);
1348                 return true;
1349             case Down:
1350                 selectCell(_col, _row + 1);
1351                 return true;
1352             case SelectDown:
1353                 if (_multiSelect) {
1354                     multiSelectCell(_lastSelectedCell.x, _lastSelectedCell.y + 1, true);
1355                 } else {
1356                     selectCell(_col, _row + 1);
1357                 }
1358                 return true;
1359             case ScrollPageLeft:
1360                 // scroll left cell by cell
1361                 while (scrollCol > nonScrollCols) {
1362                     scrollBy(-1, 0);
1363                     if (lastScrollCol <= sc)
1364                         break;
1365                 }
1366                 return true;
1367             case ScrollPageRight:
1368                 int prevCol = lastScrollCol;
1369                 while (scrollCol < prevCol) {
1370                     if (!scrollBy(1, 0))
1371                         break;
1372                 }
1373                 return true;
1374             case ScrollPageUp:
1375                 // scroll up line by line
1376                 while (scrollRow > nonScrollRows) {
1377                     scrollBy(0, -1);
1378                     if (lastScrollRow <= sr)
1379                         break;
1380                 }
1381                 return true;
1382             case ScrollPageDown:
1383                 int prevRow = lastScrollRow;
1384                 while (scrollRow < prevRow) {
1385                     if (!scrollBy(0, 1))
1386                         break;
1387                 }
1388                 return true;
1389             case SelectLineBegin:
1390                 if (!_multiSelect) goto case LineBegin;
1391                 if (_rowSelect) goto case SelectDocumentBegin;
1392                 if (sc > nonScrollCols && _col > sc) {
1393                     multiSelectCell(sc, _lastSelectedCell.y, true);
1394                 } else {
1395                     if (sc > nonScrollCols) {
1396                         _scrollX = 0;
1397                         updateScrollBars();
1398                         invalidate();
1399                     }
1400                     multiSelectCell(_headerCols, _lastSelectedCell.y, true);
1401                 }
1402                 return true;
1403             case LineBegin:
1404                 if (_rowSelect) goto case DocumentBegin;
1405                 if (sc > nonScrollCols && _col > sc) {
1406                     // move selection and don's scroll
1407                     selectCell(sc, _row);
1408                 } else {
1409                     // scroll
1410                     if (sc > nonScrollCols) {
1411                         _scrollX = 0;
1412                         updateScrollBars();
1413                         invalidate();
1414                     }
1415                     selectCell(_headerCols, _row);
1416                 }
1417                 return true;
1418             case SelectLineEnd:
1419                 if (!_multiSelect) goto case LineEnd;
1420                 if (_rowSelect) goto case SelectDocumentEnd;
1421                 if (_col < lastScrollCol) {
1422                     // move selection and don's scroll
1423                     multiSelectCell(lastScrollCol, _lastSelectedCell.y, true);
1424                 } else {
1425                     multiSelectCell(_cols - 1, _lastSelectedCell.y, true);
1426                 }
1427                 return true;
1428             case LineEnd:
1429                 if (_rowSelect) goto case DocumentEnd;
1430                 if (_col < lastScrollCol) {
1431                     // move selection and don's scroll
1432                     selectCell(lastScrollCol, _row);
1433                 } else {
1434                     selectCell(_cols - 1, _row);
1435                 }
1436                 return true;
1437             case SelectDocumentBegin:
1438                 if (!_multiSelect) goto case DocumentBegin;
1439                 if (_scrollY > 0) {
1440                     _scrollY = 0;
1441                     updateScrollBars();
1442                     invalidate();
1443                 }
1444                 multiSelectCell(_lastSelectedCell.x, _headerRows, true);
1445                 return true;
1446             case DocumentBegin:
1447                 if (_scrollY > 0) {
1448                     _scrollY = 0;
1449                     updateScrollBars();
1450                     invalidate();
1451                 }
1452                 selectCell(_col, _headerRows);
1453                 return true;
1454             case SelectDocumentEnd:
1455                 if (!_multiSelect) goto case DocumentEnd;
1456                 multiSelectCell(_lastSelectedCell.x, _rows - 1, true);
1457                 return true;
1458             case DocumentEnd:
1459                 selectCell(_col, _rows - 1);
1460                 return true;
1461             case SelectAll:
1462                 if (!_multiSelect) return true;
1463                 int endX = row;
1464                 if (_rowSelect) endX = 0;
1465                 for (int x = 0; x <= endX; ++x) {
1466                     for (int y = 0; y < rows; ++y) {
1467                         _selection.insert(Point(x, y));
1468                     }
1469                 }
1470                 invalidate();
1471                 return true;
1472             case SelectPageBegin:
1473                 if (!_multiSelect) goto case PageBegin;
1474                 if (scrollRow > nonScrollRows)
1475                     multiSelectCell(_lastSelectedCell.x, scrollRow, true);
1476                 else
1477                     multiSelectCell(_lastSelectedCell.x, _headerRows, true);
1478                 return true;
1479             case PageBegin:
1480                 if (scrollRow > nonScrollRows)
1481                     selectCell(_col, scrollRow);
1482                 else
1483                     selectCell(_col, _headerRows);
1484                 return true;
1485             case SelectPageEnd:
1486                 if (!_multiSelect) goto case PageEnd;
1487                 multiSelectCell(_lastSelectedCell.x, lastScrollRow, true);
1488                 return true;
1489             case PageEnd:
1490                 selectCell(_col, lastScrollRow);
1491                 return true;
1492             case SelectPageUp:
1493                 if (_row > sr) {
1494                     // not at top scrollable cell
1495                     multiSelectCell(_lastSelectedCell.x, sr, true);
1496                 } else {
1497                     // at top of scrollable area
1498                     if (scrollRow > nonScrollRows) {
1499                         // scroll up line by line
1500                         int prevRow = _row;
1501                         for (int i = prevRow - 1; i >= _headerRows; i--) {
1502                             multiSelectCell(_lastSelectedCell.x, i, true);
1503                             if (lastScrollRow <= prevRow)
1504                                 break;
1505                         }
1506                     } else {
1507                         // scrolled to top - move upper cell
1508                         multiSelectCell(_lastSelectedCell.x, _headerRows, true);
1509                     }
1510                 }
1511                 return true;
1512             case PageUp:
1513                 if (_row > sr) {
1514                     // not at top scrollable cell
1515                     selectCell(_col, sr);
1516                 } else {
1517                     // at top of scrollable area
1518                     if (scrollRow > nonScrollRows) {
1519                         // scroll up line by line
1520                         int prevRow = _row;
1521                         for (int i = prevRow - 1; i >= _headerRows; i--) {
1522                             selectCell(_col, i);
1523                             if (lastScrollRow <= prevRow)
1524                                 break;
1525                         }
1526                     } else {
1527                         // scrolled to top - move upper cell
1528                         selectCell(_col, _headerRows);
1529                     }
1530                 }
1531                 return true;
1532             case SelectPageDown:
1533                 if (_row < _rows - 1) {
1534                     int lr = lastScrollRow;
1535                     if (_row < lr) {
1536                         // not at bottom scrollable cell
1537                         multiSelectCell(_lastSelectedCell.x, lr, true);
1538                     } else {
1539                         // scroll down
1540                         int prevRow = _row;
1541                         for (int i = prevRow + 1; i < _rows; i++) {
1542                             multiSelectCell(_lastSelectedCell.x, i, true);
1543                             calcScrollableAreaPos();
1544                             if (scrollRow >= prevRow)
1545                                 break;
1546                         }
1547                     }
1548                 }
1549                 return true;
1550             case PageDown:
1551                 if (_row < _rows - 1) {
1552                     int lr = lastScrollRow;
1553                     if (_row < lr) {
1554                         // not at bottom scrollable cell
1555                         selectCell(_col, lr);
1556                     } else {
1557                         // scroll down
1558                         int prevRow = _row;
1559                         for (int i = prevRow + 1; i < _rows; i++) {
1560                             selectCell(_col, i);
1561                             calcScrollableAreaPos();
1562                             if (scrollRow >= prevRow)
1563                                 break;
1564                         }
1565                     }
1566                 }
1567                 return true;
1568             default:
1569                 return super.handleAction(a);
1570         }
1571     }
1572 
1573     /// calculate full content size in pixels
1574     override Point fullContentSize() {
1575         Point sz;
1576         for (int i = 0; i < _cols; i++)
1577             sz.x += _colWidths[i];
1578         for (int i = 0; i < _rows; i++)
1579             sz.y += _rowHeights[i];
1580         return sz;
1581     }
1582 
1583     protected int _minVisibleCols = 2;
1584     protected int _minVisibleRows = 2;
1585 
1586     /// returns number of columns from 0 that are taken to measure minimum visible width
1587     @property int minVisibleCols() {
1588         return _minVisibleCols;
1589     }
1590 
1591     /// sets number of columns from 0 that are taken to measure minimum visible width
1592     @property void minVisibleCols(int newMinVisibleCols) {
1593         _minVisibleCols = newMinVisibleCols;
1594         requestLayout();
1595     }
1596 
1597     /// returns number of rows from 0 that are taken to measure minimum visible height
1598     @property int minVisibleRows() {
1599         return _minVisibleRows;
1600     }
1601 
1602     /// sets number of rows from 0 that are taken to measure minimum visible height, if there are too little rows last row height is multiplied
1603     @property void minVisibleRows(int newMinVisibleRows) {
1604         _minVisibleRows = newMinVisibleRows;
1605         requestLayout();
1606     }
1607 
1608 
1609     /// calculate minimum size of widget
1610     override Point minimumVisibleContentSize() {
1611         Point sz;
1612         if (_cols == 0 || _rows == 0) {
1613              sz.x = 100;
1614              sz.y = 100;
1615              return sz;
1616         }
1617 
1618         // width:
1619         int firstVisibleCol = (showRowHeaders) ? 0 : _headerCols;
1620         for (int i = firstVisibleCol ; i < min(_cols, _minVisibleCols + firstVisibleCol) ; i++)
1621             sz.x += _colWidths[i];
1622 
1623         // height
1624         int firstVisibleRow = (showColHeaders) ? 0 : _headerRows;
1625         for (int i = firstVisibleRow ; i < min(_rows, _minVisibleRows + firstVisibleRow) ; i++)
1626             sz.y += _rowHeights[i];
1627 
1628         if (_rows<_minVisibleRows)
1629             sz.y += (_minVisibleRows - _rows) * _rowHeights[_rows-1];
1630 
1631         return sz;
1632     }
1633 
1634     override protected void drawClient(DrawBuf buf) {
1635         if (!_cols || !_rows)
1636             return; // no cells
1637         auto saver = ClipRectSaver(buf, _clientRect, 0);
1638 
1639         int nscols = nonScrollCols;
1640         int nsrows = nonScrollRows;
1641         Point nspixels = nonScrollAreaPixels;
1642         int maxVisibleCol = colByAbsoluteX(_clientRect.width + _scrollX);
1643         int maxVisibleRow = rowByAbsoluteY(_clientRect.height + _scrollY);
1644         for (int phase = 0; phase < 2; phase++) { // phase0 == background, phase1 == foreground
1645             for (int y = 0; y <= maxVisibleRow; y++) {
1646                 if (!rowVisible(y))
1647                     continue;
1648                 for (int x = 0; x <= maxVisibleCol; x++) {
1649                     if (!colVisible(x))
1650                         continue;
1651                     Rect cellRect = cellRectScroll(x, y);
1652                     if (WIDGET_STYLE_CONSOLE && phase == 1) {
1653                         cellRect.right--;
1654                     }
1655                     Rect clippedCellRect = cellRect;
1656                     if (x >= nscols && cellRect.left < nspixels.x)
1657                         clippedCellRect.left = nspixels.x; // clip scrolled left
1658                     if (y >= nsrows && cellRect.top < nspixels.y)
1659                         clippedCellRect.top = nspixels.y; // clip scrolled left
1660                     if (clippedCellRect.empty)
1661                         continue; // completely clipped out
1662 
1663                     cellRect.moveBy(_clientRect.left, _clientRect.top);
1664                     clippedCellRect.moveBy(_clientRect.left, _clientRect.top);
1665 
1666                     auto cellSaver = ClipRectSaver(buf, clippedCellRect, 0);
1667                     bool isHeader = x < _headerCols || y < _headerRows;
1668                     if (phase == 0) {
1669                         if (isHeader)
1670                             drawHeaderCellBackground(buf, cellRect, x - _headerCols, y - _headerRows);
1671                         else
1672                             drawCellBackground(buf, cellRect, x - _headerCols, y - _headerRows);
1673                     } else {
1674                         if (isHeader)
1675                             drawHeaderCell(buf, cellRect, x - _headerCols, y - _headerRows);
1676                         else
1677                             drawCell(buf, cellRect, x - _headerCols, y - _headerRows);
1678                     }
1679                 }
1680             }
1681         }
1682     }
1683 
1684     /// draw data cell content
1685     protected void drawCell(DrawBuf buf, Rect rc, int col, int row) {
1686         // override it
1687     }
1688 
1689     /// draw header cell content
1690     protected void drawHeaderCell(DrawBuf buf, Rect rc, int col, int row) {
1691         // override it
1692     }
1693 
1694     /// draw data cell background
1695     protected void drawCellBackground(DrawBuf buf, Rect rc, int col, int row) {
1696         // override it
1697     }
1698 
1699     /// draw header cell background
1700     protected void drawHeaderCellBackground(DrawBuf buf, Rect rc, int col, int row) {
1701         // override it
1702     }
1703 
1704     protected Point measureCell(int x, int y) {
1705         // override it!
1706         return Point(WIDGET_STYLE_CONSOLE ? 5 : 80, WIDGET_STYLE_CONSOLE ? 1 : 20);
1707     }
1708 
1709     protected int measureColWidth(int x) {
1710         if (!showRowHeaders && x < _headerCols)
1711             return 0;
1712         int m = 0;
1713         for (int i = 0; i < _rows; i++) {
1714             Point sz = measureCell(x - _headerCols, i - _headerRows);
1715             if (m < sz.x)
1716                 m = sz.x;
1717         }
1718         //Log.d("measureColWidth ", x, " = ", m);
1719         static if (BACKEND_GUI) {
1720             if (m < 10)
1721                 m = 10; // TODO: use min size
1722         } else {
1723             if (m < 1)
1724                 m = 1; // TODO: use min size
1725         }
1726         return m;
1727     }
1728 
1729     protected int measureRowHeight(int y) {
1730         int m = 0;
1731         for (int i = 0; i < _cols; i++) {
1732             Point sz = measureCell(i - _headerCols, y - _headerRows);
1733             if (m < sz.y)
1734                 m = sz.y;
1735         }
1736         static if (BACKEND_GUI) {
1737             if (m < 12)
1738                 m = 12; // TODO: use min size
1739         }
1740         return m;
1741     }
1742 
1743     void autoFitColumnWidth(int i) {
1744         _colWidths[i] = (i < _headerCols && !_showRowHeaders) ? 0 : measureColWidth(i) + (WIDGET_STYLE_CONSOLE ? 1 : 3.pointsToPixels);
1745         _changedSize = true;
1746     }
1747 
1748     /// extend specified column width to fit client area if grid width
1749     void fillColumnWidth(int colIndex) {
1750         int w = _clientRect.width;
1751         int totalw = 0;
1752         for (int i = 0; i < _cols; i++)
1753             totalw += _colWidths[i];
1754         if (w > totalw)
1755             _colWidths[colIndex + _headerCols] += w - totalw;
1756         _changedSize = true;
1757         invalidate();
1758     }
1759 
1760     void autoFitColumnWidths() {
1761         for (int i = 0; i < _cols; i++)
1762             autoFitColumnWidth(i);
1763         _changedSize = true;
1764         invalidate();
1765     }
1766 
1767     void autoFitRowHeight(int i) {
1768         _rowHeights[i] = (i < _headerRows && !_showColHeaders) ? 0 : measureRowHeight(i) + (WIDGET_STYLE_CONSOLE ? 0 : 2);
1769         _changedSize = true;
1770     }
1771 
1772     void autoFitRowHeights() {
1773         for (int i = 0; i < _rows; i++)
1774             autoFitRowHeight(i);
1775     }
1776 
1777     void autoFit() {
1778         autoFitColumnWidths();
1779         autoFitRowHeights();
1780         updateCumulativeSizes();
1781     }
1782 
1783     this(string ID = null, ScrollBarMode hscrollbarMode = ScrollBarMode.Visible, ScrollBarMode vscrollbarMode = ScrollBarMode.Visible) {
1784         super(ID, hscrollbarMode, vscrollbarMode);
1785         _headerCols = 1;
1786         _headerRows = 1;
1787         _selection = new RedBlackTree!Point();
1788         _defRowHeight = WIDGET_STYLE_CONSOLE ? 1 : pointsToPixels(16);
1789         _defColumnWidth = WIDGET_STYLE_CONSOLE ? 7 : 100;
1790 
1791         _showColHeaders = true;
1792         _showRowHeaders = true;
1793         acceleratorMap.add( [
1794             new Action(GridActions.Up, KeyCode.UP, 0),
1795             new Action(GridActions.Down, KeyCode.DOWN, 0),
1796             new Action(GridActions.Left, KeyCode.LEFT, 0),
1797             new Action(GridActions.Right, KeyCode.RIGHT, 0),
1798             new Action(GridActions.LineBegin, KeyCode.HOME, 0),
1799             new Action(GridActions.LineEnd, KeyCode.END, 0),
1800             new Action(GridActions.PageUp, KeyCode.PAGEUP, 0),
1801             new Action(GridActions.PageDown, KeyCode.PAGEDOWN, 0),
1802             new Action(GridActions.PageBegin, KeyCode.PAGEUP, KeyFlag.Control),
1803             new Action(GridActions.PageEnd, KeyCode.PAGEDOWN, KeyFlag.Control),
1804             new Action(GridActions.DocumentBegin, KeyCode.HOME, KeyFlag.Control),
1805             new Action(GridActions.DocumentEnd, KeyCode.END, KeyFlag.Control),
1806             new Action(GridActions.SelectUp, KeyCode.UP, KeyFlag.Shift),
1807             new Action(GridActions.SelectDown, KeyCode.DOWN, KeyFlag.Shift),
1808             new Action(GridActions.SelectLeft, KeyCode.LEFT, KeyFlag.Shift),
1809             new Action(GridActions.SelectRight, KeyCode.RIGHT, KeyFlag.Shift),
1810             new Action(GridActions.SelectLineBegin, KeyCode.HOME, KeyFlag.Shift),
1811             new Action(GridActions.SelectLineEnd, KeyCode.END, KeyFlag.Shift),
1812             new Action(GridActions.SelectPageUp, KeyCode.PAGEUP, KeyFlag.Shift),
1813             new Action(GridActions.SelectPageDown, KeyCode.PAGEDOWN, KeyFlag.Shift),
1814             new Action(GridActions.SelectPageBegin, KeyCode.PAGEUP, KeyFlag.Control | KeyFlag.Shift),
1815             new Action(GridActions.SelectPageEnd, KeyCode.PAGEDOWN, KeyFlag.Control | KeyFlag.Shift),
1816             new Action(GridActions.SelectDocumentBegin, KeyCode.HOME, KeyFlag.Control | KeyFlag.Shift),
1817             new Action(GridActions.SelectDocumentEnd, KeyCode.END, KeyFlag.Control | KeyFlag.Shift),
1818             new Action(GridActions.SelectAll, KeyCode.KEY_A, KeyFlag.Control),
1819             new Action(GridActions.ActivateCell, KeyCode.RETURN, 0),
1820         ]);
1821         focusable = true;
1822         resize(1,1);
1823     }
1824 }
1825 
1826 class StringGridWidgetBase : GridWidgetBase {
1827     this(string ID = null) {
1828         super(ID);
1829     }
1830     /// get cell text
1831     abstract UIString cellText(int col, int row);
1832     /// set cell text
1833     abstract StringGridWidgetBase setCellText(int col, int row, UIString text);
1834     /// returns row header title
1835     abstract UIString rowTitle(int row);
1836     /// set row header title
1837     abstract StringGridWidgetBase setRowTitle(int row, UIString title);
1838     /// returns row header title
1839     abstract UIString colTitle(int col);
1840     /// set col header title
1841     abstract StringGridWidgetBase setColTitle(int col, UIString title);
1842 
1843     ///// selected column
1844     //@property override int col() { return _col - _headerCols; }
1845     ///// selected row
1846     //@property override int row() { return _row - _headerRows; }
1847     ///// column count
1848     //@property override int cols() { return _cols - _headerCols; }
1849     ///// set column count
1850     //@property override GridWidgetBase cols(int c) { resize(c, rows); return this; }
1851     ///// row count
1852     //@property override int rows() { return _rows - _headerRows; }
1853     ///// set row count
1854     //@property override GridWidgetBase rows(int r) { resize(cols, r); return this; }
1855     //
1856     ///// set new size
1857     //override void resize(int cols, int rows) {
1858     //    super.resize(cols + _headerCols, rows + _headerRows);
1859     //}
1860 
1861 }
1862 
1863 /**
1864  * Grid view with string data shown. All rows are of the same height
1865  */
1866 class StringGridWidget : StringGridWidgetBase {
1867 
1868     protected UIString[][] _data;
1869     protected UIString[] _rowTitles;
1870     protected UIString[] _colTitles;
1871 
1872     /// empty parameter list constructor - for usage by factory
1873     this() {
1874         this(null);
1875     }
1876     /// create with ID parameter
1877     this(string ID) {
1878         super(ID);
1879         styleId = STYLE_STRING_GRID;
1880         onThemeChanged();
1881     }
1882 
1883     /// get cell text
1884     override UIString cellText(int col, int row) {
1885         if (col >= 0 && col < cols && row >= 0 && row < rows)
1886             return _data[row][col];
1887         return UIString.fromRaw("");
1888     }
1889 
1890     /// set cell text
1891     override StringGridWidgetBase setCellText(int col, int row, UIString text) {
1892         if (col >= 0 && col < cols && row >= 0 && row < rows)
1893             _data[row][col] = text;
1894         return this;
1895     }
1896 
1897     deprecated("This overload is deprecated, use the `UIString` overload instead")
1898     StringGridWidgetBase setCellText(int col, int row, dstring text) {
1899         if (col >= 0 && col < cols && row >= 0 && row < rows)
1900             _data[row][col] = text;
1901         return this;
1902     }
1903 
1904     /// set new size
1905     override void resize(int c, int r) {
1906         if (c == cols && r == rows)
1907             return;
1908         int oldcols = cols;
1909         int oldrows = rows;
1910         super.resize(c, r);
1911         _data.length = r;
1912         for (int y = 0; y < r; y++)
1913             _data[y].length = c;
1914         _colTitles.length = c;
1915         _rowTitles.length = r;
1916     }
1917 
1918     /// returns row header title
1919     override UIString rowTitle(int row) {
1920         return _rowTitles[row];
1921     }
1922     /// set row header title
1923     override StringGridWidgetBase setRowTitle(int row, UIString title) {
1924         _rowTitles[row] = title;
1925         return this;
1926     }
1927 
1928     deprecated("This overload is deprecated, use the `UIString` overload instead")
1929     StringGridWidgetBase setRowTitle(int row, dstring title) {
1930         _rowTitles[row] = title;
1931         return this;
1932     }
1933 
1934     /// returns row header title
1935     override UIString colTitle(int col) {
1936         return _colTitles[col];
1937     }
1938 
1939     /// set col header title
1940     override StringGridWidgetBase setColTitle(int col, UIString title) {
1941         _colTitles[col] = title;
1942         return this;
1943     }
1944 
1945     deprecated("This overload is deprecated, use the `UIString` overload instead")
1946     StringGridWidgetBase setColTitle(int col, dstring title) {
1947         _colTitles[col] = title;
1948         return this;
1949     }
1950 
1951     protected override Point measureCell(int x, int y) {
1952         if (_customCellAdapter && _customCellAdapter.isCustomCell(x, y)) {
1953             return _customCellAdapter.measureCell(x, y);
1954         }
1955         //Log.d("measureCell ", x, ", ", y);
1956         FontRef fnt = font;
1957         dstring txt;
1958         if (x >= 0 && y >= 0)
1959             txt = cellText(x, y);
1960         else if (y < 0 && x >= 0)
1961             txt = colTitle(x);
1962         else if (y >= 0 && x < 0)
1963             txt = rowTitle(y);
1964         Point sz = fnt.textSize(txt);
1965         if (sz.y < fnt.height)
1966             sz.y = fnt.height;
1967         return sz;
1968     }
1969 
1970 
1971     /// draw cell content
1972     protected override void drawCell(DrawBuf buf, Rect rc, int col, int row) {
1973         if (_customCellAdapter && _customCellAdapter.isCustomCell(col, row)) {
1974             return _customCellAdapter.drawCell(buf, rc, col, row);
1975         }
1976         if (BACKEND_GUI)
1977             rc.shrink(2, 1);
1978         else
1979             rc.right--;
1980         FontRef fnt = font;
1981         dstring txt = cellText(col, row);
1982         Point sz = fnt.textSize(txt);
1983         Align ha = Align.Left;
1984         //if (sz.y < rc.height)
1985         //    applyAlign(rc, sz, ha, Align.VCenter);
1986         int offset = WIDGET_STYLE_CONSOLE ? 0 : 1;
1987         fnt.drawText(buf, rc.left + offset, rc.top + offset, txt, textColor);
1988     }
1989 
1990     /// draw cell content
1991     protected override void drawHeaderCell(DrawBuf buf, Rect rc, int col, int row) {
1992         if (BACKEND_GUI)
1993             rc.shrink(2, 1);
1994         else
1995             rc.right--;
1996         FontRef fnt = font;
1997         dstring txt;
1998         if (row < 0 && col >= 0)
1999             txt = colTitle(col);
2000         else if (row >= 0 && col < 0)
2001             txt = rowTitle(row);
2002         if (!txt.length)
2003             return;
2004         Point sz = fnt.textSize(txt);
2005         Align ha = Align.Left;
2006         if (col < 0)
2007             ha = Align.Right;
2008         //if (row < 0)
2009         //    ha = Align.HCenter;
2010         applyAlign(rc, sz, ha, Align.VCenter);
2011         int offset = WIDGET_STYLE_CONSOLE ? 0 : 1;
2012         uint cl = textColor;
2013         cl = style.customColor("grid_cell_text_color_header", cl);
2014         fnt.drawText(buf, rc.left + offset, rc.top + offset, txt, cl);
2015     }
2016 
2017     /// draw cell background
2018     protected override void drawHeaderCellBackground(DrawBuf buf, Rect rc, int c, int r) {
2019         bool selectedCol = (c == col) && !_rowSelect;
2020         bool selectedRow = r == row;
2021         bool selectedCell = selectedCol && selectedRow;
2022         if (_rowSelect && selectedRow)
2023             selectedCell = true;
2024         if (!selectedCell && _multiSelect) {
2025             selectedCell = Point(c, r) in _selection || (_rowSelect && Point(0, r) in _selection);
2026         }
2027         // draw header cell background
2028         DrawableRef dw = c < 0 ? _cellRowHeaderBackgroundDrawable : _cellHeaderBackgroundDrawable;
2029         uint cl = _cellHeaderBackgroundColor;
2030         if (c >= 0 || r >= 0) {
2031             if (c < 0 && selectedRow) {
2032                 cl = _cellHeaderSelectedBackgroundColor;
2033                 dw = _cellRowHeaderSelectedBackgroundDrawable;
2034             } else if (r < 0 && selectedCol) {
2035                 cl = _cellHeaderSelectedBackgroundColor;
2036                 dw = _cellHeaderSelectedBackgroundDrawable;
2037             }
2038         }
2039         if (!dw.isNull)
2040             dw.drawTo(buf, rc);
2041         else
2042             buf.fillRect(rc, cl);
2043         static if (BACKEND_GUI) {
2044             uint borderColor = _cellHeaderBorderColor;
2045             buf.drawLine(Point(rc.right - 1, rc.bottom), Point(rc.right - 1, rc.top), _cellHeaderBorderColor); // vertical
2046             buf.drawLine(Point(rc.left, rc.bottom - 1), Point(rc.right - 1, rc.bottom - 1), _cellHeaderBorderColor); // horizontal
2047         }
2048     }
2049 
2050     /// draw cell background
2051     protected override void drawCellBackground(DrawBuf buf, Rect rc, int c, int r) {
2052         bool selectedCol = c == col;
2053         bool selectedRow = r == row;
2054         bool selectedCell = selectedCol && selectedRow;
2055         if (_rowSelect && selectedRow)
2056             selectedCell = true;
2057         if (!selectedCell && _multiSelect) {
2058             selectedCell = Point(c, r) in _selection || (_rowSelect && Point(0, r) in _selection);
2059         }
2060         uint borderColor = _cellBorderColor;
2061         if (c < fixedCols || r < fixedRows) {
2062             // fixed cell background
2063             buf.fillRect(rc, _fixedCellBackgroundColor);
2064             borderColor = _fixedCellBorderColor;
2065         }
2066         static if (BACKEND_GUI) {
2067             buf.drawLine(Point(rc.left, rc.bottom + 1), Point(rc.left, rc.top), borderColor); // vertical
2068             buf.drawLine(Point(rc.left, rc.bottom - 1), Point(rc.right - 1, rc.bottom - 1), borderColor); // horizontal
2069         }
2070         if (selectedCell) {
2071             static if (BACKEND_GUI) {
2072                 if (_rowSelect)
2073                     buf.drawFrame(rc, _selectionColorRowSelect, Rect(0,1,0,1), _cellBorderColor);
2074                 else
2075                     buf.drawFrame(rc, _selectionColor, Rect(1,1,1,1), _cellBorderColor);
2076             } else {
2077                 if (_rowSelect)
2078                     buf.fillRect(rc, _selectionColorRowSelect);
2079                 else
2080                     buf.fillRect(rc, _selectionColor);
2081             }
2082         }
2083     }
2084 
2085 
2086     /// handle theme change: e.g. reload some themed resources
2087     override void onThemeChanged() {
2088         super.onThemeChanged();
2089         _selectionColor = style.customColor("grid_selection_color", 0x804040FF);
2090         _selectionColorRowSelect = style.customColor("grid_selection_color_row", 0xC0A0B0FF);
2091         _fixedCellBackgroundColor = style.customColor("grid_cell_background_fixed", 0xC0E0E0E0);
2092         _cellBorderColor = style.customColor("grid_cell_border_color", 0xC0C0C0C0);
2093         _fixedCellBorderColor = style.customColor("grid_cell_border_color_fixed", _cellBorderColor);
2094         _cellHeaderBorderColor = style.customColor("grid_cell_border_color_header", 0xC0202020);
2095         _cellHeaderBackgroundColor = style.customColor("grid_cell_background_header", 0xC0909090);
2096         _cellHeaderSelectedBackgroundColor = style.customColor("grid_cell_background_header_selected", 0x80FFC040);
2097         _cellHeaderBackgroundDrawable = style.customDrawable("grid_cell_background_header");
2098         _cellHeaderSelectedBackgroundDrawable = style.customDrawable("grid_cell_background_header_selected");
2099         _cellRowHeaderBackgroundDrawable = style.customDrawable("grid_cell_background_row_header");
2100         _cellRowHeaderSelectedBackgroundDrawable = style.customDrawable("grid_cell_background_row_header_selected");
2101     }
2102 }
2103 
2104 //import dlangui.widgets.metadata;
2105 //mixin(registerWidgets!(StringGridWidget)());