1 // Written in the D programming language. 2 3 /** 4 This module contains declaration of tabbed view controls. 5 6 TabItemWidget - single tab header in tab control 7 TabWidget 8 TabHost 9 TabControl 10 11 12 Synopsis: 13 14 ---- 15 import dlangui.widgets.tabs; 16 17 ---- 18 19 Copyright: Vadim Lopatin, 2014 20 License: Boost License 1.0 21 Authors: Vadim Lopatin, coolreader.org@gmail.com 22 */ 23 module dlangui.widgets.tabs; 24 25 import dlangui.core.signals; 26 import dlangui.widgets.layouts; 27 import dlangui.widgets.controls; 28 29 import std.algorithm; 30 31 /// current tab is changed handler 32 interface TabHandler { 33 void onTabChanged(string newActiveTabId, string previousTabId); 34 } 35 36 /// tab close button pressed handler 37 interface TabCloseHandler { 38 void onTabClose(string tabId); 39 } 40 41 42 /// tab item metadata 43 class TabItem { 44 private static __gshared long _lastAccessCounter; 45 private string _iconRes; 46 private string _id; 47 private UIString _label; 48 private long _lastAccessTs; 49 this(string id, string labelRes, string iconRes = null) { 50 _id = id; 51 _label.id = labelRes; 52 _iconRes = iconRes; 53 } 54 this(string id, dstring labelRes, string iconRes = null) { 55 _id = id; 56 _label.value = labelRes; 57 _iconRes = iconRes; 58 _lastAccessTs = _lastAccessCounter++; 59 } 60 @property string iconId() const { return _iconRes; } 61 @property string id() const { return _id; } 62 @property ref UIString text() { return _label; } 63 @property TabItem iconId(string id) { _iconRes = id; return this; } 64 @property TabItem id(string id) { _id = id; return this; } 65 @property long lastAccessTs() { return _lastAccessTs; } 66 @property void lastAccessTs(long ts) { _lastAccessTs = ts; } 67 void updateAccessTs() { 68 _lastAccessTs = _lastAccessCounter++; //std.datetime.Clock.currStdTime; 69 } 70 71 protected Object _objectParam; 72 @property Object objectParam() { 73 return _objectParam; 74 } 75 @property TabItem objectParam(Object value) { 76 _objectParam = value; 77 return this; 78 } 79 80 protected int _intParam; 81 @property int intParam() { 82 return _intParam; 83 } 84 @property TabItem intParam(int value) { 85 _intParam = value; 86 return this; 87 } 88 } 89 90 /// tab item widget - to show tab header 91 class TabItemWidget : HorizontalLayout { 92 private ImageWidget _icon; 93 private TextWidget _label; 94 private ImageButton _closeButton; 95 private TabItem _item; 96 private bool _enableCloseButton; 97 Signal!TabCloseHandler tabClose; 98 @property TabItem tabItem() { return _item; } 99 @property TabControl tabControl() { return cast(TabControl)parent; } 100 this(TabItem item, bool enableCloseButton = true) { 101 styleId = STYLE_TAB_UP_BUTTON; 102 _enableCloseButton = enableCloseButton; 103 _icon = new ImageWidget(); 104 _label = new TextWidget(); 105 _label.styleId = STYLE_TAB_UP_BUTTON_TEXT; 106 _label.state = State.Parent; 107 _closeButton = new ImageButton("CLOSE"); 108 _closeButton.styleId = STYLE_BUTTON_TRANSPARENT; 109 _closeButton.drawableId = "close"; 110 _closeButton.trackHover = true; 111 _closeButton.click = &onClick; 112 if (!_enableCloseButton) { 113 _closeButton.visibility = Visibility.Gone; 114 } else { 115 _closeButton.visibility = Visibility.Visible; 116 } 117 addChild(_icon); 118 addChild(_label); 119 addChild(_closeButton); 120 setItem(item); 121 clickable = true; 122 trackHover = true; 123 _label.trackHover = true; 124 } 125 void setStyles(string tabButtonStyle, string tabButtonTextStyle) { 126 styleId = tabButtonStyle; 127 _label.styleId = tabButtonTextStyle; 128 } 129 override void onDraw(DrawBuf buf) { 130 //debug Log.d("TabWidget.onDraw ", id); 131 super.onDraw(buf); 132 } 133 protected bool onClick(Widget source) { 134 if (source.compareId("CLOSE")) { 135 Log.d("tab close button pressed"); 136 if (tabClose.assigned) 137 tabClose(_item.id); 138 } 139 return true; 140 } 141 @property TabItem item() { 142 return _item; 143 } 144 @property void setItem(TabItem item) { 145 _item = item; 146 if (item.iconId !is null) { 147 _icon.visibility = Visibility.Visible; 148 _icon.drawableId = item.iconId; 149 } else { 150 _icon.visibility = Visibility.Gone; 151 } 152 _label.text = item.text; 153 id = item.id; 154 } 155 } 156 157 /// tab item list helper class 158 class TabItemList { 159 private TabItem[] _list; 160 private int _len; 161 162 this() { 163 } 164 165 /// get item by index 166 TabItem get(int index) { 167 if (index < 0 || index >= _len) 168 return null; 169 return _list[index]; 170 } 171 /// get item by index 172 const (TabItem) get(int index) const { 173 if (index < 0 || index >= _len) 174 return null; 175 return _list[index]; 176 } 177 /// get item by index 178 TabItem opIndex(int index) { 179 return get(index); 180 } 181 /// get item by index 182 const (TabItem) opIndex(int index) const { 183 return get(index); 184 } 185 /// get item by id 186 TabItem get(string id) { 187 int idx = indexById(id); 188 if (idx < 0) 189 return null; 190 return _list[idx]; 191 } 192 /// get item by id 193 const (TabItem) get(string id) const { 194 int idx = indexById(id); 195 if (idx < 0) 196 return null; 197 return _list[idx]; 198 } 199 /// get item by id 200 TabItem opIndex(string id) { 201 return get(id); 202 } 203 @property int length() const { return _len; } 204 /// append new item 205 TabItemList add(TabItem item) { 206 return insert(item, -1); 207 } 208 /// insert new item to specified position 209 TabItemList insert(TabItem item, int index) { 210 if (index > _len || index < 0) 211 index = _len; 212 if (_list.length <= _len) 213 _list.length = _len + 4; 214 for (int i = _len; i > index; i--) 215 _list[i] = _list[i - 1]; 216 _list[index] = item; 217 _len++; 218 return this; 219 } 220 /// remove item by index 221 TabItem remove(int index) { 222 TabItem res = _list[index]; 223 for (int i = index; i < _len - 1; i++) 224 _list[i] = _list[i + 1]; 225 _len--; 226 return res; 227 } 228 /// find tab index by id 229 int indexById(string id) const { 230 for (int i = 0; i < _len; i++) { 231 if (_list[i].id.equal(id)) 232 return i; 233 } 234 return -1; 235 } 236 } 237 238 /// tab header - tab labels, with optional More button 239 class TabControl : WidgetGroupDefaultDrawing { 240 protected TabItemList _items; 241 protected ImageButton _moreButton; 242 protected bool _enableCloseButton; 243 protected TabItemWidget[] _sortedItems; 244 protected int _buttonOverlap; 245 246 protected string _tabStyle; 247 protected string _tabButtonStyle; 248 protected string _tabButtonTextStyle; 249 250 /// signal of tab change (e.g. by clicking on tab header) 251 Signal!TabHandler tabChanged; 252 253 /// signal on tab close button 254 Signal!TabCloseHandler tabClose; 255 256 protected Align _tabAlignment; 257 @property Align tabAlignment() { return _tabAlignment; } 258 @property void tabAlignment(Align a) { _tabAlignment = a; } 259 260 /// empty parameter list constructor - for usage by factory 261 this() { 262 this(null); 263 } 264 /// create with ID parameter 265 this(string ID, Align tabAlignment = Align.Top) { 266 super(ID); 267 _tabAlignment = tabAlignment; 268 setStyles(STYLE_TAB_UP, STYLE_TAB_UP_BUTTON, STYLE_TAB_UP_BUTTON_TEXT); 269 _items = new TabItemList(); 270 _moreButton = new ImageButton("MORE", "tab_more"); 271 _moreButton.styleId = STYLE_BUTTON_TRANSPARENT; 272 _moreButton.mouseEvent = &onMouse; 273 _moreButton.margins(Rect(0,0,0,0)); 274 _enableCloseButton = true; 275 styleId = _tabStyle; 276 addChild(_moreButton); // first child is always MORE button, the rest corresponds to tab list 277 } 278 void setStyles(string tabStyle, string tabButtonStyle, string tabButtonTextStyle) { 279 _tabStyle = tabStyle; 280 _tabButtonStyle = tabButtonStyle; 281 _tabButtonTextStyle = tabButtonTextStyle; 282 styleId = _tabStyle; 283 for (int i = 1; i < _children.count; i++) { 284 TabItemWidget w = cast(TabItemWidget)_children[i]; 285 if (w) { 286 w.setStyles(_tabButtonStyle, _tabButtonTextStyle); 287 } 288 } 289 _buttonOverlap = currentTheme.get(tabButtonStyle).customLength("overlap", 0); 290 } 291 292 /// when true, shows close buttons in tabs 293 @property bool enableCloseButton() { return _enableCloseButton; } 294 /// ditto 295 @property void enableCloseButton(bool enabled) { 296 _enableCloseButton = enabled; 297 } 298 299 /// returns tab count 300 @property int tabCount() const { 301 return _items.length; 302 } 303 /// returns tab item by id (null if index out of range) 304 TabItem tab(int index) { 305 return _items.get(index); 306 } 307 /// returns tab item by id (null if not found) 308 TabItem tab(string id) { 309 return _items.get(id); 310 } 311 /// returns tab item by id (null if not found) 312 const(TabItem) tab(string id) const { 313 return _items.get(id); 314 } 315 /// get tab index by tab id (-1 if not found) 316 int tabIndex(string id) { 317 return _items.indexById(id); 318 } 319 protected void updateTabs() { 320 // TODO: 321 } 322 static bool accessTimeComparator(TabItemWidget a, TabItemWidget b) { 323 return (a.tabItem.lastAccessTs > b.tabItem.lastAccessTs); 324 } 325 326 protected TabItemWidget[] sortedItems() { 327 _sortedItems.length = _items.length; 328 for (int i = 0; i < _items.length; i++) 329 _sortedItems[i] = cast(TabItemWidget)_children.get(i + 1); 330 std.algorithm.sort!(accessTimeComparator)(_sortedItems); 331 return _sortedItems; 332 } 333 334 /// find next or previous tab index, based on access time 335 int getNextItemIndex(int direction) { 336 if (_items.length == 0) 337 return -1; 338 if (_items.length == 1) 339 return 0; 340 TabItemWidget[] items = sortedItems(); 341 for (int i = 0; i < items.length; i++) { 342 if (items[i].id == _selectedTabId) { 343 int next = i + direction; 344 if (next < 0) 345 next = cast(int)(items.length - 1); 346 if (next >= items.length) 347 next = 0; 348 return _items.indexById(items[next].id); 349 } 350 } 351 return -1; 352 } 353 354 /// remove tab 355 TabControl removeTab(string id) { 356 string nextId; 357 if (id.equal(_selectedTabId)) { 358 // current tab is being closed: remember next tab id 359 int nextIndex = getNextItemIndex(1); 360 if (nextIndex < 0) 361 nextIndex = getNextItemIndex(-1); 362 if (nextIndex >= 0) 363 nextId = _items[nextIndex].id; 364 } 365 int index = _items.indexById(id); 366 if (index >= 0) { 367 Widget w = _children.remove(index + 1); 368 if (w) 369 destroy(w); 370 _items.remove(index); 371 if (id.equal(_selectedTabId)) 372 _selectedTabId = null; 373 requestLayout(); 374 } 375 if (nextId) { 376 index = _items.indexById(nextId); 377 if (index >= 0) { 378 selectTab(index, true); 379 } 380 } 381 return this; 382 } 383 384 /// change name of tab 385 void renameTab(string ID, dstring name) { 386 int index = _items.indexById(id); 387 if (index >= 0) { 388 renameTab(index, name); 389 } 390 } 391 392 /// change name of tab 393 void renameTab(int index, dstring name) { 394 _items[index].text = name; 395 for (int i = 0; i < _children.count; i++) { 396 TabItemWidget widget = cast (TabItemWidget)_children[i]; 397 if (widget && widget.item is _items[index]) { 398 widget.setItem(_items[index]); 399 requestLayout(); 400 break; 401 } 402 } 403 } 404 405 protected void onTabClose(string tabId) { 406 if (tabClose.assigned) 407 tabClose(tabId); 408 } 409 410 /// add new tab 411 TabControl addTab(TabItem item, int index = -1, bool enableCloseButton = false) { 412 _items.insert(item, index); 413 TabItemWidget widget = new TabItemWidget(item, enableCloseButton); 414 widget.parent = this; 415 widget.mouseEvent = &onMouse; 416 widget.setStyles(_tabButtonStyle, _tabButtonTextStyle); 417 widget.tabClose = &onTabClose; 418 _children.insert(widget, index); 419 updateTabs(); 420 requestLayout(); 421 return this; 422 } 423 /// add new tab by id and label string 424 TabControl addTab(string id, dstring label, string iconId = null, bool enableCloseButton = false) { 425 TabItem item = new TabItem(id, label, iconId); 426 return addTab(item, -1, enableCloseButton); 427 } 428 /// add new tab by id and label string resource id 429 TabControl addTab(string id, string labelResourceId, string iconId = null, bool enableCloseButton = false) { 430 TabItem item = new TabItem(id, labelResourceId, iconId); 431 return addTab(item, -1, enableCloseButton); 432 } 433 protected bool onMouse(Widget source, MouseEvent event) { 434 if (event.action == MouseAction.ButtonDown && event.button == MouseButton.Left) { 435 if (source.compareId("MORE")) { 436 Log.d("tab MORE button pressed"); 437 return true; 438 } 439 string id = source.id; 440 int index = tabIndex(id); 441 if (index >= 0) { 442 selectTab(index, true); 443 } 444 } 445 return true; 446 } 447 /// Measure widget according to desired width and height constraints. (Step 1 of two phase layout). 448 override void measure(int parentWidth, int parentHeight) { 449 //Log.d("tabControl.measure enter"); 450 Rect m = margins; 451 Rect p = padding; 452 // calc size constraints for children 453 int pwidth = parentWidth; 454 int pheight = parentHeight; 455 if (parentWidth != SIZE_UNSPECIFIED) 456 pwidth -= m.left + m.right + p.left + p.right; 457 if (parentHeight != SIZE_UNSPECIFIED) 458 pheight -= m.top + m.bottom + p.top + p.bottom; 459 // measure children 460 Point sz; 461 if (_moreButton.visibility == Visibility.Visible) { 462 _moreButton.measure(pwidth, pheight); 463 sz.x = _moreButton.measuredWidth; 464 sz.y = _moreButton.measuredHeight; 465 } 466 pwidth -= sz.x; 467 for (int i = 1; i < _children.count; i++) { 468 Widget tab = _children.get(i); 469 tab.visibility = Visibility.Visible; 470 tab.measure(pwidth, pheight); 471 if (sz.y < tab.measuredHeight) 472 sz.y = tab.measuredHeight; 473 if (sz.x + tab.measuredWidth > pwidth) 474 break; 475 sz.x += tab.measuredWidth - _buttonOverlap; 476 } 477 measuredContent(parentWidth, parentHeight, sz.x, sz.y); 478 //Log.d("tabControl.measure exit"); 479 } 480 /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout). 481 override void layout(Rect rc) { 482 //Log.d("tabControl.layout enter"); 483 _needLayout = false; 484 if (visibility == Visibility.Gone) { 485 return; 486 } 487 _pos = rc; 488 applyMargins(rc); 489 applyPadding(rc); 490 // more button 491 Rect moreRc = rc; 492 if (_moreButton.visibility == Visibility.Visible) { 493 moreRc.left = rc.right - _moreButton.measuredWidth; 494 _moreButton.layout(moreRc); 495 rc.right -= _moreButton.measuredWidth; 496 } 497 // tabs 498 int maxw = rc.width; 499 // measure and update visibility 500 TabItemWidget[] sorted = sortedItems(); 501 int w = 0; 502 for (int i = 0; i < sorted.length; i++) { 503 TabItemWidget widget = sorted[i]; 504 widget.visibility = Visibility.Visible; 505 widget.measure(rc.width, rc.height); 506 if (w + widget.measuredWidth < maxw) { 507 w += widget.measuredWidth - _buttonOverlap; 508 } else { 509 widget.visibility = Visibility.Gone; 510 } 511 } 512 // layout visible items 513 for (int i = 1; i < _children.count; i++) { 514 TabItemWidget widget = cast(TabItemWidget)_children.get(i); 515 if (widget.visibility != Visibility.Visible) 516 continue; 517 w = widget.measuredWidth; 518 rc.right = rc.left + w; 519 widget.layout(rc); 520 rc.left += w - _buttonOverlap; 521 } 522 //Log.d("tabControl.layout exit"); 523 } 524 525 /// Draw widget at its position to buffer 526 override void onDraw(DrawBuf buf) { 527 if (visibility != Visibility.Visible) 528 return; 529 //debug Log.d("TabControl.onDraw enter"); 530 super.Widget.onDraw(buf); 531 Rect rc = _pos; 532 applyMargins(rc); 533 applyPadding(rc); 534 auto saver = ClipRectSaver(buf, rc); 535 // draw all items except selected 536 for (int i = _children.count - 1; i >= 0; i--) { 537 Widget item = _children.get(i); 538 if (item.visibility != Visibility.Visible) 539 continue; 540 if (item.id.equal(_selectedTabId)) // skip selected 541 continue; 542 item.onDraw(buf); 543 } 544 // draw selected item 545 for (int i = 0; i < _children.count; i++) { 546 Widget item = _children.get(i); 547 if (item.visibility != Visibility.Visible) 548 continue; 549 if (!item.id.equal(_selectedTabId)) // skip all except selected 550 continue; 551 item.onDraw(buf); 552 } 553 //debug Log.d("TabControl.onDraw exit"); 554 } 555 556 protected string _selectedTabId; 557 558 @property string selectedTabId() const { 559 return _selectedTabId; 560 } 561 562 void updateAccessTs() { 563 int index = _items.indexById(_selectedTabId); 564 if (index >= 0) 565 _items[index].updateAccessTs(); 566 } 567 568 void selectTab(int index, bool updateAccess) { 569 if (index < 0 || index + 1 >= _children.count) { 570 Log.e("Tried to access tab out of bounds (index = %d, count = %d)",index,_children.count-1); 571 return; 572 } 573 if (_children.get(index + 1).compareId(_selectedTabId)) 574 return; // already selected 575 string previousSelectedTab = _selectedTabId; 576 for (int i = 1; i < _children.count; i++) { 577 if (index == i - 1) { 578 _children.get(i).state = State.Selected; 579 _selectedTabId = _children.get(i).id; 580 if (updateAccess) 581 updateAccessTs(); 582 } else { 583 _children.get(i).state = State.Normal; 584 } 585 } 586 if (tabChanged.assigned) 587 tabChanged(_selectedTabId, previousSelectedTab); 588 } 589 590 } 591 592 /// container for widgets controlled by TabControl 593 class TabHost : FrameLayout, TabHandler { 594 /// empty parameter list constructor - for usage by factory 595 this() { 596 this(null); 597 } 598 /// create with ID parameter 599 this(string ID, TabControl tabControl = null) { 600 super(ID); 601 _tabControl = tabControl; 602 if (_tabControl !is null) 603 _tabControl.tabChanged = &onTabChanged; 604 styleId = STYLE_TAB_HOST; 605 } 606 protected TabControl _tabControl; 607 /// get currently set control widget 608 @property TabControl tabControl() { return _tabControl; } 609 /// set new control widget 610 @property TabHost tabControl(TabControl newWidget) { 611 _tabControl = newWidget; 612 if (_tabControl !is null) 613 _tabControl.tabChanged = &onTabChanged; 614 return this; 615 } 616 617 protected Visibility _hiddenTabsVisibility = Visibility.Invisible; 618 @property Visibility hiddenTabsVisibility() { return _hiddenTabsVisibility; } 619 @property void hiddenTabsVisibility(Visibility v) { _hiddenTabsVisibility = v; } 620 621 /// signal of tab change (e.g. by clicking on tab header) 622 Signal!TabHandler tabChanged; 623 624 protected override void onTabChanged(string newActiveTabId, string previousTabId) { 625 if (newActiveTabId !is null) { 626 showChild(newActiveTabId, _hiddenTabsVisibility, true); 627 } 628 if (tabChanged.assigned) 629 tabChanged(newActiveTabId, previousTabId); 630 } 631 632 /// get tab content widget by id 633 Widget tabBody(string id) { 634 for (int i = 0; i < _children.count; i++) { 635 if (_children[i].compareId(id)) 636 return _children[i]; 637 } 638 return null; 639 } 640 641 /// remove tab 642 TabHost removeTab(string id) { 643 assert(_tabControl !is null, "No TabControl set for TabHost"); 644 Widget child = removeChild(id); 645 if (child !is null) { 646 destroy(child); 647 } 648 _tabControl.removeTab(id); 649 requestLayout(); 650 return this; 651 } 652 /// add new tab by id and label string 653 TabHost addTab(Widget widget, dstring label, string iconId = null, bool enableCloseButton = false) { 654 assert(_tabControl !is null, "No TabControl set for TabHost"); 655 assert(widget.id !is null, "ID for tab host page is mandatory"); 656 assert(_children.indexOf(id) == -1, "duplicate ID for tab host page"); 657 _tabControl.addTab(widget.id, label, iconId, enableCloseButton); 658 tabInitialization(widget); 659 //widget.focusGroup = true; // doesn't allow move focus outside of tab content 660 addChild(widget); 661 return this; 662 } 663 /// add new tab by id and label string resource id 664 TabHost addTab(Widget widget, string labelResourceId, string iconId = null, bool enableCloseButton = false) { 665 assert(_tabControl !is null, "No TabControl set for TabHost"); 666 assert(widget.id !is null, "ID for tab host page is mandatory"); 667 assert(_children.indexOf(id) == -1, "duplicate ID for tab host page"); 668 _tabControl.addTab(widget.id, labelResourceId, iconId, enableCloseButton); 669 tabInitialization(widget); 670 addChild(widget); 671 return this; 672 } 673 674 // handles initial tab selection & hides subsequently added tabs so 675 // they don't appear in the same frame 676 private void tabInitialization(Widget widget) { 677 if(_tabControl.selectedTabId is null) { 678 selectTab(_tabControl.tab(0).id,false); 679 } else { 680 widget.visibility = Visibility.Invisible; 681 } 682 } 683 684 /// select tab 685 void selectTab(string ID, bool updateAccess) { 686 int index = _tabControl.tabIndex(ID); 687 if (index != -1) { 688 _tabControl.selectTab(index, updateAccess); 689 } 690 } 691 // /// request relayout of widget and its children 692 // override void requestLayout() { 693 // Log.d("TabHost.requestLayout called"); 694 // super.requestLayout(); 695 // //_needLayout = true; 696 // } 697 // /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout). 698 // override void layout(Rect rc) { 699 // Log.d("TabHost.layout() called"); 700 // super.layout(rc); 701 // Log.d("after layout(): needLayout = ", needLayout); 702 // } 703 704 } 705 706 707 708 /// compound widget - contains from TabControl widget (tabs header) and TabHost (content pages) 709 class TabWidget : VerticalLayout, TabHandler, TabCloseHandler { 710 protected TabControl _tabControl; 711 protected TabHost _tabHost; 712 /// empty parameter list constructor - for usage by factory 713 this() { 714 this(null); 715 } 716 /// create with ID parameter 717 this(string ID, Align tabAlignment = Align.Top) { 718 super(ID); 719 _tabControl = new TabControl("TAB_CONTROL", tabAlignment); 720 _tabHost = new TabHost("TAB_HOST", _tabControl); 721 _tabControl.tabChanged.connect(this); 722 _tabControl.tabClose.connect(this); 723 styleId = STYLE_TAB_WIDGET; 724 if (tabAlignment == Align.Top) { 725 addChild(_tabControl); 726 addChild(_tabHost); 727 } else { 728 addChild(_tabHost); 729 addChild(_tabControl); 730 } 731 focusGroup = true; 732 } 733 734 TabControl tabControl() { return _tabControl; } 735 TabHost tabHost() { return _tabHost; } 736 737 /// signal of tab change (e.g. by clicking on tab header) 738 Signal!TabHandler tabChanged; 739 /// signal on tab close button 740 Signal!TabCloseHandler tabClose; 741 742 protected override void onTabClose(string tabId) { 743 if (tabClose.assigned) 744 tabClose(tabId); 745 } 746 747 protected override void onTabChanged(string newActiveTabId, string previousTabId) { 748 // forward to listener 749 if (tabChanged.assigned) 750 tabChanged(newActiveTabId, previousTabId); 751 } 752 753 /// add new tab by id and label string resource id 754 TabWidget addTab(Widget widget, string labelResourceId, string iconId = null, bool enableCloseButton = false) { 755 _tabHost.addTab(widget, labelResourceId, iconId, enableCloseButton); 756 return this; 757 } 758 759 /// add new tab by id and label (raw value) 760 TabWidget addTab(Widget widget, dstring label, string iconId = null, bool enableCloseButton = false) { 761 _tabHost.addTab(widget, label, iconId, enableCloseButton); 762 return this; 763 } 764 765 /// remove tab by id 766 TabWidget removeTab(string id) { 767 _tabHost.removeTab(id); 768 requestLayout(); 769 return this; 770 } 771 772 /// change name of tab 773 void renameTab(string ID, dstring name) { 774 _tabControl.renameTab(ID, name); 775 } 776 777 /// change name of tab 778 void renameTab(int index, dstring name) { 779 _tabControl.renameTab(index, name); 780 } 781 782 @property Visibility hiddenTabsVisibility() { return _tabHost.hiddenTabsVisibility; } 783 @property void hiddenTabsVisibility(Visibility v) { _tabHost.hiddenTabsVisibility = v; } 784 785 /// select tab 786 void selectTab(string ID, bool updateAccess = true) { 787 _tabHost.selectTab(ID, updateAccess); 788 } 789 790 /// select tab 791 void selectTab(int index, bool updateAccess = true) { 792 _tabControl.selectTab(index, updateAccess); 793 } 794 795 /// get tab content widget by id 796 Widget tabBody(string id) { 797 return _tabHost.tabBody(id); 798 } 799 800 /// get tab content widget by id 801 Widget tabBody(int index) { 802 string id = _tabControl.tab(index).id; 803 return _tabHost.tabBody(id); 804 } 805 806 /// returns tab item by id (null if index out of range) 807 TabItem tab(int index) { 808 return _tabControl.tab(index); 809 } 810 /// returns tab item by id (null if not found) 811 TabItem tab(string id) { 812 return _tabControl.tab(id); 813 } 814 /// returns tab count 815 @property int tabCount() const { 816 return _tabControl.tabCount; 817 } 818 /// get tab index by tab id (-1 if not found) 819 int tabIndex(string id) { 820 return _tabControl.tabIndex(id); 821 } 822 823 /// change style ids 824 void setStyles(string tabWidgetStyle, string tabStyle, string tabButtonStyle, string tabButtonTextStyle, string tabHostStyle = null) { 825 styleId = tabWidgetStyle; 826 _tabControl.setStyles(tabStyle, tabButtonStyle, tabButtonTextStyle); 827 _tabHost.styleId = tabHostStyle; 828 } 829 830 private bool _tabNavigationInProgress; 831 832 /// process key event, return true if event is processed. 833 override bool onKeyEvent(KeyEvent event) { 834 if (_tabNavigationInProgress) { 835 if (event.action == KeyAction.KeyDown || event.action == KeyAction.KeyUp) { 836 if (!(event.flags & KeyFlag.Control)) { 837 _tabNavigationInProgress = false; 838 _tabControl.updateAccessTs(); 839 } 840 } 841 } 842 if (event.action == KeyAction.KeyDown) { 843 if (event.keyCode == KeyCode.TAB && (event.flags & KeyFlag.Control)) { 844 // support Ctrl+Tab and Ctrl+Shift+Tab for navigation 845 _tabNavigationInProgress = true; 846 int direction = (event.flags & KeyFlag.Shift) ? - 1 : 1; 847 int index = _tabControl.getNextItemIndex(direction); 848 if (index >= 0) 849 selectTab(index, false); 850 return true; 851 } 852 } 853 return super.onKeyEvent(event); 854 } 855 856 @property const(TabItem) selectedTab() const { 857 return _tabControl.tab(selectedTabId); 858 } 859 860 @property TabItem selectedTab() { 861 return _tabControl.tab(selectedTabId); 862 } 863 864 @property string selectedTabId() const { 865 return _tabControl._selectedTabId; 866 } 867 868 /// get tab content widget by id 869 Widget selectedTabBody() { 870 return _tabHost.tabBody(_tabControl._selectedTabId); 871 } 872 873 }