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,71 @@
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 os import path
18
+
19
+ from PyQt5.QtCore import QStandardPaths, QTimer
20
+ from PyQt5.QtWidgets import QFileDialog
21
+
22
+ from .event_registry import EventTypes
23
+
24
+ class FileDialogService:
25
+ def __init__(self, project_manager, application_configuration, event_registry):
26
+ self._project_manager = project_manager
27
+ self._application_configuration = application_configuration
28
+ self._event_registry = event_registry
29
+
30
+ def _get_dialog(self, parent, filter_, default_directory=None):
31
+ dialog = QFileDialog(
32
+ parent,
33
+ directory=default_directory or self._get_default_directory(),
34
+ filter=filter_
35
+ )
36
+ def _notify_save_dialog_finished(result):
37
+ self._event_registry.event(EventTypes.save_dialog_finished).emit(result)
38
+ dialog.finished.connect(_notify_save_dialog_finished)
39
+ return dialog
40
+
41
+ def get_save_filename(self, parent, filter_, default_name=None):
42
+ default_directory = path.dirname(default_name) if default_name else None
43
+ dialog = self._get_dialog(parent, filter_, default_directory)
44
+
45
+ if default_name:
46
+ dialog.selectFile(default_name)
47
+ dialog.setModal(True)
48
+ dialog.setAcceptMode(QFileDialog.AcceptSave)
49
+ if dialog.exec_() != QFileDialog.Accepted or not dialog.selectedFiles():
50
+ return None
51
+ return dialog.selectedFiles()[0]
52
+
53
+ def get_open_filename(self, parent, filter_, file_mode=None):
54
+ dialog = self._get_dialog(parent, filter_)
55
+ if file_mode:
56
+ dialog.setFileMode(file_mode)
57
+ if dialog.exec_():
58
+ return dialog.selectedFiles()[0]
59
+ else:
60
+ return None
61
+
62
+ def _get_default_directory(self):
63
+ if not self._project_manager.filename:
64
+ default_location = self._application_configuration.get_value('application.default_directory')
65
+ if default_location and path.isdir(default_location):
66
+ return default_location
67
+ return QStandardPaths.writableLocation(QStandardPaths.DocumentsLocation)
68
+ elif self._project_manager.filename and not path.isdir(path.dirname(self._project_manager.filename)):
69
+ return QStandardPaths.writableLocation(QStandardPaths.DocumentsLocation)
70
+ else:
71
+ return path.dirname(self._project_manager.filename)
@@ -0,0 +1,137 @@
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 enum import Enum
18
+
19
+ from PyQt5.QtCore import Qt
20
+
21
+ class HotkeyEvents(Enum):
22
+ next_instance_y = 0
23
+ previous_instance_y = 1
24
+ next_instance_x = 2
25
+ previous_instance_x = 3
26
+ next_t = 4
27
+ previous_t = 5
28
+ zoom_in = 6
29
+ zoom_out = 7
30
+
31
+ class HotkeyManager:
32
+ keymap = {
33
+ Qt.Key_0: '0',
34
+ Qt.Key_1: '1',
35
+ Qt.Key_2: '2',
36
+ Qt.Key_3: '3',
37
+ Qt.Key_4: '4',
38
+ Qt.Key_5: '5',
39
+ Qt.Key_6: '6',
40
+ Qt.Key_7: '7',
41
+ Qt.Key_8: '8',
42
+ Qt.Key_9: '9',
43
+ Qt.Key_A: 'a',
44
+ Qt.Key_B: 'b',
45
+ Qt.Key_C: 'c',
46
+ Qt.Key_D: 'd',
47
+ Qt.Key_E: 'e',
48
+ Qt.Key_F: 'f',
49
+ Qt.Key_G: 'g',
50
+ Qt.Key_H: 'h',
51
+ Qt.Key_I: 'i',
52
+ Qt.Key_J: 'j',
53
+ Qt.Key_K: 'k',
54
+ Qt.Key_L: 'l',
55
+ Qt.Key_M: 'm',
56
+ Qt.Key_N: 'n',
57
+ Qt.Key_O: 'o',
58
+ Qt.Key_P: 'p',
59
+ Qt.Key_Q: 'q',
60
+ Qt.Key_R: 'r',
61
+ Qt.Key_S: 's',
62
+ Qt.Key_T: 't',
63
+ Qt.Key_U: 'u',
64
+ Qt.Key_V: 'v',
65
+ Qt.Key_W: 'w',
66
+ Qt.Key_X: 'x',
67
+ Qt.Key_Y: 'y',
68
+ Qt.Key_Z: 'z',
69
+ Qt.Key_BraceLeft: '{',
70
+ Qt.Key_BraceRight: '}',
71
+ Qt.Key_BracketLeft: '[',
72
+ Qt.Key_BracketRight: ']',
73
+ Qt.Key_Colon: ':',
74
+ Qt.Key_Semicolon: ';',
75
+ Qt.Key_Greater: '>',
76
+ Qt.Key_Less: '<',
77
+ Qt.Key_Comma: ',',
78
+ Qt.Key_Period: '.',
79
+ Qt.Key_Space: '<space>',
80
+ Qt.Key_Up: '<up>',
81
+ Qt.Key_Down: '<down>',
82
+ Qt.Key_Left: '<left>',
83
+ Qt.Key_Right: '<right>',
84
+ Qt.Key_Backslash: '\\',
85
+ }
86
+
87
+ defaults = {
88
+ HotkeyEvents.next_instance_y: 'w',
89
+ HotkeyEvents.previous_instance_y: 's',
90
+ HotkeyEvents.next_instance_x: 'shift+w',
91
+ HotkeyEvents.previous_instance_x: 'shift+s',
92
+ HotkeyEvents.next_t: 'd',
93
+ HotkeyEvents.previous_t: 'a',
94
+ HotkeyEvents.zoom_in: 'e',
95
+ HotkeyEvents.zoom_out: 'q'
96
+ }
97
+
98
+ def __init__(self, application_configuration):
99
+ self._application_configuration = application_configuration
100
+ self._cached_configuration = self._get_configuration()
101
+
102
+ def get_default_keystring(self, event):
103
+ return self.defaults[event]
104
+
105
+ def keyevent_to_string(self, event):
106
+ key = self.keymap.get(event.key())
107
+ if not key:
108
+ return None
109
+ modifiers = []
110
+ if Qt.ShiftModifier & event.modifiers():
111
+ modifiers.append('shift')
112
+ if Qt.ControlModifier & event.modifiers():
113
+ modifiers.append('control')
114
+ if Qt.AltModifier & event.modifiers():
115
+ modifiers.append('alt')
116
+ return '+'.join(modifiers + [key])
117
+
118
+ def assign_keystring(self, event, keystring):
119
+ path = f'application.hotkeys.{event.name}'
120
+ self._application_configuration.set_value(path, keystring)
121
+ self._cached_configuration = self._get_configuration()
122
+
123
+ def get_keystring(self, event):
124
+ return self._cached_configuration.get(event.name) or self.get_default_keystring(event)
125
+
126
+ def handle_event(self, event, handlers):
127
+ keystring = self.keyevent_to_string(event)
128
+ if not keystring:
129
+ return False
130
+ for hotkey_event in HotkeyEvents:
131
+ if self.get_keystring(hotkey_event) == keystring and handlers.get(hotkey_event):
132
+ handlers.get(hotkey_event)()
133
+ return True
134
+ return False
135
+
136
+ def _get_configuration(self):
137
+ return self._application_configuration.get_value('application.hotkeys') or {}
@@ -0,0 +1,135 @@
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 Qt, QObject
18
+ from PyQt5.QtGui import QColor, QPalette
19
+
20
+ from pyqttoolkit.views import ModuleWindow
21
+ from . import MessageType
22
+ from ..colors import rgb_to_hex
23
+
24
+ class IncompatibleWidgets(Exception):
25
+ def __init__(self):
26
+ Exception.__init__(self)
27
+
28
+ class LinkManager(QObject):
29
+ def __init__(self, parent, message_board, color_selection):
30
+ QObject.__init__(self, parent)
31
+ self._message_board = message_board
32
+ self._current_widget = None
33
+ self._color_count = 10
34
+ self._selecting_color = (220/255, 240/255, 247/255)
35
+ self._colors = color_selection.colors(self._color_count)
36
+ self._link_count = 0
37
+ self._link_groups = []
38
+
39
+ def request_link(self, widget):
40
+ if self._current_widget is None:
41
+ self._current_widget = widget
42
+ self._set_background_color(widget, self._selecting_color)
43
+ elif self._current_widget == widget:
44
+ current_group = self._current_link_group(widget)
45
+ if current_group is not None:
46
+ self._remove_widget_from_group(widget, current_group)
47
+ self._set_background_color(widget, None)
48
+ self._current_widget = None
49
+ else:
50
+ self._update_link(widget)
51
+ self._current_widget = None
52
+
53
+ def _update_link(self, widget):
54
+ try:
55
+ current_group = self._current_link_group(self._current_widget)
56
+ if current_group is not None:
57
+ if widget in current_group['widgets']:
58
+ self._remove_widget_from_group(widget, current_group)
59
+ else:
60
+ self._add_widget_to_group(widget, current_group)
61
+ else:
62
+ other_group = self._current_link_group(widget)
63
+ if other_group is not None:
64
+ self._add_widget_to_group(self._current_widget, other_group)
65
+ else:
66
+ self._create_new_group(self._current_widget, widget)
67
+ except IncompatibleWidgets:
68
+ self._message_board.post(MessageType.validation_error, self.tr('It is not possible to link these controls'))
69
+ self._set_background_color(self._current_widget, current_group and current_group['color'])
70
+
71
+ def _current_link_group(self, widget):
72
+ for group in self._link_groups:
73
+ if widget in group['widgets']:
74
+ return group
75
+ return None
76
+
77
+ def _create_new_group(self, from_widget, to_widget):
78
+ from_widget.link(to_widget)
79
+ group = {
80
+ 'widgets': [],
81
+ 'color': self._colors[self._link_count % self._color_count]
82
+ }
83
+ self._add_widget_to_group(from_widget, group, link=False)
84
+ self._add_widget_to_group(to_widget, group, link=False)
85
+ self._link_count += 1
86
+ self._link_groups.append(group)
87
+
88
+ def _add_widget_to_group(self, widget, group, link=True):
89
+ existing_group = self._current_link_group(widget)
90
+ if existing_group == group:
91
+ return
92
+ if existing_group is not None:
93
+ self._remove_widget_from_group(widget, existing_group)
94
+ if link:
95
+ for w in group['widgets']:
96
+ w.link(widget)
97
+ group['widgets'].append(widget)
98
+ self._get_module_window(widget).closing.connect(lambda _: self._remove_widget_from_group(widget, group))
99
+ for w in group['widgets']:
100
+ self._set_background_color(w, group['color'])
101
+
102
+ def _remove_widget_from_group(self, widget, group):
103
+ if group not in self._link_groups or widget not in group['widgets']:
104
+ return
105
+ group['widgets'].remove(widget)
106
+ for w in group['widgets']:
107
+ w.unlink(widget)
108
+ self._set_background_color(widget, None)
109
+ if len(group['widgets']) == 1:
110
+ remaining_widget = group['widgets'][0]
111
+ group['widgets'].remove(remaining_widget)
112
+ self._link_groups.remove(group)
113
+ self._set_background_color(remaining_widget, None)
114
+ else:
115
+ for w in group['widgets']:
116
+ self._set_background_color(w, group['color'])
117
+
118
+ def _get_module_window(self, widget):
119
+ parent = widget.parent()
120
+ while parent is not None:
121
+ if isinstance(parent, ModuleWindow):
122
+ return parent
123
+ parent = parent.parent()
124
+
125
+ raise ValueError('Could not find module window')
126
+
127
+ def _set_background_color(self, widget, color):
128
+ try:
129
+ palette = widget.linkDisplay().palette()
130
+ palette.setColor(QPalette.Background, QColor(rgb_to_hex(color)) if color is not None else Qt.transparent)
131
+ widget.linkDisplay().setAutoFillBackground(True)
132
+ widget.linkDisplay().setPalette(palette)
133
+ except RuntimeError:
134
+ # May have been deleted
135
+ pass
@@ -0,0 +1,90 @@
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
+ #pylint: disable=no-name-in-module
19
+ from PyQt5.QtCore import QObject, pyqtSignal, Qt
20
+ #pylint: enable=no-name-in-module
21
+
22
+ from ..properties import AutoProperty
23
+ from ..data import MessageType, MessageResponse
24
+
25
+ class MessageArgs:
26
+ def __init__(self, message_type, message, checkbox_message, response_type, text_format):
27
+ self._message_type = message_type
28
+ self._message = message
29
+ self._checkbox_message = checkbox_message
30
+ self._response_type = response_type
31
+ self._text_format = text_format
32
+ self._response = None
33
+ self._checkbox_value = None
34
+
35
+ @property
36
+ def message_type(self):
37
+ return self._message_type
38
+
39
+ @property
40
+ def message(self):
41
+ return self._message
42
+
43
+ @property
44
+ def checkbox_message(self):
45
+ return self._checkbox_message
46
+
47
+ @property
48
+ def response_type(self):
49
+ return self._response_type
50
+
51
+ @property
52
+ def text_format(self):
53
+ return self._text_format
54
+
55
+ @property
56
+ def response(self):
57
+ return self._response
58
+
59
+ @response.setter
60
+ def response(self, value):
61
+ self._response = value
62
+
63
+ @property
64
+ def checkbox_value(self):
65
+ return self._checkbox_value
66
+
67
+ @checkbox_value.setter
68
+ def checkbox_value(self, value):
69
+ self._checkbox_value = value
70
+
71
+ class MessageBoard(QObject):
72
+ def __init__(self, parent):
73
+ QObject.__init__(self, parent)
74
+ self._message_enabled = True
75
+
76
+ def messageEnabled(self):
77
+ return self._message_enabled
78
+
79
+ def setMessageEnabled(self, value):
80
+ self._message_enabled = value
81
+
82
+ def post(self, message_type, message, response_type=MessageResponse.ok, checkbox_message=None, text_format=Qt.AutoText):
83
+ if not self._message_enabled:
84
+ return
85
+ self.message = MessageArgs(message_type, message, checkbox_message, response_type, text_format)
86
+ return self.message
87
+
88
+ messageChanged = pyqtSignal(MessageArgs)
89
+
90
+ message = AutoProperty(MessageArgs)
@@ -0,0 +1,154 @@
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:`module_service`
18
+ The module service, creates and manages qt widget modules
19
+ """
20
+ from os.path import basename
21
+
22
+ from weakref import ref
23
+ import weakref
24
+
25
+ from PyQt5.QtCore import QObject, pyqtSlot, QTimer, Qt
26
+
27
+ from pyqttoolkit.modules import Module, CommandModule
28
+ from pyqttoolkit.views import ModuleWindow
29
+ from pyqttoolkit.modules.events import ModuleOpeningEvent
30
+
31
+ class ModuleService(QObject):
32
+ """class::ModuleService
33
+ This class creates and displays modules (views, models and view managers)
34
+ """
35
+
36
+ def __init__(self, dependency_container, theme_manager, project_updater, project_manager, garbage_collector):
37
+ super().__init__(None)
38
+ self._theme_manager = theme_manager
39
+ self._dependency_container = dependency_container
40
+ self._view_manager_refs = {}
41
+ self._window_refs = {}
42
+ self._model_refs = {}
43
+ self._view_managers = {}
44
+ self._windows = {}
45
+ self._models = {}
46
+ self._registered_modules = {}
47
+ self._project_updater = project_updater
48
+ self._project_manager = project_manager
49
+ self._garbage_collector = garbage_collector
50
+
51
+ def register(self, module):
52
+ if module.id in self._registered_modules:
53
+ raise ValueError('module')
54
+ module.setParent(self)
55
+ self._registered_modules[module.id] = module
56
+ if module.launcher_config and self._project_updater is not None and self._project_manager is not None:
57
+ self._project_updater.projectUpdated.connect(self._project_update_handler(module))
58
+
59
+ def _project_update_handler(self, module):
60
+ def _handler():
61
+ module.handle_project_updated(self._project_manager.project)
62
+ return _handler
63
+
64
+ @pyqtSlot(str, QObject)
65
+ def openModule(self, name):
66
+ """function::openModule(self, name, parent, model)
67
+ Opens the module with the specified name, setting the parent and model
68
+ """
69
+ if self._init_module(name):
70
+ module = self._registered_modules[name]
71
+ if isinstance(module, CommandModule):
72
+ result = module.execute()
73
+ elif isinstance(module, Module):
74
+ result = self._create_module(name).show()
75
+ else:
76
+ raise ValueError('Invalid module type')
77
+ self._module_opened(name)
78
+ return result
79
+ return None
80
+
81
+ def closeModules(self):
82
+ for id_, window in list(self._windows.items()):
83
+ if id_ != 'root' and not window.forceClose():
84
+ return False
85
+ return True
86
+
87
+ def modules(self):
88
+ return self._registered_modules
89
+
90
+ def _init_module(self, id_):
91
+ module = self._registered_modules[id_]
92
+ opening_event = ModuleOpeningEvent()
93
+ module.opening.emit(opening_event)
94
+ return opening_event.accepted
95
+
96
+ def _module_opened(self, id_):
97
+ self._registered_modules[id_].opened.emit()
98
+
99
+ def _create_module(self, id_):
100
+ if not id_ in self._registered_modules:
101
+ raise RuntimeError(f'Could not find module with id {id_}')
102
+ if self._registered_modules[id_].is_root:
103
+ return self._create_root_module(id_)
104
+ else:
105
+ return self._create_sub_module(id_)
106
+
107
+ def _create_root_module(self, id_):
108
+ window = self._dependency_container.resolve(self._registered_modules[id_].view_type)
109
+ model = self._dependency_container.resolve(self._registered_modules[id_].model_type)
110
+ model.setParent(window)
111
+ view_manager = self._registered_modules[id_].view_manager_type(window, model)
112
+ self._windows['root'] = window
113
+ self._models['root'] = model
114
+ self._view_managers['root'] = view_manager
115
+ return window
116
+
117
+ def _create_sub_module(self, id_):
118
+ singleton = self._registered_modules[id_].singleton
119
+ window_title = self._registered_modules[id_].title
120
+ if self._project_manager.filename:
121
+ window_title += f' - {basename(self._project_manager.filename)}'
122
+ elif self._project_updater and self._project_updater.dirty:
123
+ window_title += self.tr(' - unsaved')
124
+ window = ModuleWindow(self._theme_manager, window_title, singleton)
125
+ window.setAttribute(Qt.WA_DeleteOnClose)
126
+ model = self._dependency_container.resolve(
127
+ self._registered_modules[id_].model_type,
128
+ {'parent': window, 'module_id': id_}
129
+ )
130
+ view = self._dependency_container.resolve(self._registered_modules[id_].view_type, {'parent': window})
131
+ window.setModuleView(view)
132
+ view_manager = self._registered_modules[id_].view_manager_type(view, model)
133
+
134
+ self._view_managers[window.moduleId] = model
135
+ self._windows[window.moduleId] = window
136
+ self._models[window.moduleId] = model
137
+ self._view_manager_refs[window.moduleId] = ref(view_manager)
138
+ self._window_refs[window.moduleId] = ref(window)
139
+ self._model_refs[window.moduleId] = ref(model)
140
+ window.closing.connect(self._remove_module)
141
+ return window
142
+
143
+ def _remove_module(self, module_id):
144
+ self._view_managers.pop(module_id)
145
+ window = self._windows.pop(module_id)
146
+ self._models.pop(module_id)
147
+ window.deleteLater()
148
+ window.destroyed.connect(self._schedule_gc)
149
+
150
+ def _schedule_gc(self):
151
+ QTimer.singleShot(0, self._gc)
152
+
153
+ def _gc(self):
154
+ self._garbage_collector.check(full=True)
@@ -0,0 +1,45 @@
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 NoConverter(Exception):
18
+ def __init__(self, type_):
19
+ Exception.__init__(self, f'No converter for type {type_}')
20
+
21
+ class DictConverter:
22
+ def to_dict(self, value):
23
+ raise NotImplementedError()
24
+
25
+ def from_dict(self, value):
26
+ raise NotImplementedError()
27
+
28
+ class ObjectConverter:
29
+ def __init__(self):
30
+ self._converters = {}
31
+
32
+ def register_converter(self, converter, value_type):
33
+ self._converters.setdefault(value_type, []).append(converter)
34
+
35
+ def to_dict(self, value):
36
+ return self._get_converter(type(value), DictConverter).to_dict(value)
37
+
38
+ def from_dict(self, value, value_type, **kwargs):
39
+ return self._get_converter(value_type, DictConverter).from_dict(value, **kwargs)
40
+
41
+ def _get_converter(self, value_type, converter_type):
42
+ type_converters = [c for c in self._converters.get(value_type, []) if isinstance(c, converter_type)]
43
+ if not type_converters:
44
+ raise NoConverter(value_type)
45
+ return type_converters[0]