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)