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.
Files changed (133) hide show
  1. pyqttoolkit/__init__.py +20 -0
  2. pyqttoolkit/_version.py +21 -0
  3. pyqttoolkit/application/__init__.py +18 -0
  4. pyqttoolkit/application/application.py +86 -0
  5. pyqttoolkit/application/garbage_collector.py +65 -0
  6. pyqttoolkit/colors/__init__.py +84 -0
  7. pyqttoolkit/config/__init__.py +20 -0
  8. pyqttoolkit/config/base.py +22 -0
  9. pyqttoolkit/config/composite.py +31 -0
  10. pyqttoolkit/config/default.py +27 -0
  11. pyqttoolkit/config/environment.py +35 -0
  12. pyqttoolkit/config/multi_json_file.py +93 -0
  13. pyqttoolkit/config/with_signal.py +30 -0
  14. pyqttoolkit/config/with_value_changed.py +31 -0
  15. pyqttoolkit/data/__init__.py +21 -0
  16. pyqttoolkit/data/bits.py +23 -0
  17. pyqttoolkit/data/cache.py +64 -0
  18. pyqttoolkit/data/interval.py +26 -0
  19. pyqttoolkit/data/message.py +29 -0
  20. pyqttoolkit/data/names.py +24 -0
  21. pyqttoolkit/datetime/__init__.py +16 -0
  22. pyqttoolkit/datetime/datetime.py +51 -0
  23. pyqttoolkit/datetime/qdatetime.py +53 -0
  24. pyqttoolkit/dependencies/__init__.py +56 -0
  25. pyqttoolkit/errors/__init__.py +19 -0
  26. pyqttoolkit/exceptions/__init__.py +18 -0
  27. pyqttoolkit/filesystem/__init__.py +24 -0
  28. pyqttoolkit/logs/__init__.py +140 -0
  29. pyqttoolkit/metadata/__init__.py +23 -0
  30. pyqttoolkit/metadata/dataframe_metadata_manager.py +105 -0
  31. pyqttoolkit/metadata/dict_metadata_manager.py +63 -0
  32. pyqttoolkit/metadata/id_generator.py +20 -0
  33. pyqttoolkit/metadata/metadata_manager_base.py +54 -0
  34. pyqttoolkit/models/__init__.py +26 -0
  35. pyqttoolkit/models/code_text.py +43 -0
  36. pyqttoolkit/models/dataframetable.py +135 -0
  37. pyqttoolkit/models/dictpropertytree.py +155 -0
  38. pyqttoolkit/models/dicttable.py +140 -0
  39. pyqttoolkit/models/json_text.py +52 -0
  40. pyqttoolkit/models/list_model.py +52 -0
  41. pyqttoolkit/models/module.py +26 -0
  42. pyqttoolkit/models/roles.py +23 -0
  43. pyqttoolkit/models/series_data.py +68 -0
  44. pyqttoolkit/models/span.py +36 -0
  45. pyqttoolkit/models/validation.py +27 -0
  46. pyqttoolkit/modules/__init__.py +18 -0
  47. pyqttoolkit/modules/events/__init__.py +19 -0
  48. pyqttoolkit/modules/events/add_signal.py +31 -0
  49. pyqttoolkit/modules/events/module_opening.py +21 -0
  50. pyqttoolkit/modules/events/rejectable.py +29 -0
  51. pyqttoolkit/modules/launcher_config.py +55 -0
  52. pyqttoolkit/modules/module.py +111 -0
  53. pyqttoolkit/properties/__init__.py +18 -0
  54. pyqttoolkit/properties/auto.py +221 -0
  55. pyqttoolkit/properties/throttled.py +77 -0
  56. pyqttoolkit/scripting/__init__.py +19 -0
  57. pyqttoolkit/scripting/script_context.py +36 -0
  58. pyqttoolkit/scripting/script_runner.py +132 -0
  59. pyqttoolkit/scripting/script_template.py +42 -0
  60. pyqttoolkit/services/__init__.py +32 -0
  61. pyqttoolkit/services/autosave.py +41 -0
  62. pyqttoolkit/services/email.py +25 -0
  63. pyqttoolkit/services/event_registry.py +52 -0
  64. pyqttoolkit/services/file_dialog.py +71 -0
  65. pyqttoolkit/services/hotkey_manager.py +137 -0
  66. pyqttoolkit/services/link_manager.py +135 -0
  67. pyqttoolkit/services/message_board.py +90 -0
  68. pyqttoolkit/services/module_service.py +154 -0
  69. pyqttoolkit/services/object_converter.py +45 -0
  70. pyqttoolkit/services/project_updater.py +149 -0
  71. pyqttoolkit/services/task_runner.py +281 -0
  72. pyqttoolkit/services/theme_manager.py +46 -0
  73. pyqttoolkit/services/tool_window_service.py +46 -0
  74. pyqttoolkit/viewmanagers/__init__.py +17 -0
  75. pyqttoolkit/viewmanagers/tool.py +23 -0
  76. pyqttoolkit/views/__init__.py +50 -0
  77. pyqttoolkit/views/bulk_value_selector.py +112 -0
  78. pyqttoolkit/views/check_box.py +36 -0
  79. pyqttoolkit/views/code/__init__.py +18 -0
  80. pyqttoolkit/views/code/code_edit.py +268 -0
  81. pyqttoolkit/views/code/python_syntax.py +226 -0
  82. pyqttoolkit/views/combo_box.py +101 -0
  83. pyqttoolkit/views/count_selector.py +59 -0
  84. pyqttoolkit/views/datetime.py +111 -0
  85. pyqttoolkit/views/datetime_range_selector.py +231 -0
  86. pyqttoolkit/views/default_text_line_edit.py +50 -0
  87. pyqttoolkit/views/delegates.py +117 -0
  88. pyqttoolkit/views/dtype_edit.py +117 -0
  89. pyqttoolkit/views/editable_combo_box.py +45 -0
  90. pyqttoolkit/views/file_selector.py +51 -0
  91. pyqttoolkit/views/icon.py +67 -0
  92. pyqttoolkit/views/icon_button.py +129 -0
  93. pyqttoolkit/views/json_edit.py +21 -0
  94. pyqttoolkit/views/labelled.py +29 -0
  95. pyqttoolkit/views/layout/__init__.py +21 -0
  96. pyqttoolkit/views/layout/collabsible.py +59 -0
  97. pyqttoolkit/views/layout/group_box.py +48 -0
  98. pyqttoolkit/views/layout/group_panel.py +188 -0
  99. pyqttoolkit/views/layout/lines.py +30 -0
  100. pyqttoolkit/views/layout/link.py +38 -0
  101. pyqttoolkit/views/line_edit.py +61 -0
  102. pyqttoolkit/views/linkable.py +49 -0
  103. pyqttoolkit/views/list_view.py +43 -0
  104. pyqttoolkit/views/main_window.py +91 -0
  105. pyqttoolkit/views/metadata_selector.py +84 -0
  106. pyqttoolkit/views/modulewindow.py +81 -0
  107. pyqttoolkit/views/plot/__init__.py +26 -0
  108. pyqttoolkit/views/plot/legend_control.py +121 -0
  109. pyqttoolkit/views/plot/matplotlib/__init__.py +23 -0
  110. pyqttoolkit/views/plot/matplotlib/base.py +1056 -0
  111. pyqttoolkit/views/plot/matplotlib/colorbar.py +75 -0
  112. pyqttoolkit/views/plot/matplotlib/colormap.py +48 -0
  113. pyqttoolkit/views/plot/matplotlib/font.py +55 -0
  114. pyqttoolkit/views/plot/plot_options.py +211 -0
  115. pyqttoolkit/views/plot/plot_toolbar_options.py +133 -0
  116. pyqttoolkit/views/plot/size.py +41 -0
  117. pyqttoolkit/views/plot/tool_type.py +26 -0
  118. pyqttoolkit/views/plot/toolbar.py +100 -0
  119. pyqttoolkit/views/popout.py +41 -0
  120. pyqttoolkit/views/popup.py +41 -0
  121. pyqttoolkit/views/property_tree.py +26 -0
  122. pyqttoolkit/views/shrunk_push_button.py +34 -0
  123. pyqttoolkit/views/steppable_combo_box.py +67 -0
  124. pyqttoolkit/views/styleable.py +37 -0
  125. pyqttoolkit/views/table.py +146 -0
  126. pyqttoolkit/views/text_edit.py +31 -0
  127. pyqttoolkit/views/tool_window.py +35 -0
  128. pyqttoolkit-0.9.1.dist-info/METADATA +69 -0
  129. pyqttoolkit-0.9.1.dist-info/RECORD +133 -0
  130. pyqttoolkit-0.9.1.dist-info/WHEEL +5 -0
  131. pyqttoolkit-0.9.1.dist-info/licenses/LICENSE.txt +674 -0
  132. pyqttoolkit-0.9.1.dist-info/licenses/LICENSE_SHORT +16 -0
  133. pyqttoolkit-0.9.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,221 @@
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:`toolkit`
18
+ Defines various utils useful in views
19
+ """
20
+ import logging
21
+ from math import isnan
22
+ from weakref import ref, WeakKeyDictionary
23
+
24
+ from pyqttoolkit.logs import TRACE
25
+
26
+ LOGGER = logging.getLogger(__name__)
27
+
28
+ def updater(obj, name):
29
+ """function::updater(obj, name)
30
+ Higher order function which returns a function to update the property
31
+ specified by name in the object specified by obj
32
+ """
33
+ def _update_value(value):
34
+ setattr(obj, name, value)
35
+ return _update_value
36
+
37
+ def one_way_bind(src, dest, src_name, dest_name=None):
38
+ """function::one_way_bind(src, dest, src_name, [dest_name])
39
+ Propagate changes in src to dest, and set dest to value of src
40
+ """
41
+ dest_name = dest_name or src_name
42
+ src_signal = getattr(type(src), AutoProperty.get_notify_property_name(src_name))
43
+ dest_prop = getattr(type(dest), dest_name)
44
+ src_signal.__get__(src, type(src)).connect(dest_prop.updater(dest))
45
+ src_value = getattr(src, src_name)
46
+ setattr(dest, dest_name, src_value)
47
+ return updater
48
+
49
+ def bind(src, dest, src_name, dest_name=None, two_way=True):
50
+ """function::bind(src, dest, src_name, [dest_name[, two_way]])
51
+ Propagate changes in src to dest, and set dest to value of src
52
+ Optionally bind dest to src
53
+ """
54
+ dest_name = dest_name or src_name
55
+ one_way_bind(src, dest, src_name, dest_name)
56
+ if two_way:
57
+ one_way_bind(dest, src, dest_name, src_name)
58
+
59
+ def one_way_unbind(src, dest, src_name, dest_name=None):
60
+ dest_name = dest_name or src_name
61
+ src_signal = getattr(type(src), AutoProperty.get_notify_property_name(src_name))
62
+ dest_prop = getattr(type(dest), dest_name)
63
+ src_signal.__get__(src, type(src)).disconnect(dest_prop.updater(dest))
64
+
65
+ def unbind(src, dest, src_name, dest_name=None, two_way=True):
66
+ dest_name = dest_name or src_name
67
+ one_way_unbind(src, dest, src_name, dest_name)
68
+ if two_way:
69
+ one_way_unbind(dest, src, dest_name, src_name)
70
+
71
+ def auto_property(type_, *args, **kwargs):
72
+ """function::auto_property(type, *args, **kwargs)
73
+ Decorator function for AutoProperty, creates an AutoProperty with the given getter
74
+ """
75
+ def _(fget):
76
+ return AutoProperty(type_=type_, fget=fget, *args, **kwargs)
77
+ return _
78
+
79
+ def values_equal(left, right):
80
+ return left == right or (isinstance(left, float) and isinstance(right, float) and isnan(left) and isnan(right))
81
+
82
+ def connect_all(handler, *signals):
83
+ for signal in signals:
84
+ signal.connect(handler)
85
+
86
+ def disconnect_all(handler, *signals):
87
+ for signal in signals:
88
+ signal.disconnect(handler)
89
+
90
+ #pylint: disable=too-many-arguments
91
+ class AutoProperty:
92
+ """class::AutoProperty
93
+ Descriptor which provides property and signal functionality to objects
94
+ """
95
+ native_types = [list, str, int, float, bool]
96
+ def __init__(self, type_, fget=None, fset=None, fdel=None, default_value=None, notify=None):
97
+ self._type = type_
98
+ self._default_value = default_value
99
+ self._notify = notify
100
+ self._name = None
101
+ self._fget = fget
102
+ self._fset = fset
103
+ self._fdel = fdel
104
+ self._updaters = WeakKeyDictionary()
105
+ self._notify_depth = 0
106
+ self._max_notify_depth = 5
107
+
108
+ def __set_name__(self, obj, name):
109
+ self._name = name
110
+ if not self._notify and self._has_notify_property(obj):
111
+ self._notify = self._get_notify_property(obj)
112
+
113
+ def __get__(self, obj, type_=None):
114
+ if obj is None:
115
+ return self
116
+ if not self._name:
117
+ raise ValueError('Cannot get property, name has not been set!')
118
+ if self._uses_delegates():
119
+ if not self._fget:
120
+ raise ValueError(f'No getter provided for property {self._name}')
121
+ return self._fget(obj)
122
+ elif self._name not in obj.__dict__:
123
+ return self._default_value
124
+ else:
125
+ return obj.__dict__[self._name]
126
+
127
+ def __set__(self, obj, value):
128
+ if obj is None:
129
+ return
130
+ if not self._name:
131
+ raise ValueError('Cannot set property, name has not been set!')
132
+ if not self._uses_delegates() and not self._notify:
133
+ raise ValueError(f'Cannot set property {self._name}, unable to notify!')
134
+ if value is not None and self._type and not isinstance(value, self._type):
135
+ value_type = type(value)
136
+ raise ValueError(
137
+ f'Invalid type {value_type} provided for property {self._name}: '
138
+ f'must be of type {self._type}'
139
+ )
140
+ if self._uses_delegates():
141
+ if not self._fset:
142
+ raise ValueError(f'No setter provided for property {self._name}')
143
+ self._fset(obj, value)
144
+ elif self._name not in obj.__dict__ or not values_equal(obj.__dict__[self._name], value):
145
+ LOGGER.log(TRACE, 'Setting %s.%s to %s', obj, self._name, value)
146
+ obj.__dict__[self._name] = value
147
+ if self._notify_depth == self._max_notify_depth:
148
+ LOGGER.error('Maximum notify depth exceeded for %s.%s', obj, self._name)
149
+ if self._notify and self._notify_depth <= self._max_notify_depth:
150
+ self._notify_depth += 1
151
+ LOGGER.log(TRACE, 'Notifying change of %s.%s to %s', obj, self._name, value)
152
+ self._notify.__get__(obj, type(obj)).emit(value)
153
+ self._notify_depth -= 1
154
+
155
+ def __delete__(self, obj):
156
+ if not self._name:
157
+ raise ValueError('Cannot delete property, name has not been set!')
158
+ if self._uses_delegates():
159
+ if not self._fdel:
160
+ raise ValueError(f'No deleter provided for property {self._name}')
161
+ self._fdel(obj)
162
+ else:
163
+ del obj.__dict__[self._name]
164
+
165
+ def updater(self, obj, outer_value=None):
166
+ """function::updater(self, obj, outer_value=None)
167
+ Returns a function that can be used to update with the specified outer_value
168
+ If outer_value is not specified, updates with the inner_value passed to the inner function
169
+ """
170
+ if not obj in self._updaters:
171
+ obj_ref = ref(obj)
172
+ def _(inner_value=None):
173
+ self.__set__(obj_ref(), outer_value or inner_value)
174
+ self._updaters[obj] = _
175
+ return self._updaters[obj]
176
+
177
+ def getter(self, fget):
178
+ """function::getter(self, fget)
179
+ Function decorator to create a getter
180
+ Returns a copy of this AutoProperty with fget as the getter
181
+ """
182
+ return type(self)(
183
+ self._type, fget, self._fset, self._fdel,
184
+ self._default_value, self._notify
185
+ )
186
+
187
+ def setter(self, fset):
188
+ """function::setter(self, fset)
189
+ Function decorator to create a setter
190
+ Returns a copy of this AutoProperty with fset as the setter
191
+ """
192
+ return type(self)(
193
+ self._type, self._fget, fset, self._fdel,
194
+ self._default_value, self._notify
195
+ )
196
+
197
+ def deleter(self, fdel):
198
+ """function::deleter(self, fdel)
199
+ Function decorator to create a deleter
200
+ Returns a copy of this AutoProperty with fdel as the deleter
201
+ """
202
+ return type(self)(
203
+ self._type, self._fget, self._fset, fdel,
204
+ self._default_value, self._notify
205
+ )
206
+
207
+ def _get_notify_property(self, obj):
208
+ return getattr(obj, self.get_notify_property_name(self._name))
209
+
210
+ def _has_notify_property(self, obj):
211
+ return hasattr(obj, self.get_notify_property_name(self._name))
212
+
213
+ def _uses_delegates(self):
214
+ return self._fget or self._fset or self._fdel
215
+
216
+ @staticmethod
217
+ def get_notify_property_name(name):
218
+ """function::get_notify_property_name(name)
219
+ Returns the name of the notify property
220
+ """
221
+ return f'{name}Changed'
@@ -0,0 +1,77 @@
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
+ import time
18
+ from inspect import signature
19
+
20
+ #pylint: disable=no-name-in-module
21
+ from PyQt5.QtCore import QTimer
22
+ #pylint: enable=no-name-in-module
23
+
24
+ def arg_count(f):
25
+ return len(signature(f).parameters)
26
+
27
+ class ThrottledBoundSignal:
28
+ def __init__(self, *types, interval=100):
29
+ self._types = types
30
+ self._interval = interval
31
+ self._functions = []
32
+ self._timer = None
33
+ self._last_event_time = 0
34
+ self._current_values = None
35
+
36
+ def emit(self, *values):
37
+ self._current_values = values
38
+ time_since_last_event = time.clock() - self._last_event_time
39
+ if self._timer is None and time_since_last_event >= (self._interval / 1000):
40
+ self._emit_now()
41
+ return
42
+ if self._timer is None:
43
+ self._timer = QTimer()
44
+ self._timer.setSingleShot(True)
45
+ self._timer.timeout.connect(self._emit_now)
46
+ self._timer.start(self._interval)
47
+
48
+ def _emit_now(self):
49
+ for f in self._functions:
50
+ f(*self._current_values[:arg_count(f)])
51
+ self._last_event_time = time.clock()
52
+ self._current_values = None
53
+ self._timer = None
54
+
55
+ def connect(self, f):
56
+ self._functions.append(f)
57
+
58
+ def disconnect(self, f):
59
+ self._functions.remove(f)
60
+
61
+ class ThrottledSignal:
62
+ def __init__(self, *types, interval=1000):
63
+ self._name = None
64
+ self._types = types
65
+ self._interval = interval
66
+
67
+ def __set_name__(self, obj, name):
68
+ self._name = name
69
+
70
+ def __get__(self, obj, type_=None):
71
+ if obj is None:
72
+ return self
73
+ if not self._name:
74
+ raise ValueError('Cannot get property, name has not been set!')
75
+ if self._name not in obj.__dict__:
76
+ obj.__dict__[self._name] = ThrottledBoundSignal(*self._types, interval=self._interval)
77
+ return obj.__dict__[self._name]
@@ -0,0 +1,19 @@
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 .script_runner import ScriptRunner
18
+ from .script_context import ScriptContext
19
+ from .script_template import ScriptTemplate
@@ -0,0 +1,36 @@
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 ScriptContext:
18
+ def __init__(self, **kwargs):
19
+ self._locals = kwargs
20
+ self._result = {'value': None}
21
+ self._output = ''
22
+
23
+ @property
24
+ def locals(self):
25
+ return {**self._locals, 'result': self._result}
26
+
27
+ @property
28
+ def result(self):
29
+ return self._result['value']
30
+
31
+ @property
32
+ def output(self):
33
+ return self._output
34
+
35
+ def add_output(self, value):
36
+ self._output += value
@@ -0,0 +1,132 @@
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 contextlib import contextmanager
18
+ from functools import partial
19
+ import re
20
+ import builtins
21
+ import sys
22
+ import importlib
23
+
24
+ @contextmanager
25
+ def add_paths(paths):
26
+ for path in paths:
27
+ sys.path.insert(0, path)
28
+ try:
29
+ yield
30
+ finally:
31
+ for path in paths:
32
+ sys.path.remove(path)
33
+
34
+ class ContextWriter:
35
+ def __init__(self, context, skip_empty_output_lines=False):
36
+ self._context = context
37
+ self._skip_empty_output_lines = skip_empty_output_lines
38
+
39
+ def write(self, value):
40
+ if (value is not None and value.strip()) or not self._skip_empty_output_lines:
41
+ self._context.add_output(value)
42
+
43
+ @contextmanager
44
+ def redirect_output(context, skip_empty_output_lines=False):
45
+ writer = ContextWriter(context, skip_empty_output_lines)
46
+ _stdout = sys.stdout
47
+ sys.stdout = writer
48
+ try:
49
+ yield
50
+ finally:
51
+ sys.stdout = _stdout
52
+
53
+ class ScriptStopped(Exception):
54
+ def __init__(self):
55
+ Exception.__init__(self)
56
+
57
+ class ScriptRunner:
58
+ def __init__(self, additional_paths=None, skip_empty_output_lines=False):
59
+ self._disallowed_modules = [
60
+ r'\binspect\b', r'\bimp\b', r'\bsys\b', r'\b\.', r'\bbuiltins\b'
61
+ ]
62
+ self._disallowed_globals = [
63
+ '_', 'quit', 'eval', 'exec', 'help'
64
+ ]
65
+ self._additional_paths = additional_paths or []
66
+ self._context = None
67
+ self._skip_empty_output_lines = skip_empty_output_lines
68
+
69
+ def run(self, script, context):
70
+ script = re.sub(r'(?<!\w)result(?!\w)', 'result[\'value\']', script)
71
+ code = self.compile(script)
72
+ try:
73
+ self._context = context
74
+ with redirect_output(self._context, self._skip_empty_output_lines):
75
+ # Use one namespace so functions defined by the script can
76
+ # resolve each other recursively through their globals. Add
77
+ # runner globals last so context locals cannot replace
78
+ # protected entries such as __builtins__.
79
+ namespace = {**context.locals, **self._globals(context)}
80
+ #pylint: disable=exec-used
81
+ exec(code, namespace, namespace)
82
+ #pylint: enable=exec-used
83
+ except ScriptStopped:
84
+ pass
85
+ finally:
86
+ self._context = None
87
+
88
+ def _globals(self, context):
89
+ return {'__builtins__': {**__builtins__, '__import__': self._import(builtins.__import__), 'exit': self._exit, **{g: self._not_implemented for g in self._disallowed_globals}}}
90
+
91
+ def compile(self, script):
92
+ return compile(script, 'code', 'exec')
93
+
94
+ def _patch_plt_show(self, module):
95
+ def plt_show_as_show_figure(plt, block=None):
96
+ # Block is not used in the context of the script runner
97
+ for no, label in zip(plt.get_fignums(), plt.get_figlabels()):
98
+ self._context.show_figure(plt.figure(no), str(label))
99
+ plt.close('all')
100
+ if module.__name__ == 'matplotlib.pyplot':
101
+ module.show = partial(plt_show_as_show_figure, module)
102
+ if module.__name__ == 'matplotlib':
103
+ module.pyplot.show = partial(plt_show_as_show_figure, module.pyplot)
104
+ return module
105
+
106
+ def _import(self, import_function):
107
+ def _(module_name, *args, **kwargs):
108
+ if any(re.match(m, module_name) for m in self._disallowed_modules):
109
+ raise ImportError('Module not found')
110
+ try:
111
+ with add_paths(self._additional_paths):
112
+ module = import_function(module_name, *args, **kwargs)
113
+ if module_name in 'importlib':
114
+ module.import_module = self._import(importlib.import_module)
115
+ return self._patch_plt_show(module)
116
+ except KeyError:
117
+ raise ImportError('Module not found')
118
+ return _
119
+
120
+ def _additional_paths(self):
121
+ return self._additional_paths
122
+
123
+ def _exit(self):
124
+ raise ScriptStopped()
125
+
126
+ def add_path(self, path):
127
+ if path in self._additional_paths:
128
+ self._additional_paths.remove(path)
129
+ self._additional_paths.insert(0, path)
130
+
131
+ def _not_implemented(self, *args, **kwargs):
132
+ raise NotImplementedError()
@@ -0,0 +1,42 @@
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 datetime import datetime
18
+
19
+ from jinja2 import Environment
20
+
21
+ class ScriptTemplate:
22
+ def __init__(self, template_string, name):
23
+ self._template_string = template_string
24
+ self._template_name = name
25
+
26
+ def render(self, *args, **kwargs):
27
+ environment = Environment()
28
+ environment.globals.update(**self._get_globals())
29
+ template = environment.from_string(self._template_string)
30
+ return template.render(*args, **kwargs)
31
+
32
+ def _get_globals(self):
33
+ return {
34
+ 'zip': zip,
35
+ 'len': len,
36
+ 'template_name': self._template_name,
37
+ 'now': datetime.now().strftime('%Y-%m-%d %I:%M'),
38
+ 'indent': self._indent
39
+ }
40
+
41
+ def _indent(self, level):
42
+ return ' ' * level
@@ -0,0 +1,32 @@
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:`services`
18
+ Defines classes and functions that provide services to the models of the application
19
+ """
20
+ from .task_runner import TaskRunner
21
+ from .message_board import MessageBoard, MessageArgs, MessageType, MessageResponse
22
+ from .module_service import ModuleService
23
+ from .tool_window_service import ToolWindowService
24
+ from .link_manager import LinkManager, IncompatibleWidgets
25
+ from .email import EmailService
26
+ from .event_registry import EventRegistry, EventTypes
27
+ from .object_converter import ObjectConverter, DictConverter
28
+ from .autosave import Autosave
29
+ from .hotkey_manager import HotkeyManager, HotkeyEvents
30
+ from .theme_manager import ThemeManager
31
+ from .project_updater import ProjectUpdater
32
+ from .file_dialog import FileDialogService
@@ -0,0 +1,41 @@
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 PyQt5.QtCore import QTimer, QObject
18
+ #pylint: enable=no-name-in-module
19
+
20
+
21
+ class Autosave(QObject):
22
+ def __init__(self, save_function, filename, interval=30000):
23
+ QObject.__init__(self)
24
+ self._save_function = save_function
25
+ self._timer = QTimer()
26
+ self._interval = interval
27
+ self._timer.timeout.connect(self._save)
28
+ self._filename = filename
29
+
30
+ @property
31
+ def filename(self):
32
+ return self._filename
33
+
34
+ def start(self):
35
+ self._timer.start(self._interval)
36
+
37
+ def stop(self):
38
+ self._timer.stop()
39
+
40
+ def _save(self):
41
+ self._save_function(self._filename)
@@ -0,0 +1,25 @@
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
+ http://code.activestate.com/recipes/511443-cross-platform-startfile-and-mailto-functions/
19
+ """
20
+ import os
21
+
22
+ class EmailService:
23
+ def send_mail(self, address, subject, body, attachements=None):
24
+ mailto_string = f'mailto:{address}?subject={subject}&body={body[:2000]}'
25
+ os.startfile(mailto_string)
@@ -0,0 +1,52 @@
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 EventTypes:
18
+ application_closing = 'application_closing'
19
+ opening_project = 'opening_project'
20
+ importing_project = 'importing_project'
21
+ save_requested = 'save_requested'
22
+ close_project_requested = 'close_project_requested'
23
+ project_closed = 'project_closed'
24
+ save_dialog_finished = 'save_dialog_finished'
25
+
26
+ class FutureConnector:
27
+ def __init__(self):
28
+ self._handlers = []
29
+
30
+ def connect(self, handler):
31
+ self._handlers.append(handler)
32
+
33
+ def register(self, event):
34
+ for h in self._handlers:
35
+ event.connect(h)
36
+
37
+ class EventRegistry:
38
+ def __init__(self):
39
+ self._events = {}
40
+ self._future_events = {}
41
+
42
+ def register(self, type_, event):
43
+ self._events[type_] = event
44
+ if type_ in self._future_events:
45
+ self._future_events[type_].register(event)
46
+
47
+ def event(self, type_):
48
+ event = self._events.get(type_)
49
+ if not event:
50
+ return self._future_events.setdefault(type_, FutureConnector())
51
+ else:
52
+ return event