IS-Matrix-Forge 1.0.0.dev1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. is_matrix_forge/__init__.py +8 -0
  2. is_matrix_forge/assets/__init__.py +8 -0
  3. is_matrix_forge/assets/audio/__init__.py +36 -0
  4. is_matrix_forge/assets/audio/files/__init__.py +53 -0
  5. is_matrix_forge/assets/audio/files/plugged.wav +0 -0
  6. is_matrix_forge/assets/audio/files/unplugged.wav +0 -0
  7. is_matrix_forge/assets/char_map.json +2150 -0
  8. is_matrix_forge/assets/font_map.py +119 -0
  9. is_matrix_forge/common/__init__.py +0 -0
  10. is_matrix_forge/common/decorators/__init__.py +152 -0
  11. is_matrix_forge/common/dirs.py +20 -0
  12. is_matrix_forge/common/helpers/__init__.py +132 -0
  13. is_matrix_forge/common/helpers/github_api.py +67 -0
  14. is_matrix_forge/designer_gui/__init__.py +8 -0
  15. is_matrix_forge/designer_gui/main_window/__init__.py +252 -0
  16. is_matrix_forge/designer_gui/main_window/layout.py +117 -0
  17. is_matrix_forge/dev_tools/__init__.py +8 -0
  18. is_matrix_forge/dev_tools/debug.py +82 -0
  19. is_matrix_forge/dev_tools/presets.py +47 -0
  20. is_matrix_forge/inputmodule/__init__.py +164 -0
  21. is_matrix_forge/inputmodule/b1display.py +160 -0
  22. is_matrix_forge/inputmodule/c1minimal.py +35 -0
  23. is_matrix_forge/inputmodule/font.py +2166 -0
  24. is_matrix_forge/inputmodule/helpers/__init__.py +16 -0
  25. is_matrix_forge/inputmodule/ledmatrix.py +516 -0
  26. is_matrix_forge/led_matrix/Scripts/__init__.py +4 -0
  27. is_matrix_forge/led_matrix/Scripts/battery_monitor.py +152 -0
  28. is_matrix_forge/led_matrix/Scripts/install_presets/__init__.py +2 -0
  29. is_matrix_forge/led_matrix/Scripts/install_presets/arguments.py +84 -0
  30. is_matrix_forge/led_matrix/Scripts/install_presets/main.py +192 -0
  31. is_matrix_forge/led_matrix/__init__.py +7 -0
  32. is_matrix_forge/led_matrix/commands/__init__.py +84 -0
  33. is_matrix_forge/led_matrix/commands/map.py +46 -0
  34. is_matrix_forge/led_matrix/constants.py +82 -0
  35. is_matrix_forge/led_matrix/controller/__init__.py +26 -0
  36. is_matrix_forge/led_matrix/controller/controller.py +635 -0
  37. is_matrix_forge/led_matrix/controller/helpers/__init__.py +7 -0
  38. is_matrix_forge/led_matrix/controller/helpers/threading.py +41 -0
  39. is_matrix_forge/led_matrix/controller/multiton.py +32 -0
  40. is_matrix_forge/led_matrix/controller/threaded.py +72 -0
  41. is_matrix_forge/led_matrix/display/__init__.py +13 -0
  42. is_matrix_forge/led_matrix/display/animations/__init__.py +29 -0
  43. is_matrix_forge/led_matrix/display/animations/animation.py +593 -0
  44. is_matrix_forge/led_matrix/display/animations/effects/__init__.py +17 -0
  45. is_matrix_forge/led_matrix/display/animations/errors.py +23 -0
  46. is_matrix_forge/led_matrix/display/animations/font/__init__.py +17 -0
  47. is_matrix_forge/led_matrix/display/animations/font/font.py +4 -0
  48. is_matrix_forge/led_matrix/display/animations/font/map_builder.py +123 -0
  49. is_matrix_forge/led_matrix/display/animations/frame/__init__.py +47 -0
  50. is_matrix_forge/led_matrix/display/animations/frame/base.py +245 -0
  51. is_matrix_forge/led_matrix/display/animations/frame/helpers.py +67 -0
  52. is_matrix_forge/led_matrix/display/animations/keep_awake.py +1 -0
  53. is_matrix_forge/led_matrix/display/animations/plugged.py +0 -0
  54. is_matrix_forge/led_matrix/display/animations/text_scroller.py +104 -0
  55. is_matrix_forge/led_matrix/display/effects/__init__.py +17 -0
  56. is_matrix_forge/led_matrix/display/effects/breather.py +206 -0
  57. is_matrix_forge/led_matrix/display/effects/fade.py +0 -0
  58. is_matrix_forge/led_matrix/display/effects/scroller/__init__.py +92 -0
  59. is_matrix_forge/led_matrix/display/effects/scroller/base.py +76 -0
  60. is_matrix_forge/led_matrix/display/effects/scroller/helpers.py +62 -0
  61. is_matrix_forge/led_matrix/display/grid/__init__.py +9 -0
  62. is_matrix_forge/led_matrix/display/grid/grid.py +208 -0
  63. is_matrix_forge/led_matrix/display/grid/helpers.py +130 -0
  64. is_matrix_forge/led_matrix/display/grid/presets/__init__.py +64 -0
  65. is_matrix_forge/led_matrix/display/grid/presets/manifest.py +172 -0
  66. is_matrix_forge/led_matrix/display/helpers/__init__.py +110 -0
  67. is_matrix_forge/led_matrix/display/helpers/columns.py +33 -0
  68. is_matrix_forge/led_matrix/display/media.py +199 -0
  69. is_matrix_forge/led_matrix/display/patterns/__init__.py +59 -0
  70. is_matrix_forge/led_matrix/display/patterns/built_in/__init__.py +91 -0
  71. is_matrix_forge/led_matrix/display/patterns/built_in/commands.py +3 -0
  72. is_matrix_forge/led_matrix/display/patterns/built_in/constants.py +29 -0
  73. is_matrix_forge/led_matrix/display/patterns/built_in/stencils.py +70 -0
  74. is_matrix_forge/led_matrix/display/patterns/utils.py +2 -0
  75. is_matrix_forge/led_matrix/display/text/__init__.py +66 -0
  76. is_matrix_forge/led_matrix/display/text/scroller.py +136 -0
  77. is_matrix_forge/led_matrix/errors/__init__.py +4 -0
  78. is_matrix_forge/led_matrix/errors/base.py +14 -0
  79. is_matrix_forge/led_matrix/errors/grid.py +37 -0
  80. is_matrix_forge/led_matrix/errors/matrix.py +68 -0
  81. is_matrix_forge/led_matrix/errors/misc.py +36 -0
  82. is_matrix_forge/led_matrix/fonts.py +20 -0
  83. is_matrix_forge/led_matrix/hardware.py +172 -0
  84. is_matrix_forge/led_matrix/helpers/__init__.py +165 -0
  85. is_matrix_forge/led_matrix/helpers/core/__init__.py +17 -0
  86. is_matrix_forge/led_matrix/helpers/core/lazy_mod_load.py +84 -0
  87. is_matrix_forge/led_matrix/helpers/device.py +118 -0
  88. is_matrix_forge/led_matrix/helpers/status_handler.py +14 -0
  89. is_matrix_forge/led_matrix/helpers/stream/__init__.py +70 -0
  90. is_matrix_forge/led_matrix/helpers/stream/errors.py +17 -0
  91. is_matrix_forge/led_matrix/led_matrix.py +25 -0
  92. is_matrix_forge/led_matrix/status.py +8 -0
  93. is_matrix_forge/led_matrix/tools/__init__.py +8 -0
  94. is_matrix_forge/led_matrix/tools/identify.py +27 -0
  95. is_matrix_forge/led_matrix/utils/__init__.py +41 -0
  96. is_matrix_forge/log_engine.py +25 -0
  97. is_matrix_forge/monitor/__init__.py +68 -0
  98. is_matrix_forge/monitor/errors.py +35 -0
  99. is_matrix_forge/monitor/events.py +78 -0
  100. is_matrix_forge/monitor/gui/__init__.py +0 -0
  101. is_matrix_forge/monitor/gui/errors.py +35 -0
  102. is_matrix_forge/monitor/gui/event/__init__.py +8 -0
  103. is_matrix_forge/monitor/gui/event/collection.py +153 -0
  104. is_matrix_forge/monitor/gui/event/errors.py +58 -0
  105. is_matrix_forge/monitor/gui/event/event.py +226 -0
  106. is_matrix_forge/monitor/gui/layout/__init__.py +8 -0
  107. is_matrix_forge/monitor/gui/layout/base.py +147 -0
  108. is_matrix_forge/monitor/gui/metaclasses/__init__.py +20 -0
  109. is_matrix_forge/monitor/gui/windows/__init__.py +0 -0
  110. is_matrix_forge/monitor/gui/windows/base.py +325 -0
  111. is_matrix_forge/monitor/gui/windows/main/__init__.py +77 -0
  112. is_matrix_forge/monitor/gui/windows/main/layout.py +21 -0
  113. is_matrix_forge/monitor/helpers.py +55 -0
  114. is_matrix_forge/monitor/monitor.py +431 -0
  115. is_matrix_forge/notify/__init__.py +0 -0
  116. is_matrix_forge/notify/sounds/__init__.py +6 -0
  117. is_matrix_forge/notify/sounds/base.py +106 -0
  118. is_matrix_forge/progress.py +99 -0
  119. is_matrix_forge-1.0.0.dev1.dist-info/LICENSE.md +19 -0
  120. is_matrix_forge-1.0.0.dev1.dist-info/METADATA +190 -0
  121. is_matrix_forge-1.0.0.dev1.dist-info/RECORD +123 -0
  122. is_matrix_forge-1.0.0.dev1.dist-info/WHEEL +4 -0
  123. is_matrix_forge-1.0.0.dev1.dist-info/entry_points.txt +5 -0
