Skip to content
Snippets Groups Projects
Commit ee39281c authored by Samuel GAIST's avatar Samuel GAIST
Browse files

[widgets][protocoltemplateeditor] Implement ProtocolTemplateEditor

parent c5cff909
No related branches found
No related tags found
1 merge request!71Implement ProtocolTemplateEditor
Pipeline #29618 passed
This commit is part of merge request !71. Comments created here will be created in the context of that merge request.
......@@ -23,16 +23,131 @@
# #
###############################################################################
import copy
import pytest
import simplejson as json
from PyQt5 import QtCore
from PyQt5.QtWidgets import QInputDialog
from ..backend.assetmodel import AssetModel
from ..backend.assetmodel import AssetType
from ..widgets.protocoltemplateeditor import SetWidget
from ..widgets.protocoltemplateeditor import ProtocolTemplateEditor
@pytest.fixture()
def dataformat_model(test_prefix):
asset_model = AssetModel()
asset_model.asset_type = AssetType.DATAFORMAT
asset_model.prefix_path = test_prefix
return asset_model
@pytest.fixture()
def reference_set_json():
return {"name": "test", "outputs": {"out1": "int8"}}
@pytest.fixture()
def reference_pt_json():
return {"sets": [{"name": "original", "outputs": {"a": "int8"}}]}
class TestSetWidget:
"""Test that the editor dedicated to a set works correctly"""
def test_load_and_dump(self, qtbot, dataformat_model, reference_set_json):
set_widget = SetWidget(dataformat_model)
set_widget.load(reference_set_json)
qtbot.addWidget(set_widget)
assert set_widget.dump() == reference_set_json
def test_add_output(self, qtbot, dataformat_model, reference_set_json):
edited_json = copy.deepcopy(reference_set_json)
edited_json["outputs"]["Change_me"] = "int8"
set_widget = SetWidget(dataformat_model)
set_widget.load(reference_set_json)
qtbot.addWidget(set_widget)
qtbot.mouseClick(set_widget.add_button, QtCore.Qt.LeftButton)
assert set_widget.dump() == edited_json
def test_remove_output(self, qtbot, dataformat_model, reference_set_json):
edited_json = copy.deepcopy(reference_set_json)
edited_json["outputs"] = {}
set_widget = SetWidget(dataformat_model)
set_widget.load(reference_set_json)
qtbot.addWidget(set_widget)
qtbot.mouseClick(set_widget.remove_button, QtCore.Qt.LeftButton)
assert set_widget.dump() == reference_set_json
set_widget.outputs_tablewidget.setCurrentCell(0, 0)
qtbot.mouseClick(set_widget.remove_button, QtCore.Qt.LeftButton)
assert set_widget.dump() == edited_json
class TestProtocolTemplateEditor:
"""Test that the protocol template editor works correctly"""
def test_load_and_dump(self, qtbot):
reference_json = {"description": "test"}
def test_load_and_dump(self, qtbot, test_prefix):
asset_model = AssetModel()
asset_model.asset_type = AssetType.PROTOCOLTEMPLATE
asset_model.prefix_path = test_prefix
asset_name = "double/1"
with open(asset_model.json_path(asset_name), "rt") as json_file:
json_data = json.load(json_file)
editor = ProtocolTemplateEditor()
editor.load_json(json_data)
qtbot.addWidget(editor)
assert editor.dump_json() == json_data
def test_add_set(self, qtbot, monkeypatch, test_prefix, reference_pt_json):
edited_json = copy.deepcopy(reference_pt_json)
edited_json["schema_version"] = 1
edited_json["sets"].append(
{"name": "test_set", "outputs": {"Change_me": "int8"}}
)
monkeypatch.setattr(
QInputDialog, "getText", classmethod(lambda *args: ("test_set", True))
)
editor = ProtocolTemplateEditor()
editor.set_prefix_root(test_prefix)
editor.load_json(reference_pt_json)
qtbot.addWidget(editor)
qtbot.mouseClick(editor.add_set_button, QtCore.Qt.LeftButton)
assert editor.dump_json() == edited_json
def test_remove_set(self, qtbot, test_prefix, reference_pt_json):
edited_json = copy.deepcopy(reference_pt_json)
edited_json["schema_version"] = 1
edited_json["sets"] = []
editor = ProtocolTemplateEditor()
editor.set_prefix_root(test_prefix)
editor.load_json(reference_pt_json)
editor.load_json(reference_json)
qtbot.addWidget(editor)
qtbot.mouseClick(editor.set_widgets[0].delete_button, QtCore.Qt.LeftButton)
assert editor.dump_json() == reference_json
assert editor.dump_json() == edited_json
......@@ -23,11 +23,165 @@
# #
###############################################################################
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QComboBox
from PyQt5.QtWidgets import QFormLayout
from PyQt5.QtWidgets import QHBoxLayout
from PyQt5.QtWidgets import QHeaderView
from PyQt5.QtWidgets import QInputDialog
from PyQt5.QtWidgets import QLineEdit
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtWidgets import QScrollArea
from PyQt5.QtWidgets import QStyledItemDelegate
from PyQt5.QtWidgets import QTableWidget
from PyQt5.QtWidgets import QTableWidgetItem
from PyQt5.QtWidgets import QVBoxLayout
from PyQt5.QtWidgets import QWidget
from ..utils import frozen
from ..backend.assetmodel import AssetType
from ..backend.assetmodel import AssetModel
from .editor import AbstractAssetEditor
class DataformatDelegate(QStyledItemDelegate):
def __init__(self, dataformat_model, parent=None):
super(DataformatDelegate, self).__init__(parent)
self.dataformat_model = dataformat_model
def createEditor(self, parent, options, index):
combobox = QComboBox(parent)
combobox.setModel(self.dataformat_model)
return combobox
class SetWidget(QWidget):
dataChanged = pyqtSignal()
deletionRequested = pyqtSignal()
def __init__(self, dataformat_model, parent=None):
super(SetWidget, self).__init__(parent)
self.dataformat_model = dataformat_model
delegate = DataformatDelegate(self.dataformat_model)
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.template_lineedit = QLineEdit()
self.view_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)
self.name_lineedit.textChanged.connect(self.dataChanged)
self.outputs_tablewidget.cellChanged.connect(self.dataChanged)
self.outputs_tablewidget.itemSelectionChanged.connect(
self.__on_item_selection_changed
)
layout = QVBoxLayout(self)
layout.addLayout(delete_layout)
layout.addLayout(self.form_layout)
self.delete_button.clicked.connect(self.deletionRequested)
self.add_button.clicked.connect(self.__add_output)
self.remove_button.clicked.connect(self.__remove_outputs)
def __add_output(self):
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()
def __remove_outputs(self):
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()
def __on_item_selection_changed(self):
self.remove_button.setEnabled(bool(self.outputs_tablewidget.selectedItems()))
def dump(self):
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
def load(self, json_data):
self.name_lineedit.setText(json_data["name"])
outputs = json_data["outputs"]
self.outputs_tablewidget.setRowCount(len(outputs))
row = 0
for name, type_ in json_data["outputs"].items():
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
def name(self):
"""Name of the set"""
return self.name_lineedit.text()
@frozen
class ProtocolTemplateEditor(AbstractAssetEditor):
def __init__(self, parent=None):
......@@ -35,10 +189,112 @@ class ProtocolTemplateEditor(AbstractAssetEditor):
self.setObjectName(self.__class__.__name__)
self.set_title(self.tr("Protocol Template"))
self.set_widgets = []
self.dataformat_model = AssetModel()
self.dataformat_model.asset_type = AssetType.DATAFORMAT
container_widget = QWidget()
self.container_widget_layout = QVBoxLayout(container_widget)
self.container_widget_layout.addStretch(1)
self.scroll_area = QScrollArea()
self.scroll_area.setWidget(container_widget)
self.scroll_area.setWidgetResizable(True)
self.add_set_button = QPushButton(self.tr("+"))
self.add_set_button.setFixedSize(30, 30)
self.layout().addWidget(self.scroll_area, 1)
self.layout().addWidget(self.add_set_button, 1)
self.add_set_button.clicked.connect(self.__add_set)
def set_prefix_root(self, prefix):
"""Re-impl"""
self.dataformat_model.prefix_path = prefix
def __on_remove_requested(self):
"""Remove the widget clicked"""
self.__remove_widget(self.sender())
def __remove_widget(self, widget):
"""Removes the widget that which signal triggered this slot"""
self.container_widget_layout.removeWidget(widget)
self.set_widgets.pop(self.set_widgets.index(widget))
widget.setParent(None)
self.dataChanged.emit()
def __add_set(self):
"""Add a new set
:param content dict: dictionary to load
"""
set_names = [widget.name() for widget in self.set_widgets]
while True:
name, ok_pressed = QInputDialog.getText(
self, self.tr("Set name"), self.tr("Name")
)
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
def __show_latest_set(self):
"""Ensure that the latest set is visible"""
if self.set_widgets:
self.scroll_area.ensureWidgetVisible(self.set_widgets[-1], 10, 10)
def __load_json(self, json_object):
"""Load the json object passed as parameter"""
for set_ in json_object["sets"]:
set_widget = SetWidget(self.dataformat_model)
set_widget.load(set_)
self.set_widgets.append(set_widget)
index = self.container_widget_layout.count() - 1
self.container_widget_layout.insertWidget(index, set_widget, 2)
set_widget.dataChanged.connect(self.dataChanged)
set_widget.deletionRequested.connect(self.__on_remove_requested)
if self.set_widgets:
QTimer.singleShot(100, self.__show_latest_set)
def _load_json(self, json_object):
"""Load the json object passed as parameter"""
pass
self.blockSignals(True)
while self.set_widgets:
widget = self.set_widgets[0]
self.__remove_widget(widget)
self.blockSignals(False)
self.__load_json(json_object)
def _dump_json(self):
"""Returns the json representation of the asset"""
return {}
return {
"schema_version": 1,
"sets": [widget.dump() for widget in self.set_widgets],
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment