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

scroll.py (79456B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\carbonui\control\scroll.py
      2 import contextlib
      3 import logging
      4 import sys
      5 import weakref
      6 import blue
      7 import localization
      8 import mathext
      9 import telemetry
     10 import uthread
     11 import uthread2
     12 from carbon.common.script.util.commonutils import StripTags
     13 from carbonui import Axis, fontconst, uiconst
     14 from carbonui.control.contextMenu.menuUtil import GetContextMenuOwner, HasContextMenu
     15 from carbonui.control.label import LabelCore
     16 from carbonui.control.scrollbar import Scrollbar
     17 from carbonui.control.scrollColumnHeader import ColumnHeader
     18 from carbonui.control.scroll_const import SortDirection, GetHiddenColumnsKey
     19 from carbonui.control.scrollentries import SE_ListGroupCore
     20 from carbonui.decorative.panelUnderlay import PanelUnderlay
     21 from carbonui.fontconst import DEFAULT_FONTSIZE, DEFAULT_LETTERSPACE, DEFAULT_UPPERCASE
     22 from carbonui.primitives.container import Container
     23 from carbonui.primitives.containerAutoSize import ContainerAutoSize
     24 from carbonui.primitives.frame import Frame
     25 from carbonui.text.color import TextColor
     26 from carbonui.uianimations import animations
     27 from carbonui.uicore import uicore
     28 from carbonui.util.stringManip import GetAsUnicode
     29 from carbonui.util.various_unsorted import GetAttrs, GetWindowAbove, SortListOfTuples
     30 from carbonui.control.buttonIcon import ButtonIcon
     31 from eve.client.script.ui.control.entries.generic import Generic
     32 from eve.client.script.ui.control.entries.util import GetFromClass
     33 from eve.client.script.ui.control.eveLabel import EveCaptionLarge
     34 from eve.client.script.ui.control.eveLoadingWheel import LoadingWheel
     35 from player_messaging.client.ui_message import message_player
     36 from eve.client.script.ui.control.themeColored import LineThemeColored
     37 from eve.common.lib import appConst as const
     38 from eveexceptions import ExceptionEater
     39 from menu import MenuLabel
     40 from signals import Signal
     41 log = logging.getLogger(__name__)
     42 MINCOLUMNWIDTH = 24
     43 VERSION = uiconst.SCROLLVERSION
     44 TABMARGIN = uiconst.LABELTABMARGIN
     45 MAIN_CONTAINER_PADDING = 0
     46 PRIMARY_COLUMN = 'primaryColumn_%s'
     47 SMART_SORT_DIRECTION = 'smartSortDirection_%s'
     48 
     49 def SmartCompare(x, y, sortOrder):
     50     for column, direction in sortOrder:
     51         if direction == SortDirection.ASCENDING:
     52             if x[0][column] > y[0][column]:
     53                 return 1
     54             if x[0][column] < y[0][column]:
     55                 return -1
     56         elif direction == SortDirection.DESCENDING:
     57             if x[0][column] < y[0][column]:
     58                 return 1
     59             if x[0][column] > y[0][column]:
     60                 return -1
     61         else:
     62             raise ValueError('Unknown sort direction')
     63 
     64     return 0
     65 
     66 
     67 class Scroll(Container):
     68     default_name = 'scroll'
     69     default_multiSelect = 1
     70     default_stickToBottom = 0
     71     default_smartSort = 0
     72     default_id = None
     73     default_state = uiconst.UI_NORMAL
     74     default_discardNonVisibleNodes = False
     75     default_columnWidth = {}
     76     default_hasUnderlay = False
     77     default_rowPadding = 0
     78     default_shouldAddFinalRowPadding = True
     79     default_innerPadding = (0, 0, 0, 0)
     80     default_pushContent = True
     81     default_cursor = uiconst.UICURSOR_DEFAULT
     82     default_headerFontSize = fontconst.EVE_SMALL_FONTSIZE
     83     scrollEnabled = True
     84     sortGroups = False
     85     reverseGroupSortEnabled = False
     86     entriesTotalHeight = 0
     87     dragHoverScrollSpeed = 1.0
     88     dragHoverScrollAreaSize = 30
     89     isLinelessScroll = True
     90     tabMargin = TABMARGIN
     91     _scrollbar_vertical = None
     92     _suppress_scroll_vertical_update = False
     93 
     94     def ApplyAttributes(self, attributes):
     95         self._innerPadding = self._sanitize_inner_padding_value(attributes.get('innerPadding', self.default_innerPadding))
     96         self._pushContent = attributes.get('pushContent', self.default_pushContent)
     97         self.headerFontSize = attributes.get('headerFontSize', self.default_headerFontSize)
     98         Container.ApplyAttributes(self, attributes)
     99         self.sr.maincontainer = Container(parent=self, name='maincontainer', padding=MAIN_CONTAINER_PADDING, clipChildren=True)
    100         self.sr.scrollHeaders = Container(name='scrollHeaders', parent=self.sr.maincontainer, height=18, state=uiconst.UI_HIDDEN, align=uiconst.TOTOP)
    101         self.sr.clipper = Container(name='__clipper', align=uiconst.TOALL, parent=self.sr.maincontainer, padding=self._get_clipper_padding(), clipChildren=True)
    102         self.sr.clipper._OnSizeChange_NoBlock = self.OnClipperResize
    103         self.sr.content = Container(name='__content', align=uiconst.RELATIVE, parent=self.sr.clipper, state=uiconst.UI_NORMAL)
    104         self.Release()
    105         self.multiSelect = attributes.get('multiSelect', self.default_multiSelect)
    106         self.stickToBottom = attributes.get('stickToBottom', self.default_stickToBottom)
    107         self.discardNonVisibleNodes = attributes.get('discardNonVisibleNodes', self.default_discardNonVisibleNodes)
    108         self.smartSort = attributes.get('smartSort', self.default_smartSort)
    109         self.sr.id = attributes.get('id', self.default_id)
    110         self.loadingWheel = None
    111         self.hasUnderlay = attributes.get('hasUnderlay', self.default_hasUnderlay)
    112         self.rowPadding = attributes.get('rowPadding', self.default_rowPadding)
    113         self.shouldAddFinalRowPadding = attributes.get('shouldAddFinalRowPadding', self.default_shouldAddFinalRowPadding)
    114         self.on_content_resize = Signal('on_content_resize')
    115         self.sr.selfProxy = weakref.proxy(self)
    116         self.Prepare_()
    117         self._mouseHoverCookie = uicore.uilib.RegisterForTriuiEvents(uiconst.UI_MOUSEHOVER, self.OnGlobalMouseHover)
    118 
    119     @property
    120     def innerPadding(self):
    121         return self._innerPadding
    122 
    123     @innerPadding.setter
    124     def innerPadding(self, value):
    125         value = self._sanitize_inner_padding_value(value)
    126         if self._innerPadding != value:
    127             self._innerPadding = value
    128             self._update_clipper_padding()
    129 
    130     def _sanitize_inner_padding_value(self, value):
    131         if value is None:
    132             value = self.default_innerPadding
    133         if isinstance(value, int):
    134             value = (value,
    135              value,
    136              value,
    137              value)
    138         return value
    139 
    140     def _get_clipper_padding(self):
    141         inner_pad_left, _, inner_pad_right, _ = self._innerPadding
    142         return (inner_pad_left,
    143          0,
    144          inner_pad_right,
    145          0)
    146 
    147     def _update_clipper_padding(self):
    148         self.sr.clipper.padding = self._get_clipper_padding()
    149 
    150     def Close(self, *args):
    151         with ExceptionEater('Closing a Scroll'):
    152             uicore.uilib.UnregisterForTriuiEvents(self._mouseHoverCookie)
    153         super(Scroll, self).Close()
    154 
    155     def OnGlobalMouseHover(self, obj, *args):
    156         if uicore.IsDragging() and (obj == self or obj.IsUnder(self.sr.content)):
    157             l, t, w, h = self.GetAbsolute()
    158             if self.IsScrollbarVisible():
    159                 y = uicore.uilib.y - t
    160                 if y <= self.dragHoverScrollAreaSize:
    161                     self.Scroll(self.dragHoverScrollSpeed)
    162                 elif y > h - self.dragHoverScrollAreaSize:
    163                     self.Scroll(-self.dragHoverScrollSpeed)
    164         return True
    165 
    166     def IsScrollbarVisible(self):
    167         return self._scrollbar_vertical and self._scrollbar_vertical.display
    168 
    169     def Prepare_(self):
    170         self.Prepare_Underlay_()
    171         self.Prepare_ScrollControls_()
    172 
    173     def Prepare_Underlay_(self):
    174         if getattr(self, 'hasUnderlay', self.default_hasUnderlay):
    175             self.sr.underlay = PanelUnderlay(parent=self, name='background')
    176 
    177     def Prepare_ScrollControls_(self):
    178         self.noResultsContainer = None
    179         self.noResultsLabel = None
    180         self._scrollbar_vertical = Scrollbar(parent=self.sr.maincontainer, align=uiconst.TORIGHT, axis=Axis.VERTICAL, padLeft=8 if self._pushContent else 0, on_scroll_fraction_changed=self._on_scroll_fraction_vertical_changed, idx=0)
    181         self._scrollbar_vertical.display = False
    182 
    183     def _on_scroll_fraction_vertical_changed(self, scrollbar):
    184         with self.suppress_scroll_vertical_update():
    185             self.ScrollToProportion(proportion=scrollbar.scroll_fraction, threaded=False)
    186 
    187     @contextlib.contextmanager
    188     def suppress_scroll_vertical_update(self):
    189         self._suppress_scroll_vertical_update = True
    190         try:
    191             yield
    192         finally:
    193             self._suppress_scroll_vertical_update = False
    194 
    195     def RemoveActiveFrame(self, *args):
    196         pass
    197 
    198     def HideBackground(self, alwaysHidden = 0):
    199         if getattr(self, 'hasUnderlay', self.default_hasUnderlay):
    200             self.sr.underlay.state = uiconst.UI_HIDDEN
    201         if alwaysHidden:
    202             self.SetNoBackgroundFlag(alwaysHidden)
    203 
    204     HideUnderLay = HideBackground
    205 
    206     def SetNoBackgroundFlag(self, hide = 0):
    207         self.hideBackground = hide
    208 
    209     def Release(self):
    210         self.isTabStop = 1
    211         self._loading = 0
    212         self._position = 0
    213         self._totalHeight = 0
    214         self._fixedEntryHeight = None
    215         self._customColumnWidths = None
    216         self.sr.scaleLine = None
    217         self.sr.headers = []
    218         self.sr.widthToHeaders = {}
    219         self.sr.tabs = []
    220         self.sr.nodes = []
    221         self.visibleNodes = []
    222         self.sr.overlays = []
    223         self.sr.underlays = []
    224         self.sr.fixedColumns = {}
    225         self.stretchLastHeader = False
    226         self.sr.maxDefaultColumns = {}
    227         self.sr.minColumnWidth = {}
    228         self.sr.defaultColumnWidth = self.default_columnWidth
    229         self.sr.ignoreTabTrimming = 0
    230         self.showColumnLines = True
    231         self.sr.id = None
    232         self.sortBy = None
    233         self.sr.iconMargin = 0
    234         self.sr.hint = None
    235         self.scrollingRange = 0
    236         self.hiliteSorted = 1
    237         self.reversedSort = 0
    238         self.debug = 0
    239         self.scrolling = 0
    240         self.scalingcol = 0
    241         self.refreshingColumns = 0
    242         self.lastDrawnColumns = None
    243         self.allowFilterColumns = 0
    244         self.lastSelected = None
    245         self.lastHeaders = None
    246         self.bumpHeaders = 1
    247         self.slimHeaders = 0
    248         self.newColumns = 0
    249         self.trimFast = 0
    250         self.refs = []
    251         self.lastCharReceivedAt = 0
    252         self.currChars = ''
    253         self.hideBackground = False
    254         self._ignoreSort = 0
    255         self._showCollapseIcon = False
    256         self._columnOffset = 0
    257         self._pendingPositionUpdate = None
    258         self.loopThread = None
    259 
    260     def GetVisibleNodes(self):
    261         if self.visibleNodes:
    262             return self.visibleNodes
    263         return self.sr.nodes
    264 
    265     def HasUnderlay(self):
    266         return hasattr(self.sr, 'underlay') and self.sr.underlay is not None
    267 
    268     def OnSetFocus(self, *args):
    269         if self and not self.destroyed and self.parent and self.parent.name == 'inlines':
    270             if self.parent.parent and self.parent.parent.sr.node:
    271                 self.parent.parent.sr.node.scroll.ShowNodeIdx(self.parent.parent.sr.node.idx)
    272         if self.HasUnderlay() and hasattr(self.sr.underlay, 'AnimEntry'):
    273             self.sr.underlay.AnimEntry()
    274 
    275     def OnKillFocus(self, *args):
    276         if self.HasUnderlay() and hasattr(self.sr.underlay, 'AnimExit'):
    277             self.sr.underlay.AnimExit()
    278 
    279     def _show_header_resizer_hints(self):
    280         header_class = self.GetScrollHeaderClass()
    281         for child in self.sr.innerScrollHeaders.children:
    282             if isinstance(child, header_class):
    283                 child.show_resizer_hint()
    284 
    285     def _hide_header_resizer_hints(self):
    286         header_class = self.GetScrollHeaderClass()
    287         for child in self.sr.innerScrollHeaders.children:
    288             if isinstance(child, header_class):
    289                 child.hide_resizer_hint()
    290 
    291     def DrawHeaders(self, headers, tabs = []):
    292         self.sr.scrollHeaders.state = uiconst.UI_HIDDEN
    293         self.sr.scrollHeaders.Flush()
    294         self.sr.innerScrollHeaders = Container(name='innerScrollHeaders', parent=self.sr.scrollHeaders, clipChildren=True)
    295         self.sr.innerScrollHeaders.OnMouseEnter = lambda *args: self._show_header_resizer_hints()
    296         self.sr.innerScrollHeaders.OnMouseExit = lambda *args: self._hide_header_resizer_hints()
    297         for each in self.sr.clipper.children[:]:
    298             if each.name == '__columnLine':
    299                 each.Close()
    300             if each.name == '__columnFill':
    301                 each.Close()
    302 
    303         self.lastDrawnColumns = None
    304         self.sr.widthToHeaders = {}
    305         if not (headers and tabs and len(headers) == len(tabs)):
    306             self.sr.headers = []
    307             self.sr.tabs = []
    308             return
    309         if not self.sr.nodes:
    310             return
    311         self.sr.scrollHeaders.state = uiconst.UI_NORMAL
    312         self.sr.scrollHeaders.SetOrder(0)
    313         if not self.isLinelessScroll:
    314             LineThemeColored(parent=self.sr.scrollHeaders, align=uiconst.TOBOTTOM, opacity=uiconst.OPACITY_FRAME, idx=0)
    315         columnOffsetToUse = self.GetEffectiveColumnOffset()
    316         if self._showCollapseIcon:
    317             self.AddCollapseBtn()
    318         elif columnOffsetToUse:
    319             self.sr.innerScrollHeaders.padLeft = columnOffsetToUse
    320         headerHints = self.GetHeaderHints()
    321         i = 0
    322         totalWidth = 0
    323         maxTextHeight = 0
    324         headerClass = self.GetScrollHeaderClass()
    325         half_scale_handle_width = int(round(headerClass.SCALE_HANDLE_SIZE / 2.0))
    326         for header in headers:
    327             width = self.sr.fixedColumns.get(header, None)
    328             if width is None:
    329                 if len(headers) == 1:
    330                     width = 128
    331                 else:
    332                     width = tabs[i]
    333                     if i > 0:
    334                         width = width - tabs[i - 1]
    335             if self.smartSort:
    336                 selected = self.GetPrimaryColumn() == header
    337                 sortdir = self.GetSmartSortDirection(header)
    338             else:
    339                 selected = self.sortBy == header
    340                 sortdir = SortDirection.from_legacy_reversed_sort(self.GetSortDirection()) if selected else SortDirection.ASCENDING
    341             scalable = self.sr.id and header not in self.sr.fixedColumns and (not self.stretchLastHeader or header != headers[-1])
    342             self.sr.widthToHeaders[header] = totalWidth
    343             headerparent = headerClass(column_id=header, name=header, parent=self.sr.innerScrollHeaders, align=uiconst.TOLEFT, left=-half_scale_handle_width if i > 0 else 0, width=width + half_scale_handle_width, tab_margin=self.tabMargin, state=uiconst.UI_NORMAL, text=header, direction=sortdir, selected=selected, on_click=lambda _header = header: self.ChangeSortBy(_header), on_double_click=lambda _header = header: self.ResetColumnWidth(_header), get_menu=lambda _header = header: self.GetHeaderMenu(_header), scalable=scalable, on_scale_start=self._on_column_scale_start, header_hint=headerHints.get(header, None))
    344             headerparent.on_scale_move = lambda column = header, element = headerparent: self._on_column_scale_move(column, element)
    345             headerparent.on_scale_end = lambda column = header, element = headerparent: self._on_column_scale_end(column, element)
    346             if self.stretchLastHeader and header == headers[-1]:
    347                 headerparent.align = uiconst.TOALL
    348                 headerparent.width = 0
    349             totalWidth += width
    350             if not (self.smartSort or self.allowFilterColumns):
    351                 headerparent.GetMenu = None
    352             maxTextHeight = max(maxTextHeight, headerparent.GetHeaderHeight())
    353             if not self.isLinelessScroll and headerparent.align != uiconst.TOALL and not self.sr.ignoreTabTrimming and self.sr.nodes and self.showColumnLines:
    354                 self.BuildColumnLine(columnOffsetToUse, totalWidth, width, headerparent)
    355             i += 1
    356 
    357         self.sr.scrollHeaders.height = maxTextHeight
    358         self.lastDrawnColumns = headers
    359 
    360     def BuildColumnLine(self, columnOffsetToUse, totalWidth, headerWidth, headerparent):
    361         line = LineThemeColored(parent=self.sr.clipper, align=uiconst.RELATIVE, name='__columnLine', opacity=uiconst.OPACITY_FRAME)
    362         line.width = 1
    363         line.height = uicore.desktop.height
    364         line.top = 1
    365         lineLeft = totalWidth - 1
    366         line.originalLeft = lineLeft
    367         line.left = lineLeft + columnOffsetToUse
    368 
    369     def BuildColumnFill(self, columnOffsetToUse, totalWidth, headerWidth, headerparent):
    370         lineLeft = totalWidth - headerWidth + columnOffsetToUse
    371         columnFill = Frame(parent=self.sr.clipper, align=uiconst.RELATIVE, name='__columnFill', opacity=0.1, frameConst=uiconst.FRAME_FILLED_CORNER0, color=(0.94, 0.94, 0.94, 1.0), pos=(lineLeft,
    372          0,
    373          headerWidth,
    374          uicore.desktop.height))
    375         columnFill.originalLeft = lineLeft
    376         headerFrame = Frame(parent=headerparent, align=uiconst.TOALL, name='__columnFill', opacity=0.1, frameConst=uiconst.FRAME_FILLED_CORNER5, idx=0, color=(0.94, 0.94, 0.94, 1.0), padBottom=-10)
    377         headerparent.sr.headerFrame = headerFrame
    378 
    379     def GetScrollHeaderClass(self):
    380         return ColumnHeader
    381 
    382     def UpdateColumnLines(self):
    383         columnOffsetToUse = self.GetEffectiveColumnOffset()
    384         for each in self.sr.clipper.children:
    385             if each.name == '__columnLine':
    386                 left = getattr(each, 'originalLeft', each.left)
    387                 each.left = left + columnOffsetToUse
    388             elif each.name == '__columnFill':
    389                 left = getattr(each, 'originalLeft', each.left)
    390                 each.left = left + columnOffsetToUse
    391 
    392     def ShowHint(self, hint = None, labelClass = EveCaptionLarge):
    393         if not hint:
    394             if self.noResultsContainer:
    395                 self.noResultsContainer.Hide()
    396             return
    397         center_hint = '<center>%s</center>' % hint
    398         isNew = self.noResultsLabel is None or self.noResultsLabel.GetText() != center_hint
    399         if self.noResultsContainer is None:
    400             self.ConstructNoResultsContainer(center_hint, labelClass)
    401         self.noResultsLabel.SetText(center_hint)
    402         self.noResultsContainer.SetState(uiconst.UI_DISABLED)
    403         if isNew:
    404             animations.FadeTo(self.noResultsContainer, 0.0, 1.0, duration=0.3)
    405 
    406     def ConstructNoResultsContainer(self, center_hint, labelClass):
    407         self.noResultsContainer = Container(name='noResultsContainer', parent=self.sr.clipper, align=uiconst.TOALL)
    408         cont = ContainerAutoSize(name='cont', parent=self.noResultsContainer, align=uiconst.TOTOP)
    409         self.noResultsLabel = labelClass(name='noResultsLabel', parent=cont, align=uiconst.TOTOP, text=center_hint, autoFitToText=True, padding=16, color=TextColor.SECONDARY)
    410 
    411     def _on_column_scale_start(self):
    412         self.scalingcol = uicore.uilib.x
    413         if self.sr.scaleLine is not None:
    414             self.sr.scaleLine.Close()
    415         self.ContructScaleLine()
    416 
    417     def ContructScaleLine(self):
    418         l, t, w, h = self.GetAbsolute()
    419         self.sr.scaleLine = LineThemeColored(parent=self, align=uiconst.TOPLEFT, pos=(uicore.uilib.x - l - 1,
    420          1,
    421          2,
    422          h), idx=0, opacity=uiconst.OPACITY_FRAME)
    423 
    424     def _on_column_scale_move(self, column, element):
    425         l, t, w, h = self.GetAbsolute()
    426         element_left, _ = element.GetCurrentAbsolutePosition()
    427         minColumnWidth = self.sr.minColumnWidth.get(column, MINCOLUMNWIDTH)
    428         if self.scalingcol and self.sr.scaleLine:
    429             self.sr.scaleLine.left = mathext.clamp(value=uicore.uilib.x - l - 2, low=element_left - l + minColumnWidth, high=w - minColumnWidth)
    430 
    431     def _on_column_scale_end(self, column, element):
    432         if self.sr.scaleLine is not None:
    433             if self.sr.id and self.scalingcol != uicore.uilib.x:
    434                 currentSettings = settings.user.ui.Get('columnWidths_%s' % VERSION, {})
    435                 currentSettings.setdefault(self.sr.id, {})
    436                 currentWidth = element.width
    437                 minColumnWidth = self.sr.minColumnWidth.get(column, MINCOLUMNWIDTH)
    438                 if self.sr.scaleLine:
    439                     l, t, w, h = self.GetAbsolute()
    440                     newLeft = self.sr.scaleLine.left + l
    441                 else:
    442                     newLeft = uicore.uilib.x
    443                 diff = newLeft - self.scalingcol
    444                 newWidth = currentWidth + diff
    445                 currentSettings[self.sr.id][column] = max(minColumnWidth, newWidth)
    446                 settings.user.ui.Set('columnWidths_%s' % VERSION, currentSettings)
    447                 uthread.pool('VirtualScroll::EndScaleCol-->UpdateTabStops', self.UpdateTabStops, 'EndScaleCol')
    448             scaleLine = self.sr.scaleLine
    449             self.sr.scaleLine = None
    450             scaleLine.Close()
    451         self.scalingcol = 0
    452 
    453     def OnColumnChanged(self, *args):
    454         pass
    455 
    456     def OnNewHeaders(self, *args):
    457         pass
    458 
    459     def ShowNodeIdx(self, idx, threadedScrolling = True):
    460         if self.scrollingRange:
    461             node = self.GetNode(idx)
    462             fromTop = node.scroll_positionFromTop
    463             if fromTop is None:
    464                 return
    465             if self._position > fromTop:
    466                 portion = fromTop / float(self.scrollingRange)
    467                 self.ScrollToProportion(portion, threaded=threadedScrolling)
    468             clipperWidth, clipperHeight = self.GetContentParentSize()
    469             nodeHeight = self.GetNodeHeight(node, clipperWidth)
    470             if self._position + clipperHeight < fromTop + nodeHeight:
    471                 portion = (fromTop - clipperHeight + nodeHeight) / float(self.scrollingRange)
    472                 self.ScrollToProportion(portion, threaded=threadedScrolling)
    473 
    474     def GetNodes(self, allowNone = False):
    475         ret = []
    476         for each in self.sr.nodes:
    477             if each.internalNodes:
    478                 if allowNone:
    479                     ret += each.internalNodes
    480                 else:
    481                     for internal in each.internalNodes:
    482                         if internal:
    483                             ret.append(internal)
    484 
    485             else:
    486                 ret.append(each)
    487 
    488         return ret
    489 
    490     def SetSelected(self, idx):
    491         node = self.GetNode(idx)
    492         if node:
    493             self.SelectNode(node)
    494         self.ReportSelectionChange()
    495 
    496     def ActivateIdx(self, idx):
    497         node = self.GetNode(min(idx, len(self.GetNodes()) - 1))
    498         if node:
    499             self.SelectNode(node)
    500             self.ShowNodeIdx(node.idx)
    501         else:
    502             self.ReportSelectionChange()
    503 
    504     def _SelectNode(self, node):
    505         if getattr(node, 'selectable', 1) == 0:
    506             return
    507         node.selected = 1
    508         self.UpdateSelection(node)
    509 
    510     def _DeselectNode(self, node):
    511         node.selected = 0
    512         self.UpdateSelection(node)
    513 
    514     def SelectNodes(self, nodeList):
    515         self.DeselectAll()
    516         for node in nodeList:
    517             self._SelectNode(node)
    518 
    519         self.ReportSelectionChange()
    520 
    521     def SelectNode(self, node, multi = 0, subnode = None, checktoggle = 1):
    522         control = uicore.uilib.Key(uiconst.VK_CONTROL)
    523         shift = uicore.uilib.Key(uiconst.VK_SHIFT)
    524         selected = node.get('selected', 0)
    525         if not self.multiSelect:
    526             self.DeselectAll(0)
    527             if control:
    528                 if not selected:
    529                     self._SelectNode(node)
    530             else:
    531                 self._SelectNode(node)
    532         elif not control and not shift:
    533             self.DeselectAll(0)
    534             self._SelectNode(node)
    535         elif control and not shift:
    536             if not selected:
    537                 self._SelectNode(node)
    538             else:
    539                 self._DeselectNode(node)
    540         elif not control and shift:
    541             if self.lastSelected is not None and self.lastSelected != node.idx:
    542                 self.DeselectAll(0)
    543                 r = [self.lastSelected, node.idx]
    544                 r.sort()
    545                 for i in xrange(r[0], r[1] + 1):
    546                     _node = self.GetNode(i, checkInternalNodes=True)
    547                     if _node:
    548                         self._SelectNode(_node)
    549 
    550                 self.ReportSelectionChange()
    551                 return
    552             self.DeselectAll(0)
    553             self._SelectNode(node)
    554         elif control and shift:
    555             if self.lastSelected is not None and self.lastSelected != node.idx:
    556                 r = [self.lastSelected, node.idx]
    557                 r.sort()
    558                 for i in xrange(r[0], r[1] + 1):
    559                     _node = self.GetNode(i, checkInternalNodes=True)
    560                     if _node:
    561                         self._SelectNode(_node)
    562 
    563         else:
    564             self.DeselectAll(0)
    565             self._SelectNode(node)
    566         self.lastSelected = node.idx
    567         self.ReportSelectionChange()
    568 
    569     def ReportSelectionChange(self):
    570         self.OnSelectionChange(self.GetSelected())
    571 
    572     def OnSelectionChange(self, *args):
    573         pass
    574 
    575     def DeselectAll(self, report = 1, *args):
    576         for node in self.GetNodes():
    577             node.selected = 0
    578             self.UpdateSelection(node)
    579 
    580         if report:
    581             self.ReportSelectionChange()
    582 
    583     def SelectAll(self, *args):
    584         if not self.multiSelect:
    585             return
    586         for node in self.GetNodes():
    587             if getattr(node, 'selectable', 1) == 0:
    588                 continue
    589             node.selected = 1
    590             self.UpdateSelection(node)
    591 
    592         self.ReportSelectionChange()
    593 
    594     def ToggleSelected(self, *args):
    595         for node in self.GetNodes():
    596             node.selected = not node.get('selected', 0)
    597             self.UpdateSelection(node)
    598 
    599         self.ReportSelectionChange()
    600 
    601     def UpdateSelection(self, node):
    602         if node.panel:
    603             if node.panel.sr.selection:
    604                 node.panel.sr.selection.state = [uiconst.UI_HIDDEN, uiconst.UI_DISABLED][node.selected]
    605             elif node.selected and hasattr(node.panel, 'Select'):
    606                 node.panel.Select()
    607             elif not node.selected and hasattr(node.panel, 'Deselect'):
    608                 node.panel.Deselect()
    609 
    610     def ClearSelection(self, *args):
    611         for node in self.GetNodes():
    612             node.selected = 0
    613             self.UpdateSelection(node)
    614 
    615         self.lastSelected = None
    616         self.ReportSelectionChange()
    617 
    618     def GetSelectedNodes(self, node, toggle = 0):
    619         if not node.get('selected', 0) or toggle:
    620             self.SelectNode(node)
    621         sel = []
    622         for each in self.GetNodes():
    623             if each.get('selected', 0):
    624                 sel.append(each)
    625 
    626         return sel
    627 
    628     def GetSelected(self):
    629         sel = []
    630         for each in self.GetNodes():
    631             if each.get('selected', 0):
    632                 sel.append(each)
    633 
    634         return sel
    635 
    636     def GetSortBy(self):
    637         if self.smartSort:
    638             return None
    639         if self.sr.id:
    640             pr = settings.user.ui.Get('scrollsortby_%s' % VERSION, {})
    641             if self.sr.id in pr:
    642                 return pr[self.sr.id][0]
    643         return self.sortBy
    644 
    645     def GetSortDirection(self):
    646         if self.sr.id:
    647             pr = settings.user.ui.Get('scrollsortby_%s' % VERSION, {})
    648             if self.sr.id in pr:
    649                 return pr[self.sr.id][1]
    650         return self.reversedSort
    651 
    652     def GetSmartSortDirection(self, column, default = SortDirection.ASCENDING):
    653         if self.sr.id and self.smartSort:
    654             pr = settings.user.ui.Get(SMART_SORT_DIRECTION % VERSION, {})
    655             if self.sr.id in pr:
    656                 return pr[self.sr.id].get(column, default)
    657         return default
    658 
    659     def ToggleSmartSortDirection(self, column):
    660         if self.sr.id and self.smartSort:
    661             direction_current = self.GetSmartSortDirection(column)
    662             direction_new = SortDirection.flip(direction_current)
    663             pr = settings.user.ui.Get(SMART_SORT_DIRECTION % VERSION, {})
    664             if self.sr.id not in pr:
    665                 pr[self.sr.id] = {}
    666             pr[self.sr.id][column] = direction_new
    667             settings.user.ui.Set(SMART_SORT_DIRECTION % VERSION, pr)
    668 
    669     def GetSortValue(self, by, node, idx = None):
    670         if getattr(node, 'sortValues', None):
    671             return node.sortValues[idx]
    672         if getattr(node, 'GetSortValue', None):
    673             return node.GetSortValue(node, by, self.GetSortDirection(), idx=idx)
    674         ret = self._GetSortValue(by, node, idx)
    675         return StripTags(ret, stripOnly=['localized'])
    676 
    677     def _GetSortValue(self, by, node, idx):
    678         val = node.Get('sort_' + by, None)
    679         if val is None:
    680             val = node.Get('sort_' + by.replace('<br>', ' '), None)
    681         if val is not None:
    682             try:
    683                 val = val.lower()
    684             except:
    685                 sys.exc_clear()
    686 
    687             return val
    688         if idx is not None:
    689             strings = self.GetStringFromNode(node).split('<t>')
    690             if len(strings) > idx:
    691                 value = strings[idx].lower()
    692                 try:
    693                     value = uicore.font.DeTag(value)
    694                     isAU = value.find('au') != -1
    695                     isKM = value.find('km') != -1
    696                     value = float(value.replace('m\xef\xbf\xbd', '').replace('isk', '').replace('km', '').replace('au', '').replace(',', '').replace(' ', '').replace('m3', ''))
    697                     if isAU:
    698                         value *= const.AU
    699                     elif isKM:
    700                         value *= 1000
    701                     return value
    702                 except Exception:
    703                     sys.exc_clear()
    704                     rest = ''.join(strings[idx + 1:])
    705                     return value + rest
    706 
    707             return 'aaa'
    708         val = node.Get(by, '-')
    709         try:
    710             val = val.lower()
    711         except:
    712             sys.exc_clear()
    713 
    714         return val
    715 
    716     def GetContentContainer(self):
    717         return self.sr.content
    718 
    719     def GetColumns(self):
    720         if self.sr.id and (self.smartSort or self.allowFilterColumns):
    721             if not self.sr.headers:
    722                 return []
    723             orderedColumns = settings.user.ui.Get('columnOrder_%s' % VERSION, {}).get(self.sr.id, self.sr.headers)
    724             notInOrdered = [ header for header in self.sr.headers if header not in orderedColumns ]
    725             headers = [ header for header in orderedColumns + notInOrdered if header in self.sr.headers ]
    726             hiddenColumnsKey = GetHiddenColumnsKey(self.sr.id)
    727             hiddenColumns = settings.user.ui.Get('filteredColumns_%s' % VERSION, {}).get(hiddenColumnsKey, [])
    728             allHiddenColumns = hiddenColumns + settings.user.ui.Get('filteredColumnsByDefault_%s' % VERSION, {}).get((hiddenColumnsKey, session.languageID), [])
    729             filterColumns = filter(lambda x: x not in allHiddenColumns, headers)
    730             return filterColumns
    731         else:
    732             return self.sr.headers
    733 
    734     def GetHeaderMenu(self, label):
    735         m = []
    736         if self.smartSort:
    737             m += [(MenuLabel('/Carbon/UI/Commands/CmdMakePrimary'), self.MakePrimary, (label,))]
    738         if self.smartSort or self.allowFilterColumns:
    739             if len(self.GetColumns()) > 1:
    740                 m += [(MenuLabel('/Carbon/UI/Common/Hide', {'label': label}), self.HideColumn, (label,))]
    741             m += self.GetShowColumnMenu()
    742         return m
    743 
    744     def GetHeaderHints(self):
    745         return {}
    746 
    747     def GetShowColumnMenu(self):
    748         m = []
    749         for label in self.sr.headers:
    750             if label not in self.GetColumns():
    751                 m.append((MenuLabel('/Carbon/UI/Common/Show', {'label': label}), self.ShowColumn, (label,)))
    752 
    753         if m:
    754             m.insert(0, None)
    755         return m
    756 
    757     def MakePrimary(self, label, update = 1):
    758         all = settings.user.ui.Get(PRIMARY_COLUMN % VERSION, {})
    759         all[self.sr.id] = label
    760         settings.user.ui.Set(PRIMARY_COLUMN % VERSION, all)
    761         if update:
    762             self.ChangeColumnOrder(label, 0)
    763 
    764     def GetPrimaryColumn(self):
    765         return settings.user.ui.Get(PRIMARY_COLUMN % VERSION, {}).get(self.sr.id, None)
    766 
    767     def SetColumnsHiddenByDefault(self, columns, *args):
    768         if self.sr.id:
    769             filteredByDefault = settings.user.ui.Get('filteredColumnsByDefault_%s' % VERSION, {})
    770             key = GetHiddenColumnsKey(self.sr.id)
    771             idWithLanguage = (key, session.languageID)
    772             if idWithLanguage not in filteredByDefault:
    773                 filteredByDefault[idWithLanguage] = columns
    774                 settings.user.ui.Set('filteredColumnsByDefault_%s' % VERSION, filteredByDefault)
    775 
    776     def HideColumn(self, label):
    777         if self.sr.id:
    778             filtered = settings.user.ui.Get('filteredColumns_%s' % VERSION, {})
    779             key = GetHiddenColumnsKey(self.sr.id)
    780             if key not in filtered:
    781                 filtered[key] = []
    782             if label not in filtered[key]:
    783                 filtered[key].append(label)
    784             settings.user.ui.Set('filteredColumns_%s' % VERSION, filtered)
    785             self.OnColumnChanged(None)
    786             self.OnNewHeaders()
    787 
    788     def ShowColumn(self, label):
    789         if self.sr.id:
    790             key = GetHiddenColumnsKey(self.sr.id)
    791             filtered = settings.user.ui.Get('filteredColumns_%s' % VERSION, {})
    792             if key in filtered and label in filtered[key]:
    793                 filtered[key].remove(label)
    794             filteredByDefault = settings.user.ui.Get('filteredColumnsByDefault_%s' % VERSION, {})
    795             idWithLanguage = (key, session.languageID)
    796             if idWithLanguage in filteredByDefault and label in filteredByDefault[idWithLanguage]:
    797                 filteredByDefault[idWithLanguage].remove(label)
    798                 settings.user.ui.Set('filteredColumnsByDefault_%s' % VERSION, filteredByDefault)
    799             settings.user.ui.Set('filteredColumns_%s' % VERSION, filtered)
    800             self.OnColumnChanged(None)
    801             self.OnNewHeaders()
    802 
    803     @telemetry.ZONE_METHOD
    804     def Sort(self, by = None, reversesort = 0, forceHilite = 0):
    805         if self.smartSort:
    806             self._SmartSort()
    807         else:
    808             self._Sort(by, reversesort)
    809             if self.sortBy != by or forceHilite:
    810                 self.HiliteSorted(by, reversesort)
    811                 self.sortBy = by
    812 
    813     def _Sort(self, by = None, reversesort = 0):
    814         idx = None
    815         headers = self.GetColumns()
    816         if by in headers:
    817             idx = headers.index(by)
    818         endOrder = []
    819         self.SortAsRoot(self.sr.nodes, endOrder, by, idx, reversesort)
    820         self.SetNodes(endOrder)
    821         self.UpdatePosition(fromWhere='Sort')
    822 
    823     def _SmartSort(self):
    824         columns = self.GetColumns()
    825         primary = self.GetPrimaryColumn()
    826         sortcolumns = columns[:]
    827         if primary in columns:
    828             idx = columns.index(primary)
    829             sortcolumns = columns[idx:]
    830         if columns:
    831             sortData = []
    832             rm = []
    833             for node in self.sr.nodes:
    834                 nodeData = []
    835                 idx = 0
    836                 for header in columns:
    837                     if header not in sortcolumns:
    838                         continue
    839                     if idx in rm:
    840                         value = 0
    841                     else:
    842                         value = node.Get('sort_%s' % header, None)
    843                         if value is None:
    844                             log.warning('Cannot find sortvalue for column %s in scroll %s', header, self.sr.id)
    845                             rm.append(idx)
    846                             value = 0
    847                         else:
    848                             try:
    849                                 value = value.lower()
    850                             except:
    851                                 sys.exc_clear()
    852 
    853                     idx += 1
    854                     nodeData.append(value)
    855 
    856                 sortData.append([nodeData, node])
    857 
    858             sortOrder = [ (idx, self.GetSmartSortDirection(header)) for idx, header in enumerate(sortcolumns) if idx not in rm ]
    859             sortData.sort(lambda x, y, sortOrder = sortOrder: SmartCompare(x, y, sortOrder))
    860             self.SetNodes([ each[1] for each in sortData ])
    861             self.UpdatePositionThreaded(fromWhere='Sort(Smart)')
    862 
    863     @telemetry.ZONE_METHOD
    864     def SortAsRoot(self, nodes, endOrder, columnName, columnIndex, reversedSorting = 0, groupIndex = None):
    865         groups = []
    866         rootSortList_Groups = []
    867         rootSortList_NotGroups = []
    868         for node in nodes:
    869             if groupIndex is None and node.isSub:
    870                 continue
    871             val = self.GetSortValue(columnName, node, columnIndex)
    872             val = (val, self.GetStringFromNode(node).lower())
    873             if issubclass(node.decoClass, SE_ListGroupCore) or getattr(node, 'isListEntry', False):
    874                 rootSortList_Groups.append((val, node))
    875             else:
    876                 rootSortList_NotGroups.append((val, node))
    877 
    878         if self.sortGroups:
    879             rootSortList_Groups = SortListOfTuples(rootSortList_Groups)
    880         else:
    881             rootSortList_Groups = [ node for val, node in rootSortList_Groups ]
    882         rootSortList_NotGroups = SortListOfTuples(rootSortList_NotGroups)
    883         if reversedSorting:
    884             rootSortList_NotGroups.reverse()
    885             if self.reverseGroupSortEnabled:
    886                 rootSortList_Groups.reverse()
    887         combinedGroupsAndOthers = rootSortList_Groups + rootSortList_NotGroups
    888         if groupIndex is not None:
    889             for subIndex, subNode in enumerate(combinedGroupsAndOthers):
    890                 endOrder.insert(groupIndex + subIndex + 1, subNode)
    891 
    892         else:
    893             endOrder.extend(combinedGroupsAndOthers)
    894         if rootSortList_Groups:
    895             for groupNode in rootSortList_Groups:
    896                 groupIdx = endOrder.index(groupNode)
    897                 subNodes = groupNode.get('subNodes', [])
    898                 self.SortAsRoot(subNodes, endOrder, columnName, columnIndex, reversedSorting, groupIndex=groupIdx)
    899 
    900         return nodes
    901 
    902     def GetStringFromNode(self, node):
    903         label_or_text = node.get('label', '') or node.get('text', '')
    904         return GetAsUnicode(label_or_text)
    905 
    906     def RefreshSort(self, forceHilite = 0):
    907         if self.smartSort:
    908             self.Sort()
    909         else:
    910             sortby = self.GetSortBy()
    911             if sortby is not None:
    912                 self.Sort(sortby, self.GetSortDirection(), forceHilite=forceHilite)
    913 
    914     def ChangeSortBy(self, by, *args):
    915         if self.smartSort:
    916             old_primary = self.GetPrimaryColumn()
    917             if old_primary == by:
    918                 self.ToggleSmartSortDirection(by)
    919             self.MakePrimary(by, 0)
    920             for header in self.GetHeadersChildren():
    921                 if not self.IsExepectedColumnHeader(header):
    922                     continue
    923                 if header.id == by:
    924                     header.select(direction=self.GetSmartSortDirection(header.id))
    925                 else:
    926                     header.deselect()
    927 
    928             self.Sort()
    929         else:
    930             if self.sortBy == by:
    931                 self.reversedSort = not self.reversedSort
    932             else:
    933                 self.reversedSort = False
    934             self.sortBy = by
    935             if self.sr.id:
    936                 pr = settings.user.ui.Get('scrollsortby_%s' % VERSION, {})
    937                 pr[self.sr.id] = (self.sortBy, self.reversedSort)
    938                 settings.user.ui.Set('scrollsortby_%s' % VERSION, pr)
    939             self.RefreshSort(1)
    940 
    941     def ChangeColumnOrder(self, column, toIdx):
    942         if self.sr.id and self.smartSort:
    943             all = settings.user.ui.Get('columnOrder_%s' % VERSION, {})
    944             currentOrder = all.get(self.sr.id, self.sr.headers)[:]
    945             if column in currentOrder:
    946                 currentOrder.remove(column)
    947             currentOrder.insert(toIdx, column)
    948             all[self.sr.id] = currentOrder
    949             settings.user.ui.Set('columnOrder_%s' % VERSION, all)
    950             self.OnColumnChanged(None)
    951             self.OnNewHeaders()
    952 
    953     def HiliteSorted(self, by, rev, *args):
    954         selectedHeaderIdx = None
    955         totalWidth = 0
    956         for i, header in enumerate(self.GetHeadersChildren()):
    957             if not self.IsExepectedColumnHeader(header):
    958                 continue
    959             header.deselect()
    960             header.direction = SortDirection.ASCENDING
    961             if self.hiliteSorted and header.id == by:
    962                 header.select(direction=SortDirection.from_legacy_reversed_sort(rev))
    963                 selectedHeaderIdx = i
    964             totalWidth += header.width
    965 
    966     def IsExepectedColumnHeader(self, header):
    967         return isinstance(header, ColumnHeader)
    968 
    969     def GetHeadersChildren(self):
    970         if self.sr.innerScrollHeaders and not self.sr.innerScrollHeaders.destroyed:
    971             return self.sr.innerScrollHeaders.children
    972         return []
    973 
    974     def Clear(self):
    975         self.visibleNodes = []
    976         self.LoadContent()
    977 
    978     def ReloadNodes(self, *args, **kwargs):
    979         for node in self.GetNodes():
    980             self.PrepareSubContent(node, threadedUpdate=False)
    981             if node.panel:
    982                 node.panel.Load(node)
    983 
    984     @telemetry.ZONE_METHOD
    985     def LoadContent(self, fixedEntryHeight = None, contentList = [], sortby = None, reversesort = None, headers = [], scrollTo = None, customColumnWidths = False, showScrollTop = False, noContentHint = '', ignoreSort = False, scrolltotop = False, keepPosition = False, showCollapseIcon = False):
    986         if self.destroyed:
    987             return
    988         if scrolltotop:
    989             scrollTo = 0.0
    990         elif scrollTo is None or keepPosition:
    991             scrollTo = self.GetScrollProportion()
    992         self._loading = 1
    993         self._fixedEntryHeight = fixedEntryHeight
    994         self._customColumnWidths = customColumnWidths
    995         self._ignoreSort = ignoreSort
    996         self._showCollapseIcon = showCollapseIcon
    997         self._columnOffset = 0
    998         wnd = GetWindowAbove(self)
    999         if wnd and not wnd.destroyed and hasattr(wnd, 'ShowLoad'):
   1000             wnd.ShowLoad()
   1001         self.sr.nodes = self.sr.entries = []
   1002         self.sr.content.Flush()
   1003         self._position = self.sr.content.top = 0
   1004         if showScrollTop:
   1005             self._scrollbar_vertical.display = True
   1006         if reversesort is None:
   1007             reversesort = self.GetSortDirection()
   1008         self.sortBy = sortby
   1009         self.reversedSort = reversesort
   1010         self.AddNodes(0, contentList, initing=True)
   1011         if sortby and not ignoreSort:
   1012             self.Sort(sortby, reversesort)
   1013         if self.destroyed:
   1014             return
   1015         if noContentHint and not contentList:
   1016             self.ShowHint(noContentHint)
   1017             self.__LoadHeaders([])
   1018         else:
   1019             self.ShowHint()
   1020             self.__LoadHeaders(headers)
   1021         if self.destroyed:
   1022             return
   1023         self.RefreshNodes(fromWhere='LoadContent')
   1024         self.ScrollToProportion(scrollTo, threaded=False)
   1025         self.UpdateTabStops('LoadContent')
   1026         self.UpdateColumnLines()
   1027         if wnd and not wnd.destroyed and hasattr(wnd, 'HideLoad'):
   1028             wnd.HideLoad()
   1029         self._loading = 0
   1030 
   1031     Load = LoadContent
   1032 
   1033     def LoadHeaders(self, headers):
   1034         wnd = GetWindowAbove(self)
   1035         try:
   1036             if self.__LoadHeaders(headers):
   1037                 self.OnColumnChanged(self.sr.tabs)
   1038         finally:
   1039             if wnd and not wnd.destroyed and hasattr(wnd, 'HideLoad'):
   1040                 wnd.HideLoad()
   1041 
   1042     @telemetry.ZONE_METHOD
   1043     def __LoadHeaders(self, headers):
   1044         self.sr.headers = headers
   1045         self.UpdateTabStops('__LoadHeaders')
   1046         if self.destroyed:
   1047             return
   1048         if headers:
   1049             if not self.smartSort:
   1050                 sortby = self.GetSortBy()
   1051                 reversesort = self.GetSortDirection()
   1052                 if not sortby or sortby not in headers:
   1053                     sortby = headers[0]
   1054                 if not self._ignoreSort:
   1055                     self.Sort(sortby, reversesort)
   1056                 else:
   1057                     self.UpdatePositionThreaded(fromWhere='__LoadHeaders')
   1058             else:
   1059                 self.Sort()
   1060         if len(self.sr.nodes) or not headers:
   1061             self.lastHeaders = headers
   1062         return 1
   1063 
   1064     def ResetColumnWidths(self):
   1065         for header in self.GetColumns():
   1066             self.ResetColumnWidth(header)
   1067 
   1068     def ResetColumnWidth(self, header, onlyReset = 0):
   1069         if self.sr.id is None or self.refreshingColumns:
   1070             return
   1071         if not onlyReset:
   1072             wnd = GetWindowAbove(self)
   1073             if wnd and not wnd.destroyed and hasattr(wnd, 'ShowLoad'):
   1074                 wnd.ShowLoad()
   1075         self.refreshingColumns = 1
   1076         if header not in self.sr.fixedColumns and self.sr.id:
   1077             headertab = [(header,
   1078               self.headerFontSize,
   1079               0,
   1080               self.tabMargin,
   1081               False)]
   1082         else:
   1083             headertab = [(header,
   1084               self.headerFontSize,
   1085               0,
   1086               self.tabMargin,
   1087               False)]
   1088         if header in self.GetColumns():
   1089             idx = self.GetColumns().index(header)
   1090             width = None
   1091             if self._customColumnWidths:
   1092                 headerWidth = uicore.font.GetTextWidth(header, fontsize=self.headerFontSize, letterspace=0, uppercase=False)
   1093                 headerWidth += self.tabMargin * 2
   1094                 normHeader = header.replace('<br>', ' ')
   1095                 width = max([headerWidth] + [ node.GetColumnWidthFunction(None, node, normHeader) for node in self.sr.nodes if node.get('GetColumnWidthFunction', None) is not None ])
   1096             else:
   1097                 tabstops = self.GetTabStops(headertab, idx)
   1098                 if len(tabstops):
   1099                     width = max(MINCOLUMNWIDTH, tabstops[0])
   1100             if width is not None:
   1101                 current = settings.user.ui.Get('columnWidths_%s' % VERSION, {})
   1102                 current.setdefault(self.sr.id, {})[header] = width
   1103                 settings.user.ui.Set('columnWidths_%s' % VERSION, current)
   1104                 self.UpdateTabStops('ResetColumnWidth')
   1105         if not onlyReset and wnd and not wnd.destroyed and hasattr(wnd, 'HideLoad'):
   1106             wnd.HideLoad()
   1107         self.refreshingColumns = 0
   1108 
   1109     @telemetry.ZONE_METHOD
   1110     def ApplyTabstopsToNode(self, node, fromWhere = ''):
   1111         if self.sr.ignoreTabTrimming or not self.GetColumns():
   1112             return
   1113         tabStops = self.sr.tabs
   1114         node.tabs = tabStops
   1115         if tabStops and GetAttrs(node, 'panel', 'OnColumnResize'):
   1116             cols = []
   1117             last = 0
   1118             for tab in tabStops:
   1119                 cols.append(tab - last)
   1120                 last = tab
   1121 
   1122             cols[0] -= self.sr.maincontainer.left
   1123             node.panel.OnColumnResize(cols)
   1124         elif tabStops and node.panel and node.panel.sr.label:
   1125             label = node.panel.sr.label
   1126             columnOffsetToUse = self.GetEffectiveColumnOffset()
   1127             subTract = label.left - columnOffsetToUse
   1128             if isinstance(label, LabelCore):
   1129                 newtext = self.GetStringFromNode(node)
   1130                 if newtext and (getattr(label, 'tabs', None) != tabStops or getattr(label, 'xShift', 0) != -subTract) and newtext.find('<t>') != -1:
   1131                     label.xShift = -subTract
   1132                     label.tabs = tabStops
   1133                     label.text = newtext
   1134                     label.Update()
   1135 
   1136     def GetTabStops(self, headertabs, idx = None):
   1137         strengir = []
   1138         fontsize = fontconst.DEFAULT_FONTSIZE
   1139         letterspace = 0
   1140         shift = 0
   1141         self._columnOffset = 0
   1142         for node in self.sr.nodes:
   1143             if node.Get('GetSubContent', None):
   1144                 self._columnOffset = 16
   1145             s = self.GetStrengir(node, fontsize, letterspace, shift, idx)
   1146             if s is None:
   1147                 continue
   1148             strengir.append(s)
   1149 
   1150         return uicore.font.MeasureTabstops(strengir + headertabs, self.tabMargin)
   1151 
   1152     def GetStrengir(self, node, fontsize, letterspace, shift, idx = None):
   1153         t = self.GetStringFromNode(node)
   1154         if not t or t.find('<t>') == -1:
   1155             return
   1156         if idx is not None:
   1157             t = t.split('<t>')
   1158             if len(t) <= idx:
   1159                 return
   1160             t = t[idx]
   1161         if node.panel and node.panel.sr.label:
   1162             label = node.panel.sr.label
   1163             fontsize = label.fontsize
   1164             letterspace = label.letterspace
   1165             shift = label.left
   1166         return (t,
   1167          fontsize,
   1168          letterspace,
   1169          shift,
   1170          0)
   1171 
   1172     @telemetry.ZONE_METHOD
   1173     def UpdateTabStops(self, fromWhere = None, updatePosition = True):
   1174         headers = self.GetColumns()
   1175         headertabs = []
   1176         if headers is not None and len(headers):
   1177             headertabs = [('<t>'.join(headers),
   1178               self.headerFontSize,
   1179               0,
   1180               self.tabMargin,
   1181               False)]
   1182         tabstops = self.GetTabStops(headertabs)
   1183         if self.sr.id and headers:
   1184             userDefined = settings.user.ui.Get('columnWidths_%s' % VERSION, {}).get(self.sr.id, {})
   1185             i = 0
   1186             columnOffsetToUse = self.GetEffectiveColumnOffset()
   1187             total = columnOffsetToUse
   1188             former = total
   1189             for header in headers:
   1190                 if header in self.sr.fixedColumns:
   1191                     stopSize = self.sr.fixedColumns[header]
   1192                 else:
   1193                     userSetWidth = userDefined.get(header, None) or self.sr.defaultColumnWidth.get(header, None)
   1194                     minColumnWidth = self.sr.minColumnWidth.get(header, MINCOLUMNWIDTH)
   1195                     if userSetWidth is not None:
   1196                         stopSize = max(userSetWidth, minColumnWidth)
   1197                     else:
   1198                         stopSize = max(tabstops[i] - former, minColumnWidth)
   1199                         if header in self.sr.maxDefaultColumns:
   1200                             stopSize = min(self.sr.maxDefaultColumns.get(header, minColumnWidth), stopSize)
   1201                 total += stopSize
   1202                 former = tabstops[i]
   1203                 tabstops[i] = total
   1204                 i += 1
   1205 
   1206         didChange = tabstops != self.sr.tabs
   1207         self.sr.tabs = tabstops
   1208         if self.sr.collapseCont and not self.sr.collapseCont.destroyed:
   1209             collapseIconNeedsRefresh = False
   1210             columnOffsetToUse = self.GetEffectiveColumnOffset()
   1211             self.sr.collapseCont.display = bool(columnOffsetToUse)
   1212         else:
   1213             collapseIconNeedsRefresh = self._showCollapseIcon
   1214         if headers != self.lastDrawnColumns or didChange or collapseIconNeedsRefresh:
   1215             self.DrawHeaders(headers, tabstops)
   1216             if didChange:
   1217                 if not self.smartSort:
   1218                     self.HiliteSorted(self.GetSortBy(), self.GetSortDirection(), 'UpdateTabStops')
   1219                 if not self._loading:
   1220                     self.OnColumnChanged(tabstops)
   1221         if updatePosition:
   1222             self.UpdatePositionThreaded('UpdateTabStops')
   1223         return tabstops
   1224 
   1225     @telemetry.ZONE_METHOD
   1226     def AddNode(self, idx, node, isSub = 0, initing = False):
   1227         if idx == -1:
   1228             idx = len(self.sr.nodes)
   1229         node.panel = None
   1230         node.open = 0
   1231         node.idx = idx
   1232         node.isSub = isSub
   1233         node.scroll = self.sr.selfProxy
   1234         node.selected = node.get('isSelected', 0)
   1235         if node.get('PreLoadFunction', None):
   1236             node.PreLoadFunction(node)
   1237             if self.destroyed:
   1238                 return
   1239         self.sr.nodes.insert(idx, node)
   1240         self.PrepareSubContent(node, initing=initing)
   1241         return node
   1242 
   1243     @telemetry.ZONE_METHOD
   1244     def PrepareSubContent(self, node, initing = False, threadedUpdate = True):
   1245         if node.id:
   1246             if node.get('subNodes', []):
   1247                 rm = node.subNodes
   1248                 node.subNodes = []
   1249                 node.open = 0
   1250                 self.RemoveNodes(rm)
   1251             if node.Get('GetSubContent', None) is not None:
   1252                 forceOpen = node.get('forceOpen', False) and initing
   1253                 if forceOpen or uicore.registry.GetListGroupOpenState(node.id, default=node.get('openByDefault', False)):
   1254                     subcontent = node.GetSubContent(node)
   1255                     if self.destroyed or node not in self.GetNodes():
   1256                         return
   1257                     if not node.Get('hideNoItem', False) and not len(subcontent):
   1258                         noItemText = node.get('noItemText', localization.GetByLabel('/Carbon/UI/Controls/Common/NoItem'))
   1259                         subcontent.append(self.GetNoItemNode(text=noItemText, sublevel=node.get('sublevel', 0) + 1))
   1260                     if not self.destroyed:
   1261                         self.AddNodes(node.idx + 1, subcontent, node, initing=initing, threadedUpdate=threadedUpdate)
   1262                         if node not in self.GetNodes():
   1263                             self.RemoveNodes(subcontent)
   1264                         else:
   1265                             node.subNodes = subcontent
   1266                             node.open = 1
   1267                             return subcontent
   1268 
   1269     def SetNodes(self, nodes):
   1270         self.sr.nodes = nodes
   1271         self.RefreshNodes()
   1272 
   1273     def _GetNodePanelName(self, node):
   1274         if getattr(node, 'panelName', None):
   1275             return node.panelName
   1276         return node.name or 'entry_%s' % node.idx
   1277 
   1278     @telemetry.ZONE_METHOD
   1279     def RefreshNodes(self, fromWhere = None):
   1280         if self.destroyed:
   1281             return
   1282         clipperWidth, clipperHeight = self.sr.clipper.GetCurrentAbsoluteSize()
   1283         if not clipperWidth or not clipperHeight:
   1284             clipperWidth, clipperHeight = self.sr.clipper.GetAbsoluteSize()
   1285         _, inner_pad_top, _, inner_pad_bottom = self._innerPadding
   1286         fromTop = inner_pad_top
   1287         for nodeidx, node in enumerate(self.sr.nodes):
   1288             node.idx = nodeidx
   1289             nodeheight = self.GetNodeHeight(node, clipperWidth)
   1290             node.scroll_positionFromTop = fromTop
   1291             if node.panel:
   1292                 node.panel.align = uiconst.TOPLEFT
   1293                 node.panel.left = 0
   1294                 node.panel.top = fromTop
   1295                 node.panel.width = clipperWidth
   1296                 node.panel.height = nodeheight
   1297                 node.panel.name = self._GetNodePanelName(node)
   1298             fromTop += nodeheight
   1299             if self.shouldAddFinalRowPadding or nodeidx + 1 < len(self.sr.nodes):
   1300                 fromTop += self.rowPadding
   1301 
   1302         for overlay, attrs, x, y in self.sr.overlays + self.sr.underlays:
   1303             fromTop = max(fromTop, attrs.top + attrs.height)
   1304 
   1305         atBottom = self._position and self._position == self.scrollingRange
   1306         self._totalHeight = fromTop + inner_pad_bottom
   1307         self.scrollingRange = max(0, self._totalHeight - clipperHeight)
   1308         if self.scrollingRange and self.scrollEnabled:
   1309             self._scrollbar_vertical.display = True
   1310         else:
   1311             self._scrollbar_vertical.display = False
   1312         if self.scrollEnabled:
   1313             if not self.scrollingRange or atBottom or self.stickToBottom:
   1314                 self._position = self.scrollingRange
   1315             self._position = min(self._position, self.scrollingRange)
   1316         else:
   1317             self._position = 0
   1318         self.UpdateContentSize(clipperWidth, clipperHeight, self._totalHeight)
   1319         if self.sr.overlays_content:
   1320             self.sr.overlays_content.height = self.sr.content.height
   1321             self.sr.overlays_content.width = clipperWidth
   1322         if self.sr.underlays_content:
   1323             self.sr.underlays_content.height = self.sr.content.height
   1324             self.sr.underlays_content.width = clipperWidth
   1325 
   1326     @telemetry.ZONE_METHOD
   1327     def UpdateContentSize(self, clipperWidth, clipperHeight, contentHeight):
   1328         self.entriesTotalHeight = contentHeight
   1329         self.on_content_resize(contentHeight)
   1330         self.sr.content.height = max(clipperHeight, contentHeight)
   1331         self.sr.content.width = clipperWidth
   1332 
   1333     def GetEntriesTotalHeight(self):
   1334         return self.entriesTotalHeight
   1335 
   1336     @telemetry.ZONE_METHOD
   1337     def AddNodes(self, fromIdx, nodesData, parentNode = None, ignoreSort = 0, initing = False, threadedUpdate = True):
   1338         wnd = GetWindowAbove(self)
   1339         if wnd and not wnd.destroyed and hasattr(wnd, 'ShowLoad'):
   1340             wnd.ShowLoad()
   1341         if fromIdx == -1:
   1342             fromIdx = len(self.sr.nodes)
   1343         isSub = 0
   1344         if parentNode:
   1345             isSub = parentNode.get('sublevel', 0) + 1
   1346         nodes = []
   1347         idx = fromIdx
   1348         for data in nodesData:
   1349             newnode = self.AddNode(idx, data, isSub=isSub, initing=initing)
   1350             if newnode is None:
   1351                 continue
   1352             subs = self.CollectSubNodes(newnode, clear=0)
   1353             idx = newnode.idx + 1 + len(subs)
   1354             nodes.append(newnode)
   1355 
   1356         if parentNode:
   1357             parentNode.subNodes = nodes
   1358         if not initing:
   1359             if self.GetSortBy() and not (self._ignoreSort or ignoreSort):
   1360                 self.RefreshSort()
   1361             elif threadedUpdate:
   1362                 self.RefreshNodes()
   1363                 self.UpdatePositionThreaded(fromWhere='AddNodes')
   1364             else:
   1365                 self.RefreshNodes()
   1366                 self.UpdatePosition(fromWhere='AddNodes')
   1367             if nodes:
   1368                 self.UpdateTabStops('AddNodes', updatePosition=True)
   1369         if wnd and not wnd.destroyed and hasattr(wnd, 'HideLoad'):
   1370             wnd.HideLoad()
   1371         return nodes
   1372 
   1373     AddEntries = AddNodes
   1374 
   1375     @telemetry.ZONE_METHOD
   1376     def RemoveNodes(self, nodes):
   1377         wnd = GetWindowAbove(self)
   1378         if wnd and not wnd.destroyed and hasattr(wnd, 'ShowLoad'):
   1379             wnd.ShowLoad()
   1380         subs = []
   1381         for node in nodes:
   1382             subs.extend(self.CollectSubNodes(node))
   1383 
   1384         for nodeList in (nodes, subs):
   1385             for node in nodeList:
   1386                 if node.panel:
   1387                     node.panel.Close()
   1388                 if node in self.sr.nodes:
   1389                     self.sr.nodes.remove(node)
   1390 
   1391         self.RefreshNodes()
   1392         self.UpdatePosition()
   1393         if wnd and not wnd.destroyed and hasattr(wnd, 'HideLoad'):
   1394             wnd.HideLoad()
   1395 
   1396     RemoveEntries = RemoveNodes
   1397 
   1398     @telemetry.ZONE_METHOD
   1399     def CollectSubNodes(self, node, nodes = None, clear = 1):
   1400         if nodes is None:
   1401             nodes = []
   1402         inNodes = [ id(each) for each in nodes ]
   1403         for subnode in node.get('subNodes', []):
   1404             if subnode is None:
   1405                 continue
   1406             self.CollectSubNodes(subnode, nodes, clear)
   1407             if id(subnode) not in inNodes:
   1408                 nodes.append(subnode)
   1409 
   1410         if clear:
   1411             node.subNodes = []
   1412         return nodes
   1413 
   1414     @telemetry.ZONE_METHOD
   1415     def GetNodeHeight(self, node, clipperWidth):
   1416         func = node.GetHeightFunction
   1417         newStyle = getattr(node.decoClass, 'GetDynamicHeight', None)
   1418         if func:
   1419             allowDynamicResize = node.get('allowDynamicResize', True)
   1420             if not node.height or allowDynamicResize and node._lastClipperWidth != clipperWidth:
   1421                 node.height = func(node.decoClass, node, clipperWidth)
   1422                 node._lastClipperWidth = clipperWidth
   1423         elif newStyle:
   1424             if not node.height or node._lastClipperWidth != clipperWidth:
   1425                 node.height = newStyle(node, clipperWidth)
   1426                 node._lastClipperWidth = clipperWidth
   1427         elif self._fixedEntryHeight:
   1428             node.height = self._fixedEntryHeight
   1429         else:
   1430             node.height = getattr(node.decoClass, 'ENTRYHEIGHT', 18)
   1431         if not node.height:
   1432             if func:
   1433                 func(None, node, clipperWidth)
   1434             else:
   1435                 node.height = getattr(node.decoClass, 'ENTRYHEIGHT', 18)
   1436         return node.height
   1437 
   1438     @telemetry.ZONE_METHOD
   1439     def GetContentWidth(self):
   1440         w, h = self.GetContentParentSize()
   1441         return w
   1442 
   1443     @telemetry.ZONE_METHOD
   1444     def GetContentHeight(self):
   1445         return self._totalHeight
   1446 
   1447     GetTotalHeight = GetContentHeight
   1448 
   1449     def GetContentParentSize(self):
   1450         w, h = self.sr.clipper.GetAbsoluteSize()
   1451         return (w, h)
   1452 
   1453     def UpdatePositionThreaded(self, fromWhere = None):
   1454         if self.loopThread is None:
   1455             self.loopThread = uthread.new(self.UpdatePositionLoop)
   1456             self._pendingPositionUpdate = None
   1457         else:
   1458             self._pendingPositionUpdate = self._position
   1459 
   1460     @telemetry.ZONE_METHOD
   1461     def UpdatePositionLoop(self, fromWhere = None):
   1462         if self.destroyed:
   1463             return
   1464         while True:
   1465             nodeCreatedOrRefreshed, updatedPos = self.UpdatePosition(fromWhere='UpdatePositionLoop', doYield=True)
   1466             if self._pendingPositionUpdate == updatedPos:
   1467                 self._pendingPositionUpdate = None
   1468             if self.discardNonVisibleNodes:
   1469                 with ExceptionEater('Discarding non-visible nodes'):
   1470                     self.DiscardNonVisibleNodes()
   1471             blue.pyos.BeNice()
   1472             if self.destroyed:
   1473                 return
   1474             if not nodeCreatedOrRefreshed:
   1475                 break
   1476 
   1477         self.loopThread = None
   1478         if self._pendingPositionUpdate is not None:
   1479             uthread2.call_after_wallclocktime_delay(self._DelayedPositionCorrection, 0.5, self._pendingPositionUpdate)
   1480 
   1481     def _DelayedPositionCorrection(self, pendingValue):
   1482         if pendingValue == self._pendingPositionUpdate:
   1483             self.UpdatePositionThreaded()
   1484 
   1485     def DiscardNonVisibleNodes(self):
   1486         for eachNode in self.GetNodes():
   1487             if eachNode.panel and not eachNode.panel.destroyed:
   1488                 if eachNode not in self.visibleNodes:
   1489                     eachNode.panel.Close()
   1490                     eachNode.panel = None
   1491 
   1492     @telemetry.ZONE_METHOD
   1493     def UpdatePosition(self, fromWhere = None, doYield = False):
   1494         if self.destroyed:
   1495             return (None, None)
   1496         clipperWidth, clipperHeight = self.sr.clipper.GetAbsoluteSize()
   1497         self.sr.content.top = int(-self._position)
   1498         if self.sr.overlays_content:
   1499             self.sr.overlays_content.top = self.sr.content.top
   1500         if self.sr.underlays_content:
   1501             self.sr.underlays_content.top = self.sr.content.top
   1502         self.UpdateScrollHandle(clipperHeight, fromWhere='UpdatePosition')
   1503         tabStops = self.sr.tabs
   1504         scrollPosition = self._position
   1505         ignoreTabstops = self.sr.ignoreTabTrimming or not self.GetColumns()
   1506         nodeLoaded = False
   1507         self.visibleNodes = []
   1508         for nodeCount, node in enumerate(self.sr.nodes):
   1509             if nodeCount != node.idx or node.scroll_positionFromTop is None or node.height is None:
   1510                 self.RefreshNodes(fromWhere='UpdatePosition')
   1511             nodeheight = node.height
   1512             posFromTop = node.scroll_positionFromTop
   1513             displayScrollEntry = node.panel
   1514             if scrollPosition > posFromTop + nodeheight or scrollPosition + clipperHeight < posFromTop:
   1515                 if displayScrollEntry:
   1516                     displayScrollEntry.state = uiconst.UI_HIDDEN
   1517                 if doYield:
   1518                     blue.pyos.BeNice()
   1519                 continue
   1520             forceTabstops = False
   1521             if not displayScrollEntry or displayScrollEntry.destroyed:
   1522                 decoClass = node.decoClass
   1523                 displayScrollEntry = decoClass(parent=self.sr.content, align=uiconst.TOPLEFT, pos=(0,
   1524                  posFromTop,
   1525                  clipperWidth,
   1526                  nodeheight), state=uiconst.UI_NORMAL, name=node.name or 'entry_%s' % node.idx, node=node)
   1527                 displayScrollEntry.sr.node = node
   1528                 node.panel = displayScrollEntry
   1529                 node.scroll = self.sr.selfProxy
   1530                 if hasattr(displayScrollEntry, 'Startup'):
   1531                     displayScrollEntry.Startup(self.sr.selfProxy)
   1532                 displayScrollEntry.Load(node)
   1533                 forceTabstops = True
   1534                 nodeLoaded = True
   1535             elif not displayScrollEntry.display:
   1536                 displayScrollEntry.state = uiconst.UI_NORMAL
   1537                 if not node.isStatic:
   1538                     displayScrollEntry.Load(node)
   1539                 forceTabstops = True
   1540                 nodeLoaded = True
   1541             elif getattr(displayScrollEntry, 'currentBandingValue', None) is not None:
   1542                 if node.idx % getattr(displayScrollEntry, 'MOD_DIV', 2) != displayScrollEntry.currentBandingValue and getattr(displayScrollEntry, 'UpdateBandingColor', None):
   1543                     displayScrollEntry.UpdateBandingColor()
   1544                     displayScrollEntry.UpdateBandingFillVisibility()
   1545             self.visibleNodes.append(node)
   1546             if not ignoreTabstops:
   1547                 updateTabs = node.tabs != tabStops
   1548                 if forceTabstops or updateTabs:
   1549                     self.ApplyTabstopsToNode(node, 'UpdatePosition')
   1550 
   1551         self.OnUpdatePosition(self)
   1552         return (nodeLoaded, scrollPosition)
   1553 
   1554     def UpdateScrollHandle(self, clipperHeight, fromWhere = ''):
   1555         if self.destroyed or not self._scrollbar_vertical:
   1556             return
   1557         if not self._totalHeight:
   1558             handle_size_fraction = 0.0
   1559         else:
   1560             handle_size_fraction = clipperHeight / float(self._totalHeight)
   1561         self._scrollbar_vertical.handle_size_fraction = mathext.clamp(handle_size_fraction, 0.0, 1.0)
   1562         if not self._suppress_scroll_vertical_update:
   1563             scroll_fraction = 0.0
   1564             if self._position and self.scrollingRange:
   1565                 scroll_fraction = self._position / float(self.scrollingRange)
   1566             self._scrollbar_vertical.scroll_to_fraction(scroll_fraction)
   1567 
   1568     def OnChar(self, enteredChar, *args):
   1569         if enteredChar < 32:
   1570             return False
   1571         if not self.sr.nodes:
   1572             return False
   1573         haveCharIndex = False
   1574         for node in self.sr.nodes:
   1575             if node.charIndex is not None:
   1576                 haveCharIndex = True
   1577                 break
   1578 
   1579         if not haveCharIndex:
   1580             return False
   1581         if blue.os.TimeAsDouble(blue.os.GetWallclockTime()) - self.lastCharReceivedAt < 1.0 and self.currChars is not None:
   1582             self.currChars += unichr(enteredChar).lower()
   1583         else:
   1584             self.currChars = unichr(enteredChar).lower()
   1585         if enteredChar == uiconst.VK_SPACE:
   1586             selected = self.GetSelected()
   1587             if len(selected) == 1 and self.currChars == ' ' and GetAttrs(selected[0], 'panel', 'OnCharSpace') is not None:
   1588                 selected[0].panel.OnCharSpace(enteredChar)
   1589                 return True
   1590         uthread.new(self._OnCharThread, enteredChar)
   1591         self.lastCharReceivedAt = blue.os.TimeAsDouble(blue.os.GetWallclockTime())
   1592         return True
   1593 
   1594     def ScrollToSelectedNode(self):
   1595         selected = self.GetSelected()
   1596         if selected:
   1597             self.ScrollToNode(selected[0])
   1598 
   1599     def ScrollToNode(self, node):
   1600         numEntries = len(self.sr.nodes)
   1601         if numEntries <= 1:
   1602             return
   1603         entryPos = self.sr.nodes.index(node)
   1604         self.ScrollToProportion(float(entryPos) / (numEntries - 1))
   1605 
   1606     def _OnCharThread(self, enteredChar):
   1607         if self.destroyed:
   1608             return
   1609         charsBefore = self.currChars
   1610         blue.pyos.synchro.SleepWallclock(100)
   1611         if self.destroyed:
   1612             return
   1613         if self.currChars != charsBefore:
   1614             return
   1615         selected = self.GetSelected()
   1616         if not selected:
   1617             selected = self.sr.nodes
   1618         selected = selected[0]
   1619         numEntries = len(self.sr.nodes)
   1620         if selected not in self.sr.nodes:
   1621             return
   1622         startIndex = self.sr.nodes.index(selected)
   1623         if len(self.currChars) == 1:
   1624             startIndex += 1
   1625         entryRange = range(numEntries)[startIndex:] + range(numEntries)[:startIndex]
   1626         for i in entryRange:
   1627             entry = self.sr.nodes[i]
   1628             if entry.charIndex and entry.charIndex.lower().startswith(self.currChars):
   1629                 self.SelectNode(entry)
   1630                 self.ScrollToNode(entry)
   1631                 break
   1632 
   1633     def OnDelete(self):
   1634         pass
   1635 
   1636     def OnUpdatePosition(self, *args):
   1637         pass
   1638 
   1639     def ShowLoading(self):
   1640         if not self.loadingWheel:
   1641             self.loadingWheel = LoadingWheel(name='myLoadingWheel', parent=self, align=uiconst.CENTER, width=32, height=32)
   1642         self.ShowHint(None)
   1643 
   1644     def HideLoading(self):
   1645         if self.loadingWheel:
   1646             self.loadingWheel.Close()
   1647             self.loadingWheel = None
   1648 
   1649     def CheckOverlaysAndUnderlays(self):
   1650         for overlay, attrs, x, y in self.sr.overlays + self.sr.underlays:
   1651             if overlay is None or overlay.destroyed:
   1652                 continue
   1653             if attrs.Get('align', None) == 'right':
   1654                 overlay.left = self.GetContentWidth() - overlay.width - attrs.left
   1655             if not overlay.loaded:
   1656                 overlay.top = attrs.top
   1657                 overlay.SetAlign(uiconst.RELATIVE)
   1658                 overlay.state = uiconst.UI_NORMAL
   1659                 overlay.Load()
   1660 
   1661     def GetNode(self, idx, checkInternalNodes = False):
   1662         if checkInternalNodes:
   1663             allNodes = self.GetNodes(allowNone=True)
   1664         else:
   1665             allNodes = self.sr.nodes
   1666         if idx == -1:
   1667             if allNodes:
   1668                 return allNodes[-1]
   1669             else:
   1670                 return None
   1671         if len(allNodes) > idx:
   1672             return allNodes[idx]
   1673 
   1674     def OnKeyDown(self, key, flag):
   1675         if uiconst.VK_DELETE == key:
   1676             self.OnDelete()
   1677         elif key == uiconst.VK_PRIOR:
   1678             self.ScrollByPage(up=True)
   1679         elif key == uiconst.VK_NEXT:
   1680             self.ScrollByPage(up=False)
   1681 
   1682     def ScrollByPage(self, up = True):
   1683         visibleNodes = self.visibleNodes
   1684         numVisibleNodes = len(visibleNodes)
   1685         allNodesNum = len(self.sr.nodes)
   1686         if numVisibleNodes < 1:
   1687             return
   1688         if up:
   1689             lastVisibleNode = visibleNodes[0]
   1690             step = -1
   1691         else:
   1692             lastVisibleNode = visibleNodes[-1]
   1693             step = 1
   1694         newNodesHeight = lastVisibleNode.height
   1695         currentNodeIdx = lastVisibleNode.idx
   1696         clipperWidth, clipperHeight = self.GetContentParentSize()
   1697         while 1:
   1698             nextNodeIdx = currentNodeIdx + step
   1699             if not 0 < nextNodeIdx < allNodesNum - 1:
   1700                 break
   1701             nextNode = self.GetNode(idx=nextNodeIdx, checkInternalNodes=False)
   1702             newNodesHeight += nextNode.height
   1703             if newNodesHeight > clipperHeight:
   1704                 break
   1705             currentNodeIdx = nextNodeIdx
   1706 
   1707         self.ShowNodeIdx(currentNodeIdx, threadedScrolling=False)
   1708 
   1709     def Resizing(self):
   1710         pass
   1711 
   1712     def OnClipperResize(self, clipperWidth, clipperHeight, *args, **kw):
   1713         self.OnContentResize(clipperWidth, clipperHeight, *args, **kw)
   1714 
   1715     def OnContentResize(self, clipperWidth, clipperHeight, *args, **kw):
   1716         if self.sr.hint:
   1717             w, h = clipperWidth, clipperHeight
   1718             newWidth = w - self.sr.hint.left * 2
   1719             if abs(newWidth - self.sr.hint.width) > 12:
   1720                 self.sr.hint.width = newWidth
   1721         if self.sr.content:
   1722             self.RefreshNodes(fromWhere='OnContentResize')
   1723             self.UpdatePositionThreaded(fromWhere='OnContentResize')
   1724         if not self.destroyed:
   1725             self.Resizing()
   1726 
   1727     def BrowseNodes(self, up):
   1728         sel = self.GetSelected()
   1729         control = uicore.uilib.Key(uiconst.VK_CONTROL)
   1730         shift = uicore.uilib.Key(uiconst.VK_SHIFT)
   1731         if sel:
   1732             shiftIdx = None
   1733             if not control and shift:
   1734                 r = [ node.idx for node in sel ]
   1735                 if up:
   1736                     if r[0] < self.lastSelected:
   1737                         shiftIdx = r[0] - 1
   1738                     else:
   1739                         shiftIdx = r[-1] - 1
   1740                 elif r[0] < self.lastSelected:
   1741                     shiftIdx = r[0] + 1
   1742                 else:
   1743                     shiftIdx = r[-1] + 1
   1744             if shiftIdx is None:
   1745                 if len(sel) > 1:
   1746                     idx = sel[[-1, 0][up]].idx
   1747                 else:
   1748                     idx = sel[-1].idx
   1749                 idx += [1, -1][up]
   1750             else:
   1751                 idx = shiftIdx
   1752             total = len(self.GetNodes())
   1753             if 0 <= idx < total:
   1754                 self.ActivateIdx(idx)
   1755                 return 1
   1756         return 0
   1757 
   1758     BrowseEntries = BrowseNodes
   1759 
   1760     def OnUp(self):
   1761         if not self.GetSelected():
   1762             self.ActivateIdx(len(self.sr.nodes) - 1)
   1763             return
   1764         if not self.BrowseNodes(1):
   1765             self.Scroll(1 + 10 * uicore.uilib.Key(uiconst.VK_SHIFT))
   1766 
   1767     def OnDown(self):
   1768         if not self.GetSelected():
   1769             self.ActivateIdx(0)
   1770             return
   1771         if not self.BrowseNodes(0):
   1772             self.Scroll(-1 - 10 * uicore.uilib.Key(uiconst.VK_SHIFT))
   1773 
   1774     def OnHome(self):
   1775         self.ScrollToProportion(0.0)
   1776 
   1777     def OnEnd(self):
   1778         self.ScrollToProportion(1.0)
   1779 
   1780     def OnMouseWheel(self, *etc):
   1781         if getattr(self, 'wheeling', 0):
   1782             return
   1783         if HasContextMenu():
   1784             menuOwner = GetContextMenuOwner()
   1785             if menuOwner and menuOwner.IsUnder(self):
   1786                 return
   1787         self.wheeling = 1
   1788         self.Scroll(uicore.uilib.dz / 240.0)
   1789         self.wheeling = 0
   1790 
   1791     def Scroll(self, dz):
   1792         if not self.scrollEnabled:
   1793             return
   1794         step = 100
   1795         pos = max(0, min(self.scrollingRange, self._position - step * dz))
   1796         if pos != self._position:
   1797             self._position = int(pos)
   1798             self.stickToBottom = False
   1799             self.UpdatePositionThreaded(fromWhere='Scroll')
   1800 
   1801     def ScrollPixels(self, delta):
   1802         if not self.scrollEnabled:
   1803             return
   1804         pos = mathext.clamp(self._position - delta, 0, self.scrollingRange)
   1805         if pos != self._position:
   1806             self._position = int(pos)
   1807             self.stickToBottom = False
   1808             self.UpdatePositionThreaded(fromWhere='ScrollPixels')
   1809 
   1810     def GetScrollProportion(self):
   1811         if self.scrollingRange:
   1812             return self._position / float(self.scrollingRange)
   1813         return 0.0
   1814 
   1815     @telemetry.ZONE_METHOD
   1816     def ScrollToProportion(self, proportion, threaded = True):
   1817         if not self.scrollEnabled:
   1818             return
   1819         proportion = min(1.0, max(0.0, proportion))
   1820         pos = int(max(0, self.scrollingRange * proportion))
   1821         self._position = int(pos)
   1822         if threaded:
   1823             self.UpdatePositionThreaded(fromWhere='ScrollToPorportion')
   1824         else:
   1825             self.UpdatePosition(fromWhere='ScrollToPorportion')
   1826 
   1827     def GetMinSize(self):
   1828         return (64, 64)
   1829 
   1830     def GetMaxTextWidth(self, defaultTextWidth = 0):
   1831         nodes = self.GetNodes()
   1832         if not nodes:
   1833             return defaultTextWidth
   1834         textWidths = []
   1835         for node in nodes:
   1836             fontsize = node.Get('fontsize', DEFAULT_FONTSIZE)
   1837             hspace = node.Get('letterspace', DEFAULT_LETTERSPACE)
   1838             uppercase = node.Get('uppercase', DEFAULT_UPPERCASE)
   1839             textWidth = uicore.font.GetTextWidth(node.label, fontsize, hspace, uppercase)
   1840             padLeft = node.Get('padLeft', self.tabMargin)
   1841             padRight = node.Get('padRight', self.tabMargin)
   1842             padIndentation = uiconst.ENTRY_DEFAULT_ICONSIZE * (1 + node.get('sublevel', 0))
   1843             textWidth += padIndentation + padLeft + padRight
   1844             textWidths.append(textWidth)
   1845 
   1846         return max(textWidths)
   1847 
   1848     def GetNoItemNode(self, text, sublevel = 0, *args):
   1849         return GetFromClass(Generic, {'label': text,
   1850          'sublevel': sublevel})
   1851 
   1852     def Copy(self, *args):
   1853         myNodes = self.GetSelected() or self.GetNodes()
   1854         allLabelsList = []
   1855         for node in myNodes:
   1856             decoClass = node.decoClass
   1857             if decoClass is None:
   1858                 report_node_missing_deco_class(node)
   1859                 continue
   1860             GetCopyData = getattr(decoClass, 'GetCopyData', None)
   1861             if GetCopyData is None:
   1862                 continue
   1863             label = GetCopyData(node)
   1864             if label:
   1865                 allLabelsList.append(label)
   1866 
   1867         allLabels = '\n'.join(allLabelsList)
   1868         allLabels = allLabels.replace('<t>', '\t')
   1869         strippedText = StripTags(allLabels)
   1870         if strippedText:
   1871             blue.pyos.SetClipboardData(strippedText)
   1872             numEntriesCopied = len(allLabelsList)
   1873             text = localization.GetByLabel('UI/Common/EntriesCopied', numEntries=numEntriesCopied)
   1874             message_player(text)
   1875 
   1876     def AddCollapseBtn(self):
   1877         self.sr.collapseCont = Container(parent=self.sr.scrollHeaders, name='collapseCont', pos=(4, 0, 12, 0), align=uiconst.TOLEFT, idx=0 if self.isLinelessScroll else 1)
   1878         ButtonIcon(name='collapse', parent=self.sr.collapseCont, align=uiconst.CENTERLEFT, pos=(0, 0, 12, 12), iconSize=12, texturePath='res:/UI/Texture/classes/Scroll/Collapse.png', func=self.CollapseAll, hint=localization.GetByLabel('UI/Common/Buttons/CollapseAll'))
   1879 
   1880     def CollapseAll(self, *args):
   1881         for eachNode in self.GetNodes():
   1882             if eachNode.Get('GetSubContent', None):
   1883                 uicore.registry.SetListGroupOpenState(eachNode.id, 0)
   1884 
   1885         self.ReloadNodes()
   1886 
   1887     def GetEffectiveColumnOffset(self):
   1888         if not self._showCollapseIcon:
   1889             return 0
   1890         return self._columnOffset
   1891 
   1892     def Startup(self, minZ = None):
   1893         pass
   1894 
   1895     def SetDefaultSmartSorting(self, column, sortOrder, availableColumns = None):
   1896         if not self.sr.id:
   1897             return
   1898         try:
   1899             self._SetDefaultSmartSortingPrimary(column, availableColumns)
   1900             self._SetDefaultSmartSortingDirection(column, sortOrder)
   1901         except Exception as e:
   1902             log.exception('Failed to set deault smart sorting')
   1903 
   1904     def _SetDefaultSmartSortingPrimary(self, column, availableColumns):
   1905         primaryColumns = settings.user.ui.Get(PRIMARY_COLUMN % VERSION, {})
   1906         currentPrimaryForScroll = primaryColumns.get(self.sr.id, None)
   1907         if currentPrimaryForScroll:
   1908             if availableColumns and currentPrimaryForScroll not in availableColumns:
   1909                 primaryColumns[self.sr.id] = column
   1910         else:
   1911             primaryColumns[self.sr.id] = column
   1912         settings.user.ui.Set(PRIMARY_COLUMN % VERSION, primaryColumns)
   1913 
   1914     def _SetDefaultSmartSortingDirection(self, column, sortOrder):
   1915         sortDirections = settings.user.ui.Get(SMART_SORT_DIRECTION % VERSION, {})
   1916         directionsForScroll = sortDirections.get(self.sr.id, None)
   1917         if directionsForScroll is None:
   1918             directionsForScroll = sortDirections[self.sr.id] = {}
   1919         if column not in directionsForScroll:
   1920             directionsForScroll[column] = sortOrder
   1921         settings.user.ui.Set(SMART_SORT_DIRECTION % VERSION, sortDirections)
   1922 
   1923 
   1924 def report_node_missing_deco_class(node):
   1925     scroll_path = ''
   1926     scroll = getattr(node, 'scroll', None)
   1927     if scroll is not None:
   1928         scroll_path = gather_element_path(scroll)
   1929         scroll_path = ' at {}'.format(scroll_path)
   1930     log.error('Found a %s node without a decoClass property%s', getattr(node, '__guid__', None), scroll_path)
   1931 
   1932 
   1933 def gather_element_path(element):
   1934     path = None
   1935     while element:
   1936         name = getattr(element, 'name', None)
   1937         if name is None:
   1938             element_type = getattr(element, '__class__', None)
   1939             if element_type is not None:
   1940                 name = '[class:{}]'.format(element_type.__name__)
   1941             else:
   1942                 name = '[unknown]'
   1943         path = '{}.{}'.format(name, path) if path is not None else name
   1944         element = getattr(element, 'parent', None)
   1945 
   1946     return path