1 /++ 2 This module contains most of the functions you'll need. 3 All the 'base' classes, like QWidget are defined in this. 4 +/ 5 module qui.qui; 6 7 import std.datetime.stopwatch; 8 import utils.misc; 9 import utils.ds; 10 import std.conv : to; 11 import qui.termwrap; 12 13 import qui.utils; 14 15 /// the default foreground color 16 enum Color DEFAULT_FG = Color.white; 17 /// the default background color 18 enum Color DEFAULT_BG = Color.black; 19 /// background color of overflowing layouts 20 enum Color DEFAULT_OVERFLOW_BG = Color.red; 21 /// default active widget cycling key (tab) 22 enum dchar WIDGET_CYCLE_KEY = '\t'; 23 24 /// Available colors are in this enum 25 public alias Color = qui.termwrap.Color; 26 /// Availabe Keys (keyboard) for input 27 public alias Key = qui.termwrap.Event.Keyboard.Key; 28 /// Mouse Event 29 public alias MouseEvent = Event.Mouse; 30 /// Keyboard Event 31 public alias KeyboardEvent = Event.Keyboard; 32 33 /// mouseEvent function. Return true if the event should be dropped 34 alias MouseEventFuction = bool delegate(QWidget, MouseEvent); 35 ///keyboardEvent function. Return true if the event should be dropped 36 alias KeyboardEventFunction = bool delegate(QWidget, KeyboardEvent, bool); 37 /// resizeEvent function. Return true if the event should be dropped 38 alias ResizeEventFunction = bool delegate(QWidget); 39 /// scrollEvent function. Return true if the event should be dropped 40 alias scrollEventFunction = bool delegate(QWidget); 41 /// activateEvent function. Return true if the event should be dropped 42 alias ActivateEventFunction = bool delegate(QWidget, bool); 43 /// TimerEvent function. Return true if the event should be dropped 44 alias TimerEventFunction = bool delegate(QWidget, uint); 45 /// Init function. Return true if the event should be dropped 46 alias InitFunction = bool delegate(QWidget); 47 /// UpdateEvent function. Return true if the event should be dropped 48 alias UpdateEventFunction = bool delegate(QWidget); 49 50 /// mask of events subscribed 51 enum EventMask : uint{ 52 /// mouse clicks/presses. This value matches `MouseEvent.State.Click` 53 MousePress = 1, 54 /// mouse releases. This value matches `MouseEvent.State.Release` 55 MouseRelease = 1 << 1, 56 /// mouse move/hover. This value matches `MouseEvent.State.Hover` 57 MouseHover = 1 << 2, 58 /// key presses. This value matches `KeyboardEvent.State.Pressed` 59 KeyboardPress = 1 << 3, 60 /// key releases. This value matches `KeyboardEvent.State.Released` 61 KeyboardRelease = 1 << 4, 62 /// widget scrolling. 63 Scroll = 1 << 5, 64 /// widget resize. 65 Resize = 1 << 6, 66 /// widget activated/deactivated. 67 Activate = 1 << 7, 68 /// timer 69 Timer = 1 << 8, 70 /// initialize 71 Initialize = 1 << 9, 72 /// draw itself. 73 Update = 1 << 10, 74 /// All mouse events 75 MouseAll = MousePress | MouseRelease | MouseHover, 76 /// all keyboard events, this does **NOT** include KeyboardWidgetCycleKey 77 KeyboardAll = KeyboardPress | KeyboardRelease, 78 /// All keyboard and mouse events 79 InputAll = MouseAll | KeyboardAll, 80 //All = uint.max, /// all events (all bits=1). ** DO NOT USE THIS ** 81 } 82 83 /// A cell on terminal 84 struct Cell{ 85 /// character 86 dchar c; 87 /// foreground and background colors 88 Color fg, bg; 89 /// if properties of this are same as other 90 bool propSame(Cell B){ 91 return this.fg == B.fg && this.bg == B.fg; 92 } 93 } 94 95 /// Display buffer 96 struct Viewport{ 97 private: 98 Cell[] _buffer; 99 uint _seekX, _seekY; 100 uint _width, _height; 101 uint _actualWidth; 102 // these are subtracted, only when seek is set, not after 103 uint _offsetX, _offsetY; 104 105 /// reset 106 void _reset(){ 107 _buffer = []; 108 _seekX = 0; 109 _seekY = 0; 110 _width = 0; 111 _height = 0; 112 _actualWidth = 0; 113 _offsetX = 0; 114 _offsetY = 0; 115 } 116 117 /// seek position in _buffer calculated from _seekX & _seekY 118 @property int _seek(){ 119 return (_seekX - _offsetX) + ((_seekY - _offsetY)*_actualWidth); 120 } 121 /// set another DisplayBuffer so that it is a rectangular slice from this 122 /// 123 /// Returns: true if done, false if not 124 void _getSlice(Viewport* sub, int x, int y, uint width, uint height){ 125 sub._reset(); 126 sub._actualWidth = _actualWidth; 127 if (width + height == 0) 128 return; 129 x -= _offsetX; 130 y -= _offsetY; 131 if (x > cast(int)_width || y > cast(int)_height || 132 x + width <= 0 ||y + height <= 0) 133 return; 134 if (x < 0){ 135 sub._offsetX = -x; 136 x = 0; 137 } 138 if (y < 0){ 139 sub._offsetY = -y; 140 y = 0; 141 } 142 if (width + x > _width) 143 width = _width - x; 144 if (height + y > _height) 145 height = _height - y; 146 immutable uint buffStart = x + (y*_actualWidth), 147 buffEnd = buffStart + ((height-1)*_actualWidth) + width; 148 if (buffEnd > _buffer.length || buffStart >= buffEnd) 149 return; 150 sub._width = width; 151 sub._height = height; 152 sub._buffer = _buffer[buffStart .. buffEnd]; 153 } 154 public: 155 /// if x and y are at a position where writing can happen 156 bool isWritable(uint x, uint y){ 157 return x >= _offsetX && y >= _offsetY && 158 x < _width + _offsetX && y < _height + _offsetY; 159 } 160 /// move to a position. if x > width, moved to x=0 of next row 161 void moveTo(uint x, uint y){ 162 _seekX = x; 163 _seekY = y; 164 if (_seekY >= _width){ 165 _seekX = 0; 166 _seekY ++; 167 } 168 } 169 /// Writes a character at current position and move ahead 170 /// 171 /// Returns: false if outside writing area 172 bool write(dchar c, Color fg, Color bg){ 173 if (_seekX < _offsetX || _seekY < _offsetY){ 174 _seekX ++; 175 if (_seekY >= _width){ 176 _seekX = 0; 177 _seekY ++; 178 } 179 return false; 180 } 181 if (_seekX >= _width && _seekY >= _height) 182 return false; 183 if (_buffer.length > _seek) 184 _buffer[_seek] = Cell(c, fg, bg); 185 _seekX ++; 186 if (_seekX >= _width){ 187 _seekX = 0; 188 _seekY ++; 189 } 190 return true; 191 } 192 /// Writes a string. 193 /// 194 /// Returns: number of characters written 195 uint write(dstring s, Color fg, Color bg){ 196 uint r; 197 foreach (c; s){ 198 if (write(c, fg, bg)) 199 r ++; 200 else 201 return r; 202 } 203 return r; 204 } 205 /// Fills line, starting from current coordinates, 206 /// with maximum `max` number of chars, if `max>0` 207 /// 208 /// Returns: number of columns (characters) written 209 uint fillLine(dchar c, Color fg, Color bg, uint max=0){ 210 uint r = 0; 211 const uint currentY = _seekY; 212 bool status = true; 213 while (status && (max == 0 || r < max) && _seekY == currentY){ 214 status = write(c, fg, bg); 215 r ++; 216 } 217 return r; 218 } 219 } 220 221 /// Base class for all widgets, including layouts and QTerminal 222 /// 223 /// Use this as parent-class for new widgets 224 abstract class QWidget{ 225 private: 226 /// stores the position of this widget, relative to it's parent widget's position 227 uint _posX, _posY; 228 /// width of widget 229 uint _width; 230 /// height of widget 231 uint _height; 232 /// scroll 233 uint _scrollX, _scrollY; 234 /// viewport 235 Viewport _view; 236 /// stores if this widget is the active widget 237 bool _isActive = false; 238 /// whether this widget is requesting update 239 bool _requestingUpdate = true; 240 /// Whether to call resize before next update 241 bool _requestingResize = false; 242 /// the parent widget 243 QWidget _parent = null; 244 /// if it can make parent change scrolling 245 bool _canReqScroll = false; 246 /// if this widget is itself a scrollable container 247 bool _isScrollableContainer = false; 248 /// Events this widget is subscribed to, see `EventMask` 249 uint _eventSub = 0; 250 /// whether this widget should be drawn or not. 251 bool _show = true; 252 /// specifies ratio of height or width (depending on parent layout type) 253 uint _sizeRatio = 1; 254 255 /// custom onInit event 256 InitFunction _customInitEvent; 257 /// custom mouse event 258 MouseEventFuction _customMouseEvent; 259 /// custom keyboard event 260 KeyboardEventFunction _customKeyboardEvent; 261 /// custom resize event 262 ResizeEventFunction _customResizeEvent; 263 /// custom rescroll event 264 scrollEventFunction _customScrollEvent; 265 /// custom onActivate event, 266 ActivateEventFunction _customActivateEvent; 267 /// custom onTimer event 268 TimerEventFunction _customTimerEvent; 269 /// custom upedateEvent 270 UpdateEventFunction _customUpdateEvent; 271 272 /// Called when it needs to request an update. 273 void _requestUpdate(){ 274 if (_requestingUpdate || !(_eventSub & EventMask.Update)) 275 return; 276 _requestingUpdate = true; 277 if (_parent) 278 _parent.requestUpdate(); 279 } 280 /// Called to request this widget to resize at next update 281 void _requestResize(){ 282 if (_requestingResize) 283 return; 284 _requestingResize = true; 285 if (_parent) 286 _parent.requestResize(); 287 } 288 /// Called to request cursor to be positioned at x,y 289 /// Will do nothing if not active widget 290 void _requestCursorPos(int x, int y){ 291 if (_isActive && _parent) 292 _parent.requestCursorPos(x < 0 ? x : _posX + x - _view._offsetX, 293 y < 0 ? y : _posY + y - _view._offsetY); 294 } 295 /// Called to request _scrollX to be adjusted 296 /// Returns: true if _scrollX was modified 297 bool _requestScrollX(uint x){ 298 if (_canReqScroll && _parent && 299 _view._width < _width && x < _width - _view._width) 300 return _parent.requestScrollX(x + _posX); 301 return false; 302 } 303 /// Called to request _scrollY to be adjusted 304 /// Returns: true if _scrollY was modified 305 bool _requestScrollY(uint y){ 306 if (_canReqScroll && _parent && 307 _view._height < _height && y < _height - _view._height) 308 return _parent.requestScrollY(y + _posY); 309 return false; 310 } 311 312 /// called by parent for initialize event 313 bool _initializeCall(){ 314 if (!(_eventSub & EventMask.Initialize)) 315 return false; 316 if (_customInitEvent && _customInitEvent(this)) 317 return true; 318 return this.initialize(); 319 } 320 /// called by parent for mouseEvent 321 bool _mouseEventCall(MouseEvent mouse){ 322 if (!(_eventSub & mouse.state)) 323 return false; 324 // mouse input comes relative to visible area 325 mouse.x = (mouse.x - cast(int)this._posX) + _view._offsetX; 326 mouse.y = (mouse.y - cast(int)this._posY) + _view._offsetY; 327 if (_customMouseEvent && _customMouseEvent(this, mouse)) 328 return true; 329 return this.mouseEvent(mouse); 330 } 331 /// called by parent for keyboardEvent 332 bool _keyboardEventCall(KeyboardEvent key, bool cycle){ 333 if (!(_eventSub & key.state)) 334 return false; 335 if (_customKeyboardEvent && _customKeyboardEvent(this, key, cycle)) 336 return true; 337 return this.keyboardEvent(key, cycle); 338 } 339 /// called by parent for resizeEvent 340 bool _resizeEventCall(){ 341 _requestingResize = false; 342 _requestUpdate(); 343 if (!(_eventSub & EventMask.Resize)) 344 return false; 345 if (_customResizeEvent && _customResizeEvent(this)) 346 return true; 347 return this.resizeEvent(); 348 } 349 /// called by parent for scrollEvent 350 bool _scrollEventCall(){ 351 _requestUpdate(); 352 if (!(_eventSub & EventMask.Scroll)) 353 return false; 354 if (_customScrollEvent && _customScrollEvent(this)) 355 return true; 356 return this.scrollEvent(); 357 } 358 /// called by parent for activateEvent 359 bool _activateEventCall(bool isActive){ 360 if (!(_eventSub & EventMask.Activate)) 361 return false; 362 if (_isActive == isActive) 363 return false; 364 _isActive = isActive; 365 if (_customActivateEvent && _customActivateEvent(this, isActive)) 366 return true; 367 return this.activateEvent(isActive); 368 } 369 /// called by parent for timerEvent 370 bool _timerEventCall(uint msecs){ 371 if (!(_eventSub & EventMask.Timer)) 372 return false; 373 if (_customTimerEvent && _customTimerEvent(this, msecs)) 374 return true; 375 return timerEvent(msecs); 376 } 377 /// called by parent for updateEvent 378 bool _updateEventCall(){ 379 if (!_requestingUpdate || !(_eventSub & EventMask.Update)) 380 return false; 381 _requestingUpdate = false; 382 _view.moveTo(_view._offsetX,_view._offsetY); 383 if (_customUpdateEvent && _customUpdateEvent(this)) 384 return true; 385 return this.updateEvent(); 386 } 387 protected: 388 /// minimum width 389 uint _minWidth; 390 /// maximum width 391 uint _maxWidth; 392 /// minimum height 393 uint _minHeight; 394 /// maximum height 395 uint _maxHeight; 396 397 /// viewport coordinates. (drawable area for widget) 398 final @property uint viewportX(){ 399 return _view._offsetX; 400 } 401 /// ditto 402 final @property uint viewportY(){ 403 return _view._offsetY; 404 } 405 /// viewport size. (drawable area for widget) 406 final @property uint viewportWidth(){ 407 return _view._width; 408 } 409 /// ditto 410 final @property uint viewportHeight(){ 411 return _view._height; 412 } 413 414 /// If a coordinate is within writing area, and writing area actually exists 415 final bool isWritable(uint x, uint y){ 416 return _view.isWritable(x,y); 417 } 418 419 /// move seek for next write to terminal. 420 /// can only write in between: 421 /// `(_viewX .. _viewX + _viewWidth, _viewY .. _viewX + _viewHeight)` 422 final void moveTo(uint newX, uint newY){ 423 _view.moveTo(newX, newY); 424 } 425 /// writes a character on terminal 426 /// 427 /// Returns: true if done, false if outside writing area 428 final bool write(dchar c, Color fg, Color bg){ 429 return _view.write(c, fg, bg); 430 } 431 /// writes a (d)string to terminal. if it does not fit in one line, it is wrapped 432 /// 433 /// Returns: number of characters written 434 final uint write(dstring s, Color fg, Color bg){ 435 return _view.write(s, fg, bg); 436 } 437 /// fill current line with a character. `max` is ignored if `max==0` 438 /// 439 /// Returns: number of cells written 440 final uint fillLine(dchar c, Color fg, Color bg, uint max = 0){ 441 return _view.fillLine(c, fg, bg, max); 442 } 443 444 /// activate the passed widget if this is actually the correct widget 445 /// 446 /// Returns: if it was activated or not 447 bool searchAndActivateWidget(QWidget target) { 448 if (this == target) { 449 this._isActive = true; 450 if (this._eventSub & EventMask.Activate) 451 this._activateEventCall(true); 452 return true; 453 } 454 return false; 455 } 456 457 /// called by itself, to update events subscribed to 458 final void eventSubscribe(uint newSub){ 459 _eventSub = newSub; 460 if (_parent) 461 _parent.eventSubscribe(); 462 } 463 /// called by children when they want to re-subscribe to events 464 void eventSubscribe(){} 465 466 /// to set cursor position on terminal. will only work if this is active widget. 467 /// set x or y or both to negative to hide cursor 468 void requestCursorPos(int x, int y){ 469 _requestCursorPos(x, y); 470 } 471 472 /// called to request to scrollX 473 bool requestScrollX(uint x){ 474 return _requestScrollX(x); 475 } 476 /// called to request to scrollY 477 bool requestScrollY(uint y){ 478 return _requestScrollY(y); 479 } 480 481 /// Called after UI has been run 482 bool initialize(){ 483 return false; 484 } 485 /// Called when mouse is clicked with cursor on this widget. 486 bool mouseEvent(MouseEvent mouse){ 487 return false; 488 } 489 /// Called when key is pressed and this widget is active. 490 bool keyboardEvent(KeyboardEvent key, bool cycle){ 491 return false; 492 } 493 /// Called when widget size is changed, 494 /// or widget should recalculate it's child widgets' sizes; 495 bool resizeEvent(){ 496 return false; 497 } 498 /// Called when the widget is rescrolled, but size not changed. 499 bool scrollEvent(){ 500 return false; 501 } 502 /// called right after this widget is activated, or de-activated 503 bool activateEvent(bool isActive){ 504 return false; 505 } 506 /// called often. `msecs` is the msecs since last timerEvent, not accurate 507 bool timerEvent(uint msecs){ 508 return false; 509 } 510 /// Called to update this widget 511 bool updateEvent(){ 512 return false; 513 } 514 public: 515 /// To request parent to trigger an update event 516 void requestUpdate(){ 517 _requestUpdate(); 518 } 519 /// To request parent to trigger a resize event 520 void requestResize(){ 521 _requestResize(); 522 } 523 /// use to change the custom initialize event 524 final @property InitFunction onInitEvent(InitFunction func){ 525 return _customInitEvent = func; 526 } 527 /// use to change the custom mouse event 528 final @property MouseEventFuction onMouseEvent(MouseEventFuction func){ 529 return _customMouseEvent = func; 530 } 531 /// use to change the custom keyboard event 532 final @property KeyboardEventFunction onKeyboardEvent(KeyboardEventFunction func){ 533 return _customKeyboardEvent = func; 534 } 535 /// use to change the custom resize event 536 final @property ResizeEventFunction onResizeEvent(ResizeEventFunction func){ 537 return _customResizeEvent = func; 538 } 539 /// use to change the custom activate event 540 final @property ActivateEventFunction onActivateEvent(ActivateEventFunction func){ 541 return _customActivateEvent = func; 542 } 543 /// use to change the custom timer event 544 final @property TimerEventFunction onTimerEvent(TimerEventFunction func){ 545 return _customTimerEvent = func; 546 } 547 /// Returns: true if this widget is the current active widget 548 final @property bool isActive(){ 549 return _isActive; 550 } 551 /// Returns: EventMask of subscribed events 552 final @property uint eventSub(){ 553 return _eventSub; 554 } 555 /// specifies ratio of height or width (depending on parent layout type) 556 final @property uint sizeRatio(){ 557 return _sizeRatio; 558 } 559 /// ditto 560 final @property uint sizeRatio(uint newRatio){ 561 _sizeRatio = newRatio; 562 _requestResize(); 563 return _sizeRatio; 564 } 565 /// visibility of the widget. 566 final @property bool show(){ 567 return _show; 568 } 569 /// ditto 570 final @property bool show(bool visibility){ 571 _show = visibility; 572 _requestResize(); 573 return _show; 574 } 575 /// horizontal scroll. 576 final @property uint scrollX(){ 577 return _scrollX; 578 } 579 /// ditto 580 final @property uint scrollX(uint newVal){ 581 _requestScrollX(newVal); 582 return _scrollX; 583 } 584 /// vertical scroll. 585 final @property uint scrollY(){ 586 return _scrollY; 587 } 588 /// ditto 589 final @property uint scrollY(uint newVal){ 590 _requestScrollY(newVal); 591 return _scrollY; 592 } 593 /// width of widget 594 final @property uint width(){ 595 return _width; 596 } 597 /// fixed width setter. 598 /// This will actually do `minWidth=maxWidth=value` and call requestResize 599 @property uint width(uint value){ 600 _minWidth = value; 601 _maxWidth = value; 602 _requestResize(); 603 return value; 604 } 605 /// height of widget 606 final @property uint height(){ 607 return _height; 608 } 609 /// fixed height setter. 610 /// This will actually do `minHeight=maxHeight=value` and call requestResize 611 @property uint height(uint value){ 612 _minHeight = value; 613 _maxHeight = value; 614 _requestResize(); 615 return value; 616 } 617 /// minimum width 618 @property uint minWidth(){ 619 return _minWidth; 620 } 621 /// ditto 622 @property uint minWidth(uint value){ 623 _requestResize(); 624 return _minWidth = value; 625 } 626 /// minimum height 627 @property uint minHeight(){ 628 return _minHeight; 629 } 630 /// ditto 631 @property uint minHeight(uint value){ 632 _requestResize(); 633 return _minHeight = value; 634 } 635 /// maximum width 636 @property uint maxWidth(){ 637 return _maxWidth; 638 } 639 /// ditto 640 @property uint maxWidth(uint value){ 641 _requestResize(); 642 return _maxWidth = value; 643 } 644 /// maximum height 645 @property uint maxHeight(){ 646 return _maxHeight; 647 } 648 /// ditto 649 @property uint maxHeight(uint value){ 650 _requestResize(); 651 return _maxHeight = value; 652 } 653 } 654 655 ///Used to place widgets in an order (i.e vertical or horizontal) 656 class QLayout : QWidget{ 657 private: 658 /// array of all the widgets that have been added to this layout 659 QWidget[] _widgets; 660 /// layout type, horizontal or vertical 661 QLayout.Type _type; 662 /// stores index of active widget. -1 if none. 663 int _activeWidgetIndex = -1; 664 /// if it is overflowing 665 bool _isOverflowing = false; 666 /// Color to fill with in unoccupied space 667 Color _fillColor; 668 /// Color to fill with when overflowing 669 Color _overflowColor = DEFAULT_OVERFLOW_BG; 670 671 /// gets height/width of a widget using it's sizeRatio and min/max-height/width 672 uint _calculateWidgetSize(QWidget widget, uint ratioTotal, uint totalSpace, 673 ref bool free){ 674 immutable uint calculatedSize = 675 cast(uint)(widget._sizeRatio * totalSpace / ratioTotal); 676 if (_type == QLayout.Type.Horizontal){ 677 free = widget.minWidth == 0 && widget.maxWidth == 0; 678 return getLimitedSize(calculatedSize, widget.minWidth, widget.maxWidth); 679 } 680 free = widget.minHeight == 0 && widget.maxHeight == 0; 681 return getLimitedSize(calculatedSize, widget.minHeight, widget.maxHeight); 682 } 683 684 /// recalculates the size of every widget inside layout 685 void _recalculateWidgetsSize(){ 686 FIFOStack!QWidget widgetStack = new FIFOStack!QWidget; 687 uint totalRatio = 0; 688 uint totalSpace = _type == QLayout.Type.Horizontal ? _width : _height; 689 bool free; // @suppress(dscanner.suspicious.unmodified) shut up vscode 690 foreach (widget; _widgets){ 691 if (!widget._show) 692 continue; 693 totalRatio += widget._sizeRatio; 694 widget._height = getLimitedSize(_height, widget.minHeight, widget.maxHeight); 695 widget._width = getLimitedSize(_width, widget.minWidth, widget.maxWidth); 696 widgetStack.push(widget); 697 } 698 // do widgets with size limits 699 /// totalRatio, and space used of widgets with limits 700 uint limitWRatio, limitWSize; 701 for (int i = 0; i < widgetStack.count; i ++){ 702 QWidget widget = widgetStack.pop; 703 immutable uint space = 704 _calculateWidgetSize(widget, totalRatio, totalSpace, free); 705 if (free){ 706 widgetStack.push(widget); 707 continue; 708 } 709 if (_type == QLayout.Type.Horizontal) 710 widget._width = space; 711 else 712 widget._height = space; 713 limitWRatio += widget._sizeRatio; 714 limitWSize += space; 715 } 716 totalSpace -= limitWSize; 717 totalRatio -= limitWRatio; 718 while (widgetStack.count){ 719 QWidget widget = widgetStack.pop; 720 immutable uint space = 721 _calculateWidgetSize(widget, totalRatio, totalSpace, free); 722 if (_type == QLayout.Type.Horizontal) 723 widget._width = space; 724 else 725 widget._height = space; 726 totalRatio -= widget._sizeRatio; 727 totalSpace -= space; 728 } 729 .destroy(widgetStack); 730 } 731 732 /// find the next widget to activate 733 /// Returns: index, or -1 if no one wanna be active 734 int _nextActiveWidget(){ 735 for (int i = _activeWidgetIndex + 1; i < _widgets.length; i ++){ 736 if ((_widgets[i]._eventSub & EventMask.KeyboardAll) && _widgets[i]._show) 737 return i; 738 } 739 return -1; 740 } 741 protected: 742 override void eventSubscribe(){ 743 _eventSub = 0; 744 foreach (widget; _widgets) 745 _eventSub |= widget._eventSub; 746 747 // if children can become active, then need activate too 748 if (_eventSub & EventMask.KeyboardAll) 749 _eventSub |= EventMask.Activate; 750 751 _eventSub |= EventMask.Scroll; 752 753 if (_parent) 754 _parent.eventSubscribe(); 755 } 756 757 /// Recalculates size and position for all visible widgets 758 override bool resizeEvent(){ 759 _recalculateWidgetsSize(); // resize everything 760 // if parent is scrollable container, and there are no size limits, 761 // then grow as needed 762 if (minHeight + maxHeight + minWidth + maxWidth == 0 && 763 _parent && _parent._isScrollableContainer){ 764 _width = 0; 765 _height = 0; 766 if (_type == Type.Horizontal){ 767 foreach (widget; _widgets){ 768 if (_height < widget._height) 769 _height = widget._height; 770 _width += widget._width; 771 } 772 }else{ 773 foreach (widget; _widgets){ 774 if (_width < widget._width) 775 _width = widget._width; 776 _height += widget._height; 777 } 778 } 779 } 780 // now reposition everything 781 /// space taken by widgets before 782 uint previousSpace = 0; 783 uint w, h; 784 foreach(widget; _widgets){ 785 if (!widget._show) 786 continue; 787 if (_type == QLayout.Type.Horizontal){ 788 widget._posY = 0; 789 widget._posX = previousSpace; 790 previousSpace += widget._width; 791 w += widget._width; 792 if (widget._height > h) 793 h = widget._height; 794 }else{ 795 widget._posX = 0; 796 widget._posY = previousSpace; 797 previousSpace += widget._height; 798 h += widget._height; 799 if (widget._width > w) 800 w = widget._width; 801 } 802 } 803 _isOverflowing = w > _width || h > _height; 804 if (_isOverflowing){ 805 foreach (widget; _widgets) 806 widget._view._reset(); 807 }else{ 808 foreach (i, widget; _widgets){ 809 _view._getSlice(&(widget._view), widget._posX, widget._posY, 810 widget._width, widget._height); 811 widget._resizeEventCall(); 812 } 813 } 814 return true; 815 } 816 817 /// Resize event 818 override bool scrollEvent(){ 819 if (_isOverflowing){ 820 foreach (widget; _widgets) 821 widget._view._reset(); 822 return false; 823 } 824 foreach (i, widget; _widgets){ 825 _view._getSlice(&(widget._view), widget._posX, widget._posY, 826 widget._width, widget._height); 827 widget._scrollEventCall(); 828 } 829 return true; 830 } 831 832 /// Redirects the mouseEvent to the appropriate widget 833 override public bool mouseEvent(MouseEvent mouse){ 834 if (_isOverflowing) 835 return false; 836 int index; 837 if (_type == Type.Horizontal){ 838 foreach (i, w; _widgets){ 839 if (w._show && w._posX <= mouse.x && w._posX + w._width > mouse.x){ 840 index = cast(int)i; 841 break; 842 } 843 } 844 }else{ 845 foreach (i, w; _widgets){ 846 if (w._show && w._posY <= mouse.y && w._posY + w._height > mouse.y){ 847 index = cast(int)i; 848 break; 849 } 850 } 851 } 852 if (index > -1){ 853 if (mouse.state != MouseEvent.State.Hover && index != _activeWidgetIndex && 854 _widgets[index]._eventSub & EventMask.KeyboardAll){ 855 if (_activeWidgetIndex > -1) 856 _widgets[_activeWidgetIndex]._activateEventCall(false); 857 _widgets[index]._activateEventCall(true); 858 _activeWidgetIndex = index; 859 } 860 return _widgets[index]._mouseEventCall(mouse); 861 } 862 return false; 863 } 864 865 /// Redirects the keyboardEvent to appropriate widget 866 override public bool keyboardEvent(KeyboardEvent key, bool cycle){ 867 if (_isOverflowing) 868 return false; 869 if (_activeWidgetIndex > -1 && 870 _widgets[_activeWidgetIndex]._keyboardEventCall(key, cycle)) 871 return true; 872 873 if (!cycle) 874 return false; 875 immutable int next = _nextActiveWidget(); 876 877 if (_activeWidgetIndex > -1 && next != _activeWidgetIndex) 878 _widgets[_activeWidgetIndex]._activateEventCall(false); 879 880 if (next == -1) 881 return false; 882 _activeWidgetIndex = next; 883 _widgets[_activeWidgetIndex]._activateEventCall(true); 884 return true; 885 } 886 887 /// override initialize to initliaze child widgets 888 override bool initialize(){ 889 foreach (widget; _widgets){ 890 widget._view._reset(); 891 widget._initializeCall(); 892 } 893 return true; 894 } 895 896 /// override timer event to call child widgets' timers 897 override bool timerEvent(uint msecs){ 898 foreach (widget; _widgets) 899 widget._timerEventCall(msecs); 900 return true; 901 } 902 903 /// override activate event 904 override bool activateEvent(bool isActive){ 905 if (isActive){ 906 _activeWidgetIndex = -1; 907 _activeWidgetIndex = _nextActiveWidget(); 908 } 909 if (_activeWidgetIndex > -1) 910 _widgets[_activeWidgetIndex]._activateEventCall(isActive); 911 return true; 912 } 913 914 /// called by parent widget to update 915 override bool updateEvent(){ 916 if (_isOverflowing){ 917 foreach (y; viewportY .. viewportY + viewportHeight){ 918 moveTo(viewportX, y); 919 fillLine(' ', DEFAULT_FG, _overflowColor); 920 } 921 return false; 922 } 923 if (_type == Type.Horizontal){ 924 foreach(i, widget; _widgets){ 925 if (widget._show && widget._requestingUpdate) 926 widget._updateEventCall(); 927 foreach (y; widget._height .. viewportY + viewportHeight){ 928 moveTo(widget._posX, y); 929 fillLine(' ', DEFAULT_FG, _fillColor, widget._width); 930 } 931 } 932 }else{ 933 foreach(i, widget; _widgets){ 934 if (widget._show && widget._requestingUpdate) 935 widget._updateEventCall(); 936 if (widget._width == _width) 937 continue; 938 foreach (y; widget._posY .. widget._posY + widget._height){ 939 moveTo(widget._posX + widget._width, y); 940 fillLine(' ', DEFAULT_FG, _fillColor); 941 } 942 } 943 } 944 return true; 945 } 946 947 /// activate the passed widget if it's in the current layout 948 /// Returns: true if it was activated or not 949 override bool searchAndActivateWidget(QWidget target){ 950 immutable int lastActiveWidgetIndex = _activeWidgetIndex; 951 952 // search and activate recursively 953 _activeWidgetIndex = -1; 954 foreach (index, widget; _widgets) { 955 if ((widget._eventSub & EventMask.KeyboardAll) && widget._show && 956 widget.searchAndActivateWidget(target)){ 957 _activeWidgetIndex = cast(int)index; 958 break; 959 } 960 } 961 962 // and then manipulate the current layout 963 if (lastActiveWidgetIndex != _activeWidgetIndex && lastActiveWidgetIndex > -1) 964 _widgets[lastActiveWidgetIndex]._activateEventCall(false); 965 return _activeWidgetIndex != -1; 966 } 967 968 public: 969 /// Layout type 970 enum Type{ 971 Vertical, 972 Horizontal, 973 } 974 /// constructor 975 this(QLayout.Type type){ 976 _type = type; 977 this._fillColor = DEFAULT_BG; 978 } 979 /// destructor, kills children 980 ~this(){ 981 foreach (child; _widgets) 982 .destroy(child); 983 } 984 /// Color for unoccupied space 985 @property Color fillColor(){ 986 return _fillColor; 987 } 988 /// ditto 989 @property Color fillColor(Color newColor){ 990 return _fillColor = newColor; 991 } 992 /// Color to fill with when out of space 993 @property Color overflowColor(){ 994 return _overflowColor; 995 } 996 /// ditto 997 @property Color overflowColor(Color newColor){ 998 return _overflowColor = newColor; 999 } 1000 /// adds a widget, and makes space for it 1001 void addWidget(QWidget widget, bool allowScrollControl = false){ 1002 widget._parent = this; 1003 widget._canReqScroll = allowScrollControl && _canReqScroll; 1004 _widgets ~= widget; 1005 eventSubscribe(); 1006 } 1007 /// ditto 1008 void addWidget(QWidget[] widgets){ 1009 foreach (i, widget; widgets){ 1010 widget._parent = this; 1011 } 1012 _widgets ~= widgets; 1013 eventSubscribe(); 1014 } 1015 } 1016 1017 /// Container for widgets to make them scrollable, with optional scrollbars 1018 class ScrollContainer : QWidget{ 1019 private: 1020 /// offset in _widget before adding scrolling to it 1021 uint _offX, _offY; 1022 protected: 1023 /// the widget to be scrolled 1024 QWidget _widget; 1025 /// if vertical and horizontal scrollbars are to be shown 1026 bool _scrollbarV, _scrollbarH; 1027 /// if page down/up button should scroll 1028 bool _pgDnUp = false; 1029 /// if mouse wheel should scroll 1030 bool _mouseWheel = false; 1031 /// height and width after subtracting space for scrollbars 1032 uint _drawAreaHeight, _drawAreaWidth; 1033 1034 override void eventSubscribe(){ 1035 _eventSub = EventMask.Resize | EventMask.Scroll; 1036 if (_widget) 1037 _eventSub |= _widget._eventSub; 1038 if (_pgDnUp) 1039 _eventSub |= EventMask.KeyboardPress; 1040 if (_mouseWheel) 1041 _eventSub |= EventMask.MouseAll; 1042 if (_scrollbarH || _scrollbarV) 1043 _eventSub |= EventMask.Update; 1044 if (_parent) 1045 _parent.eventSubscribe(); 1046 } 1047 1048 /// re-assings display buffer based on _subScrollX/Y, and calls scrollEvent on child 1049 final void rescroll(bool callScrollEvent = true){ 1050 if (!_widget) 1051 return; 1052 _widget._view._reset(); 1053 if (_width == 0 || _height == 0) 1054 return; 1055 uint w = _drawAreaWidth, h = _drawAreaHeight; 1056 if (w > _widget._width) 1057 w = _widget._width; 1058 if (h > _widget._height) 1059 h = _widget._height; 1060 _view._getSlice(&(_widget._view), 0, 0, w, h); 1061 _widget._view._offsetX = _offX + _widget._scrollX; 1062 _widget._view._offsetY = _offY + _widget._scrollY; 1063 if (callScrollEvent) 1064 _widget._scrollEventCall(); 1065 } 1066 1067 override bool requestScrollX(uint x){ 1068 if (!_widget) 1069 return false; 1070 if (_widget._width <= _drawAreaWidth) 1071 x = 0; 1072 else if (x > _widget._width - _drawAreaWidth) 1073 x = _widget._width - _drawAreaWidth; 1074 1075 if (_widget._scrollX == x) 1076 return false; 1077 _widget._scrollX = x; 1078 rescroll(); 1079 return true; 1080 } 1081 override bool requestScrollY(uint y){ 1082 if (!_widget) 1083 return false; 1084 if (_widget._height <= _drawAreaHeight) 1085 y = 0; 1086 else if (y > _widget._height - _drawAreaHeight) 1087 y = _widget._height - _drawAreaHeight; 1088 1089 if (_widget._scrollY == y) 1090 return false; 1091 _widget._scrollY = y; 1092 rescroll(); 1093 return true; 1094 } 1095 1096 override bool resizeEvent(){ 1097 _offX = _view._offsetX; 1098 _offY = _view._offsetY; 1099 _drawAreaHeight = _height - (1*(_height>0 && _scrollbarV)); 1100 _drawAreaWidth = _width - (1*(_width>0 && _scrollbarV)); 1101 if (!_widget) 1102 return false; 1103 if (_scrollbarH || _scrollbarV) 1104 requestUpdate(); 1105 // try to size widget to fit 1106 if (_height > 0 && _width > 0){ 1107 _widget._width = getLimitedSize(_drawAreaWidth, _widget.minWidth, 1108 _widget.maxWidth); 1109 _widget._height = getLimitedSize(_drawAreaHeight, _widget.minHeight, 1110 _widget.maxHeight); 1111 } 1112 rescroll(false); 1113 _widget._resizeEventCall(); 1114 1115 return true; 1116 } 1117 1118 override bool scrollEvent(){ 1119 _offX = _view._offsetX; 1120 _offY = _view._offsetY; 1121 if (_scrollbarH || _scrollbarV) 1122 requestUpdate(); 1123 rescroll(); 1124 1125 return true; 1126 } 1127 1128 override bool keyboardEvent(KeyboardEvent key, bool cycle){ 1129 if (!_widget) 1130 return false; 1131 if (_widget.isActive && _widget._keyboardEventCall(key, cycle)) 1132 return true; 1133 1134 if (!cycle && _pgDnUp && _drawAreaHeight < _widget._height && 1135 key.state == KeyboardEvent.State.Pressed){ 1136 if (key.key == Key.PageUp){ 1137 return requestScrollY(_drawAreaHeight > _widget._scrollY ? 0 : 1138 _widget._scrollY - _drawAreaHeight); 1139 } 1140 if (key.key == Key.PageDown){ 1141 return requestScrollY( 1142 _drawAreaHeight + _widget._scrollY > _widget._height ? 1143 _widget._height - _drawAreaHeight : 1144 _widget._scrollY + _drawAreaHeight); 1145 } 1146 } 1147 _widget._activateEventCall(false); 1148 return false; 1149 } 1150 1151 override bool mouseEvent(MouseEvent mouse){ 1152 if (!_widget) 1153 return false; 1154 if (_widget._mouseEventCall(mouse)){ 1155 _widget._activateEventCall(true); 1156 return true; 1157 } 1158 _widget._activateEventCall(false); 1159 if (_mouseWheel && _drawAreaHeight < _widget._height){ 1160 if (mouse.button == mouse.Button.ScrollUp){ 1161 if (_widget._scrollY) 1162 return requestScrollY(_widget._scrollY - 1); 1163 } 1164 if (mouse.button == mouse.Button.ScrollDown){ 1165 if (_widget._scrollY + _drawAreaHeight < _widget._height) 1166 return requestScrollY(_widget._scrollY + 1); 1167 } 1168 } 1169 return false; 1170 } 1171 1172 override bool updateEvent(){ 1173 if (!_widget) 1174 return false; 1175 if (_widget.show) 1176 _widget._updateEventCall(); 1177 if (_width > _widget._view._width + (1*(_scrollbarV))){ 1178 foreach (y; 0 .. _widget._view._height){ 1179 moveTo(_widget._view._width, y); 1180 fillLine(' ', DEFAULT_FG, DEFAULT_BG); 1181 } 1182 } 1183 if (_height > _widget._view._height + (1*(_scrollbarH))){ 1184 foreach (y; _widget._view._height .. _height - (1*(_scrollbarH))){ 1185 moveTo(0, y); 1186 fillLine(' ', DEFAULT_BG, DEFAULT_FG); 1187 } 1188 } 1189 drawScrollbars(); 1190 1191 return true; 1192 } 1193 1194 /// draws scrollbars, very basic stuff 1195 void drawScrollbars(){ 1196 if (!_widget || _width == 0 || _height == 0) 1197 return; 1198 const dchar verticalLine = '│', horizontalLine = '─'; 1199 if (_scrollbarH && _scrollbarV){ 1200 moveTo(_width - 1, _height - 1); 1201 write('┘', DEFAULT_FG, DEFAULT_BG); // bottom right corner 1202 } 1203 if (_scrollbarH){ 1204 moveTo(0, _drawAreaHeight); 1205 fillLine(horizontalLine, DEFAULT_FG, DEFAULT_BG, _drawAreaWidth); 1206 const int maxScroll = _widget._width - _drawAreaWidth; 1207 if (maxScroll > 0){ 1208 const uint barPos = (_widget._scrollX * _drawAreaWidth) / maxScroll; 1209 moveTo(barPos, _drawAreaHeight); 1210 write(' ', DEFAULT_BG, DEFAULT_FG); 1211 } 1212 } 1213 if (_scrollbarV){ 1214 foreach (y; 0 .. _drawAreaHeight){ 1215 moveTo(_drawAreaWidth, y); 1216 write(verticalLine, DEFAULT_FG, DEFAULT_BG); 1217 } 1218 const int maxScroll = _widget._height - _drawAreaHeight; 1219 if (maxScroll > 0){ 1220 const uint barPos = (_widget._scrollY * _drawAreaHeight) / maxScroll; 1221 moveTo(_drawAreaWidth, barPos); 1222 write(' ', DEFAULT_BG, DEFAULT_FG); 1223 } 1224 } 1225 } 1226 public: 1227 /// constructor 1228 this(){ 1229 this._isScrollableContainer = true; 1230 this._scrollbarV = true; 1231 this._scrollbarH = true; 1232 } 1233 ~this(){ 1234 if (_widget) 1235 .destroy(_widget); // kill the child! 1236 } 1237 1238 /// Sets the child widget. 1239 /// 1240 /// Returns: false if alreadty has a child 1241 bool setWidget(QWidget child){ 1242 if (_widget) 1243 return false; 1244 _widget = child; 1245 _widget._parent = this; 1246 _widget._canReqScroll = true; 1247 _widget._posX = 0; 1248 _widget._posY = 0; 1249 eventSubscribe(); 1250 return true; 1251 } 1252 1253 override void requestResize(){ 1254 // just do a the resize within itself, no need to ask parent 1255 _resizeEventCall(); 1256 } 1257 1258 /// Whether to scroll on page up/down keys 1259 @property bool scrollOnPageUpDown(){ 1260 return _pgDnUp; 1261 } 1262 /// ditto 1263 @property bool scrollOnPageUpDown(bool newVal){ 1264 _pgDnUp = newVal; 1265 eventSubscribe(); 1266 return _pgDnUp; 1267 } 1268 1269 /// Whether to scroll on mouse scroll wheel 1270 @property bool scrollOnMouseWheel(){ 1271 return _mouseWheel; 1272 } 1273 /// ditto 1274 @property bool scrollOnMouseWheel(bool newVal){ 1275 _mouseWheel = newVal; 1276 eventSubscribe(); 1277 return _mouseWheel; 1278 } 1279 1280 /// Whether to show vertical scrollbar. Modifying this will request update 1281 @property bool scrollbarV(){ 1282 return _scrollbarV; 1283 } 1284 /// ditto 1285 @property bool scrollbarV(bool newVal){ 1286 if (newVal != _scrollbarV){ 1287 _scrollbarV = newVal; 1288 rescroll(); 1289 } 1290 return _scrollbarV; 1291 } 1292 /// Whether to show horizontal scrollbar. Modifying this will request update 1293 @property bool scrollbarH(){ 1294 return _scrollbarH; 1295 } 1296 /// ditto 1297 @property bool scrollbarH(bool newVal){ 1298 if (newVal != _scrollbarH){ 1299 _scrollbarH = newVal; 1300 rescroll(); 1301 } 1302 return _scrollbarH; 1303 } 1304 } 1305 1306 /// A terminal (as the name says). 1307 class QTerminal : QLayout{ 1308 private: 1309 /// To actually access the terminal 1310 TermWrapper _termWrap; 1311 /// set to false to stop UI loop in run() 1312 bool _isRunning; 1313 /// the key used for cycling active widget 1314 dchar _activeWidgetCycleKey = WIDGET_CYCLE_KEY; 1315 /// whether to stop UI loop on Interrupt 1316 bool _stopOnInterrupt = true; 1317 /// cursor position 1318 int _cursorX = -1, _cursorY = -1; 1319 1320 /// Reads InputEvent and calls appropriate functions to address those events 1321 void _readEvent(Event event){ 1322 if (event.type == Event.Type.HangupInterrupt){ 1323 if (_stopOnInterrupt) 1324 _isRunning = false; 1325 else{ // otherwise read it as a Ctrl+C 1326 KeyboardEvent keyEvent; 1327 keyEvent.key = KeyboardEvent.CtrlKeys.CtrlC; 1328 this._keyboardEventCall(keyEvent, false); 1329 } 1330 }else if (event.type == Event.Type.Keyboard){ 1331 KeyboardEvent kPress = event.keyboard; 1332 this._keyboardEventCall(kPress, false); 1333 }else if (event.type == Event.Type.Mouse){ 1334 this._mouseEventCall(event.mouse); 1335 }else if (event.type == Event.Type.Resize){ 1336 this._resizeEventCall(); 1337 } 1338 } 1339 1340 /// writes _view to _termWrap 1341 void _flushBuffer(){ 1342 if (_view._buffer.length == 0) 1343 return; 1344 Cell prev = _view._buffer[0]; 1345 _termWrap.color(prev.fg, prev.bg); 1346 uint x, y; 1347 foreach (cell; _view._buffer){ 1348 if (!prev.propSame(cell)) 1349 _termWrap.color(cell.fg, cell.bg); 1350 prev = cell; 1351 _termWrap.put(x, y, cell.c); 1352 x ++; 1353 if (x == _width){ 1354 x = 0; 1355 y ++; 1356 } 1357 } 1358 } 1359 1360 protected: 1361 1362 override void eventSubscribe(){ 1363 // ignore what children want, this needs all events 1364 // so custom event handlers can be set up 1365 _eventSub = uint.max; 1366 } 1367 1368 override void requestCursorPos(int x, int y){ 1369 _cursorX = x; 1370 _cursorY = y; 1371 } 1372 1373 override bool resizeEvent(){ 1374 _height = _termWrap.height; 1375 _width = _termWrap.width; 1376 _view._buffer.length = _width * _height; 1377 _view._width = _width; 1378 _view._actualWidth = _width; 1379 _view._height = _height; 1380 super.resizeEvent(); 1381 return true; 1382 } 1383 1384 override bool keyboardEvent(KeyboardEvent key, bool cycle){ 1385 cycle = key.state == KeyboardEvent.State.Pressed && 1386 key.key == _activeWidgetCycleKey; 1387 return super.keyboardEvent(key, cycle); 1388 } 1389 1390 override bool updateEvent(){ 1391 // resize if needed 1392 if (_requestingResize) 1393 this._resizeEventCall(); 1394 _cursorX = -1; 1395 _cursorY = -1; 1396 // no, this is not a mistake, dont change this to updateEventCall again! 1397 super.updateEvent(); 1398 // flush _view._buffer to _termWrap 1399 _flushBuffer(); 1400 // check if need to show/hide cursor 1401 if (_cursorX < 0 || _cursorY < 0) 1402 _termWrap.cursorVisible = false; 1403 else{ 1404 _termWrap.moveCursor(_cursorX, _cursorY); 1405 _termWrap.cursorVisible = true; 1406 } 1407 _termWrap.flush(); 1408 return true; 1409 } 1410 1411 public: 1412 /// time to wait between timer events (milliseconds) 1413 ushort timerMsecs; 1414 /// constructor 1415 this(QLayout.Type displayType = QLayout.Type.Vertical, ushort timerDuration = 500){ 1416 super(displayType); 1417 timerMsecs = timerDuration; 1418 1419 _termWrap = new TermWrapper(); 1420 // so it can make other widgets active on mouse events 1421 this._isActive = true; 1422 } 1423 ~this(){ 1424 .destroy(_termWrap); 1425 } 1426 1427 /// stops UI loop. **not instant** 1428 /// if it is in-between event functions, it will complete those first 1429 void terminate(){ 1430 _isRunning = false; 1431 } 1432 1433 /// whether to stop UI loop on HangupInterrupt (Ctrl+C) 1434 @property bool terminateOnHangup(){ 1435 return _stopOnInterrupt; 1436 } 1437 /// ditto 1438 @property bool terminateOnHangup(bool newVal){ 1439 return _stopOnInterrupt = newVal; 1440 } 1441 1442 /// starts the UI loop 1443 void run(){ 1444 _initializeCall(); 1445 _resizeEventCall(); 1446 _updateEventCall(); 1447 _isRunning = true; 1448 StopWatch sw = StopWatch(AutoStart.yes); 1449 while (_isRunning){ 1450 int timeout = cast(int)(timerMsecs - sw.peek.total!"msecs"); 1451 Event event; 1452 while (_termWrap.getEvent(timeout, event) > 0){ 1453 _readEvent(event); 1454 timeout = cast(int)(timerMsecs - sw.peek.total!"msecs"); 1455 _updateEventCall(); 1456 } 1457 if (sw.peek.total!"msecs" >= timerMsecs){ 1458 _timerEventCall(cast(uint)sw.peek.total!"msecs"); 1459 sw.reset; 1460 sw.start; 1461 _updateEventCall(); 1462 } 1463 } 1464 } 1465 1466 /// search the passed widget recursively and activate it 1467 /// 1468 /// Returns: true if the widget was made active, false if not found and not done 1469 bool activateWidget(QWidget target) { 1470 return this.searchAndActivateWidget(target); 1471 } 1472 1473 /// Changes the key used to cycle between active widgets. 1474 void setActiveWidgetCycleKey(dchar key){ 1475 _activeWidgetCycleKey = key; 1476 } 1477 }