pyqttoolkit 0.9.1__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.
- pyqttoolkit/__init__.py +20 -0
- pyqttoolkit/_version.py +21 -0
- pyqttoolkit/application/__init__.py +18 -0
- pyqttoolkit/application/application.py +86 -0
- pyqttoolkit/application/garbage_collector.py +65 -0
- pyqttoolkit/colors/__init__.py +84 -0
- pyqttoolkit/config/__init__.py +20 -0
- pyqttoolkit/config/base.py +22 -0
- pyqttoolkit/config/composite.py +31 -0
- pyqttoolkit/config/default.py +27 -0
- pyqttoolkit/config/environment.py +35 -0
- pyqttoolkit/config/multi_json_file.py +93 -0
- pyqttoolkit/config/with_signal.py +30 -0
- pyqttoolkit/config/with_value_changed.py +31 -0
- pyqttoolkit/data/__init__.py +21 -0
- pyqttoolkit/data/bits.py +23 -0
- pyqttoolkit/data/cache.py +64 -0
- pyqttoolkit/data/interval.py +26 -0
- pyqttoolkit/data/message.py +29 -0
- pyqttoolkit/data/names.py +24 -0
- pyqttoolkit/datetime/__init__.py +16 -0
- pyqttoolkit/datetime/datetime.py +51 -0
- pyqttoolkit/datetime/qdatetime.py +53 -0
- pyqttoolkit/dependencies/__init__.py +56 -0
- pyqttoolkit/errors/__init__.py +19 -0
- pyqttoolkit/exceptions/__init__.py +18 -0
- pyqttoolkit/filesystem/__init__.py +24 -0
- pyqttoolkit/logs/__init__.py +140 -0
- pyqttoolkit/metadata/__init__.py +23 -0
- pyqttoolkit/metadata/dataframe_metadata_manager.py +105 -0
- pyqttoolkit/metadata/dict_metadata_manager.py +63 -0
- pyqttoolkit/metadata/id_generator.py +20 -0
- pyqttoolkit/metadata/metadata_manager_base.py +54 -0
- pyqttoolkit/models/__init__.py +26 -0
- pyqttoolkit/models/code_text.py +43 -0
- pyqttoolkit/models/dataframetable.py +135 -0
- pyqttoolkit/models/dictpropertytree.py +155 -0
- pyqttoolkit/models/dicttable.py +140 -0
- pyqttoolkit/models/json_text.py +52 -0
- pyqttoolkit/models/list_model.py +52 -0
- pyqttoolkit/models/module.py +26 -0
- pyqttoolkit/models/roles.py +23 -0
- pyqttoolkit/models/series_data.py +68 -0
- pyqttoolkit/models/span.py +36 -0
- pyqttoolkit/models/validation.py +27 -0
- pyqttoolkit/modules/__init__.py +18 -0
- pyqttoolkit/modules/events/__init__.py +19 -0
- pyqttoolkit/modules/events/add_signal.py +31 -0
- pyqttoolkit/modules/events/module_opening.py +21 -0
- pyqttoolkit/modules/events/rejectable.py +29 -0
- pyqttoolkit/modules/launcher_config.py +55 -0
- pyqttoolkit/modules/module.py +111 -0
- pyqttoolkit/properties/__init__.py +18 -0
- pyqttoolkit/properties/auto.py +221 -0
- pyqttoolkit/properties/throttled.py +77 -0
- pyqttoolkit/scripting/__init__.py +19 -0
- pyqttoolkit/scripting/script_context.py +36 -0
- pyqttoolkit/scripting/script_runner.py +132 -0
- pyqttoolkit/scripting/script_template.py +42 -0
- pyqttoolkit/services/__init__.py +32 -0
- pyqttoolkit/services/autosave.py +41 -0
- pyqttoolkit/services/email.py +25 -0
- pyqttoolkit/services/event_registry.py +52 -0
- pyqttoolkit/services/file_dialog.py +71 -0
- pyqttoolkit/services/hotkey_manager.py +137 -0
- pyqttoolkit/services/link_manager.py +135 -0
- pyqttoolkit/services/message_board.py +90 -0
- pyqttoolkit/services/module_service.py +154 -0
- pyqttoolkit/services/object_converter.py +45 -0
- pyqttoolkit/services/project_updater.py +149 -0
- pyqttoolkit/services/task_runner.py +281 -0
- pyqttoolkit/services/theme_manager.py +46 -0
- pyqttoolkit/services/tool_window_service.py +46 -0
- pyqttoolkit/viewmanagers/__init__.py +17 -0
- pyqttoolkit/viewmanagers/tool.py +23 -0
- pyqttoolkit/views/__init__.py +50 -0
- pyqttoolkit/views/bulk_value_selector.py +112 -0
- pyqttoolkit/views/check_box.py +36 -0
- pyqttoolkit/views/code/__init__.py +18 -0
- pyqttoolkit/views/code/code_edit.py +268 -0
- pyqttoolkit/views/code/python_syntax.py +226 -0
- pyqttoolkit/views/combo_box.py +101 -0
- pyqttoolkit/views/count_selector.py +59 -0
- pyqttoolkit/views/datetime.py +111 -0
- pyqttoolkit/views/datetime_range_selector.py +231 -0
- pyqttoolkit/views/default_text_line_edit.py +50 -0
- pyqttoolkit/views/delegates.py +117 -0
- pyqttoolkit/views/dtype_edit.py +117 -0
- pyqttoolkit/views/editable_combo_box.py +45 -0
- pyqttoolkit/views/file_selector.py +51 -0
- pyqttoolkit/views/icon.py +67 -0
- pyqttoolkit/views/icon_button.py +129 -0
- pyqttoolkit/views/json_edit.py +21 -0
- pyqttoolkit/views/labelled.py +29 -0
- pyqttoolkit/views/layout/__init__.py +21 -0
- pyqttoolkit/views/layout/collabsible.py +59 -0
- pyqttoolkit/views/layout/group_box.py +48 -0
- pyqttoolkit/views/layout/group_panel.py +188 -0
- pyqttoolkit/views/layout/lines.py +30 -0
- pyqttoolkit/views/layout/link.py +38 -0
- pyqttoolkit/views/line_edit.py +61 -0
- pyqttoolkit/views/linkable.py +49 -0
- pyqttoolkit/views/list_view.py +43 -0
- pyqttoolkit/views/main_window.py +91 -0
- pyqttoolkit/views/metadata_selector.py +84 -0
- pyqttoolkit/views/modulewindow.py +81 -0
- pyqttoolkit/views/plot/__init__.py +26 -0
- pyqttoolkit/views/plot/legend_control.py +121 -0
- pyqttoolkit/views/plot/matplotlib/__init__.py +23 -0
- pyqttoolkit/views/plot/matplotlib/base.py +1056 -0
- pyqttoolkit/views/plot/matplotlib/colorbar.py +75 -0
- pyqttoolkit/views/plot/matplotlib/colormap.py +48 -0
- pyqttoolkit/views/plot/matplotlib/font.py +55 -0
- pyqttoolkit/views/plot/plot_options.py +211 -0
- pyqttoolkit/views/plot/plot_toolbar_options.py +133 -0
- pyqttoolkit/views/plot/size.py +41 -0
- pyqttoolkit/views/plot/tool_type.py +26 -0
- pyqttoolkit/views/plot/toolbar.py +100 -0
- pyqttoolkit/views/popout.py +41 -0
- pyqttoolkit/views/popup.py +41 -0
- pyqttoolkit/views/property_tree.py +26 -0
- pyqttoolkit/views/shrunk_push_button.py +34 -0
- pyqttoolkit/views/steppable_combo_box.py +67 -0
- pyqttoolkit/views/styleable.py +37 -0
- pyqttoolkit/views/table.py +146 -0
- pyqttoolkit/views/text_edit.py +31 -0
- pyqttoolkit/views/tool_window.py +35 -0
- pyqttoolkit-0.9.1.dist-info/METADATA +69 -0
- pyqttoolkit-0.9.1.dist-info/RECORD +133 -0
- pyqttoolkit-0.9.1.dist-info/WHEEL +5 -0
- pyqttoolkit-0.9.1.dist-info/licenses/LICENSE.txt +674 -0
- pyqttoolkit-0.9.1.dist-info/licenses/LICENSE_SHORT +16 -0
- pyqttoolkit-0.9.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# pyqttoolkit
|
|
2
|
+
# Copyright (C) 2018-2019, Simmovation Ltd.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, write to the Free Software Foundation,
|
|
16
|
+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
""":mod:`project_updater`
|
|
18
|
+
Defines the ProjectUpdater class, which enables the project to be updated
|
|
19
|
+
"""
|
|
20
|
+
import inspect
|
|
21
|
+
from threading import Lock
|
|
22
|
+
|
|
23
|
+
from PyQt5.QtCore import QObject, pyqtSignal, QEvent, QSemaphore, QThread
|
|
24
|
+
from PyQt5.QtWidgets import QApplication
|
|
25
|
+
|
|
26
|
+
def _check_attribute(name, fields, project):
|
|
27
|
+
if not name in fields:
|
|
28
|
+
raise ValueError(f'Could not access property {name} of project {type(project)}')
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ProjectProxy:
|
|
32
|
+
_storage = {}
|
|
33
|
+
|
|
34
|
+
def __init__(self, project):
|
|
35
|
+
ProjectProxy._storage[self] = {}
|
|
36
|
+
ProjectProxy._storage[self]['_fields'] = []
|
|
37
|
+
for name, obj in inspect.getmembers(type(project)):
|
|
38
|
+
if inspect.isdatadescriptor(obj):
|
|
39
|
+
ProjectProxy._storage[self]['_fields'].append(name)
|
|
40
|
+
ProjectProxy._storage[self]['_updates'] = {}
|
|
41
|
+
ProjectProxy._storage[self]['_project'] = project
|
|
42
|
+
|
|
43
|
+
def __enter__(self):
|
|
44
|
+
return self
|
|
45
|
+
|
|
46
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
47
|
+
del ProjectProxy._storage[self]
|
|
48
|
+
|
|
49
|
+
def __setattr__(self, name, value):
|
|
50
|
+
_check_attribute(name, ProjectProxy._storage[self]['_fields'], ProjectProxy._storage[self]['_project'])
|
|
51
|
+
ProjectProxy._storage[self]['_updates'][name] = value
|
|
52
|
+
|
|
53
|
+
def __getattribute__(self, name):
|
|
54
|
+
if name == 'updates':
|
|
55
|
+
return ProjectProxy._storage[self]['_updates']
|
|
56
|
+
_check_attribute(name, ProjectProxy._storage[self]['_fields'], ProjectProxy._storage[self]['_project'])
|
|
57
|
+
return getattr(ProjectProxy._storage[self]['_project'], name)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class PropertyUpdatedEvent(QEvent):
|
|
61
|
+
def __init__(self, prop):
|
|
62
|
+
super().__init__(QEvent.User)
|
|
63
|
+
self._prop = prop
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def prop(self):
|
|
67
|
+
return self._prop
|
|
68
|
+
|
|
69
|
+
class UpdateCompleteEvent(QEvent):
|
|
70
|
+
def __init__(self, handler, result):
|
|
71
|
+
super().__init__(QEvent.User)
|
|
72
|
+
self._handler = handler
|
|
73
|
+
self._result = result
|
|
74
|
+
|
|
75
|
+
def exec(self):
|
|
76
|
+
self._handler(self._result)
|
|
77
|
+
|
|
78
|
+
class UpdateEvent(QEvent):
|
|
79
|
+
def __init__(self, updater):
|
|
80
|
+
super().__init__(QEvent.User)
|
|
81
|
+
self._updater = updater
|
|
82
|
+
self._result = None
|
|
83
|
+
self._semaphore = QSemaphore(1)
|
|
84
|
+
if QApplication.instance().thread() != QThread.currentThread():
|
|
85
|
+
self._semaphore.acquire()
|
|
86
|
+
|
|
87
|
+
def exec(self):
|
|
88
|
+
try:
|
|
89
|
+
self._result = self._updater(None)
|
|
90
|
+
finally:
|
|
91
|
+
self._semaphore.release()
|
|
92
|
+
|
|
93
|
+
def result(self):
|
|
94
|
+
self._semaphore.acquire()
|
|
95
|
+
return self._result
|
|
96
|
+
|
|
97
|
+
class ProjectUpdater(QObject):
|
|
98
|
+
"""class::ProjectUpdater
|
|
99
|
+
Allows the project to be updated
|
|
100
|
+
"""
|
|
101
|
+
def __init__(self, project_manager):
|
|
102
|
+
QObject.__init__(self, project_manager)
|
|
103
|
+
self._project_manager = project_manager
|
|
104
|
+
self._is_dirty = False
|
|
105
|
+
self._lock = Lock()
|
|
106
|
+
|
|
107
|
+
projectUpdated = pyqtSignal(str)
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def dirty(self):
|
|
111
|
+
return self._is_dirty
|
|
112
|
+
|
|
113
|
+
@dirty.setter
|
|
114
|
+
def dirty(self, value):
|
|
115
|
+
self._is_dirty = value
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def update_lock(self):
|
|
119
|
+
return self._lock
|
|
120
|
+
|
|
121
|
+
def update_project(self, update_function, updated_properties=None, on_completed=None):
|
|
122
|
+
"""function::update_project(self, update_function)
|
|
123
|
+
Calls the update_function to perform the desired updates
|
|
124
|
+
"""
|
|
125
|
+
self.dirty = True
|
|
126
|
+
result = update_function(None)
|
|
127
|
+
if updated_properties:
|
|
128
|
+
for prop in updated_properties:
|
|
129
|
+
self._on_project_updated(prop)
|
|
130
|
+
else:
|
|
131
|
+
self._on_project_updated(None)
|
|
132
|
+
if on_completed:
|
|
133
|
+
QApplication.postEvent(self, UpdateCompleteEvent(on_completed, result))
|
|
134
|
+
|
|
135
|
+
def _on_project_updated(self, prop):
|
|
136
|
+
QApplication.postEvent(self, PropertyUpdatedEvent(prop))
|
|
137
|
+
|
|
138
|
+
def event(self, event):
|
|
139
|
+
if isinstance(event, PropertyUpdatedEvent):
|
|
140
|
+
self.projectUpdated.emit(event.prop)
|
|
141
|
+
return True
|
|
142
|
+
if isinstance(event, UpdateCompleteEvent):
|
|
143
|
+
event.exec()
|
|
144
|
+
return True
|
|
145
|
+
if isinstance(event, UpdateEvent):
|
|
146
|
+
with self.update_lock:
|
|
147
|
+
event.exec()
|
|
148
|
+
return True
|
|
149
|
+
return super().event(event)
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# pyqttoolkit
|
|
2
|
+
# Copyright (C) 2018-2019, Simmovation Ltd.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, write to the Free Software Foundation,
|
|
16
|
+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
""":mod:`task_runner`
|
|
18
|
+
Defines the task runner
|
|
19
|
+
"""
|
|
20
|
+
import logging
|
|
21
|
+
import inspect
|
|
22
|
+
|
|
23
|
+
from PyQt5.QtCore import QObject, pyqtSignal, QEvent
|
|
24
|
+
from PyQt5.QtWidgets import QApplication
|
|
25
|
+
|
|
26
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
27
|
+
|
|
28
|
+
from ..properties import AutoProperty
|
|
29
|
+
from ..exceptions import TaskCancelled
|
|
30
|
+
|
|
31
|
+
LOGGER = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
class BusyArgs(QObject):
|
|
34
|
+
def __init__(self, busy, indeterminate=True, cancellable=False, description=None):
|
|
35
|
+
QObject.__init__(self, None)
|
|
36
|
+
self._busy = busy
|
|
37
|
+
self._indeterminate = indeterminate
|
|
38
|
+
self._cancellable = cancellable
|
|
39
|
+
self._description = description
|
|
40
|
+
self._progress = 0
|
|
41
|
+
|
|
42
|
+
cancelled = pyqtSignal()
|
|
43
|
+
cancelComplete = pyqtSignal()
|
|
44
|
+
progressChanged = pyqtSignal(float, str)
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def description(self):
|
|
48
|
+
return self._description
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def progress(self):
|
|
52
|
+
return self._progress
|
|
53
|
+
|
|
54
|
+
def updateProgress(self, progress, description):
|
|
55
|
+
self._progress = max(99, float(progress))
|
|
56
|
+
self._description = description
|
|
57
|
+
self.progressChanged.emit(progress, description)
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def busy(self):
|
|
61
|
+
return self._busy
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def indeterminate(self):
|
|
65
|
+
return self._indeterminate
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def cancellable(self):
|
|
69
|
+
return self._cancellable
|
|
70
|
+
|
|
71
|
+
def cancel(self):
|
|
72
|
+
if self._cancellable:
|
|
73
|
+
self.cancelled.emit()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class Worker:
|
|
77
|
+
def __init__(self, task_runner, f, args, busy_args, on_completed, on_cancelled, on_error, error_description):
|
|
78
|
+
self._task_runner = task_runner
|
|
79
|
+
self._f = f
|
|
80
|
+
self._args = args
|
|
81
|
+
self._busy_args = busy_args
|
|
82
|
+
self._on_completed = on_completed
|
|
83
|
+
self._on_cancelled = on_cancelled
|
|
84
|
+
self._on_error = on_error
|
|
85
|
+
self._error_description = error_description
|
|
86
|
+
|
|
87
|
+
def run(self):
|
|
88
|
+
if self._busy_args:
|
|
89
|
+
QApplication.postEvent(self._task_runner, BusyEvent(self._busy_args))
|
|
90
|
+
try:
|
|
91
|
+
result = self._f(*self._args.args, **self._args.kwargs)
|
|
92
|
+
except TaskCancelled:
|
|
93
|
+
QApplication.postEvent(self._task_runner, CancelledEvent(self._on_cancelled))
|
|
94
|
+
#pylint: disable=broad-except
|
|
95
|
+
except Exception as e:
|
|
96
|
+
QApplication.postEvent(self._task_runner, ErrorEvent(e, self._on_error, self._error_description))
|
|
97
|
+
#pylint: enable=broad-except
|
|
98
|
+
else:
|
|
99
|
+
QApplication.postEvent(self._task_runner, ResultEvent(result, self._on_completed))
|
|
100
|
+
|
|
101
|
+
class ProgressUpdatedEvent(QEvent):
|
|
102
|
+
def __init__(self, progress, message):
|
|
103
|
+
super().__init__(QEvent.User)
|
|
104
|
+
self._progress = progress
|
|
105
|
+
self._message = message
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def progress(self):
|
|
109
|
+
return self._progress
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def message(self):
|
|
113
|
+
return self._message
|
|
114
|
+
|
|
115
|
+
class CancelledEvent(QEvent):
|
|
116
|
+
def __init__(self, on_cancelled):
|
|
117
|
+
super().__init__(QEvent.User)
|
|
118
|
+
self._on_cancelled = on_cancelled
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def on_cancelled(self):
|
|
122
|
+
return self._on_cancelled
|
|
123
|
+
|
|
124
|
+
class ResultEvent(QEvent):
|
|
125
|
+
def __init__(self, result, on_completed=None):
|
|
126
|
+
super().__init__(QEvent.User)
|
|
127
|
+
self._result = result
|
|
128
|
+
self._on_completed = on_completed
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def result(self):
|
|
132
|
+
return self._result
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def on_completed(self):
|
|
136
|
+
return self._on_completed
|
|
137
|
+
|
|
138
|
+
class ErrorEvent(QEvent):
|
|
139
|
+
def __init__(self, exception, on_error, error_description):
|
|
140
|
+
super().__init__(QEvent.User)
|
|
141
|
+
self._exception = exception
|
|
142
|
+
self._on_error = on_error
|
|
143
|
+
self._error_description = error_description
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def exception(self):
|
|
147
|
+
return self._exception
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def on_error(self):
|
|
151
|
+
return self._on_error
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def error_description(self):
|
|
155
|
+
return self._error_description
|
|
156
|
+
|
|
157
|
+
class BusyEvent(QEvent):
|
|
158
|
+
def __init__(self, busy_args):
|
|
159
|
+
super().__init__(QEvent.User)
|
|
160
|
+
self._busy_args = busy_args
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def args(self):
|
|
164
|
+
return self._busy_args
|
|
165
|
+
|
|
166
|
+
class TaskArgs:
|
|
167
|
+
def __init__(self, args, kwargs):
|
|
168
|
+
self.args = args
|
|
169
|
+
self.kwargs = kwargs
|
|
170
|
+
|
|
171
|
+
class TaskRunner(QObject):
|
|
172
|
+
"""class::TaskRunner
|
|
173
|
+
Runs tasks on a background thread
|
|
174
|
+
"""
|
|
175
|
+
def __init__(self, parent):
|
|
176
|
+
QObject.__init__(self, parent)
|
|
177
|
+
self._is_cancelled = False
|
|
178
|
+
self._executor = ThreadPoolExecutor(max_workers=1, thread_name_prefix='TaskRunner_Thread')
|
|
179
|
+
|
|
180
|
+
def shutdown(self, wait=True):
|
|
181
|
+
# Tests and short-lived apps need an explicit way to join worker
|
|
182
|
+
# threads before QApplication teardown.
|
|
183
|
+
self._executor.shutdown(wait=wait)
|
|
184
|
+
|
|
185
|
+
def run_task(self, task_function, task_args=None, on_completed=None, on_cancelled=None, on_error=None, description=None, error_description=None, show_progress=True, cancellable=False, force_indeterminate_start=False, **kwargs):
|
|
186
|
+
"""function::runTask(self, task_function, task_args, on_completed, on_error)
|
|
187
|
+
:param task_function: The function to execute
|
|
188
|
+
:param task_args: The arguments to pass to the function
|
|
189
|
+
:param on_completed: The function to execute when the task completes
|
|
190
|
+
:param on_error: The function to execute when the task errors
|
|
191
|
+
"""
|
|
192
|
+
LOGGER.info('Starting task "%s"...', description)
|
|
193
|
+
task_args = task_args or []
|
|
194
|
+
|
|
195
|
+
kwargs = {}
|
|
196
|
+
|
|
197
|
+
parameters = inspect.signature(task_function).parameters
|
|
198
|
+
|
|
199
|
+
if 'update_progress' in parameters:
|
|
200
|
+
kwargs['update_progress'] = self.update_progress
|
|
201
|
+
indeterminate = force_indeterminate_start
|
|
202
|
+
else:
|
|
203
|
+
indeterminate = True
|
|
204
|
+
|
|
205
|
+
busy_args = BusyArgs(True, indeterminate, cancellable, description) if show_progress else None
|
|
206
|
+
|
|
207
|
+
worker = Worker(
|
|
208
|
+
self, task_function, TaskArgs(task_args, kwargs),
|
|
209
|
+
busy_args=busy_args, on_completed=on_completed,
|
|
210
|
+
on_cancelled=on_cancelled, on_error=on_error,
|
|
211
|
+
error_description=error_description
|
|
212
|
+
)
|
|
213
|
+
self._executor.submit(worker.run)
|
|
214
|
+
|
|
215
|
+
def update_progress(self, percent, message):
|
|
216
|
+
QApplication.postEvent(self, ProgressUpdatedEvent(float(percent), message))
|
|
217
|
+
|
|
218
|
+
def event(self, event):
|
|
219
|
+
if isinstance(event, ProgressUpdatedEvent):
|
|
220
|
+
if self.busy is not None:
|
|
221
|
+
self.busy.updateProgress(float(event.progress), event.message)
|
|
222
|
+
return True
|
|
223
|
+
elif isinstance(event, BusyEvent):
|
|
224
|
+
self.busy = event.args
|
|
225
|
+
self.busy.cancelled.connect(self.cancel)
|
|
226
|
+
return True
|
|
227
|
+
elif isinstance(event, ResultEvent):
|
|
228
|
+
LOGGER.info('Task complete')
|
|
229
|
+
self.reset()
|
|
230
|
+
if event.on_completed is not None:
|
|
231
|
+
event.on_completed(event.result)
|
|
232
|
+
self.taskCompleted.emit()
|
|
233
|
+
return True
|
|
234
|
+
elif isinstance(event, CancelledEvent):
|
|
235
|
+
LOGGER.info('Task cancelled')
|
|
236
|
+
self.reset()
|
|
237
|
+
self.busy.cancelComplete.emit()
|
|
238
|
+
self.taskCancelled.emit()
|
|
239
|
+
if event.on_cancelled is not None:
|
|
240
|
+
event.on_cancelled()
|
|
241
|
+
return True
|
|
242
|
+
elif isinstance(event, ErrorEvent):
|
|
243
|
+
LOGGER.info('Task error')
|
|
244
|
+
self.reset()
|
|
245
|
+
LOGGER.info('Error running task: %s', event.exception)
|
|
246
|
+
if event.error_description is not None:
|
|
247
|
+
self.error.emit(event.error_description)
|
|
248
|
+
self.taskErrored.emit()
|
|
249
|
+
elif event.on_error is not None:
|
|
250
|
+
event.on_error(event.exception)
|
|
251
|
+
self.taskErrored.emit()
|
|
252
|
+
else:
|
|
253
|
+
raise event.exception
|
|
254
|
+
return True
|
|
255
|
+
return super().event(event)
|
|
256
|
+
|
|
257
|
+
def cancel(self):
|
|
258
|
+
if not self._is_cancelled:
|
|
259
|
+
if self.busy:
|
|
260
|
+
self.busy.updateProgress(self.busy.progress, self.tr('Cancelling...'))
|
|
261
|
+
self._is_cancelled = True
|
|
262
|
+
|
|
263
|
+
def isCancelled(self):
|
|
264
|
+
return self._is_cancelled
|
|
265
|
+
|
|
266
|
+
def raiseForCancelled(self):
|
|
267
|
+
if self._is_cancelled:
|
|
268
|
+
raise TaskCancelled()
|
|
269
|
+
|
|
270
|
+
def reset(self):
|
|
271
|
+
self.busy = BusyArgs(False, description=None)
|
|
272
|
+
self._is_cancelled = False
|
|
273
|
+
|
|
274
|
+
busyChanged = pyqtSignal(BusyArgs)
|
|
275
|
+
error = pyqtSignal(str)
|
|
276
|
+
cancelComplete = pyqtSignal()
|
|
277
|
+
taskCompleted = pyqtSignal()
|
|
278
|
+
taskCancelled = pyqtSignal()
|
|
279
|
+
taskErrored = pyqtSignal()
|
|
280
|
+
|
|
281
|
+
busy = AutoProperty(BusyArgs)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# pyqttoolkit
|
|
2
|
+
# Copyright (C) 2018-2019, Simmovation Ltd.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, write to the Free Software Foundation,
|
|
16
|
+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
class ThemeManager:
|
|
18
|
+
default_theme = {
|
|
19
|
+
'button_foreground': (0, 0, 0),
|
|
20
|
+
'button_background_hover': (255, 255, 255),
|
|
21
|
+
'highlight': (255, 96, 154),
|
|
22
|
+
'highlight_hover': (255, 163, 196),
|
|
23
|
+
'highlight_pressed': (255, 147, 186),
|
|
24
|
+
'highlight': (255, 96, 154),
|
|
25
|
+
'module_background': (200, 200, 200),
|
|
26
|
+
'module_text': (0, 0, 0)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
def __init__(self, application_configuration):
|
|
30
|
+
self._config = application_configuration
|
|
31
|
+
|
|
32
|
+
def get_color(self, key):
|
|
33
|
+
selected_theme = self._get_selected_theme()
|
|
34
|
+
color = self._config.get_value(f'application.themes.{selected_theme}.{key}') if selected_theme else None
|
|
35
|
+
return tuple(color) if color else self.default_theme.get(key)
|
|
36
|
+
|
|
37
|
+
def _get_selected_theme(self):
|
|
38
|
+
return self._config.get_value(f'application.themes.selected_theme') or None
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def get(widget):
|
|
42
|
+
while widget is not None and not hasattr(widget, 'themeManager'):
|
|
43
|
+
widget = widget.parent()
|
|
44
|
+
if widget is not None and hasattr(widget, 'themeManager'):
|
|
45
|
+
return widget.themeManager
|
|
46
|
+
return None
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# pyqttoolkit
|
|
2
|
+
# Copyright (C) 2018-2019, Simmovation Ltd.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, write to the Free Software Foundation,
|
|
16
|
+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
|
|
18
|
+
from PyQt5.QtWidgets import QWidget
|
|
19
|
+
|
|
20
|
+
from pyqttoolkit.views import ToolWindow
|
|
21
|
+
from pyqttoolkit.viewmanagers import ToolViewManager
|
|
22
|
+
|
|
23
|
+
class ToolWindowService:
|
|
24
|
+
def __init__(self, dependency_contianer):
|
|
25
|
+
self._dependency_container = dependency_contianer
|
|
26
|
+
self._active_tools = []
|
|
27
|
+
|
|
28
|
+
def open(self, parent, model, name, *args, extras=None):
|
|
29
|
+
extras = {'parent': None, **(extras or {})}
|
|
30
|
+
view = self._dependency_container.resolve_for(type(model), QWidget, extras, *args)
|
|
31
|
+
if hasattr(model, 'showButtons'):
|
|
32
|
+
show_buttons = model.showButtons
|
|
33
|
+
else:
|
|
34
|
+
show_buttons = True
|
|
35
|
+
|
|
36
|
+
tool_window = ToolWindow(parent, view, name, show_buttons)
|
|
37
|
+
view_manager = self._dependency_container.resolve_for(type(model), ToolViewManager, {'view': view, 'model': model})
|
|
38
|
+
tool_window.accepted.connect(self._tool_window_accepted(tool_window, view_manager))
|
|
39
|
+
self._active_tools.append((tool_window, view_manager))
|
|
40
|
+
tool_window.show()
|
|
41
|
+
|
|
42
|
+
def _tool_window_accepted(self, tool_window, view_manager):
|
|
43
|
+
def _handler():
|
|
44
|
+
view_manager.save()
|
|
45
|
+
self._active_tools.remove((tool_window, view_manager))
|
|
46
|
+
return _handler
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# pyqttoolkit
|
|
2
|
+
# Copyright (C) 2018-2019, Simmovation Ltd.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, write to the Free Software Foundation,
|
|
16
|
+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
from .tool import ToolViewManager
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# pyqttoolkit
|
|
2
|
+
# Copyright (C) 2018-2019, Simmovation Ltd.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, write to the Free Software Foundation,
|
|
16
|
+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
class ToolViewManager:
|
|
18
|
+
def __init__(self, view, model):
|
|
19
|
+
self._view = view
|
|
20
|
+
self._model = model
|
|
21
|
+
|
|
22
|
+
def save(self):
|
|
23
|
+
raise NotImplementedError()
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# pyqttoolkit
|
|
2
|
+
# Copyright (C) 2018-2019, Simmovation Ltd.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, write to the Free Software Foundation,
|
|
16
|
+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
""":mod:`components`
|
|
18
|
+
Components module
|
|
19
|
+
"""
|
|
20
|
+
from .modulewindow import ModuleWindow
|
|
21
|
+
from .editable_combo_box import EditableComboBox
|
|
22
|
+
from .shrunk_push_button import ShrunkPushButton, VShrunkPushButton
|
|
23
|
+
from .tool_window import ToolWindow
|
|
24
|
+
from .combo_box import ComboBox, BindableComboBox, typed_bindable_combo_box
|
|
25
|
+
from .steppable_combo_box import SteppableComboBox
|
|
26
|
+
from .linkable import LinkableWidget
|
|
27
|
+
from .datetime import DateTimeEdit
|
|
28
|
+
from .line_edit import LineEdit, BindableLineEdit
|
|
29
|
+
from .table import TableView
|
|
30
|
+
from .property_tree import PropertyTreeView
|
|
31
|
+
from .json_edit import JsonEdit
|
|
32
|
+
from .file_selector import FileSelector
|
|
33
|
+
from .code import CodeEdit, PythonCodeEdit
|
|
34
|
+
from .text_edit import TextEdit
|
|
35
|
+
from .metadata_selector import MetadataSelector, MetadataSelectorDropDown
|
|
36
|
+
from .default_text_line_edit import DefaultTextLineEdit
|
|
37
|
+
from .icon_button import IconButton, BindableIconButton
|
|
38
|
+
from .main_window import MainWindow
|
|
39
|
+
from .icon import Icon
|
|
40
|
+
from .dtype_edit import FloatEdit, IntEdit, InfFloatLineEdit, AutoFloatLineEdit
|
|
41
|
+
from .check_box import BindableCheckBox
|
|
42
|
+
from .bulk_value_selector import BulkValueSelectorWidget
|
|
43
|
+
from .datetime_range_selector import DatetimeRangeSelectorWidget
|
|
44
|
+
from .count_selector import CountSelectorWidget
|
|
45
|
+
from .list_view import ListView
|
|
46
|
+
from .styleable import make_styleable
|
|
47
|
+
from .popout import PopoutWidget
|
|
48
|
+
from .popup import Popup
|
|
49
|
+
from .delegates import ComboBoxItemDelegate, DateTimeItemDelegate, BulkValueSelectorItemDelegate
|
|
50
|
+
from .labelled import make_labelled
|