processview 1.5.1__tar.gz → 1.5.2__tar.gz
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.
- {processview-1.5.1 → processview-1.5.2}/PKG-INFO +1 -1
- processview-1.5.2/example/show_processmanagerwindow.py +118 -0
- {processview-1.5.1 → processview-1.5.2}/processview/core/sorting.py +1 -1
- processview-1.5.2/processview/gui/processmanager/ObservationTable.py +137 -0
- processview-1.5.2/processview/gui/processmanager/ProcessManager.py +83 -0
- processview-1.5.2/processview/gui/processmanager/ProcessManagerWidget.py +83 -0
- processview-1.5.2/processview/gui/processmanager/ProcessManagerWindow.py +17 -0
- processview-1.5.2/processview/gui/processmanager/_DatasetProcessModel.py +215 -0
- processview-1.5.2/processview/gui/processmanager/_FilterWidget.py +102 -0
- processview-1.5.2/processview/gui/processmanager/_OptionsWidget.py +36 -0
- processview-1.5.2/processview/gui/processmanager/_OrderingWidget.py +38 -0
- processview-1.5.2/processview/gui/processmanager/__init__.py +2 -0
- {processview-1.5.1 → processview-1.5.2}/processview/gui/processmanager.py +22 -21
- processview-1.5.2/processview/gui/utils/qitem_model_resetter.py +17 -0
- processview-1.5.2/processview/resources/gui/__init__.py +0 -0
- {processview-1.5.1 → processview-1.5.2}/processview/version.py +1 -1
- {processview-1.5.1 → processview-1.5.2}/processview.egg-info/PKG-INFO +1 -1
- {processview-1.5.1 → processview-1.5.2}/processview.egg-info/SOURCES.txt +12 -0
- {processview-1.5.1 → processview-1.5.2}/LICENSE +0 -0
- {processview-1.5.1 → processview-1.5.2}/README.md +0 -0
- {processview-1.5.1 → processview-1.5.2}/processview/__init__.py +0 -0
- {processview-1.5.1 → processview-1.5.2}/processview/core/__init__.py +0 -0
- {processview-1.5.1 → processview-1.5.2}/processview/core/dataset.py +0 -0
- {processview-1.5.1 → processview-1.5.2}/processview/core/helpers.py +0 -0
- {processview-1.5.1 → processview-1.5.2}/processview/core/manager/__init__.py +0 -0
- {processview-1.5.1 → processview-1.5.2}/processview/core/manager/manager.py +0 -0
- {processview-1.5.1 → processview-1.5.2}/processview/core/manager/test/__init__.py +0 -0
- {processview-1.5.1 → processview-1.5.2}/processview/core/manager/test/test_manager.py +0 -0
- {processview-1.5.1 → processview-1.5.2}/processview/core/setup.py +0 -0
- {processview-1.5.1 → processview-1.5.2}/processview/core/superviseprocess.py +0 -0
- {processview-1.5.1 → processview-1.5.2}/processview/core/test/__init__.py +0 -0
- {processview-1.5.1 → processview-1.5.2}/processview/gui/DropDownWidget.py +0 -0
- {processview-1.5.1 → processview-1.5.2}/processview/gui/__init__.py +0 -0
- {processview-1.5.1 → processview-1.5.2}/processview/gui/messagebox.py +0 -0
- {processview-1.5.1 → processview-1.5.2}/processview/gui/test/__init__.py +0 -0
- {processview-1.5.1 → processview-1.5.2}/processview/gui/test/test_process_manager.py +0 -0
- {processview-1.5.1/processview/resources → processview-1.5.2/processview/gui/utils}/__init__.py +0 -0
- {processview-1.5.1/processview/resources/gui → processview-1.5.2/processview/resources}/__init__.py +0 -0
- {processview-1.5.1 → processview-1.5.2}/processview/resources/gui/icons/advancement.png +0 -0
- {processview-1.5.1 → processview-1.5.2}/processview/resources/gui/icons/advancement.svg +0 -0
- {processview-1.5.1 → processview-1.5.2}/processview/resources/gui/icons/magnifying_glass.png +0 -0
- {processview-1.5.1 → processview-1.5.2}/processview/resources/gui/icons/magnifying_glass.svg +0 -0
- {processview-1.5.1 → processview-1.5.2}/processview/test/__init__.py +0 -0
- {processview-1.5.1 → processview-1.5.2}/processview/utils/__init__.py +0 -0
- {processview-1.5.1 → processview-1.5.2}/processview/utils/singleton.py +0 -0
- {processview-1.5.1 → processview-1.5.2}/processview.egg-info/dependency_links.txt +0 -0
- {processview-1.5.1 → processview-1.5.2}/processview.egg-info/requires.txt +0 -0
- {processview-1.5.1 → processview-1.5.2}/processview.egg-info/top_level.txt +0 -0
- {processview-1.5.1 → processview-1.5.2}/processview.egg-info/zip-safe +0 -0
- {processview-1.5.1 → processview-1.5.2}/setup.cfg +0 -0
- {processview-1.5.1 → processview-1.5.2}/setup.py +0 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
import numpy
|
|
5
|
+
import threading
|
|
6
|
+
from silx.gui import qt
|
|
7
|
+
from processview.gui.processmanager import ProcessManagerWindow
|
|
8
|
+
from processview.core.superviseprocess import SuperviseProcess
|
|
9
|
+
from processview.core.dataset import Dataset
|
|
10
|
+
from processview.core.dataset import DatasetIdentifier
|
|
11
|
+
from processview.core.manager import DatasetState
|
|
12
|
+
from processview.gui.processmanager import ProcessManager
|
|
13
|
+
import datetime
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class _DummyDataset(Dataset):
|
|
17
|
+
def __init__(self, name):
|
|
18
|
+
super().__init__()
|
|
19
|
+
self.__name = name
|
|
20
|
+
|
|
21
|
+
def __str__(self) -> str:
|
|
22
|
+
return self.__name
|
|
23
|
+
|
|
24
|
+
def get_identifier(self) -> DatasetIdentifier:
|
|
25
|
+
return _DummyIdentifier(
|
|
26
|
+
self,
|
|
27
|
+
metadata={
|
|
28
|
+
"name": self.__name,
|
|
29
|
+
"creation_time": datetime.datetime.now(),
|
|
30
|
+
"modification_time": datetime.datetime.now(),
|
|
31
|
+
},
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class _DummyIdentifier(DatasetIdentifier):
|
|
36
|
+
def to_str(self):
|
|
37
|
+
return str(self)
|
|
38
|
+
|
|
39
|
+
def __str__(self):
|
|
40
|
+
return self.name()
|
|
41
|
+
|
|
42
|
+
def __eq__(self, other):
|
|
43
|
+
return self.name() == other.name()
|
|
44
|
+
|
|
45
|
+
def __hash__(self):
|
|
46
|
+
return hash(self.name())
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
app = qt.QApplication([])
|
|
50
|
+
|
|
51
|
+
window = ProcessManagerWindow(parent=None)
|
|
52
|
+
window.show()
|
|
53
|
+
|
|
54
|
+
p1 = SuperviseProcess(name="process1")
|
|
55
|
+
p2 = SuperviseProcess(name="process2")
|
|
56
|
+
p3 = SuperviseProcess(name="process3")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
manager = ProcessManager()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class RecursiveThread(threading.Thread):
|
|
63
|
+
def __init__(self, execute_each: int):
|
|
64
|
+
self.running = True
|
|
65
|
+
self.execute_each = execute_each
|
|
66
|
+
super().__init__()
|
|
67
|
+
|
|
68
|
+
def run(self):
|
|
69
|
+
"""Method implementing thread loop that updates the plot"""
|
|
70
|
+
while self.running:
|
|
71
|
+
time.sleep(self.execute_each)
|
|
72
|
+
self.process()
|
|
73
|
+
|
|
74
|
+
def process(self):
|
|
75
|
+
raise NotImplementedError()
|
|
76
|
+
|
|
77
|
+
def stop(self):
|
|
78
|
+
"""Stop the update thread"""
|
|
79
|
+
self.running = False
|
|
80
|
+
self.join(2)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class CreateNewDataset(RecursiveThread):
|
|
84
|
+
"""Thread creating a new dataset each n seconds"""
|
|
85
|
+
|
|
86
|
+
def process(self):
|
|
87
|
+
dataset = _DummyDataset(f"scan {numpy.random.randint(0, 999999)}")
|
|
88
|
+
manager.notify_dataset_state(
|
|
89
|
+
dataset=dataset, state=DatasetState.PENDING, process=p1
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class UpdateDataset(RecursiveThread):
|
|
94
|
+
"""Thread that will update randomly one of the existing dataset"""
|
|
95
|
+
|
|
96
|
+
def process(self):
|
|
97
|
+
datasets = manager.get_datasets()
|
|
98
|
+
if len(datasets) == 0:
|
|
99
|
+
return
|
|
100
|
+
dataset_to_update = numpy.random.choice(datasets)
|
|
101
|
+
state = numpy.random.choice(DatasetState)
|
|
102
|
+
process = numpy.random.choice(manager.get_processes())
|
|
103
|
+
manager.notify_dataset_state(
|
|
104
|
+
dataset=dataset_to_update,
|
|
105
|
+
state=state,
|
|
106
|
+
process=process,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
create_new_dataset_thread = CreateNewDataset(execute_each=3)
|
|
111
|
+
create_new_dataset_thread.start()
|
|
112
|
+
update_dataset_thread = UpdateDataset(execute_each=1)
|
|
113
|
+
update_dataset_thread.start()
|
|
114
|
+
|
|
115
|
+
app.exec_()
|
|
116
|
+
|
|
117
|
+
create_new_dataset_thread.stop()
|
|
118
|
+
update_dataset_thread.stop()
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from silx.gui import qt
|
|
4
|
+
from processview.gui.messagebox import MessageBox
|
|
5
|
+
from processview.core.manager import ProcessManager as _ProcessManager
|
|
6
|
+
from processview.core.superviseprocess import SuperviseProcess
|
|
7
|
+
from processview.core.dataset import DatasetIdentifier
|
|
8
|
+
|
|
9
|
+
from .ProcessManager import ProcessManager
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ObservationTable(qt.QTableView):
|
|
13
|
+
"""
|
|
14
|
+
Redefinition of QTableView for datasets and processes
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, parent):
|
|
18
|
+
qt.QTableView.__init__(self, parent)
|
|
19
|
+
self.verticalHeader().setSectionsClickable(True)
|
|
20
|
+
|
|
21
|
+
# QMenu for the dataset name
|
|
22
|
+
self.dataset_menu = qt.QMenu()
|
|
23
|
+
self._copyAction = qt.QAction("copy")
|
|
24
|
+
self.dataset_menu.addAction(self._copyAction)
|
|
25
|
+
self._removeAction = qt.QAction("remove")
|
|
26
|
+
self.dataset_menu.addAction(self._removeAction)
|
|
27
|
+
|
|
28
|
+
# QMenu for cell from on dataset and one process
|
|
29
|
+
self.menu_dataset_vs_process = qt.QMenu()
|
|
30
|
+
self._reprocessAction = qt.QAction("reprocess")
|
|
31
|
+
self.menu_dataset_vs_process.addAction(self._reprocessAction)
|
|
32
|
+
self._cancelAction = qt.QAction("cancel")
|
|
33
|
+
self.menu_dataset_vs_process.addAction(self._cancelAction)
|
|
34
|
+
self._infoAction = qt.QAction("info")
|
|
35
|
+
self.menu_dataset_vs_process.addAction(self._infoAction)
|
|
36
|
+
self.menu_dataset_vs_process.addAction(self._removeAction)
|
|
37
|
+
|
|
38
|
+
self._target = (None, None)
|
|
39
|
+
# register target of the last menu (process, DatasetIdentifier)
|
|
40
|
+
|
|
41
|
+
# connect signal / slot
|
|
42
|
+
self._copyAction.triggered.connect(self._requestDatasetIdCopy)
|
|
43
|
+
self._removeAction.triggered.connect(self._requestRemoveDataset)
|
|
44
|
+
self._reprocessAction.triggered.connect(self._requestReprocessing)
|
|
45
|
+
self._infoAction.triggered.connect(self._requestInfo)
|
|
46
|
+
self._cancelAction.triggered.connect(self._requestCancelProcessing)
|
|
47
|
+
|
|
48
|
+
def _processAt(self, x_pos):
|
|
49
|
+
column = self.columnAt(x_pos)
|
|
50
|
+
if column >= 1:
|
|
51
|
+
processes = self.model()._processes
|
|
52
|
+
process_idx = column - 1
|
|
53
|
+
if process_idx < len(processes):
|
|
54
|
+
return processes[list(processes.keys())[process_idx]]
|
|
55
|
+
|
|
56
|
+
def _datasetAt(self, y_pos):
|
|
57
|
+
row = self.rowAt(y_pos)
|
|
58
|
+
if row >= 0:
|
|
59
|
+
datasets = self.model()._sorted_datasets
|
|
60
|
+
if row < len(datasets):
|
|
61
|
+
return datasets[list(datasets.keys())[row]]
|
|
62
|
+
|
|
63
|
+
def contextMenuEvent(self, event):
|
|
64
|
+
row = self.columnAt(event.pos().x())
|
|
65
|
+
dataset = self._datasetAt(event.pos().y())
|
|
66
|
+
if row == 0:
|
|
67
|
+
# handle column column
|
|
68
|
+
self._target = (None, dataset)
|
|
69
|
+
self.dataset_menu.exec_(event.globalPos())
|
|
70
|
+
else:
|
|
71
|
+
# handle processes column
|
|
72
|
+
process = self._processAt(event.pos().x())
|
|
73
|
+
if (
|
|
74
|
+
process is not None
|
|
75
|
+
and dataset is not None
|
|
76
|
+
and _ProcessManager().met(process=process, dataset=dataset)
|
|
77
|
+
):
|
|
78
|
+
self._target = (process, dataset)
|
|
79
|
+
self.menu_dataset_vs_process.exec_(event.globalPos())
|
|
80
|
+
else:
|
|
81
|
+
self._target = (None, None)
|
|
82
|
+
super().contextMenuEvent(event)
|
|
83
|
+
|
|
84
|
+
def _requestReprocessing(self, *args, **kwargs):
|
|
85
|
+
process, dataset = self._target
|
|
86
|
+
|
|
87
|
+
if process is not None and dataset is not None:
|
|
88
|
+
assert isinstance(process, SuperviseProcess)
|
|
89
|
+
assert isinstance(dataset, DatasetIdentifier)
|
|
90
|
+
process.reprocess(dataset.recreate_dataset())
|
|
91
|
+
|
|
92
|
+
def _requestCancelProcessing(self, *args, **kwargs):
|
|
93
|
+
process, dataset = self._target
|
|
94
|
+
if process is not None and dataset is not None:
|
|
95
|
+
assert isinstance(process, SuperviseProcess)
|
|
96
|
+
assert isinstance(dataset, DatasetIdentifier)
|
|
97
|
+
process.cancel(dataset.recreate_dataset())
|
|
98
|
+
|
|
99
|
+
def _requestDatasetIdCopy(self, *args, **kwargs):
|
|
100
|
+
_, dataset = self._target
|
|
101
|
+
if dataset is not None:
|
|
102
|
+
clipboard = qt.QGuiApplication.clipboard()
|
|
103
|
+
clipboard.setText(dataset.to_str())
|
|
104
|
+
|
|
105
|
+
def _requestRemoveDataset(self, *args, **kwargs):
|
|
106
|
+
def get_dataset_at(row: int):
|
|
107
|
+
datasets = self.model()._sorted_datasets
|
|
108
|
+
return datasets.get(
|
|
109
|
+
list(datasets.keys())[row],
|
|
110
|
+
None,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
datasets = [get_dataset_at(index.row()) for index in self.selectedIndexes()]
|
|
114
|
+
[ProcessManager().remove_dataset(dataset) for dataset in datasets]
|
|
115
|
+
self.model().remove_datasets(datasets)
|
|
116
|
+
|
|
117
|
+
def _requestInfo(self, *args, **kwargs):
|
|
118
|
+
process, dataset = self._target
|
|
119
|
+
if process is not None and dataset is not None:
|
|
120
|
+
infos = ProcessManager().get_dataset_details(
|
|
121
|
+
dataset=dataset, process=process
|
|
122
|
+
)
|
|
123
|
+
if infos in (None, ""):
|
|
124
|
+
infos = "No extra information provided"
|
|
125
|
+
|
|
126
|
+
msg = MessageBox(self)
|
|
127
|
+
msg.setInfos(infos=infos)
|
|
128
|
+
extra_info = "{} processing {}".format(process.name, dataset)
|
|
129
|
+
msg.setWindowTitle(extra_info)
|
|
130
|
+
msg.setWindowModality(qt.Qt.NonModal)
|
|
131
|
+
msg.show()
|
|
132
|
+
|
|
133
|
+
def sizeHintForColumn(self, column):
|
|
134
|
+
if column == 0:
|
|
135
|
+
return 350
|
|
136
|
+
else:
|
|
137
|
+
return super().sizeHintForColumn(column)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from silx.gui import qt
|
|
4
|
+
from processview.core.manager import ProcessManager as _ProcessManager
|
|
5
|
+
from processview.core.superviseprocess import SuperviseProcess
|
|
6
|
+
from processview.utils import docstring
|
|
7
|
+
from processview.core.manager import DatasetState
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ProcessManager(qt.QObject):
|
|
11
|
+
sigUpdated = qt.Signal()
|
|
12
|
+
"""Signal emitted when the state of some process / dataset is updated
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
sigNewProcessRegistered = qt.Signal()
|
|
16
|
+
"""Signal emitted when a new process is registered"""
|
|
17
|
+
|
|
18
|
+
sigProcessUnregistered = qt.Signal()
|
|
19
|
+
"""Signal emitted when a process is unregistered"""
|
|
20
|
+
|
|
21
|
+
sigProcessRenamed = qt.Signal(SuperviseProcess)
|
|
22
|
+
"""Emit when the process is renamed"""
|
|
23
|
+
|
|
24
|
+
def __init__(self):
|
|
25
|
+
qt.QObject.__init__(self)
|
|
26
|
+
self.manager = _ProcessManager()
|
|
27
|
+
|
|
28
|
+
# monkey patch manager updated function
|
|
29
|
+
# TODO: add / remove callback would be simpler
|
|
30
|
+
self.manager.add_update_callback(self.updated)
|
|
31
|
+
self.manager.add_new_process_callback(self.processAdded)
|
|
32
|
+
self.manager.add_process_removed_callback(self.processRemoved)
|
|
33
|
+
self.manager.add_process_name_changed_callback(self.processRenamed)
|
|
34
|
+
|
|
35
|
+
def updated(self):
|
|
36
|
+
self.sigUpdated.emit()
|
|
37
|
+
|
|
38
|
+
def processAdded(self):
|
|
39
|
+
self.sigNewProcessRegistered.emit()
|
|
40
|
+
|
|
41
|
+
def processRemoved(self):
|
|
42
|
+
self.sigProcessUnregistered.emit()
|
|
43
|
+
|
|
44
|
+
def processRenamed(self, process):
|
|
45
|
+
self.sigProcessRenamed.emit(process)
|
|
46
|
+
|
|
47
|
+
def destroyed(self, object_):
|
|
48
|
+
self.manager.remove_update_callback(self.updated)
|
|
49
|
+
qt.QObject.destroyed(object_)
|
|
50
|
+
|
|
51
|
+
# expose some of the original ProcessManager API
|
|
52
|
+
@docstring(_ProcessManager)
|
|
53
|
+
def notify_dataset_state(self, dataset, process, state, details=None) -> None:
|
|
54
|
+
self.manager.notify_dataset_state(
|
|
55
|
+
dataset=dataset, process=process, state=state, details=details
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
@docstring(_ProcessManager)
|
|
59
|
+
def get_dataset_state(self, dataset, process) -> None | DatasetState:
|
|
60
|
+
return self.manager.get_dataset_state(dataset_id=dataset, process=process)
|
|
61
|
+
|
|
62
|
+
@docstring(_ProcessManager)
|
|
63
|
+
def get_dataset_details(self, dataset, process) -> None | DatasetState:
|
|
64
|
+
return self.manager.get_dataset_details(dataset_id=dataset, process=process)
|
|
65
|
+
|
|
66
|
+
@docstring(_ProcessManager)
|
|
67
|
+
def get_dataset_stream(self, dataset, time_stamp=False) -> tuple:
|
|
68
|
+
return self.manager.get_dataset_stream(dataset=dataset, time_stamp=time_stamp)
|
|
69
|
+
|
|
70
|
+
@docstring(_ProcessManager)
|
|
71
|
+
def get_process_history(self, process, time_stamp=False) -> tuple:
|
|
72
|
+
return self.manager.get_process_history(process=process, time_stamp=time_stamp)
|
|
73
|
+
|
|
74
|
+
@docstring(_ProcessManager)
|
|
75
|
+
def get_processes(self):
|
|
76
|
+
return self.manager.get_processes()
|
|
77
|
+
|
|
78
|
+
@docstring(_ProcessManager)
|
|
79
|
+
def get_datasets(self):
|
|
80
|
+
return self.manager.get_datasets()
|
|
81
|
+
|
|
82
|
+
def remove_dataset(self, *args, **kwargs):
|
|
83
|
+
return self.manager.remove_dataset(*args, **kwargs)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from silx.gui import qt
|
|
4
|
+
from processview.core.superviseprocess import SuperviseProcess
|
|
5
|
+
|
|
6
|
+
from .ProcessManager import ProcessManager
|
|
7
|
+
from ._OptionsWidget import OptionsWidget
|
|
8
|
+
from ..DropDownWidget import DropDownWidget
|
|
9
|
+
from .ObservationTable import ObservationTable
|
|
10
|
+
from ._DatasetProcessModel import DatasetProcessModel
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ProcessManagerWidget(qt.QWidget):
|
|
14
|
+
"""
|
|
15
|
+
Main widget to display dataset vs process metadata
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, parent):
|
|
19
|
+
qt.QWidget.__init__(self, parent)
|
|
20
|
+
self.setLayout(qt.QVBoxLayout())
|
|
21
|
+
|
|
22
|
+
self._manager = ProcessManager()
|
|
23
|
+
|
|
24
|
+
self._optionsWidget = OptionsWidget(parent=self)
|
|
25
|
+
self._dropDownOptionsWidget = DropDownWidget(parent=self)
|
|
26
|
+
self._dropDownOptionsWidget.setWidget(self._optionsWidget)
|
|
27
|
+
|
|
28
|
+
self._dropDownOptionsWidget.setSizePolicy(
|
|
29
|
+
qt.QSizePolicy.Minimum, qt.QSizePolicy.Minimum
|
|
30
|
+
)
|
|
31
|
+
self.setContentsMargins(0, 0, 0, 0)
|
|
32
|
+
self.layout().setContentsMargins(0, 0, 0, 0)
|
|
33
|
+
self.layout().setSpacing(2)
|
|
34
|
+
self.layout().addWidget(self._dropDownOptionsWidget)
|
|
35
|
+
|
|
36
|
+
self.observationTable = ObservationTable(self)
|
|
37
|
+
|
|
38
|
+
self.layout().addWidget(self.observationTable)
|
|
39
|
+
self.observationTable.setModel(
|
|
40
|
+
DatasetProcessModel(parent=self.observationTable, header=tuple())
|
|
41
|
+
)
|
|
42
|
+
self.observationTable.resizeColumnsToContents()
|
|
43
|
+
self.observationTable.setSortingEnabled(True)
|
|
44
|
+
self.setSizePolicy(qt.QSizePolicy.Expanding, qt.QSizePolicy.Expanding)
|
|
45
|
+
|
|
46
|
+
# connect signal / slot
|
|
47
|
+
self._manager.sigUpdated.connect(self._updateDatasetStates)
|
|
48
|
+
self._manager.sigNewProcessRegistered.connect(self._updateProcesses)
|
|
49
|
+
self._optionsWidget.filterWidget.sigDatasetPatternEditingFinished.connect(
|
|
50
|
+
self._filterUpdated
|
|
51
|
+
)
|
|
52
|
+
self._optionsWidget.filterWidget.sigProcessPatternEditingFinished.connect(
|
|
53
|
+
self._filterUpdated
|
|
54
|
+
)
|
|
55
|
+
self._manager.sigProcessUnregistered.connect(self._updateProcesses)
|
|
56
|
+
self._optionsWidget._orderingWidget.sigSortTypeChanged.connect(
|
|
57
|
+
self.observationTable.model()._setSorting
|
|
58
|
+
)
|
|
59
|
+
self._manager.sigProcessRenamed.connect(self._renameProcess)
|
|
60
|
+
# update to fit existing processes / datasets
|
|
61
|
+
self._updateProcesses()
|
|
62
|
+
self._updateDatasetStates()
|
|
63
|
+
|
|
64
|
+
def _updateDatasetStates(self):
|
|
65
|
+
self.observationTable.model().setDatasets(self._manager.get_datasets())
|
|
66
|
+
|
|
67
|
+
def _updateProcesses(self):
|
|
68
|
+
self.observationTable.model().setProcesses(self._manager.get_processes())
|
|
69
|
+
|
|
70
|
+
def _renameProcess(self, process: SuperviseProcess):
|
|
71
|
+
self.observationTable.model().headerDataChanged.emit(
|
|
72
|
+
qt.Qt.Horizontal, 0, len(self._manager.get_processes())
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def _filterUpdated(self):
|
|
76
|
+
self.observationTable.model().process_patterns = (
|
|
77
|
+
self._dropDownOptionsWidget.getProcessPatterns()
|
|
78
|
+
)
|
|
79
|
+
self._updateProcesses()
|
|
80
|
+
self.observationTable.model().dataset_patterns = (
|
|
81
|
+
self._dropDownOptionsWidget.getDatasetPatterns()
|
|
82
|
+
)
|
|
83
|
+
self._updateDatasetStates()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from silx.gui import qt
|
|
4
|
+
from .ProcessManagerWidget import ProcessManagerWidget
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ProcessManagerWindow(qt.QMainWindow):
|
|
8
|
+
"""
|
|
9
|
+
Main window of the process manager
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, parent):
|
|
13
|
+
qt.QMainWindow.__init__(self, parent)
|
|
14
|
+
self.setWindowFlags(qt.Qt.Widget)
|
|
15
|
+
|
|
16
|
+
self._centralWidget = ProcessManagerWidget(parent=self)
|
|
17
|
+
self.setCentralWidget(self._centralWidget)
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import fnmatch
|
|
4
|
+
import datetime
|
|
5
|
+
|
|
6
|
+
from silx.gui import qt
|
|
7
|
+
from processview.core.sorting import SortType
|
|
8
|
+
from processview.core.manager import DatasetState
|
|
9
|
+
from processview.core.dataset import DatasetIdentifier
|
|
10
|
+
from processview.gui.utils.qitem_model_resetter import qitem_model_resetter
|
|
11
|
+
|
|
12
|
+
from .ProcessManager import ProcessManager
|
|
13
|
+
|
|
14
|
+
from collections import OrderedDict
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
_DATASET_STATE_BACKGROUND = {
|
|
18
|
+
DatasetState.ON_GOING: qt.QColor("#839684"), # light blue
|
|
19
|
+
DatasetState.SUCCEED: qt.QColor("#068c0c"), # green
|
|
20
|
+
DatasetState.FAILED: qt.QColor("#f52718"), # red
|
|
21
|
+
DatasetState.PENDING: qt.QColor("#609ab3"), # blue gray
|
|
22
|
+
DatasetState.SKIPPED: qt.QColor("#f08e0e"), # light orange
|
|
23
|
+
DatasetState.WAIT_USER_VALIDATION: qt.QColor("#cb34c1"), # pink
|
|
24
|
+
DatasetState.CANCELLED: qt.QColor("#a4a8a2"), # light black
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class DatasetProcessModel(qt.QAbstractTableModel):
|
|
29
|
+
def __init__(self, parent, header, *args):
|
|
30
|
+
qt.QAbstractTableModel.__init__(self, parent, *args)
|
|
31
|
+
self.header = header
|
|
32
|
+
self._processes = OrderedDict()
|
|
33
|
+
# processes with order as key and Process as value
|
|
34
|
+
self._sorted_datasets = OrderedDict()
|
|
35
|
+
self._unsorted_datasets = OrderedDict()
|
|
36
|
+
self._processPatterns = tuple()
|
|
37
|
+
# is there some process name pattern to follow ?
|
|
38
|
+
self._datasetPatterns = tuple()
|
|
39
|
+
# is there some dataset id pattern to follow ?
|
|
40
|
+
self._sort_type = SortType.FIRST_APPEARANCE
|
|
41
|
+
|
|
42
|
+
def _match_dataset_patterns(self, dataset):
|
|
43
|
+
if len(self._datasetPatterns) == 0:
|
|
44
|
+
return True
|
|
45
|
+
for pattern in self._datasetPatterns:
|
|
46
|
+
if fnmatch.fnmatch(str(dataset), pattern):
|
|
47
|
+
return True
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
def _match_process_patterns(self, process):
|
|
51
|
+
if len(self.process_patterns) == 0:
|
|
52
|
+
return True
|
|
53
|
+
for pattern in self._processPatterns:
|
|
54
|
+
if process.name is None or pattern is None:
|
|
55
|
+
continue
|
|
56
|
+
elif fnmatch.fnmatch(process.name, pattern):
|
|
57
|
+
return True
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def process_patterns(self):
|
|
62
|
+
return self._processPatterns
|
|
63
|
+
|
|
64
|
+
@process_patterns.setter
|
|
65
|
+
def process_patterns(self, patterns):
|
|
66
|
+
self._processPatterns = patterns
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def dataset_patterns(self):
|
|
70
|
+
return self._datasetPatterns
|
|
71
|
+
|
|
72
|
+
@dataset_patterns.setter
|
|
73
|
+
def dataset_patterns(self, patterns):
|
|
74
|
+
self._datasetPatterns = patterns
|
|
75
|
+
|
|
76
|
+
def remove_datasets(self, datasets):
|
|
77
|
+
remaining_datasets = list(self._unsorted_datasets)
|
|
78
|
+
for dataset in datasets:
|
|
79
|
+
if not isinstance(dataset, DatasetIdentifier):
|
|
80
|
+
raise ValueError(
|
|
81
|
+
f"dataset is expected to be a dataset identifier. Get {type(dataset)} instead"
|
|
82
|
+
)
|
|
83
|
+
try:
|
|
84
|
+
remaining_datasets.remove(dataset)
|
|
85
|
+
except Exception:
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
with qitem_model_resetter(self):
|
|
89
|
+
self.setDatasets(remaining_datasets)
|
|
90
|
+
|
|
91
|
+
def clear(self):
|
|
92
|
+
with qitem_model_resetter(self):
|
|
93
|
+
self._processes = OrderedDict()
|
|
94
|
+
|
|
95
|
+
def rowCount(self, parent=None):
|
|
96
|
+
return len(self._unsorted_datasets)
|
|
97
|
+
|
|
98
|
+
def columnCount(self, parent=None):
|
|
99
|
+
return len(self._processes) + 1
|
|
100
|
+
|
|
101
|
+
def setProcesses(self, processes):
|
|
102
|
+
assert isinstance(processes, (tuple, list))
|
|
103
|
+
with qitem_model_resetter(self):
|
|
104
|
+
self._processes = {}
|
|
105
|
+
processes = list(filter(self._match_process_patterns, processes))
|
|
106
|
+
for i_process, process in enumerate(processes):
|
|
107
|
+
self._processes[i_process] = process
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def sort_datasets(datasets: list, sort_type):
|
|
111
|
+
if sort_type is SortType.FIRST_APPEARANCE:
|
|
112
|
+
pass
|
|
113
|
+
elif sort_type is SortType.LAST_APPEARANCE:
|
|
114
|
+
datasets.reverse()
|
|
115
|
+
elif sort_type in (SortType.ALPHABETICAL, SortType.REVERSE_ALPHABETICAL):
|
|
116
|
+
datasets = sorted(
|
|
117
|
+
datasets,
|
|
118
|
+
key=lambda x: x.name() or "",
|
|
119
|
+
reverse=sort_type is SortType.REVERSE_ALPHABETICAL,
|
|
120
|
+
)
|
|
121
|
+
elif sort_type in (SortType.CREATION_TIME, SortType.REVERSE_CREATION_TIME):
|
|
122
|
+
datasets = sorted(
|
|
123
|
+
datasets,
|
|
124
|
+
key=lambda x: x.creation_time() or datetime.datetime.fromtimestamp(0),
|
|
125
|
+
reverse=sort_type is SortType.REVERSE_CREATION_TIME,
|
|
126
|
+
)
|
|
127
|
+
elif sort_type in (
|
|
128
|
+
SortType.MODIFICATION_TIME,
|
|
129
|
+
SortType.REVERSE_MODIFICATION_TIME,
|
|
130
|
+
):
|
|
131
|
+
datasets = sorted(
|
|
132
|
+
datasets,
|
|
133
|
+
key=lambda x: x.modification_time()
|
|
134
|
+
or datetime.datetime.fromtimestamp(0),
|
|
135
|
+
reverse=sort_type is SortType.REVERSE_MODIFICATION_TIME,
|
|
136
|
+
)
|
|
137
|
+
else:
|
|
138
|
+
raise ValueError("sort type not handled")
|
|
139
|
+
return datasets
|
|
140
|
+
|
|
141
|
+
def setDatasets(self, datasets):
|
|
142
|
+
assert isinstance(datasets, (tuple, list))
|
|
143
|
+
with qitem_model_resetter(self):
|
|
144
|
+
self._unsorted_datasets = datasets
|
|
145
|
+
# filter datasets
|
|
146
|
+
datasets = list(filter(self._match_dataset_patterns, datasets))
|
|
147
|
+
# sort datasets
|
|
148
|
+
datasets = self.sort_datasets(datasets=datasets, sort_type=self._sort_type)
|
|
149
|
+
|
|
150
|
+
self._sorted_datasets.clear()
|
|
151
|
+
for i_dataset, dataset in enumerate(datasets):
|
|
152
|
+
self._sorted_datasets[i_dataset] = dataset
|
|
153
|
+
|
|
154
|
+
def headerData(self, col, orientation, role):
|
|
155
|
+
if orientation == qt.Qt.Horizontal and role == qt.Qt.DisplayRole:
|
|
156
|
+
if col == 0:
|
|
157
|
+
return
|
|
158
|
+
else:
|
|
159
|
+
process_idx = col - 1
|
|
160
|
+
if process_idx < len(self._processes):
|
|
161
|
+
return self._processes[process_idx].name
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
def data(self, index, role):
|
|
165
|
+
if index.isValid() is False:
|
|
166
|
+
return None
|
|
167
|
+
|
|
168
|
+
if role not in (qt.Qt.DisplayRole, qt.Qt.ToolTipRole, qt.Qt.BackgroundRole):
|
|
169
|
+
return None
|
|
170
|
+
|
|
171
|
+
if index.column() == 0:
|
|
172
|
+
# if dataset name
|
|
173
|
+
if role == qt.Qt.DisplayRole:
|
|
174
|
+
return self._sorted_datasets[index.row()].short_description() or str(
|
|
175
|
+
self._sorted_datasets[index.row()]
|
|
176
|
+
)
|
|
177
|
+
elif role == qt.Qt.ToolTipRole:
|
|
178
|
+
return self._sorted_datasets[index.row()].long_description()
|
|
179
|
+
elif role == qt.Qt.BackgroundRole:
|
|
180
|
+
return qt.QColor("#dedede")
|
|
181
|
+
else:
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
dataset_short_name = self._sorted_datasets[index.row()]
|
|
185
|
+
process_name = self._processes[index.column() - 1]
|
|
186
|
+
dataset_process_state = ProcessManager().get_dataset_state(
|
|
187
|
+
dataset=dataset_short_name, process=process_name
|
|
188
|
+
)
|
|
189
|
+
dataset_process_details = ProcessManager().get_dataset_details(
|
|
190
|
+
dataset=dataset_short_name, process=process_name
|
|
191
|
+
)
|
|
192
|
+
if role == qt.Qt.BackgroundRole:
|
|
193
|
+
if dataset_process_state is None:
|
|
194
|
+
# if "unmet"
|
|
195
|
+
return qt.QColor("#ffffff")
|
|
196
|
+
else:
|
|
197
|
+
return _DATASET_STATE_BACKGROUND[dataset_process_state]
|
|
198
|
+
elif role == qt.Qt.DisplayRole:
|
|
199
|
+
if dataset_process_state is None:
|
|
200
|
+
return ""
|
|
201
|
+
else:
|
|
202
|
+
return dataset_process_state.value
|
|
203
|
+
if role == qt.Qt.ToolTipRole:
|
|
204
|
+
if dataset_process_details is None:
|
|
205
|
+
return ""
|
|
206
|
+
else:
|
|
207
|
+
return dataset_process_details
|
|
208
|
+
|
|
209
|
+
def _setSorting(self, sort_type: SortType):
|
|
210
|
+
sort_type = SortType(sort_type)
|
|
211
|
+
changed = self._sort_type is not sort_type
|
|
212
|
+
if changed:
|
|
213
|
+
self._sort_type = sort_type
|
|
214
|
+
self.setDatasets(datasets=self._unsorted_datasets)
|
|
215
|
+
self.layoutChanged.emit()
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from silx.gui import qt
|
|
4
|
+
from silx.gui import icons
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class FilterWidget(qt.QWidget):
|
|
8
|
+
"""
|
|
9
|
+
Widget to define some filtering pattern on dataset and / or processes
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
sigProcessPatternEditingFinished = qt.Signal()
|
|
13
|
+
"""signal emit when the process pattern editing finished"""
|
|
14
|
+
|
|
15
|
+
sigDatasetPatternEditingFinished = qt.Signal()
|
|
16
|
+
"""signal emit when the dataset pattern editing finished"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, parent=None, name=" filter", font_size=12, icon_size=20):
|
|
19
|
+
qt.QWidget.__init__(self, parent)
|
|
20
|
+
self.setLayout(qt.QGridLayout())
|
|
21
|
+
self.layout().setSpacing(2)
|
|
22
|
+
self.layout().setContentsMargins(0, 0, 0, 0)
|
|
23
|
+
|
|
24
|
+
font = self.font()
|
|
25
|
+
font.setPixelSize(font_size)
|
|
26
|
+
self.setFont(font)
|
|
27
|
+
|
|
28
|
+
icon = icons.getQPixmap("processview:gui/icons/magnifying_glass")
|
|
29
|
+
self._researchLabelIcon = qt.QLabel("", parent=self)
|
|
30
|
+
self._researchLabelIcon.setPixmap(icon)
|
|
31
|
+
self.layout().addWidget(qt.QLabel(name, self), 0, 1, 1, 1)
|
|
32
|
+
self.layout().addWidget(self._researchLabelIcon, 0, 2, 1, 1)
|
|
33
|
+
|
|
34
|
+
# filter by dataset id / name
|
|
35
|
+
self._datasetLabel = qt.QLabel("dataset", self)
|
|
36
|
+
self.layout().addWidget(self._datasetLabel, 1, 2, 1, 2)
|
|
37
|
+
self._datasetPatternLE = qt.QLineEdit("", self)
|
|
38
|
+
self.layout().addWidget(self._datasetPatternLE, 1, 4, 1, 1)
|
|
39
|
+
tooltip = (
|
|
40
|
+
"Provide one or several dataset name or pattern to only "
|
|
41
|
+
" display those datasets. Pattern should be separated by a "
|
|
42
|
+
"semi colon. Handle linux wild card. Example: "
|
|
43
|
+
"`pattern1; *suffix; prefix*`"
|
|
44
|
+
)
|
|
45
|
+
for widget in self._datasetLabel, self._datasetPatternLE:
|
|
46
|
+
widget.setToolTip(tooltip)
|
|
47
|
+
self._clearDatasetPatternPB = qt.QPushButton("clear", self)
|
|
48
|
+
self._clearDatasetPatternPB.setAutoDefault(True)
|
|
49
|
+
self.layout().addWidget(self._clearDatasetPatternPB, 1, 5, 1, 1)
|
|
50
|
+
|
|
51
|
+
# filter by process name
|
|
52
|
+
self._processLabel = qt.QLabel("process", self)
|
|
53
|
+
self.layout().addWidget(self._processLabel, 2, 2, 1, 2)
|
|
54
|
+
self._processPatternLE = qt.QLineEdit("", self)
|
|
55
|
+
self.layout().addWidget(self._processPatternLE, 2, 4, 1, 1)
|
|
56
|
+
tooltip = (
|
|
57
|
+
"Provide one or several process name or pattern to only "
|
|
58
|
+
" display those datasets. Pattern should be separated by a "
|
|
59
|
+
"semi colon. Handle linux wild card. Example: "
|
|
60
|
+
"`pattern1; *suffix; prefix*`"
|
|
61
|
+
)
|
|
62
|
+
for widget in self._processLabel, self._processPatternLE:
|
|
63
|
+
widget.setToolTip(tooltip)
|
|
64
|
+
self._clearProcessPatternPB = qt.QPushButton("clear", self)
|
|
65
|
+
self._clearProcessPatternPB.setAutoDefault(True)
|
|
66
|
+
self.layout().addWidget(self._clearProcessPatternPB, 2, 5, 1, 1)
|
|
67
|
+
|
|
68
|
+
# connect signal / slot
|
|
69
|
+
self._clearProcessPatternPB.released.connect(self._processPatternLE.clear)
|
|
70
|
+
self._clearProcessPatternPB.released.connect(
|
|
71
|
+
self._processPatternEditingFinished
|
|
72
|
+
)
|
|
73
|
+
self._clearDatasetPatternPB.released.connect(self._datasetPatternLE.clear)
|
|
74
|
+
self._clearDatasetPatternPB.released.connect(
|
|
75
|
+
self._datasetPatternEditingFinished
|
|
76
|
+
)
|
|
77
|
+
self._datasetPatternLE.editingFinished.connect(
|
|
78
|
+
self._datasetPatternEditingFinished
|
|
79
|
+
)
|
|
80
|
+
self._processPatternLE.editingFinished.connect(
|
|
81
|
+
self._processPatternEditingFinished
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def getProcessPatterns(self) -> tuple:
|
|
85
|
+
patterns = self._processPatternLE.text().lstrip(" ").rstrip(" ")
|
|
86
|
+
if patterns == "":
|
|
87
|
+
return ("*",)
|
|
88
|
+
res = patterns.replace(" ", "")
|
|
89
|
+
return tuple(res.split(";"))
|
|
90
|
+
|
|
91
|
+
def getDatasetPatterns(self) -> tuple:
|
|
92
|
+
patterns = self._datasetPatternLE.text().lstrip(" ").rstrip(" ")
|
|
93
|
+
if patterns == "":
|
|
94
|
+
return ("*",)
|
|
95
|
+
res = patterns.replace(" ", "")
|
|
96
|
+
return tuple(res.split(";"))
|
|
97
|
+
|
|
98
|
+
def _datasetPatternEditingFinished(self, *args, **kwargs):
|
|
99
|
+
self.sigDatasetPatternEditingFinished.emit()
|
|
100
|
+
|
|
101
|
+
def _processPatternEditingFinished(self, *args, **kwargs):
|
|
102
|
+
self.sigProcessPatternEditingFinished.emit()
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from silx.gui import qt
|
|
4
|
+
|
|
5
|
+
from ._OrderingWidget import _OrderingWidget
|
|
6
|
+
from ._FilterWidget import FilterWidget
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class OptionsWidget(qt.QWidget):
|
|
10
|
+
|
|
11
|
+
def __init__(self, parent):
|
|
12
|
+
super().__init__(parent)
|
|
13
|
+
self.setLayout(qt.QGridLayout())
|
|
14
|
+
|
|
15
|
+
# dataset ordering
|
|
16
|
+
self._orderingWidget = _OrderingWidget(parent=self)
|
|
17
|
+
self.layout().setContentsMargins(2, 2, 2, 2)
|
|
18
|
+
self.layout().setSpacing(0)
|
|
19
|
+
self.layout().addWidget(self._orderingWidget, 2, 1, 3, 3)
|
|
20
|
+
|
|
21
|
+
# filtering
|
|
22
|
+
self._filterWidget = FilterWidget(parent=self)
|
|
23
|
+
self.layout().setContentsMargins(2, 2, 2, 2)
|
|
24
|
+
self.layout().setSpacing(0)
|
|
25
|
+
self.layout().addWidget(self._filterWidget, 4, 1, 3, 3)
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def filterWidget(self):
|
|
29
|
+
return self._filterWidget
|
|
30
|
+
|
|
31
|
+
# expose API
|
|
32
|
+
def getProcessPatterns(self):
|
|
33
|
+
return self._filterWidget.getProcessPatterns()
|
|
34
|
+
|
|
35
|
+
def getDatasetPatterns(self):
|
|
36
|
+
return self._filterWidget.getDatasetPatterns()
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from silx.gui import qt
|
|
4
|
+
from processview.core.sorting import SortType, tooltips
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class _OrderingWidget(qt.QWidget):
|
|
8
|
+
"""Widget to let the user define the ordering of datasets"""
|
|
9
|
+
|
|
10
|
+
sigSortTypeChanged = qt.Signal(str)
|
|
11
|
+
"""emit when sort type changed. Paramater is the name of the new sorting type"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, parent=None, *args, **kwargs) -> None:
|
|
14
|
+
super().__init__(parent, *args, **kwargs)
|
|
15
|
+
self.setLayout(qt.QFormLayout())
|
|
16
|
+
self._sortingTypeCB = qt.QComboBox(self)
|
|
17
|
+
for sort_type in SortType:
|
|
18
|
+
self._sortingTypeCB.addItem(sort_type.value)
|
|
19
|
+
self._sortingTypeCB.setItemData(
|
|
20
|
+
self._sortingTypeCB.findText(sort_type.value),
|
|
21
|
+
tooltips[sort_type],
|
|
22
|
+
qt.Qt.ToolTipRole,
|
|
23
|
+
)
|
|
24
|
+
self.layout().addRow("sorting", self._sortingTypeCB)
|
|
25
|
+
|
|
26
|
+
# set up
|
|
27
|
+
self._sortingTypeCB.setCurrentIndex(
|
|
28
|
+
self._sortingTypeCB.findText(SortType.FIRST_APPEARANCE.value)
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# connect signal / slot
|
|
32
|
+
self._sortingTypeCB.currentIndexChanged.connect(self._changed)
|
|
33
|
+
|
|
34
|
+
def _changed(self, *args, **kwargs):
|
|
35
|
+
self.sigSortTypeChanged.emit(self.getCurrentSort().value)
|
|
36
|
+
|
|
37
|
+
def getCurrentSort(self) -> SortType:
|
|
38
|
+
return SortType(self._sortingTypeCB.currentText())
|
|
@@ -13,9 +13,10 @@ from processview.core.manager import ProcessManager as _ProcessManager
|
|
|
13
13
|
from processview.core.sorting import SortType, tooltips
|
|
14
14
|
from processview.core.superviseprocess import SuperviseProcess
|
|
15
15
|
from processview.gui.DropDownWidget import DropDownWidget
|
|
16
|
-
from
|
|
16
|
+
from processview.gui import icons as icons
|
|
17
17
|
from processview.gui.messagebox import MessageBox
|
|
18
18
|
from processview.utils import docstring
|
|
19
|
+
from processview.gui.utils.qitem_model_resetter import qitem_model_resetter
|
|
19
20
|
|
|
20
21
|
_DATASET_STATE_BACKGROUND = {
|
|
21
22
|
DatasetState.ON_GOING: qt.QColor("#839684"), # light blue
|
|
@@ -302,12 +303,12 @@ class _DatasetProcessModel(qt.QAbstractTableModel):
|
|
|
302
303
|
except Exception:
|
|
303
304
|
pass
|
|
304
305
|
|
|
305
|
-
self
|
|
306
|
-
|
|
306
|
+
with qitem_model_resetter(self):
|
|
307
|
+
self.setDatasets(remaining_datasets)
|
|
307
308
|
|
|
308
309
|
def clear(self):
|
|
309
|
-
|
|
310
|
-
|
|
310
|
+
with qitem_model_resetter(self):
|
|
311
|
+
self._processes = OrderedDict()
|
|
311
312
|
|
|
312
313
|
def rowCount(self, parent=None):
|
|
313
314
|
return len(self._unsorted_datasets)
|
|
@@ -317,11 +318,11 @@ class _DatasetProcessModel(qt.QAbstractTableModel):
|
|
|
317
318
|
|
|
318
319
|
def setProcesses(self, processes):
|
|
319
320
|
assert isinstance(processes, (tuple, list))
|
|
320
|
-
self
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
321
|
+
with qitem_model_resetter(self):
|
|
322
|
+
self._processes = {}
|
|
323
|
+
processes = list(filter(self._match_process_patterns, processes))
|
|
324
|
+
for i_process, process in enumerate(processes):
|
|
325
|
+
self._processes[i_process] = process
|
|
325
326
|
|
|
326
327
|
@staticmethod
|
|
327
328
|
def sort_datasets(datasets: list, sort_type):
|
|
@@ -357,16 +358,16 @@ class _DatasetProcessModel(qt.QAbstractTableModel):
|
|
|
357
358
|
|
|
358
359
|
def setDatasets(self, datasets):
|
|
359
360
|
assert isinstance(datasets, (tuple, list))
|
|
360
|
-
self
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
361
|
+
with qitem_model_resetter(self):
|
|
362
|
+
self._unsorted_datasets = datasets
|
|
363
|
+
# filter datasets
|
|
364
|
+
datasets = list(filter(self._match_dataset_patterns, datasets))
|
|
365
|
+
# sort datasets
|
|
366
|
+
datasets = self.sort_datasets(datasets=datasets, sort_type=self._sort_type)
|
|
365
367
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
self.endResetModel()
|
|
368
|
+
self._sorted_datasets.clear()
|
|
369
|
+
for i_dataset, dataset in enumerate(datasets):
|
|
370
|
+
self._sorted_datasets[i_dataset] = dataset
|
|
370
371
|
|
|
371
372
|
def headerData(self, col, orientation, role):
|
|
372
373
|
if orientation == qt.Qt.Horizontal and role == qt.Qt.DisplayRole:
|
|
@@ -483,9 +484,9 @@ class _FilterWidget(qt.QWidget):
|
|
|
483
484
|
font.setPixelSize(font_size)
|
|
484
485
|
self.setFont(font)
|
|
485
486
|
|
|
486
|
-
icon = icons.
|
|
487
|
+
icon = icons.getQIcon("magnifying_glass")
|
|
487
488
|
self._researchLabelIcon = qt.QLabel("", parent=self)
|
|
488
|
-
self._researchLabelIcon.setPixmap(icon)
|
|
489
|
+
self._researchLabelIcon.setPixmap(icon.pixmap(icon_size, state=qt.QIcon.On))
|
|
489
490
|
self.layout().addWidget(qt.QLabel(name, self), 0, 1, 1, 1)
|
|
490
491
|
self.layout().addWidget(self._researchLabelIcon, 0, 2, 1, 1)
|
|
491
492
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from silx.gui import qt
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@contextmanager
|
|
8
|
+
def qitem_model_resetter(model: qt.QAbstractItemModel):
|
|
9
|
+
"""Context manager for resetting a QAbstractItemModel.
|
|
10
|
+
|
|
11
|
+
Make sure `beginResetModel()` is emit at the beginning and endResetModel() at the end.
|
|
12
|
+
"""
|
|
13
|
+
model.beginResetModel()
|
|
14
|
+
try:
|
|
15
|
+
yield
|
|
16
|
+
finally:
|
|
17
|
+
model.endResetModel()
|
|
File without changes
|
|
@@ -2,6 +2,7 @@ LICENSE
|
|
|
2
2
|
README.md
|
|
3
3
|
setup.cfg
|
|
4
4
|
setup.py
|
|
5
|
+
example/show_processmanagerwindow.py
|
|
5
6
|
processview/__init__.py
|
|
6
7
|
processview/version.py
|
|
7
8
|
processview.egg-info/PKG-INFO
|
|
@@ -25,8 +26,19 @@ processview/gui/DropDownWidget.py
|
|
|
25
26
|
processview/gui/__init__.py
|
|
26
27
|
processview/gui/messagebox.py
|
|
27
28
|
processview/gui/processmanager.py
|
|
29
|
+
processview/gui/processmanager/ObservationTable.py
|
|
30
|
+
processview/gui/processmanager/ProcessManager.py
|
|
31
|
+
processview/gui/processmanager/ProcessManagerWidget.py
|
|
32
|
+
processview/gui/processmanager/ProcessManagerWindow.py
|
|
33
|
+
processview/gui/processmanager/_DatasetProcessModel.py
|
|
34
|
+
processview/gui/processmanager/_FilterWidget.py
|
|
35
|
+
processview/gui/processmanager/_OptionsWidget.py
|
|
36
|
+
processview/gui/processmanager/_OrderingWidget.py
|
|
37
|
+
processview/gui/processmanager/__init__.py
|
|
28
38
|
processview/gui/test/__init__.py
|
|
29
39
|
processview/gui/test/test_process_manager.py
|
|
40
|
+
processview/gui/utils/__init__.py
|
|
41
|
+
processview/gui/utils/qitem_model_resetter.py
|
|
30
42
|
processview/resources/__init__.py
|
|
31
43
|
processview/resources/gui/__init__.py
|
|
32
44
|
processview/resources/gui/icons/advancement.png
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{processview-1.5.1/processview/resources → processview-1.5.2/processview/gui/utils}/__init__.py
RENAMED
|
File without changes
|
{processview-1.5.1/processview/resources/gui → processview-1.5.2/processview/resources}/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{processview-1.5.1 → processview-1.5.2}/processview/resources/gui/icons/magnifying_glass.png
RENAMED
|
File without changes
|
{processview-1.5.1 → processview-1.5.2}/processview/resources/gui/icons/magnifying_glass.svg
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|