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,101 @@
|
|
|
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:`combo_box`
|
|
18
|
+
Defines the ComboBox class
|
|
19
|
+
"""
|
|
20
|
+
from enum import Enum
|
|
21
|
+
|
|
22
|
+
from PyQt5.QtWidgets import QComboBox
|
|
23
|
+
from PyQt5.QtCore import Qt, pyqtSignal
|
|
24
|
+
|
|
25
|
+
from pyqttoolkit.properties import AutoProperty
|
|
26
|
+
from pyqttoolkit.models.roles import DataRole
|
|
27
|
+
from pyqttoolkit.views.styleable import make_styleable
|
|
28
|
+
|
|
29
|
+
class ComboBox(QComboBox):
|
|
30
|
+
def __init__(self, parent, padding=20):
|
|
31
|
+
QComboBox.__init__(self, parent)
|
|
32
|
+
self.setMaxVisibleItems(20)
|
|
33
|
+
self.setMinimumWidth(50)
|
|
34
|
+
self.setSizeAdjustPolicy(QComboBox.AdjustToContents)
|
|
35
|
+
self.view().setTextElideMode(Qt.ElideNone)
|
|
36
|
+
self._padding = padding
|
|
37
|
+
|
|
38
|
+
def setModel(self, value):
|
|
39
|
+
QComboBox.setModel(self, value)
|
|
40
|
+
self._update_minimum_width()
|
|
41
|
+
value.dataChanged.connect(self._update_minimum_width)
|
|
42
|
+
|
|
43
|
+
def _update_minimum_width(self):
|
|
44
|
+
width = self.minimumWidth()
|
|
45
|
+
if self.model():
|
|
46
|
+
for i in range(self.model().rowCount()):
|
|
47
|
+
index = self.model().createIndex(i, 0)
|
|
48
|
+
display_text = self.model().data(index, Qt.DisplayRole)
|
|
49
|
+
width = max(width, self.fontMetrics().width(display_text))
|
|
50
|
+
self.view().setMinimumWidth(width + self._padding)
|
|
51
|
+
|
|
52
|
+
ComboBox = make_styleable(ComboBox)
|
|
53
|
+
|
|
54
|
+
def typed_bindable_combo_box(type_):
|
|
55
|
+
class TypedBindableComboBox(ComboBox):
|
|
56
|
+
def __init__(self, parent):
|
|
57
|
+
ComboBox.__init__(self, parent)
|
|
58
|
+
|
|
59
|
+
self.currentIndexChanged.connect(self._handle_index_changed)
|
|
60
|
+
self.valueChanged.connect(self._update_index)
|
|
61
|
+
|
|
62
|
+
valueChanged = pyqtSignal(type_)
|
|
63
|
+
|
|
64
|
+
value = AutoProperty(type_)
|
|
65
|
+
|
|
66
|
+
def setModel(self, value):
|
|
67
|
+
super().setModel(value)
|
|
68
|
+
if hasattr(value, 'dataChanged'):
|
|
69
|
+
value.dataChanged.connect(self._handle_data_changed)
|
|
70
|
+
|
|
71
|
+
def _handle_data_changed(self):
|
|
72
|
+
self._update_index(self.value)
|
|
73
|
+
self._handle_index_changed(self.currentIndex())
|
|
74
|
+
|
|
75
|
+
def _handle_index_changed(self, index):
|
|
76
|
+
if 0 <= index < self.model().rowCount():
|
|
77
|
+
model_index = self.model().createIndex(index, 0)
|
|
78
|
+
self.value = self._get_data_value(model_index)
|
|
79
|
+
|
|
80
|
+
def _update_index(self, value):
|
|
81
|
+
index = self._get_index(value)
|
|
82
|
+
index = index if index is not None else -1
|
|
83
|
+
if -1 <= index < self.model().rowCount():
|
|
84
|
+
self.setCurrentIndex(index)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _get_index(self, value):
|
|
88
|
+
for i in range(self.model().rowCount()):
|
|
89
|
+
model_index = self.model().createIndex(i, 0)
|
|
90
|
+
if self._get_data_value(model_index) == value:
|
|
91
|
+
return i
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
def _get_data_value(self, model_index):
|
|
95
|
+
value = self.model().data(model_index, DataRole)
|
|
96
|
+
value = value if value is not None else self.model().data(model_index, Qt.DisplayRole)
|
|
97
|
+
return value if not isinstance(value, Enum) else value.name
|
|
98
|
+
|
|
99
|
+
return make_styleable(TypedBindableComboBox)
|
|
100
|
+
|
|
101
|
+
BindableComboBox = typed_bindable_combo_box(str)
|
|
@@ -0,0 +1,59 @@
|
|
|
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 pyqtSignal, Qt
|
|
18
|
+
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QScrollBar, QLineEdit
|
|
19
|
+
from PyQt5.QtGui import QIntValidator
|
|
20
|
+
|
|
21
|
+
class CountSelectorWidget(QWidget):
|
|
22
|
+
def __init__(self, parent, minimum, maximum, initial=0):
|
|
23
|
+
QWidget.__init__(self, parent)
|
|
24
|
+
|
|
25
|
+
self._slider = QScrollBar(Qt.Horizontal, self)
|
|
26
|
+
self._slider.setMinimum(minimum)
|
|
27
|
+
self._slider.setMaximum(maximum)
|
|
28
|
+
self._slider.setSingleStep(1)
|
|
29
|
+
# self._slider.setTickInterval(1)
|
|
30
|
+
|
|
31
|
+
self._linedit = QLineEdit(self)
|
|
32
|
+
self._linedit.setValidator(QIntValidator(minimum, maximum, self))
|
|
33
|
+
self._linedit.setMinimumWidth(20)
|
|
34
|
+
|
|
35
|
+
self._layout = QHBoxLayout(self)
|
|
36
|
+
self._layout.setContentsMargins(0, 0, 0, 0)
|
|
37
|
+
self._layout.addWidget(self._slider, 4)
|
|
38
|
+
self._layout.addWidget(self._linedit, 1)
|
|
39
|
+
|
|
40
|
+
self._slider.valueChanged.connect(self._update_value)
|
|
41
|
+
self._linedit.textChanged.connect(self._update_value)
|
|
42
|
+
self._update_value(initial)
|
|
43
|
+
|
|
44
|
+
valueChanged = pyqtSignal(int)
|
|
45
|
+
|
|
46
|
+
def _update_value(self, value):
|
|
47
|
+
value = int(value) if value else 0
|
|
48
|
+
self.value = value
|
|
49
|
+
self.valueChanged.emit(value)
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def value(self):
|
|
53
|
+
return self._slider.value()
|
|
54
|
+
|
|
55
|
+
@value.setter
|
|
56
|
+
def value(self, value):
|
|
57
|
+
if self._slider.value() != value:
|
|
58
|
+
self._slider.setValue(value)
|
|
59
|
+
self._linedit.setText(str(value))
|
|
@@ -0,0 +1,111 @@
|
|
|
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:`datetime`
|
|
18
|
+
Defines the DateTimeEdit class
|
|
19
|
+
"""
|
|
20
|
+
from enum import Enum
|
|
21
|
+
|
|
22
|
+
from PyQt5.QtCore import QDateTime, QTime, pyqtSignal
|
|
23
|
+
from PyQt5.QtWidgets import QDateTimeEdit
|
|
24
|
+
|
|
25
|
+
from pyqttoolkit.properties import auto_property, AutoProperty
|
|
26
|
+
|
|
27
|
+
from .styleable import make_styleable
|
|
28
|
+
|
|
29
|
+
class DateTimeEdit(QDateTimeEdit):
|
|
30
|
+
class Limit(Enum):
|
|
31
|
+
min = 0
|
|
32
|
+
max = 1
|
|
33
|
+
|
|
34
|
+
def __init__(self, parent, default=Limit.min, calendar_popup=True):
|
|
35
|
+
QDateTimeEdit.__init__(self, parent)
|
|
36
|
+
self.setDisplayFormat('dd-MMM-yyyy hh:mm')
|
|
37
|
+
self.setButtonSymbols(QDateTimeEdit.NoButtons)
|
|
38
|
+
self._min_date = self._max_date = None
|
|
39
|
+
self._start_date = self._end_date = None
|
|
40
|
+
self.isLimit = False
|
|
41
|
+
self._default = default
|
|
42
|
+
self.isLimitChanged.connect(self._handle_is_limit_changed)
|
|
43
|
+
self.editingFinished.connect(self._handle_date_time_editing_finished)
|
|
44
|
+
self.setCalendarPopup(calendar_popup)
|
|
45
|
+
if calendar_popup:
|
|
46
|
+
self.calendarWidget().clicked.connect(self._handle_date_changed_from_calendar)
|
|
47
|
+
|
|
48
|
+
valueChanged = pyqtSignal(QDateTime)
|
|
49
|
+
isLimitChanged = pyqtSignal(bool)
|
|
50
|
+
|
|
51
|
+
isLimit = AutoProperty(bool)
|
|
52
|
+
|
|
53
|
+
def focusOutEvent(self, event=None):
|
|
54
|
+
if self._start_date and self.dateTime() < self._start_date:
|
|
55
|
+
self.setDateTime(self._start_date)
|
|
56
|
+
elif self._min_date and self.dateTime() < self._min_date:
|
|
57
|
+
self.setDateTime(self._min_date)
|
|
58
|
+
elif self._end_date and self.dateTime() > self._end_date:
|
|
59
|
+
self.setDateTime(self._end_date)
|
|
60
|
+
elif self._max_date and self.dateTime() > self._max_date:
|
|
61
|
+
self.setDateTime(self._max_date)
|
|
62
|
+
return event and QDateTimeEdit.focusOutEvent(self, event)
|
|
63
|
+
|
|
64
|
+
def setDateTimeRange(self, start_date, end_date):
|
|
65
|
+
self._start_date = start_date
|
|
66
|
+
self._end_date = end_date
|
|
67
|
+
|
|
68
|
+
def setMinimumDateTime(self, min_date):
|
|
69
|
+
self._min_date = min_date
|
|
70
|
+
super().setMinimumDateTime(min_date)
|
|
71
|
+
|
|
72
|
+
def setMaximumDateTime(self, max_date):
|
|
73
|
+
self._max_date = max_date
|
|
74
|
+
super().setMaximumDateTime(max_date)
|
|
75
|
+
|
|
76
|
+
@auto_property(QDateTime)
|
|
77
|
+
def value(self):
|
|
78
|
+
return QDateTime() if self.isLimit else self.dateTime()
|
|
79
|
+
|
|
80
|
+
@value.setter
|
|
81
|
+
def value(self, value):
|
|
82
|
+
value = value or QDateTime()
|
|
83
|
+
if not value.isValid() and self._start_date is not None and self._end_date is not None:
|
|
84
|
+
date_time = self._start_date if self._default == self.Limit.min else self._end_date
|
|
85
|
+
is_limit = True
|
|
86
|
+
else:
|
|
87
|
+
date_time = value
|
|
88
|
+
is_limit = False
|
|
89
|
+
if self.dateTime() != date_time:
|
|
90
|
+
self.setDateTime(date_time)
|
|
91
|
+
self.isLimit = is_limit
|
|
92
|
+
self.valueChanged.emit(value)
|
|
93
|
+
|
|
94
|
+
def reset(self):
|
|
95
|
+
self.value = QDateTime()
|
|
96
|
+
|
|
97
|
+
def _handle_is_limit_changed(self, is_limit):
|
|
98
|
+
stylesheet = 'DateTimeEdit {font: italic}' if is_limit else 'DateTimeEdit {font: normal}'
|
|
99
|
+
self.setStyleSheet(stylesheet)
|
|
100
|
+
|
|
101
|
+
def _handle_date_time_editing_finished(self):
|
|
102
|
+
value = self.dateTime()
|
|
103
|
+
if value is not None or (isinstance(value, QDateTime) and value.isValid()):
|
|
104
|
+
self.isLimit = False
|
|
105
|
+
self.valueChanged.emit(self.value)
|
|
106
|
+
self.focusOutEvent()
|
|
107
|
+
|
|
108
|
+
def _handle_date_changed_from_calendar(self, value):
|
|
109
|
+
self.value = QDateTime(value, QTime(0, 0))
|
|
110
|
+
|
|
111
|
+
DateTimeEdit = make_styleable(DateTimeEdit)
|
|
@@ -0,0 +1,231 @@
|
|
|
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:`datetime_range_selector`
|
|
18
|
+
Defines the DatetimeRangeSelectorWidget
|
|
19
|
+
"""
|
|
20
|
+
import weakref
|
|
21
|
+
|
|
22
|
+
from PyQt5.QtCore import pyqtSignal, QDateTime, QSize
|
|
23
|
+
from PyQt5.QtWidgets import QGridLayout, QSizePolicy, QMenu, QAction, QPushButton
|
|
24
|
+
|
|
25
|
+
from datetime import timedelta, datetime
|
|
26
|
+
from typing import Optional, Tuple
|
|
27
|
+
|
|
28
|
+
from pyqttoolkit.properties import auto_property, bind, unbind
|
|
29
|
+
from pyqttoolkit.datetime.qdatetime import step_qdatetime
|
|
30
|
+
from pyqttoolkit.services.link_manager import IncompatibleWidgets
|
|
31
|
+
from .linkable import LinkableWidget
|
|
32
|
+
from .datetime import DateTimeEdit
|
|
33
|
+
from .icon_button import IconButton
|
|
34
|
+
|
|
35
|
+
class DatetimeRangeSelectorWidget(LinkableWidget):
|
|
36
|
+
"""class::DatetimeRangeSelectorWidget
|
|
37
|
+
Widget to select a datetime range
|
|
38
|
+
"""
|
|
39
|
+
def __init__(self, parent, paging=False, link_manager=None, reset_enabled=True, link_type=None,
|
|
40
|
+
update_limits_on_value_change: bool = True,
|
|
41
|
+
default_limits: Optional[Tuple[datetime, datetime]]=None,
|
|
42
|
+
set_to_limit_buttons: bool = False):
|
|
43
|
+
LinkableWidget.__init__(self, parent, link_manager, link_type)
|
|
44
|
+
self._date_range_start = self._date_range_end = None
|
|
45
|
+
self._interval = None
|
|
46
|
+
self._update_limits_on_value_change = update_limits_on_value_change
|
|
47
|
+
|
|
48
|
+
if paging:
|
|
49
|
+
self._back_one = IconButton('left.svg', self.tr('Scroll Left'), self, QSize(8, 30))
|
|
50
|
+
self._back_one.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
|
|
51
|
+
self._back_one.clicked.connect(self._step(-1))
|
|
52
|
+
self._from_selector = DateTimeEdit(self, DateTimeEdit.Limit.min)
|
|
53
|
+
self._to_selector = DateTimeEdit(self, DateTimeEdit.Limit.max)
|
|
54
|
+
if default_limits:
|
|
55
|
+
self._from_selector.setDateTimeRange(*default_limits)
|
|
56
|
+
self._to_selector.setDateTimeRange(*default_limits)
|
|
57
|
+
if paging:
|
|
58
|
+
self._forward_one = IconButton('right.svg', self.tr('Scroll Right'), self, QSize(8, 30))
|
|
59
|
+
self._forward_one.clicked.connect(self._step(1))
|
|
60
|
+
self._forward_one.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
|
|
61
|
+
|
|
62
|
+
self._layout = QGridLayout(self)
|
|
63
|
+
self._layout.setContentsMargins(0, 0, 0, 0)
|
|
64
|
+
self._layout.addWidget(self._from_selector, 0, 1)
|
|
65
|
+
self._layout.addWidget(self._to_selector, 1, 1)
|
|
66
|
+
|
|
67
|
+
if paging:
|
|
68
|
+
self._layout.addWidget(self._back_one, 0, 0, 2, 1)
|
|
69
|
+
self._layout.addWidget(self._forward_one, 0, 2, 2, 1)
|
|
70
|
+
|
|
71
|
+
if set_to_limit_buttons:
|
|
72
|
+
self._set_to_start = QPushButton(self.tr('Set to Start'), self)
|
|
73
|
+
self._set_to_start.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
|
74
|
+
self._set_to_start.setFixedWidth(65)
|
|
75
|
+
self._set_to_end = QPushButton(self.tr('Set to End'), self)
|
|
76
|
+
self._set_to_end.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
|
77
|
+
self._set_to_end.setFixedWidth(65)
|
|
78
|
+
|
|
79
|
+
self._layout.addWidget(self._set_to_start, 0, 2)
|
|
80
|
+
self._layout.addWidget(self._set_to_end, 1, 2)
|
|
81
|
+
|
|
82
|
+
def _handle_set_to_limit_clicked(widget, prop):
|
|
83
|
+
view_ref = weakref.ref(self)
|
|
84
|
+
def _():
|
|
85
|
+
view = view_ref()
|
|
86
|
+
if view:
|
|
87
|
+
widget.reset()
|
|
88
|
+
prop.__get__(view).emit(widget.value)
|
|
89
|
+
return _
|
|
90
|
+
|
|
91
|
+
self._set_to_start.clicked.connect(
|
|
92
|
+
_handle_set_to_limit_clicked(
|
|
93
|
+
self._from_selector, DatetimeRangeSelectorWidget.dateFromChanged))
|
|
94
|
+
self._set_to_end.clicked.connect(
|
|
95
|
+
_handle_set_to_limit_clicked(
|
|
96
|
+
self._to_selector, DatetimeRangeSelectorWidget.dateToChanged))
|
|
97
|
+
|
|
98
|
+
self._from_selector.editingFinished.connect(self._date_from_editing_finished)
|
|
99
|
+
self._to_selector.editingFinished.connect(self._date_to_editing_finished)
|
|
100
|
+
|
|
101
|
+
self._menu = QMenu(self)
|
|
102
|
+
self._reset = QAction(self.tr('Reset'), self)
|
|
103
|
+
self._reset.triggered.connect(self._handle_reset)
|
|
104
|
+
self._menu.addAction(self._reset)
|
|
105
|
+
self._reset_enabled = reset_enabled
|
|
106
|
+
|
|
107
|
+
dateFromChanged = pyqtSignal(QDateTime)
|
|
108
|
+
dateToChanged = pyqtSignal(QDateTime)
|
|
109
|
+
datesChanged = pyqtSignal(QDateTime, QDateTime)
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def date_from_selector(self):
|
|
113
|
+
return self._from_selector
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def date_to_selector(self):
|
|
117
|
+
return self._to_selector
|
|
118
|
+
|
|
119
|
+
def contextMenuEvent(self, event):
|
|
120
|
+
if self._reset_enabled:
|
|
121
|
+
self._menu.exec_(event.globalPos())
|
|
122
|
+
|
|
123
|
+
def _handle_reset(self):
|
|
124
|
+
self.setDates(self._date_range_start, self._date_range_end)
|
|
125
|
+
|
|
126
|
+
def link(self, other):
|
|
127
|
+
if not isinstance(other, DatetimeRangeSelectorWidget):
|
|
128
|
+
raise IncompatibleWidgets()
|
|
129
|
+
bind(self, other, 'dateFrom')
|
|
130
|
+
bind(self, other, 'dateTo')
|
|
131
|
+
self.datesChanged.connect(other.setDates)
|
|
132
|
+
other.datesChanged.connect(self.setDates)
|
|
133
|
+
|
|
134
|
+
def unlink(self, other):
|
|
135
|
+
unbind(self, other, 'dateFrom')
|
|
136
|
+
unbind(self, other, 'dateTo')
|
|
137
|
+
self.datesChanged.disconnect(other.setDates)
|
|
138
|
+
other.datesChanged.disconnect(self.setDates)
|
|
139
|
+
|
|
140
|
+
def setDates(self, dateFrom, dateTo):
|
|
141
|
+
if self.dateFrom != dateFrom or self.dateTo != dateTo:
|
|
142
|
+
signals_blocked = self.blockSignals(True)
|
|
143
|
+
self.dateFrom = dateFrom
|
|
144
|
+
self.dateTo = dateTo
|
|
145
|
+
self.blockSignals(signals_blocked)
|
|
146
|
+
self.datesChanged.emit(dateFrom, dateTo)
|
|
147
|
+
|
|
148
|
+
def setDateRange(self, start_date, end_date):
|
|
149
|
+
"""function::setDateRange(self, start_data, end_date)
|
|
150
|
+
Set the date range for this widget
|
|
151
|
+
"""
|
|
152
|
+
self._from_selector.setDateTimeRange(start_date, end_date)
|
|
153
|
+
self._to_selector.setDateTimeRange(start_date, end_date)
|
|
154
|
+
self._date_range_start = start_date
|
|
155
|
+
self._date_range_end = end_date
|
|
156
|
+
|
|
157
|
+
def setInterval(self, interval):
|
|
158
|
+
self._interval = interval
|
|
159
|
+
|
|
160
|
+
if interval < timedelta(seconds=1):
|
|
161
|
+
display_format = 'dd-MMM-yyyy hh:mm:ss.zz'
|
|
162
|
+
elif interval < timedelta(minutes=1):
|
|
163
|
+
display_format = 'dd-MMM-yyyy hh:mm:ss'
|
|
164
|
+
else:
|
|
165
|
+
display_format = 'dd-MMM-yyyy hh:mm'
|
|
166
|
+
|
|
167
|
+
for selector in self._from_selector, self._to_selector:
|
|
168
|
+
selector.setDisplayFormat(display_format)
|
|
169
|
+
|
|
170
|
+
@auto_property(QDateTime)
|
|
171
|
+
def dateFrom(self):
|
|
172
|
+
"""function::dateFrom(self)
|
|
173
|
+
Auto property accessor for dateFrom
|
|
174
|
+
"""
|
|
175
|
+
return self._from_selector.dateTime()
|
|
176
|
+
|
|
177
|
+
@dateFrom.setter
|
|
178
|
+
def dateFrom(self, value):
|
|
179
|
+
"""function::dateFrom(self, value)
|
|
180
|
+
Auto property setter for dateFrom
|
|
181
|
+
"""
|
|
182
|
+
value = value or QDateTime()
|
|
183
|
+
if self._from_selector.dateTime() != value:
|
|
184
|
+
self._from_selector.setDateTime(value)
|
|
185
|
+
self.dateFromChanged.emit(value)
|
|
186
|
+
|
|
187
|
+
@auto_property(QDateTime)
|
|
188
|
+
def dateTo(self):
|
|
189
|
+
"""function::dateTo(self)
|
|
190
|
+
Auto property accessor for dateTo
|
|
191
|
+
"""
|
|
192
|
+
return self._to_selector.dateTime()
|
|
193
|
+
|
|
194
|
+
@dateTo.setter
|
|
195
|
+
def dateTo(self, value):
|
|
196
|
+
"""function::dateTo(self, value)
|
|
197
|
+
Auto property setter for dateTo
|
|
198
|
+
"""
|
|
199
|
+
value = value or QDateTime()
|
|
200
|
+
if self._to_selector.dateTime() != value:
|
|
201
|
+
self._to_selector.setDateTime(value)
|
|
202
|
+
self.dateToChanged.emit(value)
|
|
203
|
+
|
|
204
|
+
def sizeHint(self):
|
|
205
|
+
"""function::sizeHint(self)
|
|
206
|
+
Override the QWidget.sizeHint method to provide a size hint
|
|
207
|
+
"""
|
|
208
|
+
return self._layout.sizeHint()
|
|
209
|
+
|
|
210
|
+
def _date_from_editing_finished(self):
|
|
211
|
+
value = self._from_selector.dateTime()
|
|
212
|
+
if self._update_limits_on_value_change:
|
|
213
|
+
self._to_selector.setMinimumDateTime(value)
|
|
214
|
+
self.dateFromChanged.emit(value)
|
|
215
|
+
|
|
216
|
+
def _date_to_editing_finished(self):
|
|
217
|
+
value = self._to_selector.dateTime()
|
|
218
|
+
if self._update_limits_on_value_change:
|
|
219
|
+
self._from_selector.setMaximumDateTime(value)
|
|
220
|
+
self.dateToChanged.emit(value)
|
|
221
|
+
|
|
222
|
+
def _step(self, fraction):
|
|
223
|
+
def _():
|
|
224
|
+
signals_blocked = self.blockSignals(True)
|
|
225
|
+
step_qdatetime(self, fraction, self._interval, self._date_range_start, self._date_range_end)
|
|
226
|
+
self.blockSignals(signals_blocked)
|
|
227
|
+
self.datesChanged.emit(self.dateFrom, self.dateTo)
|
|
228
|
+
return _
|
|
229
|
+
|
|
230
|
+
def step(self, fraction):
|
|
231
|
+
return self._step(fraction)()
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# pyqttoolkit
|
|
2
|
+
# Copyright (C) 2018-2019, Simmovation Ltd.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, write to the Free Software Foundation,
|
|
16
|
+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
from PyQt5.QtCore import pyqtSignal
|
|
18
|
+
#pylint: enable=no-name-in-module
|
|
19
|
+
|
|
20
|
+
from pyqttoolkit.properties import AutoProperty
|
|
21
|
+
from pyqttoolkit.views import LineEdit
|
|
22
|
+
|
|
23
|
+
class DefaultTextLineEdit(LineEdit):
|
|
24
|
+
def __init__(self, parent):
|
|
25
|
+
LineEdit.__init__(self, parent)
|
|
26
|
+
self.editComplete.connect(self._update_name)
|
|
27
|
+
self.nameChanged.connect(self._handle_name_changed)
|
|
28
|
+
self.defaultNameChanged.connect(self._handle_name_changed)
|
|
29
|
+
|
|
30
|
+
defaultNameChanged = pyqtSignal(str)
|
|
31
|
+
nameChanged = pyqtSignal(str)
|
|
32
|
+
|
|
33
|
+
defaultName = AutoProperty(str)
|
|
34
|
+
name = AutoProperty(str)
|
|
35
|
+
|
|
36
|
+
def focusInEvent(self, event):
|
|
37
|
+
self._handle_interaction()
|
|
38
|
+
LineEdit.focusInEvent(self, event)
|
|
39
|
+
|
|
40
|
+
def _update_name(self, value):
|
|
41
|
+
self.name = value
|
|
42
|
+
|
|
43
|
+
def _handle_name_changed(self):
|
|
44
|
+
self.setText(self.name)
|
|
45
|
+
if not self.name:
|
|
46
|
+
self.setPlaceholderText(self.defaultName)
|
|
47
|
+
|
|
48
|
+
def _handle_interaction(self):
|
|
49
|
+
if not self.name:
|
|
50
|
+
self.name = self.defaultName
|
|
@@ -0,0 +1,117 @@
|
|
|
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 QStringListModel, Qt, QDateTime
|
|
20
|
+
from PyQt5.QtWidgets import QStyledItemDelegate, QTableView, QWidget, QHBoxLayout, QPushButton
|
|
21
|
+
|
|
22
|
+
from pyqttoolkit.models.roles import DataRole, EditorAuxDataRole
|
|
23
|
+
from pyqttoolkit.views import BindableComboBox, DateTimeEdit, BulkValueSelectorWidget, Popup
|
|
24
|
+
|
|
25
|
+
class ComboBoxItemDelegate(QStyledItemDelegate):
|
|
26
|
+
def __init__(self, parent):
|
|
27
|
+
super().__init__(parent)
|
|
28
|
+
|
|
29
|
+
def setComboBoxModel(self, model):
|
|
30
|
+
self._combo_box_model = model
|
|
31
|
+
|
|
32
|
+
def createEditor(self, parent, option, index):
|
|
33
|
+
combo_box = BindableComboBox(parent)
|
|
34
|
+
combo_box.setModel(self._combo_box_model)
|
|
35
|
+
combo_box.move(parent.pos())
|
|
36
|
+
value = self.parent().model().data(index, DataRole)
|
|
37
|
+
if isinstance(self.parent(), QTableView):
|
|
38
|
+
combo_box.setFixedWidth(self.parent().columnWidth(index.column()) * self.parent().columnSpan(index.row(), index.column()))
|
|
39
|
+
combo_box.setFixedHeight(self.parent().rowHeight(index.row()) * self.parent().rowSpan(index.row(), index.column()))
|
|
40
|
+
else:
|
|
41
|
+
combo_box.setFixedWidth(self.parent().columnWidth(index.column()))
|
|
42
|
+
combo_box.setFixedHeight(self.parent().rowHeight(index))
|
|
43
|
+
if isinstance(value, Enum):
|
|
44
|
+
value = value.name
|
|
45
|
+
combo_box.value = value
|
|
46
|
+
combo_box.currentIndexChanged.connect(self.closeEditor(combo_box))
|
|
47
|
+
return combo_box
|
|
48
|
+
|
|
49
|
+
def setEditorData(self, editor, index):
|
|
50
|
+
editor.showPopup()
|
|
51
|
+
|
|
52
|
+
def closeEditor(self, control):
|
|
53
|
+
def _handler():
|
|
54
|
+
self.parent().commitData(control)
|
|
55
|
+
self.parent().closeEditor(control, QStyledItemDelegate.NoHint)
|
|
56
|
+
return _handler
|
|
57
|
+
|
|
58
|
+
class DateTimeItemDelegate(QStyledItemDelegate):
|
|
59
|
+
def __init__(self, parent):
|
|
60
|
+
super().__init__(parent)
|
|
61
|
+
self._popup = None
|
|
62
|
+
|
|
63
|
+
def createEditor(self, parent, option, index):
|
|
64
|
+
date_range, default = self.parent().model().data(index, EditorAuxDataRole)
|
|
65
|
+
editor = DateTimeEdit(parent, default)
|
|
66
|
+
editor.setDateTimeRange(*date_range)
|
|
67
|
+
value = self.parent().model().data(index, DataRole)
|
|
68
|
+
editor.value = QDateTime(value) if value is not None else None
|
|
69
|
+
if default is not None:
|
|
70
|
+
reset_button_text = self.tr('Set to Start') if default == DateTimeEdit.Limit.min else self.tr('Set to End')
|
|
71
|
+
start_end_buttons = QWidget(parent)
|
|
72
|
+
start_end_buttons_layout = QHBoxLayout(start_end_buttons)
|
|
73
|
+
start_end_buttons_layout.setContentsMargins(0, 0, 0, 0)
|
|
74
|
+
button = QPushButton(reset_button_text, start_end_buttons)
|
|
75
|
+
button.clicked.connect(editor.reset)
|
|
76
|
+
start_end_buttons_layout.addWidget(button)
|
|
77
|
+
self._popup = Popup(editor, start_end_buttons)
|
|
78
|
+
|
|
79
|
+
return editor
|
|
80
|
+
|
|
81
|
+
def setEditorData(self, editor, index):
|
|
82
|
+
if self._popup is not None:
|
|
83
|
+
cell_rect = editor.rect()
|
|
84
|
+
self._popup.popupAtPosition(editor, cell_rect.bottomLeft())
|
|
85
|
+
return super().setEditorData(editor, index)
|
|
86
|
+
|
|
87
|
+
def setModelData(self, editor, model, index):
|
|
88
|
+
if self._popup is not None:
|
|
89
|
+
self._popup.close()
|
|
90
|
+
self._popup = None
|
|
91
|
+
model.setData(index, editor.value, Qt.EditRole)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class BulkValueSelectorItemDelegate(QStyledItemDelegate):
|
|
95
|
+
def __init__(self, parent):
|
|
96
|
+
super().__init__(parent)
|
|
97
|
+
|
|
98
|
+
def createEditor(self, parent, option, index):
|
|
99
|
+
editor = BulkValueSelectorWidget(parent)
|
|
100
|
+
editor.setFixedHeight(int(self.parent().height() * 0.8))
|
|
101
|
+
editor.setFixedWidth(200)
|
|
102
|
+
editor.values = QStringListModel(self.parent().model().data(index, EditorAuxDataRole), self)
|
|
103
|
+
editor.selectedValues = self.parent().model().data(index, DataRole)
|
|
104
|
+
popup = Popup(parent, editor)
|
|
105
|
+
editor.dataCommitted.connect(self.closePopup(popup))
|
|
106
|
+
return popup
|
|
107
|
+
|
|
108
|
+
def setEditorData(self, editor, index):
|
|
109
|
+
editor.popupAtPosition(self.parent(), self.parent().visualRect(index))
|
|
110
|
+
|
|
111
|
+
def setModelData(self, editor, model, index):
|
|
112
|
+
model.setData(index, sorted(editor.contents.selectedValues))
|
|
113
|
+
|
|
114
|
+
def closePopup(self, popup):
|
|
115
|
+
def _handler(selected_values):
|
|
116
|
+
self.parent().closeEditor(popup, QStyledItemDelegate.NoHint)
|
|
117
|
+
return _handler
|