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,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]
|