From 351b446a1514fae2ecd9a75ca37659604a46164f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=8E=89=E5=AE=87=E6=B8=85=E9=9F=B3?= <2422523675@qq.com>
Date: Sat, 30 Dec 2023 16:15:07 +0800
Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BB=A3=E7=A0=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.idea/SimpleIDE.iml | 10 ++
.idea/codeStyles/codeStyleConfig.xml | 5 +
.../inspectionProfiles/profiles_settings.xml | 6 +
.idea/misc.xml | 7 +
.idea/modules.xml | 8 +
.idea/vcs.xml | 6 +
.idea/workspace.xml | 169 ++++++++++++++++++
DockPanel.py | 120 +++++++++++++
Manager.py | 127 +++++++++++++
SplitPanel.py | 122 +++++++++++++
__pycache__/DockPanel.cpython-312.pyc | Bin 0 -> 8419 bytes
__pycache__/Manager.cpython-312.pyc | Bin 0 -> 3658 bytes
test.py | 2 +
13 files changed, 582 insertions(+)
create mode 100644 .idea/SimpleIDE.iml
create mode 100644 .idea/codeStyles/codeStyleConfig.xml
create mode 100644 .idea/inspectionProfiles/profiles_settings.xml
create mode 100644 .idea/misc.xml
create mode 100644 .idea/modules.xml
create mode 100644 .idea/vcs.xml
create mode 100644 .idea/workspace.xml
create mode 100644 DockPanel.py
create mode 100644 Manager.py
create mode 100644 SplitPanel.py
create mode 100644 __pycache__/DockPanel.cpython-312.pyc
create mode 100644 __pycache__/Manager.cpython-312.pyc
create mode 100644 test.py
diff --git a/.idea/SimpleIDE.iml b/.idea/SimpleIDE.iml
new file mode 100644
index 0000000..6cb8b9a
--- /dev/null
+++ b/.idea/SimpleIDE.iml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..a55e7a1
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..de071b2
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..bc24bff
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 0000000..5ecedbf
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,169 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1703843144486
+
+
+ 1703843144486
+
+
+
+
\ No newline at end of file
diff --git a/DockPanel.py b/DockPanel.py
new file mode 100644
index 0000000..46f440a
--- /dev/null
+++ b/DockPanel.py
@@ -0,0 +1,120 @@
+from PyQt5.QtWidgets import QWidget
+from PyQt5.QtWidgets import QApplication
+from PyQt5.QtWidgets import QLabel
+from PyQt5.QtWidgets import QVBoxLayout
+from PyQt5.QtWidgets import QHBoxLayout
+from PyQt5.QtWidgets import QFrame
+from PyQt5.QtWidgets import QPushButton
+from PyQt5.QtCore import Qt, pyqtSignal
+from PyQt5.QtCore import QSize, QPoint, QMimeData
+from PyQt5.QtGui import QDrag
+from Manager import DragManager
+
+
+class DragHeader(QFrame):
+ close_request = pyqtSignal(QWidget)
+ retrieve_request = pyqtSignal(QWidget)
+ adjust_request = pyqtSignal(QWidget, QPoint)
+
+ def __init__(self, title: str, parent: 'DockPanel'):
+ super().__init__(parent)
+
+ self.__place_cube = parent
+ self.__pressed_mark = (False, QPoint)
+
+ self.layout_inst = QHBoxLayout(self)
+ self.layout_inst.setSpacing(0)
+ self.layout_inst.setContentsMargins(0,0,0,0)
+
+ self.show_label=QLabel(title, self)
+ self.show_label.setContentsMargins(2, 0, 0, 0)
+ self.layout_inst.addWidget(self.show_label, stretch=1, alignment=Qt.AlignLeft)
+
+ self.min_button=QPushButton("-")
+ self.min_button.clicked.connect(lambda : self.retrieve_request[QWidget].emit(parent))
+ self.min_button.setMaximumSize(22,22)
+ self.min_button.setMaximumSize(22,22)
+ self.layout_inst.addWidget(self.min_button,stretch=0, alignment=Qt.AlignRight)
+ self.close_button=QPushButton("x")
+ self.close_button.clicked.connect(lambda : self.close_request[QWidget].emit(parent))
+ self.close_button.setMaximumSize(22,22)
+ self.close_button.setMaximumSize(22,22)
+ self.layout_inst.addWidget(self.close_button, stretch=0, alignment=Qt.AlignRight)
+ pass
+
+ def mousePressEvent(self, a0):
+ super(DragHeader, self).mousePressEvent(a0)
+ self.__pressed_mark = (True, self.__place_cube.mapToParent(a0.pos()))
+ pass
+
+ def mouseReleaseEvent(self, a0):
+ super(DragHeader, self).mouseReleaseEvent(a0)
+ self.__pressed_mark = (False, QPoint())
+ pass
+
+ def mouseMoveEvent(self, a0):
+ super(DragHeader, self).mouseMoveEvent(a0)
+ if self.__pressed_mark[0]:
+ self.adjust_request[QWidget,QPoint].emit(self, self.__place_cube.mapToParent(a0.pos()))
+
+
+class DockPanel(QWidget):
+
+ def __init__(self, title:str, show_inst:QWidget|None, pinst:QWidget):
+ super(DockPanel, self).__init__(pinst)
+
+ self.setMinimumSize(60, 40)
+ self.setWindowTitle(title)
+ self.parent_res = pinst
+
+ self.layout_inst = QVBoxLayout(self)
+ self.layout_inst.setSpacing(0)
+ self.layout_inst.setContentsMargins(0,2,0,0)
+
+ self.drag_header = DragHeader(title, self)
+ self.layout_inst.addWidget(self.drag_header)
+ self.drag_header.setMinimumHeight(26)
+ self.drag_header.setMaximumHeight(26)
+ self.drag_header.setFrameShape(QFrame.Shape.Panel)
+ self.drag_header.setFrameShadow(QFrame.Shadow.Raised)
+ self.drag_header.setLineWidth(3)
+ self.drag_header.adjust_request[QWidget,QPoint].connect(self.__adjust_accept)
+
+ if show_inst is not None:
+ self.layout_inst.addWidget(show_inst)
+ else:
+ self.layout_inst.addWidget(QWidget())
+
+ self.default_header = True
+ self.can_replace = True
+ self.can_retrieve = True
+ self.can_close = True
+
+ DragManager.instance().register_dockpanel(self)
+ pass
+
+ def reset_parent_res(self, pinst):
+ self.parent_res = pinst
+
+ def __adjust_accept(self, view: QWidget, pt: QPoint):
+ drag_trans = QDrag(self)
+ mine_data = QMimeData()
+ mine_data.setText("view-drags(" + str(self.__hash__()) + ")")
+ drag_trans.setMimeData(mine_data)
+ drag_trans.setPixmap(self.grab(self.rect()))
+ drag_trans.setHotSpot(QPoint(5,5))
+ drag_trans.exec()
+
+ def __del__(self):
+ DragManager.instance().remove_dockpanel(self)
+
+ def sync_status(self):
+ self.drag_header.setVisible(self.default_header)
+
+
+if __name__ == "__main__":
+ app=QApplication([])
+ app.installEventFilter(DragManager())
+ win=DockPanel("empty-window",None,None)
+ win.show()
+ app.exec()
\ No newline at end of file
diff --git a/Manager.py b/Manager.py
new file mode 100644
index 0000000..562b52b
--- /dev/null
+++ b/Manager.py
@@ -0,0 +1,127 @@
+from PyQt5.QtWidgets import QWidget, QApplication
+from PyQt5.QtCore import QObject, QEvent, QRect, QMargins, Qt
+from PyQt5.QtGui import QPainter, QColor
+from enum import Enum
+
+
+class AcceptPanel(QWidget):
+
+ def __init__(self, parent):
+ super(AcceptPanel, self).__init__(parent)
+ self.left_rect = QRect()
+ self.right_rect = QRect()
+ self.top_rect = QRect()
+ self.bottom_rect = QRect()
+ self.center_rect = QRect()
+ self.hover_rect = QRect()
+ self.setMouseTracking(True)
+ self.setAcceptDrops(True)
+
+ def resizeEvent(self, a0):
+ total_rect = self.rect()
+ total_rect = total_rect - QMargins(5, 5, 5, 5)
+ anchor_width = 30
+
+ self.left_rect = QRect(int(total_rect.left()), int(total_rect.center().y() - anchor_width / 2), int(anchor_width), int(anchor_width))
+ self.right_rect = QRect(int(total_rect.right() - anchor_width), int(total_rect.center().y() - anchor_width / 2), int(anchor_width), int(anchor_width))
+ self.top_rect = QRect(int(total_rect.center().x() - anchor_width / 2), int(total_rect.top()), int(anchor_width), int(anchor_width))
+ self.bottom_rect = QRect(int(total_rect.center().x() - anchor_width / 2), int(total_rect.bottom() - anchor_width), int(anchor_width), int(anchor_width))
+ self.center_rect = QRect(int(total_rect.center().x() - anchor_width / 2), int(total_rect.center().y() - anchor_width / 2), int(anchor_width), int(anchor_width))
+
+ def async_with(self, target_view: QWidget):
+ self.resize(target_view.size())
+
+ def paintEvent(self, a0):
+ super(AcceptPanel, self).paintEvent(a0)
+ painter = QPainter(self)
+ painter.fillRect(self.rect(), Qt.lightGray)
+
+ painter.fillRect(self.hover_rect, Qt.gray)
+
+ painter.fillRect(self.top_rect, Qt.green)
+ painter.fillRect(self.bottom_rect, Qt.green)
+ painter.fillRect(self.left_rect, Qt.green)
+ painter.fillRect(self.right_rect, Qt.green)
+ painter.fillRect(self.center_rect, Qt.green)
+
+ def mouseMoveEvent(self, a0):
+ self.hover_rect = self.rect()
+ print(a0.pos())
+
+ if self.left_rect.contains(a0.pos()):
+ self.hover_rect.setWidth(int(self.hover_rect.width() / 3))
+ elif self.right_rect.contains(a0.pos()):
+ self.hover_rect.setWidth(int(self.hover_rect.width() / 3))
+ self.hover_rect.moveLeft(int(self.rect().right() - self.hover_rect.width()))
+ elif self.top_rect.contains(a0.pos()):
+ self.hover_rect.setHeight(int(self.hover_rect.height() / 3))
+ elif self.center_rect.contains(a0.pos()):
+ pass
+ elif self.bottom_rect.contains(a0.pos()):
+ self.hover_rect.setHeight(int(self.hover_rect.height() / 3))
+ self.hover_rect.moveTop(int(self.rect().bottom() - self.hover_rect.height()))
+ else:
+ self.hover_rect = QRect()
+
+ self.update()
+
+
+class DragManager(QObject):
+ __unique_inst: 'DragManager' = None
+
+ def __init__(self):
+ super(DragManager, self).__init__()
+ self.__dock_map = {}
+
+ @classmethod
+ def instance(cls):
+ if cls.__unique_inst is None:
+ cls.__unique_inst = DragManager()
+ return cls.__unique_inst
+
+ def __peak_window(self, obj: QWidget):
+ if obj is None:
+ return None
+
+ if obj.__class__.__name__ == "QMainWindow":
+ return obj
+
+ return self.__peak_window(obj.parent())
+
+ def eventFilter(self, sender: QObject | None, event: QEvent | None):
+ if event.type() == QEvent.Type.DragMove:
+ if sender.isWindowType():
+ sender.requestActivate()
+
+ for ip in self.__dock_map:
+ view_inst: QWidget = self.get_dockpanel(ip)
+ if view_inst is None:
+ return False
+
+ gpos = sender.mapToGlobal(event.pos())
+ point = view_inst.mapFromGlobal(gpos)
+ if (view_inst.rect().contains(point)) and (self.__peak_window(view_inst).isActiveWindow()):
+ print(view_inst.rect())
+
+ pass
+
+ return super(DragManager, self).eventFilter(sender, event)
+
+ def register_dockpanel(self, view):
+ self.__dock_map[str(view.__hash__())] = view
+
+ def remove_dockpanel(self, view):
+ if str(view.__hash__()) in self.__dock_map:
+ self.__dock_map.pop(str(view.__hash__()))
+
+ def get_dockpanel(self, address: str):
+ if address in self.__dock_map:
+ return self.__dock_map[address]
+ return None
+
+
+if __name__ == "__main__":
+ app = QApplication([])
+ accept = AcceptPanel(None)
+ accept.show()
+ app.exec()
diff --git a/SplitPanel.py b/SplitPanel.py
new file mode 100644
index 0000000..58ceb87
--- /dev/null
+++ b/SplitPanel.py
@@ -0,0 +1,122 @@
+import Manager
+from DockPanel import DockPanel
+from PyQt5.QtWidgets import QApplication, QWidget, QFrame
+from enum import Enum
+from PyQt5.QtCore import pyqtSignal, QPoint
+from PyQt5.QtCore import QRect, Qt
+from PyQt5.QtWidgets import QMainWindow
+
+
+class SplitType(Enum):
+ SPLIT_H = 0
+ SPLIT_V = 1
+
+
+class DragSplitter(QFrame):
+ adjustSignal = pyqtSignal(QPoint)
+
+ def __init__(self, split:SplitType, parent:QWidget):
+ super(DragSplitter, self).__init__(parent)
+
+ self.setFrameShape(QFrame.Shape.WinPanel)
+ self.setFrameShadow(QFrame.Shadow.Raised)
+
+ if split == SplitType.SPLIT_H:
+ self.setCursor(Qt.CursorShape.SplitHCursor)
+ else:
+ self.setCursor(Qt.CursorShape.SplitVCursor)
+
+ pass
+
+ def mouseMoveEvent(self, a0):
+ super().mouseMoveEvent(a0)
+ up_point = self.mapToParent(a0.pos())
+ self.adjustSignal.emit(up_point)
+
+
+class SplitPanel(QWidget):
+ def __init__(self, a: DockPanel, b: DockPanel, split: SplitType, parent:QWidget = None):
+ super(SplitPanel, self).__init__(parent)
+
+ self.splitter_widget = DragSplitter(split, self)
+ self.splitter_widget.adjustSignal[QPoint].connect(self.__splitter_adjust)
+
+ self.split_member = (a, b)
+ self.split_member[0].setParent(self)
+ self.split_member[1].setParent(self)
+
+ self.split_info = (split, 0.5, 7)
+ self.sync_status()
+ pass
+
+ def __view_list(self) -> [DockPanel]:
+ retval: [DockPanel] = [self.split_member[0], self.split_member[1]]
+ return retval
+
+ def set_split_info(self, o: SplitType, pos: float, width: float = 8):
+ self.split_info = (o, pos, width)
+ self.sync_status()
+
+ def sync_status(self):
+ if self.split_info[0] == SplitType.SPLIT_H:
+ total_width = self.width()
+ width_a = total_width * self.split_info[1]
+ width_b = total_width - width_a - self.split_info[2]
+
+ self.split_member[0].setGeometry(0, 0, int(width_a), self.height())
+ self.split_member[1].setGeometry(int(width_a + self.split_info[2]), 0, int(width_b), self.height())
+
+ handle_rect = QRect(int(width_a), 0, int(self.split_info[2]), self.height())
+ self.splitter_widget.setGeometry(handle_rect)
+ else:
+ total_height = self.height()
+ height_a = total_height * self.split_info[1]
+ height_b = total_height - height_a - self.split_info[2]
+
+ self.split_member[0].setGeometry(0, 0, self.width(), int(height_a))
+ self.split_member[1].setGeometry(0, int(height_a + self.split_info[2]) - 1, self.width(), int(height_b) + 1)
+
+ handle_rect = QRect(0, int(height_a), self.width(), int(self.split_info[2]))
+ self.splitter_widget.setGeometry(handle_rect)
+ pass
+
+ def resizeEvent(self, a0):
+ super().resizeEvent(a0)
+ self.sync_status()
+
+ def __splitter_adjust(self, pos: QPoint):
+ if self.split_info[0] == SplitType.SPLIT_H:
+ leftw = self.split_member[0].minimumWidth()
+ rightw = self.split_member[1].minimumWidth()
+ if (pos.x() >= leftw) and (pos.x() <= self.width() - rightw):
+ self.split_info = (self.split_info[0], pos.x() / self.width(), self.split_info[2])
+ else:
+ toph = self.split_member[0].minimumHeight()
+ bottomh = self.split_member[1].minimumHeight()
+ if (pos.y() >= toph) and (pos.y() <= self.height() - bottomh):
+ self.split_info = (self.split_info[0], pos.y() / self.height(), self.split_info[2])
+ self.sync_status()
+
+ def replace_view(self, new: DockPanel, old: DockPanel):
+ if old in self.__view_list() and new not in self.__view_list():
+ if self.split_member[0] == old:
+ self.split_member = (new, self.split_member[1], self.split_member[2])
+ else:
+ self.split_member = (self.split_member[0], new, self.split_member[2])
+ old.setParent(None)
+ self.sync_status()
+ pass
+
+
+if __name__ == "__main__":
+ app = QApplication([])
+ ow = QMainWindow()
+ app.installEventFilter(Manager.DragManager.instance())
+
+ a = DockPanel("docka", None, None)
+ b = DockPanel("dockb", None, None)
+ win = SplitPanel(a, b, SplitType.SPLIT_H)
+ ow.setCentralWidget(win)
+
+ ow.show()
+ app.exec()
\ No newline at end of file
diff --git a/__pycache__/DockPanel.cpython-312.pyc b/__pycache__/DockPanel.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..82689eaf4ef13a785c619c4db7b60be23f4b0385
GIT binary patch
literal 8419
zcmcIJYitzPxih=7yJx*?8yf?DVH+ER7XrlCflyun3^=LrwPPnO3+-gs8QZ(;i!)=u
zzV4MPafL0Vw~>0IxXP8fQ6j~NNcE5Y$j@B0QWZ5)nC_xAqo77={vh>-Rnv#~b-(Y-
z&cl0`gtY3xocYfCeBb%r`@8DuDh|rRuYRh$xryWchz+awO2GPMp5xx(WKQOjT#AqK
zJi;D;Jy8#Xy#RZoUIq&Q3sHf=K7f5uAA|h>`=fpa2LKL40}Lifk_twHsj6rdYX_kn
ziiT3*XgF0Jt!8~y$(mGcw3fl41q$h(U(1(;+f>`0!CP(Ta+z{eJ1R#VOHJUQinAcPgFL`b#lF2Z|}TK*_P;a
zuQXw6EziOJeP1^|Q$JH5+58BnM0g`G`n|Y3sb~O(M^9z5$#_iC;+ZtG{i8$D6(tE!
zX!P~dnVUn>OlBG$TQU)YVg2lek0V+-6F8IA5rKL%Q#@v_)rN;VA0B!?=C|s16
zxu^%oRh$ajlGLk#i*6lPEW
zpy~zgCLgUC<08bU!V3;6lB`g}r^U4-V3d`pf*1{CVpm6`w33XK7=)eZhZ@`KgMh=^
z{B7>G=N(QnF9Y?Wcai%EF#n>*!hr{PkrPYz6KU2S#zB)P?7z+Te=a?X`8LlIUep*!U68Y1RuxXP7T%^=bNas
zySIGzr{J^A*~fw^pCSudqctPZY(dWVBcCN|fXQ~NU-nxs19;f9gA>}U8O}G+;lLJ@
z=&~T^yWj!t^2oql*52^ym3QoRR#}itWMa|wpITRmJ1SGN0J*?<
zbu5`tHqaONxg5l*f*y4jollM=KC(q
zb95)ni)=A`>U0)t7_n$*Y!e;Iiefe?#S}3%eMO?f>bXC
zbkexAwLm()Z0%eS3Z!YZedh-gOBsY)zG~T()AB9PVWV-Cgo~sRrWZ+zOiS+HtjlTFOuCl**x&%(iVUhij~OPJHqZN_xjPrjH}5ftfjBqmq^U@D=wwHcM064737R
zm)X_Lp&p&|EY~leE|3FX(`%B>Jhs@?uXpu-EPZzH^W&c$X9r(|rHr{_w(f$bLRLdH
zwEiYkZ}V=?%h7Rj+)u!hn&U0Y1g<|n$0ck(3YL~i0@<;w%<&1^4|9g@mOyLI3D2C%
zGXo9o86J-WXauIwdZ>)rRAyRLMo_ZOUI%HRd$Bi^lCtBO5!1u;W-}^%4!ZAn86z7W
z>3Mnt+A5;h(NW^vqtKXbcJXf6&KQEIxD1pdu<)CSrJXuCu;M9@zSZ`w+<3A3kluZ$
z*nL{>KK;qT-yZtkL%)78-#v7%?E+)!igd;cFggzd@NMXNmWc*p^jpdp(g;^NRCs`Guv8D4)^kdxCZs2n;^X_AOtTkX?}m|UfY4E
zMWV#U>69q%S!^4SXlus
z-cr^DP3ui8E_S@m8IHXSb3!5^*Xz`<*SV0nUV0kjSEELf6Vs};!82fn#k70K>hu^^
z$FchVZ+Hf}XPe63&_3ia467nk?g!kzObg`TYN)mtYSTk)OV@rM+OZnmR1CN4;r0
zANg}NdiX#w+^>iGSH?f?FCHDzj}8@&eqTTO{d*TL6)%eVMKOO-${)RQKO9^0a^X(X
zc-ErTOhec!ibjXJo@DaoWL-r-I827AN3WLhRv7EEK*0H9ug>hHI>2MujYH}Va;=Px|iwj;MY
zzirPwq2a-n_FQ0jYkte~^TTUGqpx}?w#LC{`RHHp^SIv&RTWf!gG%PtIIJ0pZ2uT+
zZ5K-yu8%;MJN|}f8saoXkiD}Y2eP0D6aG>(9gg~foa~3Ofa0GJWO7T02ISx^4r1^s
zsDmu7u7bEaM3MKHT{eQuFF-$H*zjfX8?%)GOu~yGRo7pK-fs{#uf?`J3WBX@V0Ed;
zJOsWabHV0RRPW9%PQX292i$J84yV{+X`KP?a|O|qH{Pw%s>&fT5E|B&D6%o&hC7vT
zfjilX;wg8k5az*5J0Npz?_ghfJb~J$tjNA^T-8#x1e(vV$_~n%)f!3`D~XO0;QA!c
zjV>$Sc()2cqU)3jp>Cz%^&3w?rB_y*R0HG23a^nsby`-m{Z@zeyaknvLZ8)kzVM3u
zPRoK^xApwE^;__aD{++WdiHhN0sG_<;M_nC0RP(j!CI}>Y}T^5fVJ%R*G^iq5-(Yh
z^SxUF6R2j(iX+WDnl&=~2#wJrrnph0C`!vC%osgF-7_BPGz}ipTBya%7zCnGD+a-s
zB8dO*i!+OjBr_@so
zb6DneY)XPK4m}0K$6&+9QjqLI)(p6X;fGQ7eN~D>=w^grhoN{{fv`)PqG*#$W5qnF
z7jQoa`K*MdD3?@G{Ut%!QqLY@T(>ZW;TDo(}@_4@GV0oZtMJs)J
zS04m*>_jt;!9d6gv#gM+QXd9nA6)-%?!CFi;wT%(i2MRHOnCqEI2FOBOjjTgvkUxpg)g*up*)T)PD
zi{V{*cvo)bez^Bb^qic&4{p3aQ-Iz!7WB5PwzQk5w|3ySX|-uP#G5r=kN$*vjdxfl2caT#0s__dMnuW@_TGJ
zy@JE8+3q^?2uhZJ!>S54-a3WpIbnD
zbP!wM_)bac6gayfrlrhZaKL9%w2}Bth*b??l1f($fns6?^FV_cNCjmyGm9iBH4GIcF0rfwif&h0*
z^HTrME-h4j(Xjnz-(3i}-HtWp;1#)(k)E7yc
zPTH1c3M9f>$iqwHzqt7Gi#hGXnfGSi|M&cMQ=nn$cAaehpNZUP?%KOo3uF&0z+BCJ
z(qyJ1Yz~I|Am8Hu^;e*Bb9bWje$Z_f>#ZvBwIdDqlBL8V)38*A4KNv-f)LD6tq@7<
zfB_Y6QzB?U6lDd1jM+LjAz9ZHPDRHKGX!oKK7g45;XZlcYj7Keb?>0Z4d3#TuqD?S
zftiBSvLJ_(9sFK{LB>_D$JO{1FspAs2gRQR-FSu6nRHA9E2B-Tvs)b8T-{f32|5+?
zoJ%*dwA?^(=V5*4;rrx8h9bl!XYr78Se?fT1;MnHX!*^U4jGQlU^R!;%UIzPvul+C
zH|8v0g)y#r1}X^L)`ac87njD?IQT4|Um3VF#K2Dif5D!$W1KIzCIo!Vx$|ord{(xu
zm$)wF4pu;P){;X!pymbtu<#dRAFgZGo-~(#YCFvhmbYx{B)eD4I@zc{nq407OpbX
zk&K2{%4Usv<^v>?EO&D*o&+Dj@WO53=K;tGQwkIBn2Bm8`H=lLW9$?3HVh#LtN#Vn
znwRJKhg{b~uIC}w`HXAd`N~>|6Pnpe{2%B8ViW)X
literal 0
HcmV?d00001
diff --git a/__pycache__/Manager.cpython-312.pyc b/__pycache__/Manager.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..9b8cf98af6e032569acb3fa15fba7a97f57b55da
GIT binary patch
literal 3658
zcmb6cU2hZF_0D+gOfq)HNxVsbWO1@dUdebMHO(oO93j_1}HI76LHw*A4ZDO@#at7Y?{9lieS|WQ!<7VG<ZLM7iPqHb}l;k6PvN_V6Y>BimgeB*R!mSd;t#aEgn`cB=@<>hp#)ZV1
z-kJ9^nS>UL8d^GKHqHD*Q|46z5KS}Fm*Q&7Fg-J87uA#@ai%a6PA}-HwKT<8mkYtUXrs`W38y1DdHM!YCxd{unfjxD=vt3VQ}{Yh;d5
zT&sM`uYKy`wgY^_%73|p@XVEsVdLv$?WtBvHiU4D)
z;#EbU^8uZIjr^R6w9OK!)f7;Ta5NR2SLu|wPp(@3xU{trhkWnf10+q?jAWAc2+3c*p
zhmww6k(1F3_=VHe#GKhI%dtdM*JW8pdBK%$4o#lEc!8$TW%|VnOUC7N>f)@H%p}wg
zC(mB2C^(W?qHS<~IZ&hKh$FQbFnSjZU$2m7yzlyzn`3!idcqHv_V=uDpNqSIB6W5^
z*_ItHz+?;bi{p_7SP_k?){Yy&64g_CQ_T`8!V!t3KKNMnkr>kzOXxa!B1m6@(d5xJ
z(Ns*W*T~sLQhfl174kx8Eebt(p=U=JcorPK>sy<6;_WSo?L{$|7lS)|kai+RSNc*s
zR`nt5aPYHq%)$h8WLB6Z828dNUP}k35ftXiVYok=WoFSnP6F6SYXn{pz>VNoYP%A5
z5UlJJreIV{*@-1_Gyqf>HK}M)g{WzW-i#W(B44$V(DY=Y6tkG69Y90(!(c~F#gIN(
zt~tKEuRcC%P)M$0*sqY1(7F~Xb##GgH{Mx)rzrI0g}&S;kA=5O9i3|*y%0N!VqaeD
z%e`^$$ekm1j~2wy9e&g{vqx7`ib_qlip3>X4G>_ACbA6P59Y+=%Chm=2Tov4H-N(_zOMlR%dE$t)|u;<
zL6ZY}oZ*}^@!oG^9qvHCW*l_z-+dE@A49JHfg{bt2s(5|zcYi9`UAKhlV!Insb;0c
zIjO_sj3ua}=xvkx7^Ws~g)p=u)2r!r##s9zRiUQq#``ftTZ}^cYDXF$bUhi%Y$lpg
z6Q%&g_T%*VMEX)RVY)JD-SolEIhsyZ_Be{Yk&mTQ1|+0TUq>dbnr?AbZE>bML$#Da
z{eZIzykJH9IV}NVCEiZOKE$GiCaY!4=0#1tYBd~_n@73sOd5G$rt3TmK^s=A`bXb_
zo#n3DWUZ0VgES==pq`fm|HkR{)1RIB;^ghntx)m6X#T+HV_~!;zE%|b^J4!KaiHYy
zEc%D?{-G!S!!J7bZ>4XfUlQJTbdxO&O2xrb`N30<*ur3Fv$@pKv(qu~j|0IMUA@IF
zDc>dCAN-T_K-wNIbe-JfO5J_M?$Lbr=tHj1ePYvH3iNJG-Iyu_28w}W`M|OJ>eIm3
zqp@NroDYTnrWHazEr#avq4}pF?O9;oP3iW?t&zh1BToavVCE1BzCnnDTgQZGCa@hgn
z(hZ8G_i|LfEXxn*nO`@U8IgrwiRjAM?k)L-oG^CA>(gB@+n7w+||0~lm
zFOU(ru`8tW`gD_y;
zw{I$#$$4QATJ_fWjq%*6e8=#Pceo^WSSm^F6ic|-BFo9NvXH=+gjbe7U5F;^cZKx=
zGeudBrc!Cc`lkR7PwOFR3f40;slt0xp{N7JD6+~S#l*5IvQ?%inxCS9Y!9OjCOkVV
z1~D~}kY$On>l}vedv<}t7Hic`%xrxQhOd83O8rAC;muvH$)
zx?onuS-GXj{bsIfm%yjo-sYZQa)<5@+&Nk%IDfeKNcrohZy&Xmmb!Avdp{O
zf!xG@34AQeSc0b{uPI(wnlVm{%oz6HBb}nNElo|YgPKTF^_9w|;Ow7Y&?vs|C{}NZ
zPY#Oz`z+oMh%E