stack.py (20646B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\carbonui\window\stack.py 2 import logging 3 import uthread 4 import uthread2 5 from carbonui import uiconst 6 from carbonui.primitives.base import ReverseScaleDpi 7 from carbonui.control.contextMenu.menuData import MenuData 8 from carbonui.decorative.divider_line import DividerLine 9 from carbonui.primitives.container import Container 10 from carbonui.primitives.containerAutoSize import ContainerAutoSize 11 from carbonui.uicore import uicore 12 from carbonui.util.various_unsorted import Transplant 13 from carbonui.control.window import Window 14 from carbonui.window.header.base import WindowHeaderBase 15 from carbonui.control.tab import Tab 16 from carbonui.control.tabGroup import TabGroup 17 from eve.client.script.ui.shared import closeWindowStackPrompt 18 from menu import MenuLabel 19 log = logging.getLogger(__name__) 20 PREFERRED_IDX_IN_STACK_CONFIG = 'preferredIdxInStack3' 21 22 class WindowStack(Window): 23 24 def ApplyAttributes(self, attributes): 25 self._initializingStack = True 26 self.contentCont = None 27 self.tabsCont = None 28 self._tabGroup = None 29 self._detaching = 0 30 self._inserting = 0 31 super(WindowStack, self).ApplyAttributes(attributes) 32 self.ConstructContentParent() 33 self._initializingStack = False 34 35 @property 36 def compact(self): 37 wnd = self.GetActiveWindow() 38 return wnd and wnd.compact 39 40 @compact.setter 41 def compact(self, value): 42 wnd = self.GetActiveWindow() 43 if wnd and wnd.compact != value: 44 wnd.compact = value 45 self._emit_on_compact_mode_changed() 46 47 @property 48 def compactable(self): 49 wnd = self.GetActiveWindow() 50 return wnd and wnd.compactable 51 52 @property 53 def killable(self): 54 return all((window.killable for window in self.GetWindows())) 55 56 @property 57 def is_stack(self): 58 return True 59 60 def _on_header_tab_selected(self, tab_id, old_tab_id): 61 old_window = old_tab_id 62 new_window = tab_id 63 if old_window is None or new_window is not None and new_window.compact != old_window.compact: 64 self._emit_on_compact_mode_changed() 65 self._update_window_controls() 66 self._update_window_caption() 67 68 def ConstructContentParent(self): 69 self.contentCont = Container(name='__content', parent=self.sr.maincontainer, align=uiconst.TOALL, idx=self.sr.headerParent.GetOrder() + 1) 70 71 def Prepare_LoadingIndicator_(self): 72 self.sr.loadingIndicator = None 73 74 def Prepare_ScaleAreas_(self): 75 super(WindowStack, self).Prepare_ScaleAreas_() 76 for wnd in self.GetWindows(): 77 wnd.Prepare_ScaleAreas_() 78 79 def Prepare_Header_(self): 80 self.header = WindowStackHeader(callback=self._on_header_tab_selected) 81 82 def CheckStackWith(self, dropWnd): 83 if self.closing: 84 return 85 if not self.IsStackable(): 86 return 87 if self.sr.modalParent is not None or dropWnd.sr.modalParent is not None or dropWnd == self: 88 return 89 if not isinstance(dropWnd, WindowStack): 90 self.InsertWnd(dropWnd, 0, 1, is_dropping=True) 91 else: 92 for wnd in dropWnd.GetWindows()[:]: 93 self.InsertWnd(wnd, 0, 1, is_dropping=True) 94 95 dropWnd.Close() 96 uicore.registry.SetFocus(self) 97 98 def Check(self, updatewnd = 0, autoselecttab = 1, checknone = 0): 99 if self.closing: 100 return 101 myWindows = self.GetWindows() 102 if checknone and len(myWindows) == 0: 103 self.Close() 104 return 105 self.SetMinWH() 106 tabs = [] 107 for wnd in myWindows: 108 if wnd is None or wnd.destroyed: 109 continue 110 tabs.append([wnd.caption or '', 111 wnd, 112 self, 113 wnd]) 114 wnd.HideBackground() 115 wnd.state = uiconst.UI_PICKCHILDREN 116 117 self._UpdateWindowStates(myWindows) 118 if len(tabs): 119 self.header.update_tabs(tabs, auto_select=bool(autoselecttab)) 120 all_tabs = self.header.tab_group.GetTabs() 121 if all_tabs: 122 for i in xrange(len(all_tabs)): 123 tab = all_tabs[i] 124 wnd = myWindows[i] 125 if wnd.isBlinking: 126 tab.Blink() 127 128 if not self.active and any((window.active for window in myWindows)): 129 self.SetActive() 130 131 def _update_window_caption(self): 132 active_window = self.GetActiveWindow() 133 if active_window: 134 self.caption = active_window.caption 135 else: 136 self.caption = '' 137 138 def _UpdateWindowStates(self, myWindows): 139 if any((wnd.IsOverlayed() for wnd in myWindows)): 140 self.SetOverlayed() 141 else: 142 self.SetNotOverlayed() 143 if any((wnd.IsLightBackgroundEnabled() for wnd in myWindows)): 144 self.EnableLightBackground() 145 else: 146 self.DisableLightBackground() 147 148 def IsLightBackgroundConfigurable(self): 149 return all((wnd.IsLightBackgroundConfigurable() for wnd in self.GetWindows())) 150 151 def EnableLightBackground(self): 152 super(WindowStack, self).EnableLightBackground() 153 for wnd in self.GetWindows(): 154 wnd.EnableLightBackground() 155 156 def DisableLightBackground(self): 157 super(WindowStack, self).DisableLightBackground() 158 for wnd in self.GetWindows(): 159 wnd.DisableLightBackground() 160 161 def SetOverlayed(self): 162 super(WindowStack, self).SetOverlayed() 163 for wnd in self.GetWindows(): 164 wnd.SetOverlayed() 165 166 def SetNotOverlayed(self): 167 super(WindowStack, self).SetNotOverlayed() 168 for wnd in self.GetWindows(): 169 wnd.SetNotOverlayed() 170 171 def GetMenu(self, *args): 172 menu = [] 173 if self.IsKillable(): 174 menu.append((MenuLabel('/Carbon/UI/Controls/Window/CloseWindowStack'), self.CloseByUser)) 175 if not self.stacked and self.IsMinimizable(): 176 if self.state == uiconst.UI_NORMAL: 177 menu.append((MenuLabel('/Carbon/UI/Controls/Window/MinimizeWindowStack'), self.ToggleMinimize)) 178 else: 179 menu.append((MenuLabel('/Carbon/UI/Controls/Window/MaximizeWindowStack'), self.ToggleMinimize)) 180 return menu 181 182 def GetMenuMoreOptions(self): 183 wnd = self.GetActiveWindow() 184 if wnd: 185 return wnd.GetMenuMoreOptions() 186 else: 187 return MenuData() 188 189 def get_wnd_menu_unique_name(self): 190 wnd = self.GetActiveWindow() 191 if wnd: 192 return wnd.get_wnd_menu_unique_name() 193 else: 194 return None 195 196 def GetCustomHeaderButtons(self): 197 wnd = self.GetActiveWindow() 198 if wnd: 199 return wnd.GetCustomHeaderButtons() 200 else: 201 return [] 202 203 def GetWindowLinkData(self): 204 wnd = self.GetActiveWindow() 205 if wnd: 206 return wnd.GetWindowLinkData() 207 else: 208 return None 209 210 def RemoveWnd(self, wnd, grab, correctpos = 1, idx = 0, dragging = 0, check = 1): 211 if wnd.parent != self.contentCont: 212 return 213 if hasattr(wnd, 'OnTabSelect'): 214 uthread.worker('WindowStack::RemoveWnd', wnd.OnTabSelect) 215 wnd._detaching = True 216 Transplant(wnd, self.parent, idx) 217 if hasattr(wnd, '_stack_cacheContents'): 218 wnd.cacheContents = wnd._stack_cacheContents 219 self.RemovePreferredIdx(self.windowID, wnd.windowID) 220 self._SetRemovedWindowSize(wnd) 221 wnd.RegisterStackID(None) 222 wnd.OnStackRemove(correctpos, dragging, grab) 223 if check: 224 self.Check() 225 wnd._detaching = False 226 wnd._dragging = dragging 227 myWindows = self.GetWindows() 228 if len(myWindows) == 1 and not self.IsCollapsed(): 229 w = myWindows[0] 230 aL, aT, aW, aH = self.GetAbsolute() 231 x, y = aL, aT 232 self.RemoveWnd(w, (0, 0), 1, 1, check=0) 233 self.RemovePreferredIdx(self.windowID, w) 234 w.left, w.top = x, y 235 sm.ChainEvent('ProcessWindowUnstacked', wnd, self) 236 return 237 if len(self.GetWindows()) == 0: 238 self.Close() 239 sm.ChainEvent('ProcessWindowUnstacked', wnd, self) 240 241 def CloseByUser(self, *args): 242 if len(self.GetWindows()) > 1: 243 response = closeWindowStackPrompt.ask() 244 if response == closeWindowStackPrompt.Response.close_all: 245 for window in self.GetWindows(): 246 window.CloseByUser(*args) 247 248 elif response == closeWindowStackPrompt.Response.close_current: 249 self.GetActiveWindow().CloseByUser(*args) 250 else: 251 canCloseStack = True 252 for wnd in self.GetWindows()[:]: 253 canClose = wnd.CloseByUser() 254 if canClose is False: 255 canCloseStack = False 256 257 if canCloseStack: 258 super(WindowStack, self).CloseByUser(*args) 259 260 def _SetRemovedWindowSize(self, wnd): 261 wnd.width = wnd._fixedWidth or self.width 262 wnd.height = wnd._fixedHeight or self.height 263 wnd.CheckMaxSize() 264 265 def RegisterPositionAndSize(self, key = None, windowID = None): 266 super(WindowStack, self).RegisterPositionAndSize(key, windowID) 267 for each in self.GetWindows(): 268 each.RegisterPositionAndSize() 269 270 def GetCollapsedHeight(self): 271 _, margin_top, _, margin_bottom = self.GetWindowBorderPadding() 272 _, border_top, _, border_bottom = self.GetWindowBorderSize() 273 return margin_top + border_top + self.header_height + border_bottom + margin_bottom 274 275 @classmethod 276 def Reload(cls, instance): 277 pass 278 279 def GetCaption(self, update = 1): 280 caption = '' 281 for wnd in self.GetWindows(): 282 if wnd is not None and not wnd.destroyed: 283 caption = '%s%s-' % (caption, wnd.GetCaption(update)) 284 285 if caption != '': 286 return caption[:-1] 287 return caption 288 289 def OnCollapsed(self, *args): 290 self.contentCont.display = False 291 for wnd in self.GetWindows(): 292 wnd.OnCollapsed(*args) 293 294 def OnExpanded(self, *args): 295 self.contentCont.display = True 296 for wnd in self.GetWindows(): 297 wnd.OnExpanded(*args) 298 299 def OnEndScale_(self, *args): 300 for wnd in self.GetWindows(): 301 if wnd.state == uiconst.UI_PICKCHILDREN: 302 wnd._emit_on_end_scale() 303 wnd.OnEndScale_(wnd) 304 305 def OnStartScale_(self, wnd, *args): 306 for wnd in self.GetWindows(): 307 if wnd.state == uiconst.UI_PICKCHILDREN: 308 wnd._emit_on_start_scale() 309 310 def InsertWnd(self, wnd, adjustlocation = 1, show = 0, hilite = 0, is_dropping = False): 311 while self._initializingStack: 312 uthread2.Yield() 313 314 previous_window = self.GetActiveWindow() 315 windowID = getattr(wnd, 'windowID', None) 316 preferredIdx = self.GetAndRegisterPreferredIdxInStack(self.windowID, windowID, is_dropping) 317 self._inserting = True 318 l, t, mywidth, myheight = self.GetAbsolute() 319 if not len(self.GetWindows()) and adjustlocation: 320 log.info('WindowStack initing, taking size from %s %s %s %s %s %s' % (wnd.windowID, 321 wnd.left, 322 'l,t,w,h', 323 wnd.top, 324 wnd.width, 325 wnd.height)) 326 self.width = wnd.width 327 if not self.IsCollapsed(): 328 self.height = wnd.height 329 self.left = wnd.left 330 self.top = wnd.top 331 if wnd.IsCollapsed(): 332 wnd.Expand() 333 wnd.SetParent(self.contentCont, idx=preferredIdx) 334 wnd.OnStackInsert(self) 335 self.Check(0, show != 1) 336 if show: 337 self.ShowWnd(wnd, hilite, previous_window=previous_window) 338 wnd.RegisterStackID(self) 339 self.CleanupParent('snapIndicator') 340 self._inserting = False 341 if self.IsMinimized(): 342 self.Maximize() 343 wnd.Prepare_ScaleAreas_() 344 sm.ChainEvent('ProcessWindowStacked', wnd, self) 345 self.UpdateIntersectionBackground() 346 347 def SetMinWH(self): 348 allMinW = 0 349 allMinH = 0 350 for each in self.GetWindows(): 351 allMinW = max(allMinW, each.minsize[0]) 352 allMinH = max(allMinH, each.minsize[1]) 353 354 if self.IsCollapsed(): 355 self.minsize = [allMinW, allMinH] 356 else: 357 self.SetMinSize([allMinW, allMinH]) 358 359 def ShowWnd(self, wnd, hilite = 0, previous_window = None): 360 if wnd not in self.GetWindows() or wnd is None or wnd.destroyed: 361 return 362 tab_group = self.GetTabGroup() 363 if tab_group is not None: 364 tab_group.ShowPanel(wnd, hilite) 365 self._on_header_tab_selected(tab_id=wnd, old_tab_id=previous_window) 366 367 def GetActiveWindow(self): 368 if self.closing: 369 return 370 tab_group = self.GetTabGroup() 371 if tab_group is not None: 372 return tab_group.GetSelectedPanel() 373 374 def IsResizeable(self): 375 if self.IsLocked(): 376 return False 377 for wnd in self.GetWindows(): 378 if not wnd.IsResizeable(): 379 return False 380 381 return True 382 383 def IsMinimizable(self): 384 for wnd in self.GetWindows(): 385 if not wnd.IsMinimizable(): 386 return False 387 388 return True 389 390 def Compact(self): 391 wnd = self.GetActiveWindow() 392 if wnd: 393 wnd.compact = True 394 395 def UnCompact(self): 396 wnd = self.GetActiveWindow() 397 if wnd: 398 wnd.compact = False 399 400 def Detach(self, wnd, grab): 401 if self.IsLocked(): 402 return False 403 if wnd is not None and not wnd.destroyed and not getattr(wnd, '_detaching', 0): 404 uicore.registry.SetFocus(uicore.desktop) 405 self._detaching = True 406 self.RemoveWnd(wnd, grab, 1, 0, 1) 407 if not self.destroyed: 408 self._detaching = False 409 uicore.registry.SetFocus(wnd) 410 return 1 411 412 def Load(self, wnd): 413 if self.destroyed: 414 return 415 if self.IsCollapsed() and not self._detaching and not self._inserting: 416 self.Expand() 417 for _wnd in self.GetWindows(): 418 if _wnd is not wnd: 419 _wnd.state = uiconst.UI_HIDDEN 420 421 wnd.state = uiconst.UI_PICKCHILDREN 422 423 def GetMinWidth(self, checkgroup = 1): 424 trueMinWidth = self.minsize[0] 425 for wnd in self.GetWindows(): 426 trueMinWidth = max(wnd.GetMinWidth(), trueMinWidth) 427 428 return trueMinWidth 429 430 def GetMinHeight(self): 431 trueMinHeight = self.minsize[1] 432 for wnd in self.GetWindows(): 433 trueMinHeight = max(wnd.GetMinHeight(), trueMinHeight) 434 435 return trueMinHeight 436 437 def OnStartMaximize_(self, *args): 438 for wnd in self.GetWindows(): 439 wnd.OnStartMaximize_(wnd) 440 441 def OnEndMaximize_(self, *args): 442 for wnd in self.GetWindows(): 443 wnd.OnEndMaximize_(wnd) 444 445 def OnStartMinimize_(self, *args): 446 for wnd in self.GetWindows(): 447 wnd.OnStartMinimize_(wnd) 448 449 def OnEndMinimize_(self, *args): 450 for wnd in self.GetWindows(): 451 wnd.OnEndMinimize_(wnd) 452 453 def OnResize_(self, *args): 454 for wnd in self.GetWindows(): 455 wnd._OnResize() 456 457 def GetWindows(self): 458 if self.contentCont is None: 459 return [] 460 else: 461 return [ each for each in self.contentCont.children if isinstance(each, Window) ] 462 463 def RegisterPreferredIdxInStack(self, stackID, windowID, idx = -1): 464 allPreferred = settings.char.windows.Get(PREFERRED_IDX_IN_STACK_CONFIG, {}) 465 stackInfo = allPreferred.get(stackID, {}) 466 stackInfo[windowID] = idx 467 allPreferred[stackID] = stackInfo 468 settings.char.windows.Set(PREFERRED_IDX_IN_STACK_CONFIG, allPreferred) 469 470 def GetAndRegisterPreferredIdxInStack(self, stackID, windowID, is_dropping = False): 471 allPreferred = settings.char.windows.Get(PREFERRED_IDX_IN_STACK_CONFIG, {}) 472 stackInfo = allPreferred.get(stackID, {}) 473 preferredIdx = stackInfo.get(windowID, -1) 474 if is_dropping: 475 tab_index = 0 476 tabs = self.header.tab_group.GetTabs() 477 cursorX, cursorY = uicore.uilib.cursorPos 478 cursorX = ReverseScaleDpi(cursorX) 479 cursorY = ReverseScaleDpi(cursorY) 480 for tab in tabs: 481 if cursorX <= tab.absoluteRight and cursorY >= self.header.absoluteTop and cursorY <= self.header.absoluteBottom: 482 preferredIdx = tab_index 483 break 484 tab_index += 1 485 486 if preferredIdx < 0: 487 if len(self.contentCont.children) < 1: 488 preferredIdx = 0 489 else: 490 preferredIdx = len(self.contentCont.children) 491 self.RegisterPreferredIdxInStack(stackID, windowID, preferredIdx) 492 return preferredIdx 493 494 def RemovePreferredIdx(self, stackID, windowID): 495 allPreferred = settings.char.windows.Get(PREFERRED_IDX_IN_STACK_CONFIG, {}) 496 stackInfo = allPreferred.pop(stackID, None) 497 if stackInfo: 498 oldPreferredIdx = stackInfo.pop(windowID, None) 499 openWndIDs = {x.windowID for x in uicore.registry.GetWindows()} 500 stackInfoAsList = stackInfo.items() 501 stackInfoAsList.sort(key=lambda x: x[1]) 502 newStackInfo = {} 503 for eachWndID, eachIdx in stackInfoAsList: 504 if eachWndID in openWndIDs and eachIdx > oldPreferredIdx: 505 eachIdx -= max(0, 1) 506 newStackInfo[eachWndID] = eachIdx 507 508 allPreferred[stackID] = newStackInfo 509 settings.char.windows.Set(PREFERRED_IDX_IN_STACK_CONFIG, allPreferred) 510 511 def GetTabGroup(self): 512 if self.header is not None and self.header.tab_group is not None and not self.header.tab_group.destroyed: 513 return self.header.tab_group 514 515 def ForceUpdateSelectedTabSetting(self): 516 tabGroup = self.GetTabGroup() 517 if not tabGroup: 518 return 519 tab = tabGroup.GetSelectedTab() 520 if not tab: 521 return 522 tab.UpdateSelectedSettings(True) 523 524 525 class WindowStackTab(Tab): 526 527 def GetMenu(self): 528 wnd = self.GetPanel() 529 return wnd.GetMenu() 530 531 532 class WindowStackHeader(WindowHeaderBase, ContainerAutoSize): 533 534 def __init__(self, callback = None): 535 self._callback = callback 536 self._tab_group = None 537 self._line = None 538 super(WindowStackHeader, self).__init__(alignMode=uiconst.TOTOP, clipChildren=True) 539 540 @property 541 def tab_group(self): 542 return self._tab_group 543 544 def mount(self, window): 545 super(WindowStackHeader, self).mount(window) 546 self._update_padding(window) 547 self._layout(window) 548 window.on_header_inset_changed.connect(self._on_header_inset_changed) 549 window.on_collapsed_changed.connect(self._on_window_collapsed_changed) 550 551 def unmount(self, window): 552 super(WindowStackHeader, self).unmount(window) 553 window.on_header_inset_changed.disconnect(self._on_header_inset_changed) 554 window.on_collapsed_changed.disconnect(self._on_window_collapsed_changed) 555 self._clear() 556 557 def update_tabs(self, tabs, auto_select = True): 558 if self._tab_group is None: 559 raise RuntimeError("Attempting to update header when it's not mounted") 560 self._tab_group.ClearTabs() 561 for tab_data in tabs: 562 self._tab_group.AddTab(*tab_data) 563 564 if auto_select: 565 self._tab_group.AutoSelect() 566 567 def _layout(self, window): 568 pad_left, pad_right = window.header_inset 569 self._tab_group = TabGroup(name='tabparent', parent=self, padding=(pad_left, 570 0, 571 pad_right, 572 0), groupID=window.name, tabClass=WindowStackTab, callback=self._callback, show_line=False) 573 self._line = DividerLine(parent=self, align=uiconst.TOBOTTOM_NOPUSH) 574 575 def _clear(self): 576 self.Flush() 577 self._tab_group = None 578 self._line = None 579 580 def _on_header_inset_changed(self, window): 581 self._update_padding(window) 582 583 def _on_window_collapsed_changed(self, window): 584 self._line.display = False if window.collapsed else True 585 586 def _update_padding(self, window): 587 if self._tab_group is not None: 588 self._tab_group.padLeft, self._tab_group.padRight = window.header_inset