protocoltemplateeditor.py 10.5 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# vim: set fileencoding=utf-8 :
###############################################################################
#                                                                             #
# Copyright (c) 2019 Idiap Research Institute, http://www.idiap.ch/           #
# Contact: beat.support@idiap.ch                                              #
#                                                                             #
# This file is part of the beat.editor module of the BEAT platform.           #
#                                                                             #
# Commercial License Usage                                                    #
# Licensees holding valid commercial BEAT licenses may use this file in       #
# accordance with the terms contained in a written agreement between you      #
# and Idiap. For further information contact tto@idiap.ch                     #
#                                                                             #
# Alternatively, this file may be used under the terms of the GNU Affero      #
# Public License version 3 as published by the Free Software and appearing    #
# in the file LICENSE.AGPL included in the packaging of this file.            #
# The BEAT platform is distributed in the hope that it will be useful, but    #
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY  #
# or FITNESS FOR A PARTICULAR PURPOSE.                                        #
#                                                                             #
# You should have received a copy of the GNU Affero Public License along      #
# with the BEAT platform. If not, see http://www.gnu.org/licenses/.           #
#                                                                             #
###############################################################################

Samuel GAIST's avatar
Samuel GAIST committed
26
from PyQt5.QtCore import QTimer
27
from PyQt5.QtCore import pyqtSignal
28
from PyQt5.QtCore import pyqtSlot
29
30
31
32
33
34
35
36
37
38
from PyQt5.QtWidgets import QFormLayout
from PyQt5.QtWidgets import QHBoxLayout
from PyQt5.QtWidgets import QHeaderView
from PyQt5.QtWidgets import QLineEdit
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtWidgets import QTableWidget
from PyQt5.QtWidgets import QTableWidgetItem
from PyQt5.QtWidgets import QVBoxLayout
from PyQt5.QtWidgets import QWidget

39
from ..backend.asset import AssetType
40
from ..backend.assetmodel import DataFormatModel
41
from ..backend.delegates import AssetItemDelegate
Samuel GAIST's avatar
Samuel GAIST committed
42
from ..decorators import frozen
43
from .dialogs import NameInputDialog
44
from .editor import AbstractAssetEditor
Samuel GAIST's avatar
Samuel GAIST committed
45
from .scrollwidget import ScrollWidget
46
47


48
class SetWidget(QWidget):
49
    """Editor for a set"""
50
51
52
53
54

    dataChanged = pyqtSignal()
    deletionRequested = pyqtSignal()

    def __init__(self, dataformat_model, parent=None):
55
        super().__init__(parent)
56
57

        self.dataformat_model = dataformat_model
58
        delegate = AssetItemDelegate(self.dataformat_model)
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102

        self.delete_button = QPushButton(self.tr("-"))
        self.delete_button.setFixedSize(30, 30)

        delete_layout = QHBoxLayout()
        delete_layout.addStretch(1)
        delete_layout.addWidget(self.delete_button)

        self.name_lineedit = QLineEdit()
        self.outputs_tablewidget = QTableWidget()
        self.outputs_tablewidget.verticalHeader().setVisible(False)
        self.outputs_tablewidget.setColumnCount(2)
        self.outputs_tablewidget.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch
        )
        self.outputs_tablewidget.setHorizontalHeaderLabels(
            [self.tr("Name"), self.tr("Type")]
        )
        self.outputs_tablewidget.setItemDelegateForColumn(1, delegate)
        self.outputs_tablewidget.setMinimumHeight(200)

        self.add_button = QPushButton(self.tr("+"))
        self.add_button.setFixedSize(30, 30)
        self.remove_button = QPushButton(self.tr("-"))
        self.remove_button.setFixedSize(30, 30)
        self.remove_button.setEnabled(False)
        button_layout = QHBoxLayout()
        button_layout.addStretch(1)
        button_layout.addWidget(self.add_button)
        button_layout.addWidget(self.remove_button)

        outputs_layout = QVBoxLayout()
        outputs_layout.addWidget(self.outputs_tablewidget, 2)
        outputs_layout.addLayout(button_layout)

        self.form_layout = QFormLayout()
        self.form_layout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)
        self.form_layout.addRow(self.tr("Name"), self.name_lineedit)
        self.form_layout.addRow(self.tr("Outputs"), outputs_layout)

        layout = QVBoxLayout(self)
        layout.addLayout(delete_layout)
        layout.addLayout(self.form_layout)

103
        self.name_lineedit.textChanged.connect(self.dataChanged)
104
        self.delete_button.clicked.connect(self.deletionRequested)
105
106
        self.add_button.clicked.connect(self.__addOutput)
        self.remove_button.clicked.connect(self.__removeOutputs)
107
108
        self.outputs_tablewidget.cellChanged.connect(self.dataChanged)
        self.outputs_tablewidget.itemSelectionChanged.connect(
109
            self.__onItemSelectionChanged
110
        )
111

112
113
    @pyqtSlot()
    def __addOutput(self):
114
115
        """Add a new output"""

116
117
118
119
120
121
122
123
124
        new_row = self.outputs_tablewidget.rowCount()
        self.outputs_tablewidget.setRowCount(new_row + 1)
        name_item = QTableWidgetItem(self.tr("Change_me"))
        type_item = QTableWidgetItem(self.dataformat_model.index(0).data())
        self.outputs_tablewidget.setItem(new_row, 0, name_item)
        self.outputs_tablewidget.setItem(new_row, 1, type_item)
        self.outputs_tablewidget.scrollToItem(type_item)
        self.dataChanged.emit()

