NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

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}