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