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