tabGroup.py (35280B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\carbonui\control\tabGroup.py 2 import logging 3 import weakref 4 import eveformat 5 import eveicon 6 import mathext 7 import telemetry 8 import uthread 9 from carbonui import fontconst, uiconst 10 from carbonui.control.buttonIcon import ButtonIcon 11 from carbonui.control.contextMenu.menuData import MenuData 12 from carbonui.control.contextMenu.menuEntryData import MenuEntryData 13 from carbonui.control.tab import Tab 14 from carbonui.decorative.divider_line import DividerLine 15 from carbonui.decorative.selectionIndicatorLine import SelectionIndicatorLine 16 from carbonui.primitives.container import Container 17 from carbonui.primitives.containerAutoSize import ContainerAutoSize 18 from carbonui.uianimations import animations 19 from carbonui.uicore import uicore 20 from carbonui.util.bunch import Bunch 21 from carbonui.util.dpi import reverse_scale_dpi, scale_dpi 22 from carbonui.util.various_unsorted import divide_evenly 23 from eve.client.script.ui import eveThemeColor 24 log = logging.getLogger(__name__) 25 26 class TabGroup(Container): 27 __guid__ = 'uicontrols.TabGroup' 28 default_name = 'tabgroup' 29 default_align = uiconst.TOTOP 30 default_height = 32 31 default_state = uiconst.UI_PICKCHILDREN 32 default_minTabsize = 20 33 default_groupID = None 34 default_callback = None 35 default_padBottom = 8 36 default_tabClass = Tab 37 default_tabAlign = uiconst.TOLEFT 38 default_autoSelect = True 39 default_analyticID = None 40 default_tabSpacing = 24 41 default_minTabSpacing = 12 42 default_fontsize = fontconst.EVE_MEDIUM_FONTSIZE 43 default_labelPadding = 16 44 default_leftMargin = 0 45 default_rightMargin = 0 46 default_tabReservedSpace = 0 47 _line = None 48 _main_cont = None 49 _tab_sizes_dirty = False 50 _trailing_buttons = None 51 _updating_size = False 52 _updating_tab_sizes = False 53 _selected_tab = None 54 _selection_indicator = None 55 _selection_indicator_destination = (0, 0) 56 57 def __init__(self, show_line = True, auto_size = False, show_selection_indicator = True, **kwargs): 58 if 'showLine' in kwargs: 59 show_line = kwargs['showLine'] 60 self._auto_size = auto_size 61 self._show_line = show_line 62 self._show_selection_indicator = show_selection_indicator 63 super(TabGroup, self).__init__(**kwargs) 64 65 @telemetry.ZONE_METHOD 66 def ApplyAttributes(self, attributes): 67 Container.ApplyAttributes(self, attributes) 68 self._inited = 0 69 self.tabData = [] 70 self.tabsmenu = None 71 self.sr.tabs = [] 72 self.mytabs = [] 73 self.totalTabWidth = 0 74 self.linkedrows = [] 75 self.selectedTab = None 76 self._tab_sizes_dirty = False 77 self._tabs_by_label = weakref.WeakValueDictionary() 78 self._autoSelectThread = None 79 self.leftMargin = attributes.get('leftMargin', self.default_leftMargin) 80 self.rightMargin = attributes.get('rightMargin', self.default_rightMargin) 81 self.minTabsize = attributes.get('minTabsize', self.default_minTabsize) 82 self.labelPadding = attributes.get('labelPadding', self.default_labelPadding) 83 self.callback = attributes.get('callback', self.default_callback) 84 self.tabReservedSpace = attributes.get('tabReservedSpace', self.default_tabReservedSpace) 85 self.fontsize = attributes.get('fontsize', self.default_fontsize) 86 self.tabClass = attributes.get('tabClass', self.default_tabClass) 87 self.groupID = attributes.get('groupID', self.default_groupID) 88 self.autoselecttab = attributes.get('autoselecttab', self.default_autoSelect) 89 self.silently = attributes.get('silently', False) 90 self.settingsID = attributes.get('settingsID', self.groupID) 91 self.tabAlign = attributes.get('tabAlign', self.default_tabAlign) 92 self._analyticID = attributes.get('analyticID', self.default_analyticID) 93 self._tab_spacing = attributes.get('tabSpacing', self.default_tabSpacing) 94 self.minTabSpacing = attributes.get('minTabSpacing', self.default_minTabSpacing) 95 self.UIIDPrefix = attributes.UIIDPrefix 96 self.tabs = attributes.tabs 97 self._layout() 98 99 @property 100 def show_line(self): 101 return self._show_line 102 103 @show_line.setter 104 def show_line(self, value): 105 if self._show_line != value: 106 self._show_line = value 107 self._update_line() 108 109 def _layout(self): 110 if self._show_selection_indicator: 111 self._selection_indicator = SelectionIndicatorLine(parent=ContainerAutoSize(parent=self, align=uiconst.TOBOTTOM_NOPUSH), align=uiconst.TOPLEFT, height=1, width=0, selected=True) 112 if self.tabAlign in uiconst.PROPORTIONALALIGNMENTS: 113 align = uiconst.TOALL 114 else: 115 align = uiconst.TOLEFT 116 self._main_cont = ContainerAutoSize(parent=self, align=align, alignMode=self.tabAlign) 117 if self._show_line: 118 self._construct_line() 119 if self.tabAlign in uiconst.PROPORTIONALALIGNMENTS: 120 self.tabsCont = Container(name='tabsCont', parent=self._main_cont) 121 else: 122 self.tabsCont = ContainerAutoSize(name='tabsCont', parent=self._main_cont, align=self.tabAlign, alignMode=self.tabAlign) 123 self._trailing_buttons = TrailingContainer(parent=self._main_cont, align=uiconst.TOLEFT, alignMode=uiconst.TOLEFT, spacing_left=self.tabSpacing, callback=self._on_trailing_container_size_changed, only_use_callback_when_size_changes=True) 124 if self.tabs: 125 for each in self.tabs: 126 if isinstance(each, dict): 127 self.AddTab(**each) 128 else: 129 self.AddTab(*each) 130 131 if self.autoselecttab: 132 self.AutoSelect(self.silently) 133 134 def _construct_line(self): 135 self._line = DividerLine(parent=self._main_cont, align=uiconst.TOBOTTOM_NOPUSH, idx=0) 136 137 def _update_line(self): 138 if self._show_line: 139 if self._line is None: 140 self._construct_line() 141 else: 142 self._line.display = True 143 elif self._line is not None: 144 self._line.display = False 145 146 @property 147 def analyticID(self): 148 return self._analyticID 149 150 @analyticID.setter 151 def analyticID(self, value): 152 self._analyticID = value 153 154 @property 155 def tabSpacing(self): 156 return self._tab_spacing 157 158 @tabSpacing.setter 159 def tabSpacing(self, value): 160 if self._tab_spacing != value: 161 self._tab_spacing = value 162 self.UpdateSizes() 163 164 tab_spacing = tabSpacing 165 166 @telemetry.ZONE_METHOD 167 def Startup(self, tabs, groupID = None, _notUsed_callback = None, _notUsed_isSub = 0, _notUsed_detachable = 0, autoselecttab = 1, UIIDPrefix = None, silently = False): 168 self.settingsID = groupID 169 self.autoselecttab = autoselecttab 170 self.UIIDPrefix = UIIDPrefix 171 self.silently = silently 172 for each in tabs: 173 if isinstance(each, dict): 174 self.AddTab(**each) 175 else: 176 self.AddTab(*each) 177 178 if self.autoselecttab: 179 self.AutoSelect(self.silently) 180 181 def AddTab(self, label, panel = None, code = None, tabID = None, panelParent = None, hint = '', tabClass = None, enabled = True, callback = None, uniqueName = None, **kwargs): 182 if tabClass is None: 183 tabClass = self.tabClass 184 if not self._inited: 185 self._inited = True 186 label, name = self.GetNameLabel(label) 187 uiID = self.GetUIID(name, tabID) 188 tabData = self.GetTabData(tabID, code, hint, label, panel, panelParent, tabClass, uiID, callback) 189 newtab = tabClass(parent=self.tabsCont, labelPadding=self.labelPadding, fontsize=self.fontsize, align=self.tabAlign, tabID=tabID, enabled=enabled, uniqueUiName=uniqueName, **kwargs) 190 self.mytabs.append(newtab) 191 newtab.Startup(self, tabData) 192 self._tabs_by_label[tabData.label] = newtab 193 self.sr.Set('%s_tab' % tabData.label, newtab) 194 self.sr.tabs.append(newtab) 195 self.tabData.append(tabData) 196 self.height = max(self.height, int(self.GetMaxHeight() * 1.7)) 197 self.UpdateSizes() 198 return newtab 199 200 def AddButton(self, icon, func = None, hint = None, btnClass = ButtonIcon): 201 return btnClass(parent=ContainerAutoSize(parent=self._trailing_buttons, align=uiconst.TOLEFT), align=uiconst.CENTER, width=24, height=24, texturePath=icon, iconSize=16, func=func, hint=hint) 202 203 def Flush(self): 204 self.ClearTabs() 205 self._trailing_buttons.Flush() 206 207 def ClearTabs(self): 208 for child in self.tabsCont.children[:]: 209 if isinstance(child, Tab): 210 child.Close() 211 212 self.sr.tabs = [] 213 self._tabs_by_label.clear() 214 self.tabData = [] 215 self.mytabs = [] 216 self._KillAutoSelectThread() 217 218 def GetNameLabel(self, label): 219 if isinstance(label, tuple): 220 name, label = label 221 else: 222 name = label 223 return (label, name) 224 225 def GetUIID(self, name, tabID = None): 226 if isinstance(tabID, (basestring, int)): 227 if self.UIIDPrefix is not None: 228 return '%s%s' % (self.UIIDPrefix, tabID) 229 else: 230 return unicode(tabID) 231 elif self.UIIDPrefix is not None: 232 secondPart = name.replace(' ', '') 233 secondPart = secondPart.capitalize() 234 return '%s%s' % (self.UIIDPrefix, secondPart) 235 return 'tab' 236 237 def GetTabData(self, tabID, code, hint, label, panel, panelParent, tabClass, uiID, callback): 238 tabData = Bunch() 239 tabData.label = label 240 tabData.code = code 241 tabData.args = tabID 242 tabData.panel = panel 243 tabData.panelparent = panelParent 244 tabData.hint = hint 245 tabData.name = uiID 246 tabData.tabClass = tabClass 247 tabData.callback = callback 248 return tabData 249 250 def GetMaxHeight(self): 251 maxHeight = 0 252 for tab in self.mytabs: 253 tabHeight = tab.GetHeight() 254 maxHeight = max(tabHeight, maxHeight) 255 256 return maxHeight 257 258 def UpdateUIScaling(self, value, oldValue): 259 super(TabGroup, self).UpdateUIScaling(value, oldValue) 260 if not self.destroyed: 261 self.UpdateSizes() 262 263 @telemetry.ZONE_METHOD 264 def Prepare_Tabsmenu_(self): 265 if self.tabsmenu is None: 266 self.tabsmenu = ContainerAutoSize(parent=self._trailing_buttons, align=uiconst.TOLEFT) 267 TabOverflowMenuButton(parent=self.tabsmenu, align=uiconst.CENTER, tab_group=self) 268 269 def _CloseTabsMenu(self): 270 if self.tabsmenu is not None: 271 self.tabsmenu.Close() 272 self.tabsmenu = None 273 274 def GetTabsMenu(self): 275 return [ (eveformat.bold(each.label.text) if each.IsSelected() else each.label.text, self.SelectByIdx, (self.sr.tabs.index(each),)) for each in self.sr.tabs ] 276 277 def _OnSizeChange_NoBlock(self, width, height): 278 Container._OnSizeChange_NoBlock(self, width, height) 279 280 def UpdateAlignment(self, budgetLeft = 0, budgetTop = 0, budgetWidth = 0, budgetHeight = 0, updateChildrenOnly = False): 281 if self._auto_size: 282 if self.align in uiconst.ALIGNMENTS_WITH_RELEVANT_WIDTH: 283 budget_width = max(0, reverse_scale_dpi(budgetWidth) - self.padLeft - self.padRight) 284 else: 285 budget_width = None 286 self._update_size(budget_width) 287 result = super(TabGroup, self).UpdateAlignment(budgetLeft, budgetTop, budgetWidth, budgetHeight, updateChildrenOnly) 288 if self._show_selection_indicator: 289 self._update_selection_indicator(animate=False) 290 return result 291 else: 292 a, b, c, d, size_changed = super(TabGroup, self).UpdateAlignment(budgetLeft, budgetTop, budgetWidth, budgetHeight, updateChildrenOnly) 293 if size_changed or self._tab_sizes_dirty: 294 self._update_tab_sizes() 295 if self._childrenAlignmentDirty: 296 super(TabGroup, self).UpdateAlignment(budgetLeft, budgetTop, budgetWidth, budgetHeight, updateChildrenOnly=True) 297 if self._show_selection_indicator: 298 self._update_selection_indicator(animate=False) 299 return (a, 300 b, 301 c, 302 d, 303 size_changed) 304 305 def _get_height(self): 306 height = 32 307 for tab in self.tabs: 308 height = max(height, tab.GetHeight()) 309 310 return height 311 312 def _on_trailing_container_size_changed(self): 313 self._tab_sizes_dirty = True 314 315 def _update_size(self, budget_width = None): 316 if self.destroyed or self._updating_size: 317 return 318 self._updating_size = True 319 try: 320 if self.align in uiconst.ALIGNMENTS_WITH_RELEVANT_WIDTH: 321 tabs = self.mytabs[:] 322 for tab in tabs: 323 tab.UpdateTabSize() 324 325 tab_count = len(tabs) 326 gap_count = max(0, tab_count - 1) 327 total_size = sum((max(self.minTabsize, tab.sr.width) for tab in tabs)) + gap_count * self.tabSpacing 328 if self._trailing_buttons is not None and len(self._trailing_buttons.children) > 0: 329 if self._trailing_buttons._alignmentDirty or self._trailing_buttons._childrenAlignmentDirty or self._trailing_buttons._forceUpdateAlignment: 330 self._trailing_buttons.UpdateAlignment(budgetLeft=0, budgetTop=0, budgetWidth=scale_dpi(budget_width), budgetHeight=self._get_height(), updateChildrenOnly=False) 331 if tab_count > 0: 332 total_size += self.tabSpacing 333 total_size += self._trailing_buttons.width 334 width_before = self.width 335 if budget_width is not None and total_size > budget_width: 336 width = budget_width 337 else: 338 width = total_size 339 if self.align in uiconst.ALIGNMENTS_WITH_INCLUDED_HORIZONTAL_PADDING: 340 width += self.padLeft + self.padRight 341 self.width = width 342 if width_before != self.width: 343 self._tab_sizes_dirty = True 344 else: 345 self.width = 0 346 if self._tab_sizes_dirty: 347 self._update_tab_sizes() 348 finally: 349 self._updating_size = False 350 351 @telemetry.ZONE_METHOD 352 def UpdateSizes(self, absSize = None): 353 self._tab_sizes_dirty = True 354 self.FlagAlignmentDirty(hint='TabGroup.UpdateSizes') 355 356 def _update_tab_sizes(self): 357 if self.destroyed or not self.mytabs: 358 return 359 self._tab_sizes_dirty = True 360 if self._updating_tab_sizes: 361 return 362 self._updating_tab_sizes = True 363 try: 364 self._tab_sizes_dirty = False 365 if self._auto_size: 366 tab_group_width = self.width 367 else: 368 tab_group_width, _ = self.GetCurrentAbsoluteSize() 369 tabs = self.mytabs[:] 370 if not self._auto_size: 371 for tab in tabs: 372 tab.UpdateTabSize() 373 374 tab_count = len(tabs) 375 space_count = tab_count - 1 376 available_width = tab_group_width 377 if len(self._trailing_buttons.children) > 0: 378 space_count += 1 379 available_width -= self._trailing_buttons.width + self._trailing_buttons.padRight 380 tabs_min_total_space = sum((min(self.minTabsize, tab.sr.width) for tab in tabs)) + space_count * self.minTabSpacing 381 overflow_at_min = max(tabs_min_total_space - available_width, 0) 382 if overflow_at_min > 0: 383 selected_tab_index = self.GetSelectedIdx() or 0 384 first_visible_index = 0 385 last_visible_index = 0 386 accumulated_width = 0 387 if len(self._trailing_buttons.children) > 0: 388 accumulated_width += self.minTabSpacing 389 out_of_space = False 390 for i, tab in enumerate(tabs): 391 tab_space = self.minTabSpacing if i != 0 else 0 392 tab_size = min(self.minTabsize, tab.sr.width) 393 out_of_space = out_of_space or accumulated_width + tab_space + tab_size > available_width 394 if out_of_space and i > selected_tab_index: 395 tab.state = uiconst.UI_HIDDEN 396 continue 397 accumulated_width += tab_space + tab_size 398 last_visible_index = i 399 400 for i, tab in enumerate(tabs): 401 if accumulated_width <= available_width or i == last_visible_index: 402 break 403 tab.state = uiconst.UI_HIDDEN 404 tab_space = self.minTabSpacing 405 tab_size = min(self.minTabsize, tab.sr.width) 406 accumulated_width -= tab_size + tab_space 407 first_visible_index = i + 1 408 409 tabs = tabs[first_visible_index:last_visible_index + 1] 410 tab_count = len(tabs) 411 if tab_count != len(self.mytabs): 412 self.Prepare_Tabsmenu_() 413 else: 414 self._CloseTabsMenu() 415 if len(self._trailing_buttons.children) > 0: 416 space_count = tab_count 417 else: 418 space_count = tab_count - 1 419 tabs_total_width = sum((tab.sr.width for tab in tabs)) 420 spacing_total_width = space_count * self.tabSpacing 421 total_width = tabs_total_width + spacing_total_width 422 overflow = max(total_width - available_width, 0) 423 if overflow > 0: 424 spacing_min_total = space_count * self.minTabSpacing 425 compressed_spacing_total = max(spacing_total_width - overflow, spacing_min_total) 426 overflow -= spacing_total_width - compressed_spacing_total 427 else: 428 compressed_spacing_total = spacing_total_width 429 tab_sizes = [ TabSize(i, tab.sr.width) for i, tab in enumerate(tabs) ] 430 if overflow > 0: 431 tab_sizes = sorted(tab_sizes, reverse=True, key=lambda tab: (tab.size, -tab.index)) 432 remaining_overflow = overflow 433 for i in range(tab_count): 434 if remaining_overflow == 0: 435 break 436 next_diff = max(tab_sizes[i].size - self.minTabsize, 0) 437 if i + 1 < tab_count: 438 next_diff = min(next_diff, tab_sizes[i].size - tab_sizes[i + 1].size) 439 compress_by = min(next_diff * (i + 1), remaining_overflow) 440 if compress_by == 0: 441 continue 442 for x, tab in enumerate(tab_sizes[:i + 1]): 443 tab.size -= divide_evenly(compress_by, i - x, i + 1) 444 445 remaining_overflow -= compress_by 446 447 tab_sizes = [ tab.size for tab in sorted(tab_sizes, key=lambda tab: tab.index) ] 448 prev_tab = None 449 gap = self.tabSpacing 450 for i, (tab, size) in enumerate(zip(tabs, tab_sizes)): 451 if space_count > 0: 452 gap = divide_evenly(compressed_spacing_total, i, space_count) 453 if i == 0: 454 tab.left = 0 455 tab.gap_left = gap 456 else: 457 tab.left = gap 458 tab.gap_left = gap 459 prev_tab.gap_right = gap 460 if not tab.enabled: 461 tab.Disable() 462 else: 463 tab.Enable() 464 tab.width = size 465 prev_tab = tab 466 467 if prev_tab is not None: 468 prev_tab.gap_right = gap 469 if space_count > 0: 470 self._trailing_buttons.spacing_left = divide_evenly(compressed_spacing_total, space_count - 1, space_count) 471 else: 472 self._trailing_buttons.spacing_left = 0 473 finally: 474 self._updating_tab_sizes = False 475 476 def _UpdateSizesOld(self, absSize = None): 477 if self.destroyed or not self._inited or not (self.sr and self.mytabs) or not self.IsUnder(uicore.desktop): 478 return 479 if self._updating_tab_sizes: 480 return 481 self._updating_tab_sizes = True 482 try: 483 if absSize: 484 mw, _ = absSize 485 else: 486 mw, _ = self.GetAbsoluteSize() 487 for tab in self.mytabs: 488 tab.UpdateTabSize() 489 490 totalSpacingWidth = max(len(self.mytabs) - 1, 0) * self.tabSpacing 491 totalTabWidth = sum([ each.sr.width for each in self.mytabs ]) 492 totalSpace = mw - self.leftMargin - self.rightMargin 493 needToShrink = max(0, totalTabWidth + totalSpacingWidth - totalSpace) 494 totalShrunk = 0 495 allMin = True 496 for each in self.mytabs: 497 portionOfFull = each.sr.width / float(totalTabWidth) 498 each.portionOfFull = portionOfFull 499 each.width = mathext.clamp(value=each.sr.width, low=self.minTabsize, high=each.sr.width - int(needToShrink * portionOfFull)) 500 if each.width > self.minTabsize: 501 allMin = False 502 totalShrunk += each.sr.width - each.width 503 504 needMore = max(0, needToShrink - totalShrunk) 505 while needMore and not allMin: 506 _allMin = True 507 for each in self.mytabs: 508 if each.width > self.minTabsize and needMore > 0: 509 each.width -= 1 510 needMore = max(0, needMore - 1) 511 if each.width > self.minTabsize: 512 _allMin = False 513 514 allMin = _allMin 515 516 allMin = True 517 for each in self.mytabs: 518 if each.width != self.minTabsize: 519 allMin = False 520 521 if self.tabsmenu: 522 self.tabsmenu.Close() 523 self.tabsmenu = None 524 active = self.GetSelectedTab() 525 i = 0 526 i2 = 0 527 totalWidth = 0 528 totalVisible = 0 529 hidden = False 530 countActive = None 531 startHiddenIdx = None 532 for each in self.mytabs: 533 if allMin and (hidden or totalWidth + each.width > totalSpace): 534 if each == active: 535 countActive = i2 536 each.state = uiconst.UI_HIDDEN 537 if not hidden: 538 startHiddenIdx = i 539 hidden = True 540 i2 += 1 541 else: 542 each.state = uiconst.UI_NORMAL 543 totalWidth += each.width 544 totalVisible += 1 545 i += 1 546 547 if allMin: 548 if countActive is not None and startHiddenIdx is not None: 549 totalWidth = 0 550 totalVisible = 0 551 i = 0 552 for each in self.mytabs: 553 if i <= countActive: 554 each.state = uiconst.UI_HIDDEN 555 elif startHiddenIdx <= i <= startHiddenIdx + countActive: 556 if not each.enabled: 557 each.Disable() 558 else: 559 each.Enable() 560 if each.state == uiconst.UI_NORMAL: 561 totalWidth += each.width 562 totalVisible += 1 563 i += 1 564 565 self.totalTabWidth = totalWidth 566 totalVisibleWidth = self.leftMargin + self.tabReservedSpace 567 visibleSpacingWidth = totalVisible * self.tabSpacing 568 leftover = max(0, totalSpace - totalWidth - visibleSpacingWidth) 569 for each in self.mytabs: 570 if each.state == uiconst.UI_NORMAL: 571 each.width = mathext.clamp(value=each.sr.width, low=self.minTabsize, high=each.width + leftover / totalVisible) 572 totalVisibleWidth += each.width 573 574 if hidden: 575 self.Prepare_Tabsmenu_() 576 self.tabsmenu.left = totalVisibleWidth 577 self.tabsmenu.state = uiconst.UI_NORMAL 578 for tabgroup in self.linkedrows: 579 if tabgroup != self: 580 tabgroup.UpdateSizes() 581 582 finally: 583 self._updating_tab_sizes = False 584 585 def GetTabs(self): 586 if not self.destroyed: 587 return self.sr.tabs 588 589 def GetTotalWidth(self): 590 tw = sum([ each.sr.width for each in self.mytabs ]) 591 return tw + self.leftMargin + self.rightMargin + self.tabReservedSpace 592 593 def AddRow(self, tabgroup): 594 for tab in tabgroup.sr.tabs: 595 tab.tabgroup = self 596 self.sr.tabs.append(tab) 597 598 self.linkedrows.append(tabgroup) 599 if self not in self.linkedrows: 600 self.linkedrows.append(self) 601 tabgroup.UpdateSizes() 602 603 @telemetry.ZONE_METHOD 604 def AutoSelect(self, silently = False, useCallback = True): 605 if self.destroyed: 606 return 607 self._KillAutoSelectThread() 608 idx = self._FindAutoSelectTabIDx() 609 tab = self.sr.tabs[min(len(self.sr.tabs) - 1, idx)] 610 self._autoSelectThread = uthread.new(tab.Select, silently=silently, useCallback=useCallback, saveSelectedName=False) 611 self.selectedTab = tab 612 613 def CancelAutoSelect(self): 614 self._KillAutoSelectThread() 615 616 def _KillAutoSelectThread(self): 617 if hasattr(self, '_autoSelectThread') and self._autoSelectThread: 618 self._autoSelectThread.kill() 619 self._autoSelectThread = None 620 621 def _FindAutoSelectTabIDx(self): 622 if self.settingsID: 623 panelName = settings.user.tabgroups.Get('%s_names' % self.settingsID, None) 624 if panelName: 625 tab = self.GetPanelByName(panelName) 626 if tab and tab.enabled: 627 return self.sr.tabs.index(tab) 628 storedIdx = settings.user.tabgroups.Get(self.settingsID, None) 629 if storedIdx: 630 try: 631 tab = self.sr.tabs[storedIdx] 632 if tab and tab.enabled: 633 return storedIdx 634 except IndexError: 635 pass 636 637 for i, tab in enumerate(self.sr.tabs): 638 if tab.enabled: 639 return i 640 641 def SelectByIdx(self, idx, silent = 1): 642 if len(self.sr.tabs) > idx: 643 self.sr.tabs[idx].Select(silent) 644 645 def SelectPrev(self): 646 idx = self.GetSelectedIdx() 647 if idx is None: 648 return 649 idx -= 1 650 if idx < 0: 651 idx = len(self.sr.tabs) - 1 652 self.SelectByIdx(idx, silent=False) 653 654 def SelectNext(self): 655 idx = self.GetSelectedIdx() 656 if idx is None: 657 return 658 idx += 1 659 if idx > len(self.sr.tabs) - 1: 660 idx = 0 661 self.SelectByIdx(idx, silent=False) 662 663 def GetSelectedIdx(self): 664 for idx, tab in enumerate(self.sr.tabs): 665 if tab.IsSelected(): 666 return idx 667 668 def GetSelectedID(self): 669 return self.GetSelectedArgs() 670 671 def SelectByID(self, panelID, silent = False, useCallback = True): 672 for tab in self.sr.tabs: 673 if tab.sr.args == panelID: 674 if not tab.IsSelected(): 675 tab.Select(silently=silent, useCallback=useCallback) 676 elif tab.IsSelected(): 677 tab.Deselect() 678 679 def ShowPanel(self, panel, *args): 680 for tab in self.sr.tabs: 681 if tab.sr.panel == panel: 682 tab.Select(1) 683 684 def ShowPanelByID(self, panelID, silent = False, *args): 685 for tab in self.sr.tabs: 686 if tab.sr.args == panelID: 687 tab.Select(silently=silent) 688 689 def ShowPanelByName(self, panelname, blink = 1): 690 if panelname: 691 tab = self.GetPanelByName(panelname) 692 if tab: 693 tab.Select(1) 694 else: 695 log.warning("ShowPanelByName: Can't find panel with name %s", panelname) 696 697 def GetPanelByName(self, panelname): 698 return self.GetTabByLabel(panelname) 699 700 def GetTabByLabel(self, label): 701 tab = self._tabs_by_label.get(label, None) 702 if tab is not None and not tab.destroyed: 703 return tab 704 705 def GetTabByID(self, panelID): 706 for tab in self.sr.tabs: 707 if tab.sr.args == panelID: 708 return tab 709 710 def BlinkPanelByName(self, panelname, blink = 1): 711 if panelname: 712 tab = self.GetPanelByName(panelname) 713 if tab: 714 tab.Blink(blink) 715 else: 716 log.warning("BlinkPanelByName: Can't find panel with name %s", panelname) 717 718 def BlinkPanelByID(self, panelID): 719 tab = self.GetTabByID(panelID) 720 if tab: 721 tab.Blink() 722 723 def _OnClose(self, *args): 724 self.callback = None 725 self.linkedrows = [] 726 for each in self.sr.tabs: 727 if each is not None and not each.destroyed: 728 each.Close() 729 730 self.sr.tabs = None 731 self._tabs_by_label.clear() 732 self.btns = [] 733 Container._OnClose(self, *args) 734 735 def GetSelectedTab(self): 736 if self.destroyed: 737 return None 738 for tab in self.sr.tabs: 739 if tab.IsSelected(): 740 return tab 741 742 def GetSelectedPanel(self): 743 tab = self.GetSelectedTab() 744 if tab is not None: 745 return tab.sr.panel 746 747 def ReloadVisible(self): 748 tab = self.GetSelectedTab() 749 if tab: 750 tab.Select(1) 751 752 def GetSelectedArgs(self): 753 if not self.sr.tabs: 754 return None 755 for tab in self.sr.tabs: 756 if tab.IsSelected(): 757 return tab.sr.args 758 759 def OnGlobalFontSizeChanged(self): 760 super(TabGroup, self).OnGlobalFontSizeChanged() 761 self.UpdateSizes() 762 763 def _get_selection_indicator_destination(self): 764 tab = self.GetSelectedTab() 765 if tab: 766 left = reverse_scale_dpi(tab.displayX) 767 width = reverse_scale_dpi(tab.displayWidth) 768 return (left, width) 769 else: 770 return (0, 0) 771 772 def _update_selection_indicator(self, initial_selection = False, animate = True): 773 if self._show_selection_indicator and self._selection_indicator: 774 left, width = self._get_selection_indicator_destination() 775 if self._selection_indicator_destination != (left, width): 776 self._selection_indicator_destination = (left, width) 777 if animate and not initial_selection: 778 MIN_DURATION = 0.05 779 MAX_DURATION = 0.15 780 RANGE = 200 781 distance = abs(self._selection_indicator.left - left) 782 duration = MIN_DURATION + (MAX_DURATION - MIN_DURATION) * min(1.0, distance / RANGE) 783 animations.MorphScalar(self._selection_indicator, 'left', startVal=self._selection_indicator.left, endVal=left, duration=duration) 784 animations.MorphScalar(self._selection_indicator, 'width', startVal=self._selection_indicator.width, endVal=width, duration=duration) 785 else: 786 animations.StopAnimation(self._selection_indicator, 'left') 787 animations.StopAnimation(self._selection_indicator, 'width') 788 self._selection_indicator.left = left 789 self._selection_indicator.width = width 790 791 def _set_selected_tab(self, tab): 792 previous_tab = self._selected_tab 793 self._selected_tab = tab 794 if self._show_selection_indicator: 795 self._update_selection_indicator(initial_selection=previous_tab is None) 796 797 798 class TabOverflowMenuButton(ButtonIcon): 799 expandOnLeft = True 800 801 def __init__(self, tab_group, parent = None, align = uiconst.TOPLEFT): 802 self._tab_group_ref = weakref.ref(tab_group) 803 super(TabOverflowMenuButton, self).__init__(parent=parent, align=align, width=24, height=24, iconSize=16, texturePath=eveicon.expand_more) 804 805 def _select_tab_by_index(self, index): 806 tab_group = self._tab_group_ref() 807 if tab_group is not None: 808 tab_group.SelectByIdx(index) 809 810 def GetMenu(self): 811 tab_group = self._tab_group_ref() 812 if tab_group is None: 813 return [] 814 tabs = tab_group.GetTabs() 815 if tabs is None: 816 return [] 817 return MenuData([ MenuEntryData(text=eveformat.color(tab.label.text, eveThemeColor.THEME_FOCUS) if tab.IsSelected() else tab.label.text, func=lambda _index = index: self._select_tab_by_index(_index)) for index, tab in enumerate(tabs) ]) 818 819 820 class TrailingContainer(ContainerAutoSize): 821 822 def __init__(self, spacing_left = 0, spacing_right = 0, **kwargs): 823 if any((x in kwargs for x in ('padding', 'padLeft', 'padRight'))): 824 raise TypeError("Don't override this container's padding. Use spacing_left and spacing_right to adjust the container's padding.") 825 self._spacing = (spacing_left, spacing_right) 826 super(TrailingContainer, self).__init__(**kwargs) 827 828 @property 829 def spacing_left(self): 830 return self._spacing[0] 831 832 @spacing_left.setter 833 def spacing_left(self, value): 834 if self.spacing_left != value: 835 self._spacing = (value, self.spacing_right) 836 if len(self.children) > 0: 837 self._apply_spacing() 838 839 @property 840 def spacing_right(self): 841 return self._spacing[1] 842 843 @spacing_right.setter 844 def spacing_right(self, value): 845 if self.spacing_right != value: 846 self._spacing = (self.spacing_left, value) 847 if len(self.children) > 0: 848 self._apply_spacing() 849 850 def _apply_spacing(self): 851 self.padLeft, self.padRight = self._spacing 852 853 def _remove_spacing(self): 854 self.padLeft = 0 855 self.padRight = 0 856 857 def _InsertChildRO(self, idx, child): 858 super(TrailingContainer, self)._InsertChildRO(idx, child) 859 self._apply_spacing() 860 861 def _AppendChildRO(self, child): 862 super(TrailingContainer, self)._AppendChildRO(child) 863 self._apply_spacing() 864 865 def _RemoveChildRO(self, child): 866 super(TrailingContainer, self)._RemoveChildRO(child) 867 if len(self.children) == 0: 868 self._remove_spacing() 869 870 871 class TabSize(object): 872 873 def __init__(self, index, size): 874 self.index = index 875 self.size = size 876 877 878 def GetTabData(label, panel = None, code = None, tabID = None, panelParent = None, hint = '', tabClass = None, enabled = True, callback = None, uniqueName = None): 879 return {'label': label, 880 'panel': panel, 881 'code': code, 882 'tabID': tabID, 883 'panelParent': panelParent, 884 'hint': hint, 885 'tabClass': tabClass, 886 'enabled': enabled, 887 'callback': callback, 888 'uniqueName': uniqueName}