composeui 0.1.0__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.
- composeui/__init__.py +11 -0
- composeui/core/__init__.py +0 -0
- composeui/core/application.py +368 -0
- composeui/core/chartcolors.py +23 -0
- composeui/core/chartimage.py +336 -0
- composeui/core/dataframesource.py +307 -0
- composeui/core/jobs.py +426 -0
- composeui/core/model.py +66 -0
- composeui/core/registry.py +66 -0
- composeui/core/reload.py +476 -0
- composeui/core/renderer.py +193 -0
- composeui/core/signal.py +358 -0
- composeui/core/sqlitemodel.py +1183 -0
- composeui/core/study.py +121 -0
- composeui/core/translation.py +221 -0
- composeui/core/translationcheck.py +254 -0
- composeui/core/view.py +5508 -0
- composeui/images/composeui.svg +15 -0
- composeui/locales/fr.json +73 -0
- composeui/mypy.py +46 -0
- composeui/py.typed +0 -0
- composeui/qtcore/__init__.py +0 -0
- composeui/qtcore/maprenderer.py +115 -0
- composeui/qtcore/qtapplication.py +222 -0
- composeui/qtcore/qtrenderer.py +2862 -0
- composeui/qtcore/widgets/__init__.py +0 -0
- composeui/qtcore/widgets/map.qml +123 -0
- composeui/qtcore/widgets/qchart.py +393 -0
- composeui/qtcore/widgets/qdatefield.py +101 -0
- composeui/qtcore/widgets/qexpander.py +60 -0
- composeui/qtcore/widgets/qfilepathfield.py +34 -0
- composeui/qtcore/widgets/qfiltertable.py +394 -0
- composeui/qtcore/widgets/qformdialog.py +64 -0
- composeui/qtcore/widgets/qformlabel.py +53 -0
- composeui/qtcore/widgets/qmap.py +68 -0
- composeui/qtcore/widgets/qmarkicon.py +37 -0
- composeui/qtcore/widgets/qmetric.py +44 -0
- composeui/qtcore/widgets/qplaintext.py +29 -0
- composeui/qtcore/widgets/qradiogroup.py +57 -0
- composeui/qtcore/widgets/qsidebar.py +116 -0
- composeui/qtcore/widgets/qsourcetable.py +216 -0
- composeui/qtcore/widgets/qsplitter.py +69 -0
- composeui/qtcore/widgets/qtexttable.py +703 -0
- composeui/qtcore/widgets/qvaluecombobox.py +41 -0
- composeui/qtcore/widgets/qvalueslider.py +64 -0
- composeui/qtcore/widgets/qwindowrelease.py +32 -0
- composeui/textualcore/__init__.py +0 -0
- composeui/textualcore/textualapplication.py +121 -0
- composeui/textualcore/textualrenderer.py +2893 -0
- composeui/textualcore/widgets/__init__.py +0 -0
- composeui/textualcore/widgets/composebutton.py +17 -0
- composeui/textualcore/widgets/composechart.py +212 -0
- composeui/textualcore/widgets/composecheckbox.py +35 -0
- composeui/textualcore/widgets/composecollapsible.py +41 -0
- composeui/textualcore/widgets/composecombobox.py +54 -0
- composeui/textualcore/widgets/composedateinput.py +325 -0
- composeui/textualcore/widgets/composedialog.py +90 -0
- composeui/textualcore/widgets/composefilepathinput.py +85 -0
- composeui/textualcore/widgets/composefiltertable.py +367 -0
- composeui/textualcore/widgets/composeformdialog.py +86 -0
- composeui/textualcore/widgets/composeinput.py +50 -0
- composeui/textualcore/widgets/composelist.py +20 -0
- composeui/textualcore/widgets/composemarkdown.py +17 -0
- composeui/textualcore/widgets/composemetric.py +60 -0
- composeui/textualcore/widgets/composeradioset.py +54 -0
- composeui/textualcore/widgets/composesidebar.py +111 -0
- composeui/textualcore/widgets/composeslider.py +144 -0
- composeui/textualcore/widgets/composesourcetable.py +288 -0
- composeui/textualcore/widgets/composesplitter.py +211 -0
- composeui/textualcore/widgets/composetabbedcontent.py +35 -0
- composeui/textualcore/widgets/composetable.py +476 -0
- composeui/textualcore/widgets/composetoolbar.py +62 -0
- composeui/textualcore/widgets/composetree.py +139 -0
- composeui/webcore/__init__.py +0 -0
- composeui/webcore/headers.py +57 -0
- composeui/webcore/icons.py +58 -0
- composeui/webcore/limits.py +55 -0
- composeui/webcore/origin.py +63 -0
- composeui/webcore/page.py +146 -0
- composeui/webcore/routes.py +303 -0
- composeui/webcore/session.py +436 -0
- composeui/webcore/static/app.css +2 -0
- composeui/webcore/static/cally.js +1312 -0
- composeui/webcore/static/daisyui.LICENSE +21 -0
- composeui/webcore/static/deckgl.LICENSE +19 -0
- composeui/webcore/static/deckgl.min.js +6423 -0
- composeui/webcore/static/htmx.min.js +1 -0
- composeui/webcore/static/page.js +1175 -0
- composeui/webcore/static/uPlot.LICENSE +21 -0
- composeui/webcore/static/uPlot.iife.min.js +2 -0
- composeui/webcore/webapplication.py +495 -0
- composeui/webcore/webrenderer.py +5113 -0
- composeui-0.1.0.dist-info/METADATA +222 -0
- composeui-0.1.0.dist-info/RECORD +97 -0
- composeui-0.1.0.dist-info/WHEEL +5 -0
- composeui-0.1.0.dist-info/licenses/LICENSE +9 -0
- composeui-0.1.0.dist-info/top_level.txt +1 -0
composeui/core/signal.py
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
"""The signals, which carry events between the views, the model and the app's code."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import functools
|
|
6
|
+
import inspect
|
|
7
|
+
import logging
|
|
8
|
+
import sys
|
|
9
|
+
import weakref
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
from typing import TYPE_CHECKING, Any, Generic, TypeVar, Union, cast, overload
|
|
12
|
+
|
|
13
|
+
from typing_extensions import Protocol, TypeAliasType, TypeVarTuple, Unpack
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from collections.abc import Sequence
|
|
17
|
+
from weakref import ReferenceType
|
|
18
|
+
|
|
19
|
+
from qtpy.QtCore import SignalInstance
|
|
20
|
+
|
|
21
|
+
from composeui.core.model import Model
|
|
22
|
+
from composeui.core.view import MainView
|
|
23
|
+
|
|
24
|
+
__all__ = ["Signal"]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
LOGGER = logging.getLogger("COMPOSEUI_SIGNAL")
|
|
28
|
+
_STREAM_HANDLER = logging.StreamHandler(sys.stdout)
|
|
29
|
+
_STREAM_HANDLER.setFormatter(logging.Formatter("%(asctime)s: %(message)s", "%Y-%m-%d %H:%M:%S"))
|
|
30
|
+
LOGGER.addHandler(_STREAM_HANDLER)
|
|
31
|
+
|
|
32
|
+
Ts = TypeVarTuple("Ts")
|
|
33
|
+
_Rest = TypeVarTuple("_Rest")
|
|
34
|
+
_FirstT = TypeVar("_FirstT")
|
|
35
|
+
|
|
36
|
+
_MainViewT_contra = TypeVar("_MainViewT_contra", contravariant=True)
|
|
37
|
+
_ModelT_contra = TypeVar("_ModelT_contra", contravariant=True)
|
|
38
|
+
_MainViewT = TypeVar("_MainViewT", bound="MainView")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class _MainViewCallback(Protocol[_MainViewT_contra, Unpack[Ts]]):
|
|
42
|
+
def __call__(self, *args: Unpack[Ts], main_view: _MainViewT_contra) -> None: ...
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class _MainViewOnlyCallback(Protocol[_MainViewT_contra]):
|
|
46
|
+
def __call__(self, *, main_view: _MainViewT_contra) -> None: ...
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class _ModelCallback(Protocol[_ModelT_contra, Unpack[Ts]]):
|
|
50
|
+
def __call__(self, *args: Unpack[Ts], model: _ModelT_contra) -> None: ...
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class _BoundCallback(Protocol[_MainViewT_contra, _ModelT_contra, Unpack[Ts]]):
|
|
54
|
+
def __call__(
|
|
55
|
+
self, *args: Unpack[Ts], main_view: _MainViewT_contra, model: _ModelT_contra
|
|
56
|
+
) -> None: ...
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# `main_view` and `model` are typed `Any`: a signal can't know its app's classes, and a
|
|
60
|
+
# callback asks for those, not `MainView` and `Model` (`connect` still checks a main view)
|
|
61
|
+
CallbackT = TypeAliasType(
|
|
62
|
+
"CallbackT",
|
|
63
|
+
(
|
|
64
|
+
Union[
|
|
65
|
+
Callable[[Unpack[Ts]], None],
|
|
66
|
+
_MainViewCallback[Any, Unpack[Ts]],
|
|
67
|
+
_ModelCallback[Any, Unpack[Ts]],
|
|
68
|
+
_BoundCallback[Any, Any, Unpack[Ts]],
|
|
69
|
+
]
|
|
70
|
+
),
|
|
71
|
+
type_params=(Ts,),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _positional_count(callback: Callable[..., None]) -> int | None:
|
|
76
|
+
"""Get how many positional arguments `callback` takes; None for any number."""
|
|
77
|
+
try:
|
|
78
|
+
params = inspect.signature(callback).parameters.values()
|
|
79
|
+
except (TypeError, ValueError):
|
|
80
|
+
return None
|
|
81
|
+
positional = (inspect.Parameter.POSITIONAL_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD)
|
|
82
|
+
if any(param.kind is inspect.Parameter.VAR_POSITIONAL for param in params):
|
|
83
|
+
return None
|
|
84
|
+
return sum(
|
|
85
|
+
param.kind in positional and param.name not in ("main_view", "model") for param in params
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class Signal(Generic[Unpack[Ts]]):
|
|
90
|
+
"""An event that a view, a model or the app emits, with the callbacks connected to it.
|
|
91
|
+
|
|
92
|
+
`connect` adds a callback, and `emit` calls each callback with the signal's arguments. A
|
|
93
|
+
callback with a ``main_view`` or ``model`` parameter gets it by name (see `connect`).
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def __init__(self) -> None:
|
|
97
|
+
self._name = ""
|
|
98
|
+
self._qt_signal = _QtSignal()
|
|
99
|
+
self._callbacks: list[Callable[[Unpack[Ts]], None]] = [self._log_signal]
|
|
100
|
+
self._logger = LOGGER
|
|
101
|
+
self._main_view: ReferenceType[MainView] | None = None
|
|
102
|
+
self._model: ReferenceType[Model] | None = None
|
|
103
|
+
|
|
104
|
+
def set_name(self, name: str) -> None:
|
|
105
|
+
self._name = name
|
|
106
|
+
|
|
107
|
+
def set_main_view(self, main_view: MainView) -> None:
|
|
108
|
+
self._main_view = weakref.ref(main_view)
|
|
109
|
+
for callback in self._callbacks:
|
|
110
|
+
if isinstance(callback, InjectedCallback):
|
|
111
|
+
callback.set_main_view(self._main_view)
|
|
112
|
+
|
|
113
|
+
def set_model(self, model: Model) -> None:
|
|
114
|
+
self._model = weakref.ref(model)
|
|
115
|
+
for callback in self._callbacks:
|
|
116
|
+
if isinstance(callback, InjectedCallback):
|
|
117
|
+
callback.set_model(self._model)
|
|
118
|
+
|
|
119
|
+
def add_qt_signal(self, signal: SignalInstance) -> None:
|
|
120
|
+
"""Call this signal's callbacks when the Qt signal `signal` emits too."""
|
|
121
|
+
self._qt_signal.add_signal(signal, self._callbacks)
|
|
122
|
+
|
|
123
|
+
@overload
|
|
124
|
+
def connect(self, callback: Callable[[Unpack[Ts]], None], *, now: bool = False) -> None: ...
|
|
125
|
+
@overload
|
|
126
|
+
def connect(
|
|
127
|
+
self, callback: _MainViewCallback[_MainViewT, Unpack[Ts]], *, now: bool = False
|
|
128
|
+
) -> None: ...
|
|
129
|
+
@overload
|
|
130
|
+
def connect(self, callback: _ModelCallback[Any, Unpack[Ts]], *, now: bool = False) -> None: ...
|
|
131
|
+
@overload
|
|
132
|
+
def connect(
|
|
133
|
+
self, callback: _BoundCallback[_MainViewT, Any, Unpack[Ts]], *, now: bool = False
|
|
134
|
+
) -> None: ...
|
|
135
|
+
# a callback taking none of the arguments
|
|
136
|
+
@overload
|
|
137
|
+
def connect(self, callback: Callable[[], None], *, now: bool = False) -> None: ...
|
|
138
|
+
@overload
|
|
139
|
+
def connect(
|
|
140
|
+
self, callback: _MainViewOnlyCallback[_MainViewT], *, now: bool = False
|
|
141
|
+
) -> None: ...
|
|
142
|
+
@overload
|
|
143
|
+
def connect(self, callback: _ModelCallback[Any], *, now: bool = False) -> None: ...
|
|
144
|
+
@overload
|
|
145
|
+
def connect(self, callback: _BoundCallback[_MainViewT, Any], *, now: bool = False) -> None: ...
|
|
146
|
+
@overload
|
|
147
|
+
def connect(
|
|
148
|
+
self: Signal[_FirstT, Unpack[_Rest]],
|
|
149
|
+
callback: Callable[[_FirstT], None],
|
|
150
|
+
*,
|
|
151
|
+
now: bool = False,
|
|
152
|
+
) -> None: ...
|
|
153
|
+
def connect(self, callback: Callable[..., None], *, now: bool = False) -> None:
|
|
154
|
+
"""Call `callback` on each emit, and once right away too if `now`.
|
|
155
|
+
|
|
156
|
+
The callback gets the `main_view` and the `model` it asks for by name. It gets only as
|
|
157
|
+
many of the signal's arguments as it takes, the first ones, and none when called `now`.
|
|
158
|
+
"""
|
|
159
|
+
injected_callback = self._inject_callback(callback)
|
|
160
|
+
self._callbacks.append(injected_callback)
|
|
161
|
+
self._qt_signal.connect(injected_callback)
|
|
162
|
+
if now:
|
|
163
|
+
cast("Callable[[], None]", injected_callback)()
|
|
164
|
+
|
|
165
|
+
def disconnect(self) -> None:
|
|
166
|
+
self._qt_signal.disconnect(self._callbacks)
|
|
167
|
+
self._callbacks = [self._log_signal]
|
|
168
|
+
self._qt_signal.connect(self._log_signal)
|
|
169
|
+
|
|
170
|
+
def emit(self, *args: Unpack[Ts]) -> None:
|
|
171
|
+
for callback in self._callbacks:
|
|
172
|
+
callback(*args)
|
|
173
|
+
|
|
174
|
+
def _log_signal(self, *args: Unpack[Ts]) -> None:
|
|
175
|
+
log_msgs = ["EMIT SIGNAL %s"]
|
|
176
|
+
log_args = [self._name]
|
|
177
|
+
nb_callbacks = 0
|
|
178
|
+
for callback in self._callbacks:
|
|
179
|
+
if callback != self._log_signal:
|
|
180
|
+
callback_name = self.get_function_name(callback)
|
|
181
|
+
callback_file = self.get_function_file(callback)
|
|
182
|
+
if len(args) > 0:
|
|
183
|
+
log_msgs.append(" -> CALL %s(%s)\n %s")
|
|
184
|
+
else:
|
|
185
|
+
log_msgs.append(" -> CALL %s()\n %s")
|
|
186
|
+
log_args.append(callback_name)
|
|
187
|
+
if len(args) > 0:
|
|
188
|
+
log_args.append(",".join(repr(arg) for arg in args))
|
|
189
|
+
log_args.append(callback_file)
|
|
190
|
+
nb_callbacks += 1
|
|
191
|
+
if nb_callbacks > 0:
|
|
192
|
+
self._logger.debug("\n".join(log_msgs), *log_args)
|
|
193
|
+
|
|
194
|
+
def _inject_callback(
|
|
195
|
+
self, callback: Callable[..., None]
|
|
196
|
+
) -> (
|
|
197
|
+
Callable[[Unpack[Ts]], None]
|
|
198
|
+
| InjectedCallback[Unpack[Ts]]
|
|
199
|
+
| _FirstArgumentsCallback[Unpack[Ts]]
|
|
200
|
+
):
|
|
201
|
+
requested_bounds = self._requested_bounds(callback)
|
|
202
|
+
if len(requested_bounds) > 0:
|
|
203
|
+
return InjectedCallback[Unpack[Ts]](
|
|
204
|
+
callback=callback,
|
|
205
|
+
requested_bounds=requested_bounds,
|
|
206
|
+
optional_bounds=self._optional_bounds(callback),
|
|
207
|
+
main_view=self._main_view,
|
|
208
|
+
model=self._model,
|
|
209
|
+
)
|
|
210
|
+
count = _positional_count(callback)
|
|
211
|
+
if count is not None:
|
|
212
|
+
return _FirstArgumentsCallback[Unpack[Ts]](callback, count)
|
|
213
|
+
return cast("Callable[[Unpack[Ts]], None]", callback)
|
|
214
|
+
|
|
215
|
+
@staticmethod
|
|
216
|
+
def _requested_bounds(callback: Callable[..., None]) -> frozenset[str]:
|
|
217
|
+
names = ("main_view", "model")
|
|
218
|
+
try:
|
|
219
|
+
params = inspect.signature(callback).parameters
|
|
220
|
+
except (TypeError, ValueError):
|
|
221
|
+
return frozenset()
|
|
222
|
+
return frozenset(name for name in names if name in params)
|
|
223
|
+
|
|
224
|
+
@staticmethod
|
|
225
|
+
def _optional_bounds(callback: Callable[..., None]) -> frozenset[str]:
|
|
226
|
+
"""Get the bounds `callback` has a default for, left out when there is none to give.
|
|
227
|
+
|
|
228
|
+
Asked once `_requested_bounds` found some: its signature can be read.
|
|
229
|
+
"""
|
|
230
|
+
params = inspect.signature(callback).parameters
|
|
231
|
+
return frozenset(
|
|
232
|
+
name
|
|
233
|
+
for name in ("main_view", "model")
|
|
234
|
+
if name in params and params[name].default is not inspect.Parameter.empty
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
@staticmethod
|
|
238
|
+
def get_function_name(callback: CallbackT[Any]) -> str:
|
|
239
|
+
if isinstance(callback, (InjectedCallback, _FirstArgumentsCallback)):
|
|
240
|
+
wrapped = callback.get_callback()
|
|
241
|
+
return "<collected>" if wrapped is None else Signal.get_function_name(wrapped) # type: ignore[arg-type]
|
|
242
|
+
if isinstance(callback, functools.partial):
|
|
243
|
+
inner = Signal.get_function_name(callback.func)
|
|
244
|
+
args = list(map(repr, callback.args))
|
|
245
|
+
kwargs = [f"{n}={type(arg).__name__}" for n, arg in callback.keywords.items()]
|
|
246
|
+
return f"{inner}({', '.join(args + kwargs)})"
|
|
247
|
+
name = getattr(callback, "__qualname__", None) or getattr(callback, "__name__", None)
|
|
248
|
+
return name or type(callback).__name__
|
|
249
|
+
|
|
250
|
+
@staticmethod
|
|
251
|
+
def get_function_file(callback: CallbackT[Any]) -> str:
|
|
252
|
+
if isinstance(callback, (InjectedCallback, _FirstArgumentsCallback)):
|
|
253
|
+
wrapped = callback.get_callback()
|
|
254
|
+
return "" if wrapped is None else Signal.get_function_file(wrapped) # type: ignore[arg-type]
|
|
255
|
+
if isinstance(callback, functools.partial):
|
|
256
|
+
return Signal.get_function_file(callback.func)
|
|
257
|
+
code = getattr(callback, "__code__", None)
|
|
258
|
+
if code is None:
|
|
259
|
+
# built-ins (e.g. `list.append`) and callable instances have no code object
|
|
260
|
+
return repr(callback)
|
|
261
|
+
return f'File "{code.co_filename}", line {code.co_firstlineno}'
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
if TYPE_CHECKING:
|
|
265
|
+
SignalNoArgs = Signal[()]
|
|
266
|
+
else:
|
|
267
|
+
SignalNoArgs = Signal
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class _QtSignal:
|
|
271
|
+
def __init__(self) -> None:
|
|
272
|
+
self._qt_signals: list[SignalInstance] = []
|
|
273
|
+
|
|
274
|
+
def add_signal(self, signal: SignalInstance, callbacks: Sequence[Callable[..., None]]) -> None:
|
|
275
|
+
"""Add the Qt signal `signal`, and connect `callbacks` to it."""
|
|
276
|
+
self._qt_signals.append(signal)
|
|
277
|
+
for callback in callbacks:
|
|
278
|
+
signal.connect(callback)
|
|
279
|
+
|
|
280
|
+
def connect(self, callback: Callable[..., None]) -> None:
|
|
281
|
+
for qt_signal in self._qt_signals:
|
|
282
|
+
qt_signal.connect(callback)
|
|
283
|
+
|
|
284
|
+
def disconnect(self, callbacks: Sequence[Callable[..., None]]) -> None:
|
|
285
|
+
for qt_signal in self._qt_signals:
|
|
286
|
+
for callback in callbacks:
|
|
287
|
+
qt_signal.disconnect(callback)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
class _FirstArgumentsCallback(Generic[Unpack[Ts]]):
|
|
291
|
+
"""A callback taking fewer positional arguments than its signal emits: the first ones."""
|
|
292
|
+
|
|
293
|
+
def __init__(self, callback: Callable[..., None], count: int) -> None:
|
|
294
|
+
self._callback = callback
|
|
295
|
+
self._count = count
|
|
296
|
+
|
|
297
|
+
def get_callback(self) -> Callable[..., None]:
|
|
298
|
+
return self._callback
|
|
299
|
+
|
|
300
|
+
def __call__(self, *args: Unpack[Ts]) -> None:
|
|
301
|
+
self._callback(*args[: self._count])
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
class InjectedCallback(Generic[Unpack[Ts]]):
|
|
305
|
+
"""A callback that gets the ``main_view`` and ``model`` it asks for by name."""
|
|
306
|
+
|
|
307
|
+
def __init__(
|
|
308
|
+
self,
|
|
309
|
+
*,
|
|
310
|
+
callback: Callable[..., None],
|
|
311
|
+
requested_bounds: frozenset[str],
|
|
312
|
+
optional_bounds: frozenset[str] = frozenset(),
|
|
313
|
+
main_view: ReferenceType[MainView] | None,
|
|
314
|
+
model: ReferenceType[Model] | None,
|
|
315
|
+
) -> None:
|
|
316
|
+
# a view's own method stays weak, so its signals don't keep the view alive; a function
|
|
317
|
+
# or a partial (e.g. `Application.connect`'s) has no owner that would hold it
|
|
318
|
+
self._callback: Callable[[], Callable[..., None] | None]
|
|
319
|
+
if inspect.ismethod(callback):
|
|
320
|
+
self._callback = cast("Callable[[], Any]", weakref.WeakMethod(callback))
|
|
321
|
+
else:
|
|
322
|
+
self._callback = lambda: callback
|
|
323
|
+
self._requested_bounds = requested_bounds
|
|
324
|
+
self._optional_bounds = optional_bounds
|
|
325
|
+
self._main_view = main_view
|
|
326
|
+
self._model = model
|
|
327
|
+
#: how many of the signal's arguments the callback takes, the first ones; None for all
|
|
328
|
+
self._count = _positional_count(callback)
|
|
329
|
+
|
|
330
|
+
def set_main_view(self, main_view: ReferenceType[MainView]) -> None:
|
|
331
|
+
self._main_view = main_view
|
|
332
|
+
|
|
333
|
+
def set_model(self, model: ReferenceType[Model]) -> None:
|
|
334
|
+
self._model = model
|
|
335
|
+
|
|
336
|
+
def get_callback(self) -> Callable[..., None] | None:
|
|
337
|
+
"""Get the callback; None once it's a method of a view collected since."""
|
|
338
|
+
return self._callback()
|
|
339
|
+
|
|
340
|
+
def __call__(self, *args: Unpack[Ts]) -> None:
|
|
341
|
+
callback = self._callback()
|
|
342
|
+
if callback is None:
|
|
343
|
+
return None # its view is gone: as Qt, which drops a deleted receiver's slot
|
|
344
|
+
kwargs: dict[str, MainView | Model] = {}
|
|
345
|
+
if "main_view" in self._requested_bounds:
|
|
346
|
+
main_view = None if self._main_view is None else self._main_view()
|
|
347
|
+
if main_view is None:
|
|
348
|
+
msg = "The main view is missing."
|
|
349
|
+
raise ValueError(msg)
|
|
350
|
+
kwargs["main_view"] = main_view
|
|
351
|
+
if "model" in self._requested_bounds:
|
|
352
|
+
model = None if self._model is None else self._model()
|
|
353
|
+
if model is not None:
|
|
354
|
+
kwargs["model"] = model
|
|
355
|
+
elif "model" not in self._optional_bounds:
|
|
356
|
+
msg = "The model is missing."
|
|
357
|
+
raise ValueError(msg)
|
|
358
|
+
return callback(*args[: self._count], **kwargs)
|