125
126
    @pyqtSlot()
    def __removeOutputs(self):
127
128
        """Remove the selected output(s)"""

129
130
131
132
133
134
135
136
137
138
139
140
141
142
        selected_items = self.outputs_tablewidget.selectedItems()

        rows = []
        while selected_items:
            item = selected_items.pop()
            rows.append(item.row())

        rows = set(sorted(rows))

        while rows:
            row = rows.pop()
            self.outputs_tablewidget.removeRow(row)
        self.dataChanged.emit()

143
144
    @pyqtSlot()
    def __onItemSelectionChanged(self):
145
146
        self.remove_button.setEnabled(bool(self.outputs_tablewidget.selectedItems()))

147
148
    def name(self):
        """Name of the set"""
149

150
        return self.name_lineedit.text()
151
152

    def load(self, json_data):
153
154
        """Load this widget with the content of json_data"""

155
156
        self.name_lineedit.setText(json_data.get("name"))
        outputs = json_data.get("outputs", {})
157
158
        self.outputs_tablewidget.setRowCount(len(outputs))
        row = 0
159
        for name, type_ in outputs.items():
160
161
162
163
164
165
            name_item = QTableWidgetItem(name)
            type_item = QTableWidgetItem(type_)
            self.outputs_tablewidget.setItem(row, 0, name_item)
            self.outputs_tablewidget.setItem(row, 1, type_item)
            row += 1

166
167
    def dump(self):
        """Returns the json representation of this set"""
168

169
170
171
172
173
174
175
176
177
        json_data = {"name": self.name_lineedit.text()}
        outputs = {}
        for i in range(self.outputs_tablewidget.rowCount()):
            outputs[
                self.outputs_tablewidget.item(i, 0).text()
            ] = self.outputs_tablewidget.item(i, 1).text()

        json_data["outputs"] = outputs
        return json_data
178
179


180
181
@frozen
class ProtocolTemplateEditor(AbstractAssetEditor):
182
183
    """Editor for protocol template objects"""

184
    def __init__(self, parent=None):
185
        super().__init__(AssetType.PROTOCOLTEMPLATE, parent)
186
187
188
        self.setObjectName(self.__class__.__name__)
        self.set_title(self.tr("Protocol Template"))

189
        self.dataformat_model = DataFormatModel()
190

191
        self.scroll_widget = ScrollWidget()
192
193
194
195

        self.add_set_button = QPushButton(self.tr("+"))
        self.add_set_button.setFixedSize(30, 30)

196
        self.layout().addWidget(self.scroll_widget, 1)
197
198
        self.layout().addWidget(self.add_set_button, 1)

199
        self.scroll_widget.dataChanged.connect(self.dataChanged)
200
        self.add_set_button.clicked.connect(self.__addSet)
201
202
        self.contextChanged.connect(
            lambda: self.dataformat_model.setPrefixPath(self.prefix_path)
203
        )
204

205
206
207
208
    @property
    def set_widgets(self):
        return self.scroll_widget.widget_list

209
210
    @pyqtSlot()
    def __onRemoveRequested(self):
211
212
213
214
215
216
217
        """Remove the widget clicked"""

        self.__remove_widget(self.sender())

    def __remove_widget(self, widget):
        """Removes the widget that which signal triggered this slot"""

218
        self.scroll_widget.removeWidget(widget)
219

220
221
    @pyqtSlot()
    def __addSet(self):
222
        """Add a new set"""
223
224
225
226

        set_names = [widget.name() for widget in self.set_widgets]

        while True:
227
            name, ok_pressed = NameInputDialog.getText(self, self.tr("Set name"))
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
            if not ok_pressed:
                break

            if ok_pressed and name not in set_names:
                self.__load_json(
                    {
                        "sets": [
                            {
                                "name": name,
                                "outputs": {
                                    "Change_me": self.dataformat_model.index(0).data()
                                },
                            }
                        ]
                    }
                )
                self.dataChanged.emit()
                break

247
248
    @pyqtSlot()
    def __showLatestSet(self):
249
250
251
        """Ensure that the latest set is visible"""

        if self.set_widgets:
252
            self.scroll_widget.ensureWidgetVisible(self.set_widgets[-1], 10, 10)
253
254
255
256

    def __load_json(self, json_object):
        """Load the json object passed as parameter"""

257
        for set_ in json_object.get("sets", []):
258
259
            set_widget = SetWidget(self.dataformat_model)
            set_widget.load(set_)
260
            self.scroll_widget.addWidget(set_widget)
261
            set_widget.deletionRequested.connect(self.__onRemoveRequested)
262
263

        if self.set_widgets:
264
            QTimer.singleShot(100, self.__showLatestSet)
265

266
267
268
269
270
    def _asset_models(self):
        """Returns a list of all asset models used"""

        return [self.dataformat_model]

271
272
    def _load_json(self, json_object):
        """Load the json object passed as parameter"""
273
274
275
276
277
278
279
280
281
282

        self.blockSignals(True)

        while self.set_widgets:
            widget = self.set_widgets[0]
            self.__remove_widget(widget)

        self.blockSignals(False)

        self.__load_json(json_object)
283
284
285

    def _dump_json(self):
        """Returns the json representation of the asset"""
286

287
288
289
290
        return {
            "schema_version": 1,
            "sets": [widget.dump() for widget in self.set_widgets],
        }