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

group.py (28930B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\carbonui\button\group.py
      2 import logging
      3 import weakref
      4 import eveicon
      5 import localization
      6 from carbonui import uiconst
      7 from carbonui.button.const import ButtonFrameType, ButtonVariant
      8 from carbonui.control.button import Button
      9 from carbonui.control.contextMenu.menuData import MenuData
     10 from carbonui.primitives.childrenlist import PyChildrenList
     11 from carbonui.primitives.container import Container
     12 from carbonui.primitives.containerAutoSize import ContainerAutoSize, UIChildrenListAutoSize
     13 from carbonui.uiconst import Axis, AxisAlignment, Density
     14 from carbonui.util.dpi import reverse_scale_dpi
     15 from carbonui.util.various_unsorted import divide_evenly
     16 log = logging.getLogger(__name__)
     17 
     18 class ButtonSizeMode(object):
     19     DYNAMIC = 1
     20     EQUAL = 2
     21     STRETCH = 3
     22     DYNAMIC_STRETCH = 4
     23 
     24 
     25 class OverflowAlign(object):
     26     RIGHT = 1
     27     LEFT = 2
     28 
     29 
     30 class ButtonData(object):
     31 
     32     def __init__(self, text, callback = None, hint = None, icon = None, modal_result = False, is_default = False, is_cancel = False, ui_name = None, style = None, variant = None, button_class = Button, analytic_id = None, legacy_args = (), legacy_color = None, legacy_density = None):
     33         self.text = text
     34         self.callback = callback
     35         self.hint = hint
     36         self.icon = icon
     37         self.modal_result = modal_result
     38         self.is_default = is_default
     39         self.is_cancel = is_cancel
     40         self.ui_name = ui_name
     41         self.style = style
     42         self.variant = variant
     43         self.button_class = button_class
     44         self.analytic_id = analytic_id
     45         self._legacy_args = legacy_args
     46         self._legacy_color = legacy_color
     47         self._legacy_density = legacy_density
     48 
     49     @classmethod
     50     def from_legacy_tuple(cls, data, button_class):
     51         kwargs = {'text': data[0],
     52          'callback': data[1]}
     53         if len(data) > 2:
     54             kwargs['legacy_args'] = data[2]
     55         if len(data) > 4:
     56             kwargs['modal_result'] = data[4]
     57         if len(data) > 5:
     58             kwargs['is_default'] = data[5]
     59         if len(data) > 6:
     60             kwargs['is_cancel'] = data[6]
     61         if len(data) > 7:
     62             kwargs['hint'] = data[7]
     63         if len(data) > 8:
     64             kwargs['legacy_color'] = data[8]
     65         if len(data) > 9:
     66             kwargs['button_class'] = data[9] or button_class
     67         if len(data) > 10:
     68             kwargs['ui_name'] = data[10]
     69         if len(data) > 11:
     70             kwargs['style'] = data[11]
     71         if len(data) > 12:
     72             kwargs['legacy_density'] = data[12]
     73         if len(data) > 13:
     74             kwargs['variant'] = data[13]
     75         if len(data) > 14:
     76             kwargs['icon'] = data[14]
     77         if len(data) > 15:
     78             kwargs['analytic_id'] = data[15]
     79         return ButtonData(**kwargs)
     80 
     81     def create_button(self, density):
     82         if self._legacy_density is not None:
     83             density = self._legacy_density
     84         return self.button_class(name=self.ui_name or '%s_Btn' % self.text, label=self.text, texturePath=self.icon, func=self.callback, hint=self.hint, style=self.style, variant=self.variant, density=density, btn_modalresult=self.modal_result, btn_default=self.is_default, btn_cancel=self.is_cancel, args=self._legacy_args, color=self._legacy_color, fontStyle=self.button_class.default_fontStyle, fontFamily=self.button_class.default_fontFamily, fontPath=self.button_class.default_fontPath, fontsize=self.button_class.default_fontsize, analyticID=self.analytic_id)
     85 
     86 
     87 IMPORTANCE_BY_VARIANT = {ButtonVariant.GHOST: 0,
     88  ButtonVariant.NORMAL: 1,
     89  ButtonVariant.PRIMARY: 2}
     90 
     91 class ButtonGroup(Container):
     92     default_align = uiconst.TOBOTTOM
     93     default_state = uiconst.UI_PICKCHILDREN
     94     _button_container = None
     95     _overflow_button = None
     96 
     97     def __init__(self, buttons = None, button_alignment = AxisAlignment.CENTER, button_size_mode = ButtonSizeMode.EQUAL, density = Density.NORMAL, orientation = Axis.HORIZONTAL, overflow_align = OverflowAlign.RIGHT, ignore_overflow = False, **kwargs):
     98         self._button_alignment = button_alignment
     99         self._button_size_mode = button_size_mode
    100         self._density = density
    101         self._orientation = orientation
    102         self._overflow_align = overflow_align
    103         self._ignore_overflow = ignore_overflow
    104         super(ButtonGroup, self).__init__(**kwargs)
    105         self._layout()
    106         self.children = ButtonGroupChildrenList(self)
    107         if 'btns' in kwargs:
    108             for data in kwargs['btns']:
    109                 self._add_button_data(ButtonData.from_legacy_tuple(data=data, button_class=Button))
    110 
    111         else:
    112             for button in buttons or []:
    113                 self.add_button(button)
    114 
    115         if len(self._button_container.children) > 0:
    116             self._resize_self()
    117 
    118     @property
    119     def button_alignment(self):
    120         return self._button_alignment
    121 
    122     @button_alignment.setter
    123     def button_alignment(self, value):
    124         if self._button_alignment != value:
    125             self._button_alignment = value
    126             self._update_button_container_alignment()
    127 
    128     @property
    129     def button_size_mode(self):
    130         return self._button_size_mode
    131 
    132     @button_size_mode.setter
    133     def button_size_mode(self, value):
    134         if self._button_size_mode != value:
    135             self._button_size_mode = value
    136             self._update_button_size_mode()
    137 
    138     @property
    139     def buttons(self):
    140         buttons = []
    141         if self._button_container and not self._button_container.destroyed:
    142             for element in self._button_container.children:
    143                 if isinstance(element, ButtonWrapper):
    144                     button = element.button
    145                     if button:
    146                         buttons.append(button)
    147 
    148         return buttons
    149 
    150     @property
    151     def density(self):
    152         return self._density
    153 
    154     @density.setter
    155     def density(self, value):
    156         if self._density != value:
    157             self._density = value
    158             self._update_button_density()
    159 
    160     @property
    161     def orientation(self):
    162         return self._orientation
    163 
    164     @orientation.setter
    165     def orientation(self, value):
    166         if self.orientation != value:
    167             self._orientation = value
    168             self._update_button_container_alignment()
    169             if not self.destroyed:
    170                 self.FlagAlignmentDirty()
    171 
    172     def add_button(self, button, index = -1):
    173         if self.destroyed:
    174             return
    175         button.density = self._density
    176         ButtonWrapper(parent=self._button_container, align=uiconst.TOPLEFT, idx=index, button=button, on_child_removed=self._on_button_removed_from_wrapper)
    177         self._adjust_overflow_button_index()
    178         if hasattr(button, 'label') and button.label is not None:
    179             self.sr.Set('%s_Btn' % button.label, button)
    180         self.FlagAlignmentDirty('ButtonGroup.add_button()')
    181         return button
    182 
    183     def remove_button(self, button):
    184         if self.destroyed:
    185             return
    186         if button.parent is not None and button.parent.parent != self._button_container:
    187             raise ValueError('That button is not in this ButtonGroup')
    188         self._remove_button(button, wrapper=button.parent)
    189 
    190     def _remove_button(self, button, wrapper = None):
    191         if wrapper is None:
    192             wrapper = button.parent
    193         if wrapper is not None and self._button_container and not self._button_container.destroyed:
    194             self._button_container.children.remove(wrapper)
    195         if hasattr(button, 'label'):
    196             try:
    197                 del self.sr['%s_Btn' % button.label]
    198             except KeyError:
    199                 log.warning('Unable to remove the SR reference for a button: %s', '%s_Btn' % button.label)
    200 
    201     def _add_button_data(self, button_data):
    202         button = button_data.create_button(density=self._density)
    203         return self.add_button(button)
    204 
    205     def _create_overflow_button(self):
    206         if self._overflow_button is None:
    207             self._overflow_button = OverflowButton(parent=self._button_container, align=uiconst.TOPLEFT, get_menu=self._get_overflow_menu, density=self._density)
    208             self._overflow_button.display = False
    209 
    210     def _adjust_overflow_button_index(self):
    211         if self._overflow_button:
    212             button_count = len(self._button_container.children)
    213             current_index = self._overflow_button.GetOrder()
    214             if current_index < button_count - 1:
    215                 self._overflow_button.SetOrder(-1)
    216 
    217     def _show_overflow_button(self):
    218         if self._overflow_button is not None:
    219             self._overflow_button.display = True
    220 
    221     def _hide_overflow_button(self):
    222         if self._overflow_button is not None:
    223             self._overflow_button.display = False
    224 
    225     def AddButton(self, label, func, args = None, fixedWidth = None, isModalResult = False, isDefault = False, isCancel = False, hint = None, color = None, btnClass = None, uiName = None, style = None, density = None, variant = None, texturePath = None, analytic_id = None):
    226         return self._add_button_data(button_data=ButtonData.from_legacy_tuple(data=(label,
    227          func,
    228          args,
    229          fixedWidth,
    230          isModalResult,
    231          isDefault,
    232          isCancel,
    233          hint,
    234          color,
    235          btnClass,
    236          uiName,
    237          style,
    238          density,
    239          variant,
    240          texturePath,
    241          analytic_id), button_class=Button))
    242 
    243     def Flush(self):
    244         self.FlushButtons()
    245 
    246     def FlushButtons(self):
    247         if self.destroyed:
    248             return
    249         for child in self._button_container.children[:]:
    250             if isinstance(child, ButtonWrapper):
    251                 child.Close()
    252 
    253         to_remove = []
    254         for name in self.sr.keys():
    255             if name.endswith('_Btn'):
    256                 to_remove.append(name)
    257 
    258         for name in to_remove:
    259             del self.sr[name]
    260 
    261     def ResetLayout(self):
    262         if not self.destroyed:
    263             self.FlagAlignmentDirty()
    264 
    265     def _layout(self):
    266         self._button_container = ContainerAutoSize(name='btns', parent=self, align=self._get_button_container_align())
    267         self._create_overflow_button()
    268 
    269     def _get_button_container_align(self):
    270         if self._button_alignment == AxisAlignment.START:
    271             return uiconst.TOPLEFT
    272         if self._button_alignment == AxisAlignment.CENTER:
    273             if self._orientation == Axis.HORIZONTAL:
    274                 return uiconst.CENTERTOP
    275             if self._orientation == Axis.VERTICAL:
    276                 return uiconst.CENTERLEFT
    277         elif self._button_alignment == AxisAlignment.END:
    278             if self._orientation == Axis.HORIZONTAL:
    279                 return uiconst.TOPRIGHT
    280             if self._orientation == Axis.VERTICAL:
    281                 return uiconst.BOTTOMLEFT
    282 
    283     def _update_button_container_alignment(self):
    284         if self._button_container and not self._button_container.destroyed:
    285             self._button_container.align = self._get_button_container_align()
    286 
    287     def _update_button_density(self):
    288         if not self.destroyed:
    289             for button in self._iter_buttons():
    290                 button.density = self._density
    291 
    292             if self._overflow_button is not None:
    293                 self._overflow_button.density = self._density
    294 
    295     def _get_button_gap(self):
    296         if self._density == Density.COMPACT:
    297             return 4
    298         else:
    299             return 8
    300 
    301     def _update_layout(self, budget_width, budget_height):
    302         if self._orientation == Axis.HORIZONTAL:
    303             if self.align in uiconst.ALIGNMENTS_WITH_RELEVANT_WIDTH:
    304                 if self._button_size_mode == ButtonSizeMode.STRETCH:
    305                     budget_width = self.width
    306                 else:
    307                     budget_width = None
    308             else:
    309                 budget_width = budget_width
    310         elif self.align in uiconst.ALIGNMENTS_WITH_RELEVANT_HEIGHT:
    311             budget_height = None
    312         else:
    313             budget_height = budget_height
    314         if not self._ignore_overflow:
    315             self._update_button_overflow(budget_width, budget_height)
    316         if self._button_size_mode == ButtonSizeMode.EQUAL:
    317             self._resize_buttons_equal()
    318         elif self._button_size_mode == ButtonSizeMode.DYNAMIC:
    319             self._resize_buttons_dynamic()
    320         elif self._button_size_mode == ButtonSizeMode.STRETCH:
    321             self._resize_buttons_stretch(budget_width)
    322         elif self._button_size_mode == ButtonSizeMode.DYNAMIC_STRETCH:
    323             self._resize_buttons_dynamic_stretch(budget_width)
    324         self._position_buttons()
    325         self._update_button_frames()
    326         self._resize_self()
    327 
    328     def _iter_buttons(self):
    329         for element in self._button_container.children:
    330             if isinstance(element, ButtonWrapper):
    331                 button = element.button
    332                 if button:
    333                     yield button
    334 
    335     def _iter_visible_buttons(self):
    336         for element in self._button_container.children:
    337             if isinstance(element, ButtonWrapper):
    338                 if element.display:
    339                     button = element.button
    340                     if button and button.display:
    341                         yield button
    342 
    343     def _iter_button_wrappers(self, check_reverse = False):
    344         elements = self._button_container.children
    345         if check_reverse and self._overflow_align == OverflowAlign.LEFT:
    346             elements = reversed(elements)
    347         return (element for element in elements if isinstance(element, ButtonWrapper))
    348 
    349     def _update_button_overflow(self, budget_width, budget_height):
    350         overflow_at_wrapper_index = self._check_button_overflow(budget_width, budget_height)
    351         if overflow_at_wrapper_index is not None:
    352             self._show_overflow_button()
    353             if self._orientation == Axis.HORIZONTAL:
    354                 remaining_budget_width = budget_width - (self._overflow_button.get_intrinsic_width() + self._get_button_gap())
    355                 overflow_at_wrapper_index = self._check_button_overflow(remaining_budget_width, budget_height)
    356             else:
    357                 remaining_budget_height = budget_height - (self._overflow_button.height + self._get_button_gap())
    358                 overflow_at_wrapper_index = self._check_button_overflow(budget_width, remaining_budget_height)
    359             self._update_overflow_button_variant(overflow_at_wrapper_index)
    360         else:
    361             self._hide_overflow_button()
    362         self._update_button_wrapper_display(overflow_at_wrapper_index)
    363 
    364     def _check_button_overflow(self, budget_width, budget_height):
    365         main_axis_budget = budget_width if self._orientation == Axis.HORIZONTAL else budget_height
    366         if main_axis_budget is None:
    367             return
    368         visible_button_count = 0
    369         max_button_size = 0
    370         total_size = 0
    371         gap_size = self._get_button_gap()
    372         for wrapper_index, wrapper in enumerate(self._iter_button_wrappers(check_reverse=True)):
    373             button = wrapper.button
    374             if not button or not button.display:
    375                 continue
    376             visible_button_count += 1
    377             if self._button_size_mode in {ButtonSizeMode.EQUAL, ButtonSizeMode.STRETCH}:
    378                 if self._orientation == Axis.HORIZONTAL:
    379                     max_button_size = max(max_button_size, button.get_intrinsic_width())
    380                 elif self._orientation == Axis.VERTICAL:
    381                     max_button_size = max(max_button_size, button.height)
    382                 total_gap_size = max(visible_button_count - 1, 0) * gap_size
    383                 total_size = max_button_size * visible_button_count + total_gap_size
    384             elif self._button_size_mode == ButtonSizeMode.DYNAMIC:
    385                 if self._orientation == Axis.HORIZONTAL:
    386                     button_size = button.get_intrinsic_width()
    387                 else:
    388                     button_size = button.height
    389                 total_size += button_size
    390                 if visible_button_count > 1:
    391                     total_size += gap_size
    392             if main_axis_budget is not None and total_size > main_axis_budget:
    393                 return wrapper_index
    394 
    395     def _resize_buttons_equal(self):
    396         max_button_width = 0
    397         for button in self._iter_visible_buttons():
    398             max_button_width = max(button.get_intrinsic_width(), max_button_width)
    399 
    400         if self._orientation == Axis.HORIZONTAL:
    401             for button in self._iter_visible_buttons():
    402                 button.width = max_button_width
    403 
    404         elif self._orientation == Axis.VERTICAL:
    405             for button in self._iter_visible_buttons():
    406                 button.width = max_button_width
    407 
    408             if self._overflow_button is not None and self._overflow_button.display:
    409                 self._overflow_button.width = max_button_width
    410 
    411     def _resize_buttons_dynamic(self):
    412         for button in self._iter_visible_buttons():
    413             button.Update_Size_()
    414 
    415         if self._overflow_button is not None and self._overflow_button.display:
    416             self._overflow_button.Update_Size_()
    417 
    418     def _resize_buttons_stretch(self, budget_width):
    419         if self._orientation == Axis.VERTICAL:
    420             if self.align in uiconst.ALIGNMENTS_WITH_RELEVANT_WIDTH:
    421                 budget_width = self.width
    422             for button in self._iter_visible_buttons():
    423                 button.width = max(budget_width, button.get_intrinsic_width())
    424 
    425             if self._overflow_button is not None and self._overflow_button.display:
    426                 self._overflow_button.width = max(budget_width, self._overflow_button.get_intrinsic_width())
    427         else:
    428             buttons = list(self._iter_visible_buttons())
    429             button_count = len(buttons)
    430             if self._overflow_button is not None and self._overflow_button.display and button_count > 0:
    431                 budget_width -= self._overflow_button.get_intrinsic_width() + self._get_button_gap()
    432             total_button_width = budget_width - max(0, button_count - 1) * self._get_button_gap()
    433             if self.maxWidth:
    434                 total_button_width = min(total_button_width, self.maxWidth)
    435             for i, button in enumerate(buttons):
    436                 allocated_width = divide_evenly(value=total_button_width, index=i, total_count=button_count)
    437                 button.width = max(allocated_width, button.get_intrinsic_width())
    438 
    439             if self._overflow_button is not None and self._overflow_button.display:
    440                 if button_count > 0:
    441                     self._overflow_button.width = self._overflow_button.get_intrinsic_width()
    442                 else:
    443                     self._overflow_button.width = max(budget_width, self._overflow_button.get_intrinsic_width())
    444 
    445     def _resize_buttons_dynamic_stretch(self, budget_width):
    446         if self._orientation == Axis.VERTICAL:
    447             if self.align in uiconst.ALIGNMENTS_WITH_RELEVANT_WIDTH:
    448                 budget_width = self.width
    449             for button in self._iter_visible_buttons():
    450                 button.width = max(budget_width, button.get_intrinsic_width())
    451 
    452             if self._overflow_button is not None and self._overflow_button.display:
    453                 self._overflow_button.width = max(budget_width, self._overflow_button.get_intrinsic_width())
    454         else:
    455 
    456             def is_icon_button(btn):
    457                 return btn.label is None or btn.label == ''
    458 
    459             buttons = list(self._iter_visible_buttons())
    460             button_count = len(buttons)
    461             if self._overflow_button is not None and self._overflow_button.display and button_count > 0:
    462                 budget_width -= self._overflow_button.get_intrinsic_width() + self._get_button_gap()
    463             total_button_width = budget_width - max(0, button_count - 1) * self._get_button_gap()
    464             if self.maxWidth:
    465                 total_button_width = min(total_button_width, self.maxWidth)
    466             for button in buttons:
    467                 if is_icon_button(button):
    468                     width = button.height
    469                     button.width = width
    470                     total_button_width -= width
    471 
    472             big_button_count = len([ b for b in buttons if not is_icon_button(b) ])
    473             for i, button in enumerate([ b for b in buttons if not is_icon_button(b) ]):
    474                 if is_icon_button(button):
    475                     continue
    476                 allocated_width = divide_evenly(value=total_button_width, index=i, total_count=big_button_count)
    477                 button.width = max(allocated_width, button.get_intrinsic_width())
    478 
    479             if self._overflow_button is not None and self._overflow_button.display:
    480                 if button_count > 0:
    481                     self._overflow_button.width = self._overflow_button.get_intrinsic_width()
    482                 else:
    483                     self._overflow_button.width = max(budget_width, self._overflow_button.get_intrinsic_width())
    484 
    485     def _position_buttons(self):
    486         left = 0
    487         top = 0
    488         button_gap = self._get_button_gap()
    489         for wrapper in self._iter_button_wrappers():
    490             if not wrapper.display:
    491                 continue
    492             button = wrapper.button
    493             if not button:
    494                 continue
    495             wrapper.top = top
    496             wrapper.left = left
    497             if self._orientation == Axis.VERTICAL:
    498                 top += button.height + button_gap
    499             else:
    500                 left += button.width + button_gap
    501 
    502         if self._overflow_button and self._overflow_button.display:
    503             self._overflow_button.top = top
    504             self._overflow_button.left = left
    505 
    506     def _update_button_frames(self):
    507         buttons = list(self._iter_visible_buttons())
    508         if self._overflow_button is not None and self._overflow_button.display:
    509             buttons.append(self._overflow_button)
    510         button_count = len(buttons)
    511         for i, button in enumerate(buttons):
    512             if self._button_size_mode == ButtonSizeMode.STRETCH and button_count == 1:
    513                 frame_type = ButtonFrameType.CUT_BOTTOM_LEFT_RIGHT
    514             elif i == button_count - 1:
    515                 if self._button_size_mode == ButtonSizeMode.STRETCH and self._orientation == Axis.VERTICAL:
    516                     frame_type = ButtonFrameType.CUT_BOTTOM_LEFT_RIGHT
    517                 else:
    518                     frame_type = ButtonFrameType.CUT_BOTTOM_RIGHT
    519             elif i == 0 and self._button_size_mode == ButtonSizeMode.STRETCH and not self._orientation == Axis.VERTICAL:
    520                 frame_type = ButtonFrameType.CUT_BOTTOM_LEFT
    521             else:
    522                 frame_type = ButtonFrameType.RECTANGLE
    523             button.frame_type = frame_type
    524 
    525     def _update_button_wrapper_display(self, overflow_at_wrapper_index):
    526         for i, wrapper in enumerate(self._iter_button_wrappers(check_reverse=True)):
    527             if overflow_at_wrapper_index is None or i < overflow_at_wrapper_index:
    528                 if wrapper.button.display:
    529                     wrapper.display = True
    530             elif i >= overflow_at_wrapper_index:
    531                 wrapper.display = False
    532 
    533     def _update_overflow_button_variant(self, overflow_at_wrapper_index):
    534         variant = ButtonVariant.GHOST
    535         if self._overflow_button.display:
    536             for i, wrapper in enumerate(self._iter_button_wrappers(check_reverse=True)):
    537                 if i >= overflow_at_wrapper_index:
    538                     if IMPORTANCE_BY_VARIANT[wrapper.button.variant] > IMPORTANCE_BY_VARIANT[variant]:
    539                         variant = wrapper.button.variant
    540 
    541             self._overflow_button.variant = variant
    542 
    543     def _resize_self(self):
    544         content_width, content_height = self._button_container.GetAutoSize()
    545         width = 0
    546         height = 0
    547         if self._button_size_mode == ButtonSizeMode.STRETCH:
    548             width = self.width
    549         elif self.align in uiconst.ALIGNMENTS_WITH_RELEVANT_WIDTH:
    550             width += content_width
    551             if self.align in uiconst.ALIGNMENTS_WITH_INCLUDED_HORIZONTAL_PADDING:
    552                 width += self.padLeft + self.padRight
    553         if self.align in uiconst.ALIGNMENTS_WITH_RELEVANT_HEIGHT:
    554             height += content_height
    555             if self.align in uiconst.ALIGNMENTS_WITH_INCLUDED_VERTICAL_PADDING:
    556                 height += self.padTop + self.padBottom
    557         self.width = width
    558         self.height = height
    559 
    560     def _update_button_size_mode(self):
    561         if not self.destroyed:
    562             self.FlagAlignmentDirty()
    563 
    564     def _get_overflow_menu(self):
    565         menu = MenuData()
    566         for wrapper in self._iter_button_wrappers(check_reverse=True):
    567             if not wrapper.display:
    568                 button = wrapper.button
    569                 if button and button.display:
    570                     menu_entry_data = button.get_menu_entry_data()
    571                     if menu_entry_data is not None:
    572                         menu.AppendMenuEntryData(menu_entry_data)
    573 
    574         return menu
    575 
    576     def _on_button_removed_from_wrapper(self, wrapper, button):
    577         self._remove_button(button, wrapper)
    578 
    579     def SetAlign(self, align):
    580         previous_align = self.align
    581         super(ButtonGroup, self).SetAlign(align)
    582         if self.align != previous_align:
    583             self._update_button_container_alignment()
    584 
    585     def UpdateAlignment(self, budgetLeft = 0, budgetTop = 0, budgetWidth = 0, budgetHeight = 0, updateChildrenOnly = False):
    586         budget_width = reverse_scale_dpi(budgetWidth)
    587         budget_width = max(0, budget_width - self.padLeft - self.padRight)
    588         budget_height = reverse_scale_dpi(budgetHeight)
    589         budget_height = max(0, budget_height - self.padTop - self.padBottom)
    590         self._update_layout(budget_width, budget_height)
    591         return super(ButtonGroup, self).UpdateAlignment(budgetLeft, budgetTop, budgetWidth, budgetHeight, updateChildrenOnly)
    592 
    593 
    594 class ButtonGroupChildrenList(PyChildrenList):
    595 
    596     def __init__(self, button_group):
    597         super(ButtonGroupChildrenList, self).__init__(owner=button_group, children=button_group.children)
    598 
    599     def append(self, obj):
    600         return self.insert(-1, obj)
    601 
    602     def insert(self, idx, obj):
    603         button_group = self.GetOwner()
    604         if button_group:
    605             if not isinstance(obj, Button):
    606                 log.error('Warning while adding child to ButtonGroup at {}'.format(_prepare_ancestor_chain_debug_description(button_group)))
    607                 super(ButtonGroupChildrenList, self).insert(idx, obj)
    608             else:
    609                 button_group.add_button(obj, index=idx)
    610             return self
    611 
    612     def remove(self, obj):
    613         button_group = self.GetOwner()
    614         if button_group:
    615             button_group.remove_button(obj)
    616             return self
    617 
    618 
    619 def _prepare_ancestor_chain_debug_description(element):
    620     parts = []
    621     while element:
    622         parts.append('{}(name="{}")'.format(element.__class__.__name__, getattr(element, 'name', '???')))
    623         element = getattr(element, 'parent', None)
    624 
    625     return '.'.join(reversed(parts))
    626 
    627 
    628 class OverflowButton(Button):
    629     expandOnLeft = True
    630 
    631     def __init__(self, get_menu, **kwargs):
    632         self._get_menu = get_menu
    633         super(OverflowButton, self).__init__(hint=localization.GetByLabel('UI/Common/More'), texturePath=eveicon.more_vertical, **kwargs)
    634 
    635     def GetMenu(self):
    636         return self._get_menu()
    637 
    638     def GetMenuPosition(self, element):
    639         return (self.absoluteLeft, self.absoluteBottom)
    640 
    641 
    642 class ButtonWrapper(ContainerAutoSize):
    643 
    644     def __init__(self, button, on_child_removed, **kwargs):
    645         self._button_ref = weakref.ref(button)
    646         self._on_child_removed = on_child_removed
    647         super(ButtonWrapper, self).__init__(**kwargs)
    648         if button.parent is not None:
    649             button.SetParent(None)
    650         button.align = uiconst.TOPLEFT
    651         button.on_display_changed.connect(self._on_button_display_changed)
    652         button.SetParent(self)
    653         self.display = button.display
    654 
    655     @property
    656     def button(self):
    657         return self._button_ref()
    658 
    659     def Close(self):
    660         button = self._button_ref()
    661         if button:
    662             button.on_display_changed.disconnect(self._on_button_display_changed)
    663         super(ButtonWrapper, self).Close()
    664 
    665     def GetChildrenList(self):
    666         return ButtonWrapperChildrenList(owner=self, on_child_removed=self._emit_on_child_removed)
    667 
    668     def _emit_on_child_removed(self, child):
    669         self._on_child_removed(self, child)
    670 
    671     def _on_button_display_changed(self, button):
    672         self.display = button.display
    673         if self.parent and not self.parent.destroyed:
    674             self.parent.FlagAlignmentDirty()
    675 
    676 
    677 class ButtonWrapperChildrenList(UIChildrenListAutoSize):
    678 
    679     def __init__(self, owner, on_child_removed):
    680         self._on_child_removed = on_child_removed
    681         super(ButtonWrapperChildrenList, self).__init__(owner)
    682 
    683     def remove(self, obj):
    684         super(ButtonWrapperChildrenList, self).remove(obj)
    685         owner = self.GetOwner()
    686         if owner:
    687             self._on_child_removed(obj)