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)());