@@ -0,0 +1,153 @@
1
+ from __future__ import annotations
2
+ from typing import Optional
3
+
4
+ from .event import MOD_LOGGER as __MOD_LOGGER, Event, Loggable
5
+ from .errors import EventExistsError, EventLookupError
6
+
7
+ __initialized = False
8
+
9
+ if __initialized:
10
+ from ..windows.base import WindowBase
11
+
12
+
13
+ MOD_LOGGER = __MOD_LOGGER.parent.get_child('collection')
14
+
15
+
16
+ class EventCollection(Loggable):
17
+ def __init__(
18
+ self,
19
+ window: WindowBase
20
+ ):
21
+ """
22
+ Initializes an EventCollection instance.
23
+
24
+ This constructor sets up the logging and initializes an empty list to hold events.
25
+ """
26
+ super().__init__(MOD_LOGGER)
27
+ self.__events = []
28
+
29
+ def __iter__(self):
30
+ return iter(self.__events)
31
+
32
+ def __len__(self):
33
+ return len(self.__events)
34
+
35
+ @property
36
+ def event_names(self):
37
+ return [e.key for e in self.__events]
38
+
39
+ @property
40
+ def events(self):
41
+ return self.__events
42
+
43
+ @property
44
+ def exit_event_exists(self) -> bool:
45
+ """
46
+ Checks if an ExitEvent exists in the collection.
47
+
48
+ This method checks if any events in the collection are an instance of ExitEvent.
49
+
50
+ Returns:
51
+ bool:
52
+ True if an ExitEvent exists in the collection, False otherwise.
53
+ """
54
+ from ..event.event import ExitEvent
55
+
56
+ return any(isinstance(event, ExitEvent) for event in self.events)
57
+
58
+ @property
59
+ def times_failed(self):
60
+ """
61
+ The total number of times all events in the collection have failed.
62
+
63
+ This property is a shortcut to get the total number of times all events
64
+ in the collection have failed. It does this by summing up the `times_failed`
65
+ property of each event in the collection.
66
+
67
+ Returns:
68
+ int:
69
+ The total number of times all events in the collection have failed.
70
+ """
71
+ times = 0
72
+
73
+ if self.events:
74
+ for event in self.events:
75
+ times += event.times_failed
76
+ else:
77
+ self.method_logger.warn_once('No events in collection, returning 0')
78
+
79
+ return times
80
+
81
+ @property
82
+ def times_handled(self) -> int:
83
+ times = 0
84
+
85
+ if self.events:
86
+ for event in self.events:
87
+ times += event.times_handled
88
+ else:
89
+ self.method_logger.warn_once('No events in collection, returning 0')
90
+
91
+ return times
92
+
93
+ def add_event(self, event: Event) -> None:
94
+ if not isinstance(event, Event):
95
+ raise TypeError(f"event must be of type `Event`, not {type(event)}")
96
+
97
+ if event.key in self.event_names:
98
+ raise EventExistsError(event_name=event.key)
99
+
100
+ self.__events.append(event)
101
+
102
+ def clear(self) -> None:
103
+ self.method_logger.info('Clearing all events in collection...')
104
+ self.events.clear()
105
+
106
+ def create_event(self, key: str, callback: Optional[callable] = None) -> None:
107
+ if key is None and self.exit_event_exists:
108
+ raise EventExistsError(event_name='ExitEvent')
109
+ elif key is None and not self.exit_event_exists:
110
+ from ..event.event import ExitEvent
111
+ return self.add_event(ExitEvent())
112
+
113
+ key = key.upper()
114
+
115
+ if key in self.event_names:
116
+ raise EventExistsError(event_name=key)
117
+
118
+ return self.add_event(Event(key=key, callback=callback))
119
+
120
+ def handle_event(self, event: str, callback_args: tuple = (), callback_kwargs: Optional[dict] = None):
121
+ print('Reached handle_event')
122
+ event = self.lookup(event)
123
+
124
+ if event is None:
125
+ raise EventLookupError(event_name=event)
126
+
127
+ return event.handle(callback_args=callback_args, callback_kwargs=callback_kwargs)
128
+
129
+ def lookup(self, event_key: str) -> Optional[Event]:
130
+ """
131
+ Looks up an event in the collection by its key.
132
+
133
+ Parameters:
134
+ event_key (str):
135
+ The key of the event to be looked up.
136
+
137
+ Returns:
138
+ Event or None:
139
+ The event with the given key, or None if not found.
140
+
141
+ Raises:
142
+ TypeError:
143
+ If 'event_key' is not of type `str`.
144
+ """
145
+ if not isinstance(event_key, str):
146
+ raise TypeError(f"'event_key' must be of type `str`, not {type(event_key)}")
147
+
148
+ return next(
149
+ (e for e in self.__events if e.key.lower() == event_key.lower()), None
150
+ )
151
+
152
+
153
+ __initialized = True
@@ -0,0 +1,58 @@
1
+ from inspyre_toolbox.exceptional import CustomRootException
2
+
3
+
4
+ class EventError(CustomRootException):
5
+ default_message = 'An error occurred with event handling!'
6
+
7
+
8
+ class EventExistsError(EventError, ValueError):
9
+ """
10
+ Raised when an event already exists in the collection.
11
+
12
+ Class Attributes:
13
+ default_message (str):
14
+ **Read-Only**. The default message for the error.
15
+ """
16
+ default_message = 'Event already exists in collection!'
17
+
18
+ def __init__(self, message: str = None, event_name: str = None, skip_print: bool = False) -> None:
19
+ """
20
+ Initializes the EventExistsError.
21
+
22
+ Parameters:
23
+ message (Optional[str]):
24
+ The error message to be displayed. If None, uses the default message.
25
+ """
26
+ if event_name is not None:
27
+ self.default_message = f"'{event_name}' already exists in collection!"
28
+ if message is not None:
29
+ self.default_message = f"{self.default_message}\n\n Additional information from caller:\n {message}"
30
+
31
+ super().__init__(message=self.default_message, skip_print=skip_print)
32
+
33
+
34
+ class EventLookupError(EventError, ValueError):
35
+ """
36
+ Raised when an event is not found in the collection.
37
+
38
+ Class Attributes:
39
+ default_message (str): Read-Only. The default message for the error.
40
+ """
41
+ default_message = 'Event not found in collection!'
42
+
43
+ def __init__(self, message: str = None, event_name: str = None, skip_print: bool = False) -> None:
44
+ """
45
+ Initializes the EventLookupError.
46
+
47
+ Parameters:
48
+ message (Optional[str]):
49
+ The error message to be displayed. If None, uses the default message.
50
+ """
51
+ if event_name is not None:
52
+ self.default_message = f"'{event_name}' not found in collection!"
53
+ if message is not None:
54
+ self.default_message = f"{self.default_message}\n\n Additional information from caller:\n {message}"
55
+
56
+ self.__additional_info = 'Test'
57
+
58
+ super().__init__(message=self.default_message, skip_print=skip_print)
@@ -0,0 +1,226 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Optional
4
+ from is_matrix_forge.log_engine import ROOT_LOGGER as PARENT_LOGGER, Loggable
5
+ from dataclasses import dataclass, field
6
+
7
+
8
+ __initialized = False
9
+
10
+
11
+ if __initialized:
12
+ from ..windows.base import WindowBase
13
+
14
+
15
+ MOD_LOGGER = PARENT_LOGGER.get_child('monitor.gui.event.event')
16
+
17
+
18
+ @dataclass
19
+ class ElementArgMap():
20
+ pos_args: tuple = field(default_factory=tuple)
21
+ kw_args: dict = field(default_factory=dict)
22
+
23
+
24
+ class Event(Loggable):
25
+ def __init__(
26
+ self,
27
+ key: Optional[str],
28
+ window: WindowBase,
29
+ callback: Optional[callable] = None,
30
+ element_arg_map: Optional[dict] = None,
31
+
32
+ ) -> None:
33
+ """
34
+ Initializes the Event.
35
+
36
+ Parameters:
37
+ key (str):
38
+ The unique string identifier for the event.
39
+
40
+ callback (Optional[callable]):
41
+ The callable to be called when the event is triggered.
42
+
43
+ pass_window_values (bool):
44
+ Whether to pass window values to the callback function.
45
+ """
46
+ super().__init__(MOD_LOGGER)
47
+ self.__callback = None
48
+ self.__failed = 0
49
+ self.__initialized = False
50
+ self.__key = None
51
+ self.__times_handled = 0
52
+
53
+ self.key = key
54
+
55
+ if callback is not None:
56
+ self.callback = callback
57
+
58
+ self.__initialized = True
59
+
60
+ @property
61
+ def callback(self) -> callable:
62
+ """
63
+ The callback function for the event.
64
+
65
+ The callback function is called whenever the event is triggered. The
66
+ function should accept zero or more arguments.
67
+
68
+ Returns:
69
+ callable:
70
+ The callback function for the event.
71
+
72
+ Raises:
73
+ TypeError:
74
+ If the callback is not callable.
75
+
76
+ """
77
+ return self.__callback
78
+
79
+ @callback.setter
80
+ def callback(self, new: callable) -> None:
81
+ """
82
+ The callback function for the event.
83
+
84
+ The callback function is called whenever the event is triggered. The
85
+ function should accept zero or more arguments.
86
+
87
+ Parameters:
88
+ new (callable):
89
+ The new value for the callback.
90
+
91
+ Raises:
92
+ TypeError:
93
+ If the new value is not callable.
94
+ """
95
+ if not callable(new):
96
+ raise TypeError(f"callback must be callable, not {type(new)}")
97
+
98
+ self.__callback = new
99
+
100
+ @property
101
+ def times_failed(self) -> int:
102
+ """
103
+ The number of times the callback function has failed. (**Read-Only**)
104
+
105
+ If the callback function fails, an error is raised and the number of
106
+ times it has failed is incremented.
107
+
108
+ Returns:
109
+ int:
110
+ The number of times the callback function has failed.
111
+ """
112
+ return self.__failed
113
+
114
+
115
+ @property
116
+ def key(self):
117
+ """
118
+ The key of the event.
119
+
120
+ The key is the identifier for this event. It is used to look up the event
121
+ in the collection.
122
+
123
+ Returns:
124
+ str:
125
+ The key of the event.
126
+ """
127
+ return self.__key
128
+
129
+ @key.setter
130
+ def key(self, new: Optional[str]):
131
+ """
132
+ The key of the event.
133
+
134
+ The key is the identifier for this event. It is used to look up the event
135
+ in the collection.
136
+
137
+ If the event is an `ExitEvent` (a special event given to every window
138
+ handler collection), the key is allowed to be `None`. Otherwise, the key
139
+ must be a string.
140
+
141
+ Parameters:
142
+ new (Optional[str]):
143
+ The new key of the event.
144
+
145
+ Raises:
146
+ RuntimeError:
147
+ If the key is changed after initialization.
148
+ TypeError:
149
+ If the new key is not a string.
150
+ """
151
+ # Allow for `key` to allow `None` as a value if it is from `ExitEvent` (a special event given to every window
152
+ # handler collection)
153
+ if new is None and isinstance(self, ExitEvent):
154
+ self._Event__key = None
155
+ return
156
+
157
+ if self.__initialized:
158
+ raise RuntimeError("Cannot change key after initialization")
159
+
160
+ if not isinstance(new, str):
161
+ raise TypeError(f"key must be of type `str`, not {type(new)}")
162
+
163
+ self.__key = new.upper()
164
+
165
+ @property
166
+ def times_handled(self):
167
+ """
168
+ The number of times the event has been handled.
169
+
170
+ This property returns the count of how many times the `handle` method
171
+ has been invoked for this event.
172
+
173
+ Returns:
174
+ int:
175
+ The number of times the event has been handled.
176
+ """
177
+ return self.__times_handled
178
+
179
+ def handle(self, callback_args: tuple = (), callback_kwargs: Optional[dict] = None):
180
+ """
181
+ Handles an event by calling the associated callback.
182
+
183
+ Parameters:
184
+ callback_args (tuple):
185
+ The arguments to be passed to the callback.
186
+
187
+ callback_kwargs (Optional[dict]):
188
+ The keyword arguments to be passed to the callback.
189
+
190
+ Returns:
191
+ Any:
192
+ The return value of the callback, or None if no callback is associated with the event.
193
+ """
194
+ if callback_kwargs is None:
195
+ callback_kwargs = {}
196
+
197
+ self.__times_handled += 1
198
+
199
+ if self.callback:
200
+ try:
201
+ return self.callback(*callback_args, **callback_kwargs)
202
+ except Exception as e:
203
+ self.__failed += 1
204
+ raise e from e
205
+
206
+ return None
207
+
208
+
209
+ class ExitEvent(Event):
210
+ """
211
+ A special event that can be applied to any PySimpleGUI main loop to recognize and handle a press of the 'X' button.
212
+ """
213
+ def __init__(self):
214
+ super().__init__(key=None,)
215
+
216
+ @property
217
+ def key(self) -> None:
218
+ return None
219
+
220
+ @key.setter
221
+ def key(self, new: None) -> None:
222
+ raise ValueError('The key of an ExitEvent cannot be set.')
223
+
224
+
225
+ __initialized = True
226
+
@@ -0,0 +1,8 @@
1
+ """
2
+ Author: Taylor B. tayjaybabee@gmail.com
3
+ Date: 2025-05-02 16:04:13
4
+ LastEditors: Taylor B. tayjaybabee@gmail.com
5
+ LastEditTime: 2025-05-02 16:13:48
6
+ FilePath: is_matrix_forge/monitor/gui/layout/__init__.py
7
+ Description: 这是默认设置,可以在设置》工具》File Description中进行配置
8
+ """
@@ -0,0 +1,147 @@
1
+ from abc import abstractmethod
2
+ import copy
3
+
4
+ from is_matrix_forge.log_engine import ROOT_LOGGER, Loggable
5
+ from is_matrix_forge.monitor.gui.metaclasses import SingletonABCMeta
6
+ from inspyre_toolbox.exceptional import CustomRootException
7
+
8
+
9
+ class LayoutError(CustomRootException):
10
+ default_message = 'An error occurred with the layout!'
11
+
12
+
13
+ class LayoutNotBuiltError(LayoutError):
14
+ """
15
+ Raised when the layout is not built yet.
16
+
17
+ Class Attributes:
18
+ default_message (str):
19
+ **Read-Only**. The default message for the error.
20
+ """
21
+ default_message = 'Layout not built yet!'
22
+
23
+
24
+ class LayoutAlreadyBuiltError(LayoutError):
25
+ """
26
+ Raised when the layout is already built.
27
+
28
+ Class Attributes:
29
+ default_message (str):
30
+ **Read-Only**. The default message for the error.
31
+ """
32
+ default_message = 'Layout already built!'
33
+
34
+
35
+ LOGGER = ROOT_LOGGER.get_child('monitor.gui.layout.base')
36
+
37
+
38
+ class Layout(Loggable, metaclass=SingletonABCMeta):
39
+
40
+ @property
41
+ @abstractmethod
42
+ def BLUEPRINT(self):
43
+ """Return a 2D list of PySimpleGUI elements used to build this layout."""
44
+ raise NotImplementedError
45
+
46
+ def __init__(self, with_logging_parent=None):
47
+ if with_logging_parent is None:
48
+ with_logging_parent = LOGGER
49
+
50
+ super().__init__(with_logging_parent)
51
+
52
+ self.__layout = None
53
+ self.__is_built = False
54
+
55
+ @property
56
+ def built(self):
57
+ """
58
+ Check if the layout has been built.
59
+
60
+ Returns:
61
+ bool:
62
+ True if the layout has been built, False otherwise.
63
+ """
64
+ return self.__is_built
65
+
66
+ @property
67
+ def layout(self):
68
+ """
69
+ Get a fresh deep-copy of the built layout.
70
+
71
+ Returns:
72
+ List[List[PySimpleGUI.Element]]:
73
+ A 2D list of `PySimpleGUI` elements representing the layout.
74
+
75
+ Raises:
76
+ LayoutNotBuiltError:
77
+ If the layout has not been built yet.
78
+ """
79
+ log = self.method_logger
80
+ if not self.built:
81
+ log.warning('Attempted to access layout before it was built!')
82
+ raise LayoutNotBuiltError()
83
+
84
+ if self.__layout is None:
85
+ err_msg = 'Layout is None, but built is True!'
86
+ log.warning(err_msg)
87
+ raise LayoutNotBuiltError(err_msg)
88
+
89
+ log.debug('Returning a deep-copy of the layout.')
90
+ return copy.deepcopy(self.__layout)
91
+
92
+ def build(self):
93
+ """
94
+ Build the layout from the `BLUEPRINT` attribute once.
95
+
96
+ Returns:
97
+ List[List[PySimpleGUI.Element]]:
98
+ A 2D list of `PySimpleGUI` elements representing the layout, deep-copied.
99
+
100
+ Raises:
101
+ LayoutAlreadyBuiltError:
102
+ If the layout has already been built.
103
+ """
104
+ log = self.method_logger
105
+ if self.built:
106
+ log.warning('Attempted to build layout when it was already built!')
107
+ raise LayoutAlreadyBuiltError()
108
+
109
+ blueprint = self.BLUEPRINT
110
+ if not isinstance(blueprint, list) or not all(isinstance(r, list) for r in blueprint):
111
+ raise LayoutError('BLUEPRINT must be a 2D list of PySimpleGUI elements.')
112
+
113
+ self.__layout = copy.deepcopy(blueprint)
114
+ log.debug('Layout built successfully.')
115
+ self.__is_built = True
116
+ log.debug('Marked layout as built.')
117
+
118
+ return self.layout
119
+
120
+ def rebuild(self):
121
+ """
122
+ Clear the current layout and build a new one from `BLUEPRINT`.
123
+
124
+ Returns:
125
+ List[List[PySimpleGUI.Element]]:
126
+ A 2D list of `PySimpleGUI` elements representing the layout, deep-copied.
127
+
128
+ Raises:
129
+ LayoutNotBuiltError:
130
+ If the layout has not been built yet.
131
+ """
132
+ log = self.method_logger
133
+
134
+ if not self.built:
135
+ log.warning('Attempted to rebuild layout when it was not built yet!')
136
+ raise LayoutNotBuiltError('Cannot rebuild layout when it was not built yet!')
137
+
138
+ log.debug('Rebuilding layout...')
139
+ log.debug('Clearing current layout...')
140
+
141
+ self.__layout = None
142
+ log.debug('Layout set to None.')
143
+ self.__is_built = False
144
+ log.debug('Layout marked as not built.')
145
+ log.debug('Building new layout and returning...')
146
+
147
+ return self.build()
@@ -0,0 +1,20 @@
1
+ from abc import ABCMeta
2
+ from threading import Lock
3
+
4
+
5
+ class SingletonABCMeta(ABCMeta):
6
+ """
7
+ A metaclass that ensures all subclasses are singletons.
8
+
9
+ This metaclass can be combined with abstract base classes
10
+ to make all concrete subclasses singletons.
11
+ """
12
+ _instances = {}
13
+ _lock = Lock()
14
+
15
+ def __call__(cls, *args, **kwargs):
16
+ with SingletonABCMeta._lock:
17
+ if cls not in SingletonABCMeta._instances:
18
+ instance = super().__call__(*args, **kwargs)
19
+ SingletonABCMeta._instances[cls] = instance
20
+ return SingletonABCMeta._instances[cls]
File without changes