Commit 952053fc authored by Flavio TARSETTI's avatar Flavio TARSETTI
Browse files

Merge branch '237_improve_button_usage_experience' into 'master'

Improve usage experience

Closes #270, #235, and #237

See merge request !138
parents 303106cf 1c7821ac
Pipeline #40577 passed with stages
in 11 minutes and 53 seconds
......@@ -28,7 +28,6 @@ import random
import pytest
from PyQt5 import QtCore
from PyQt5.QtWidgets import QInputDialog
from ..backend.assetmodel import DataFormatModel
from ..widgets.dataformateditor import DataformatArrayWidget
......@@ -37,6 +36,7 @@ from ..widgets.dataformateditor import DataformatObjectWidget
from ..widgets.dataformateditor import DataformatWidget
from ..widgets.dataformateditor import default_dataformat
from ..widgets.dataformateditor import default_object_dataformat
from ..widgets.dialogs import NameInputDialog
@pytest.fixture()
......@@ -103,14 +103,14 @@ class TestDataformatObjectWidget:
reference_json = {"test_type": value}
monkeypatch.setattr(
QInputDialog, "getText", classmethod(lambda *args: ("test_type", True))
NameInputDialog, "getText", classmethod(lambda *args: ("test_type", True))
)
editor.add_type_action.trigger()
assert editor.dump() == reference_json
monkeypatch.setattr(
QInputDialog, "getText", classmethod(lambda *args: ("test_object", True))
NameInputDialog, "getText", classmethod(lambda *args: ("test_object", True))
)
editor.add_object_action.trigger()
reference_json["test_object"] = default_object_dataformat()
......@@ -118,7 +118,7 @@ class TestDataformatObjectWidget:
assert editor.dump() == reference_json
monkeypatch.setattr(
QInputDialog,
NameInputDialog,
"getText",
classmethod(lambda *args: ("test_type_array", True)),
)
......@@ -128,7 +128,7 @@ class TestDataformatObjectWidget:
assert editor.dump() == reference_json
monkeypatch.setattr(
QInputDialog,
NameInputDialog,
"getText",
classmethod(lambda *args: ("test_object_array", True)),
)
......@@ -243,7 +243,7 @@ class TestDataformatEditor:
value = model_index.data()
monkeypatch.setattr(
QInputDialog, "getText", classmethod(lambda *args: ("test_type", True))
NameInputDialog, "getText", classmethod(lambda *args: ("test_type", True))
)
editor.add_type_action.trigger()
reference_json = {"test_type": value}
......@@ -253,7 +253,7 @@ class TestDataformatEditor:
assert validated, errors
monkeypatch.setattr(
QInputDialog, "getText", classmethod(lambda *args: ("test_object", True))
NameInputDialog, "getText", classmethod(lambda *args: ("test_object", True))
)
editor.add_object_action.trigger()
reference_json["test_object"] = default_object_dataformat()
......@@ -263,7 +263,7 @@ class TestDataformatEditor:
assert validated, errors
monkeypatch.setattr(
QInputDialog,
NameInputDialog,
"getText",
classmethod(lambda *args: ("test_type_array", True)),
)
......@@ -275,7 +275,7 @@ class TestDataformatEditor:
assert validated, errors
monkeypatch.setattr(
QInputDialog,
NameInputDialog,
"getText",
classmethod(lambda *args: ("test_object_array", True)),
)
......
......@@ -29,11 +29,11 @@ import pytest
import simplejson as json
from PyQt5 import QtCore
from PyQt5.QtWidgets import QInputDialog
from ..backend.asset import Asset
from ..backend.asset import AssetType
from ..backend.assetmodel import DataFormatModel
from ..widgets.dialogs import NameInputDialog
from ..widgets.protocoltemplateeditor import ProtocolTemplateEditor
from ..widgets.protocoltemplateeditor import SetWidget
......@@ -123,7 +123,7 @@ class TestProtocolTemplateEditor:
)
monkeypatch.setattr(
QInputDialog, "getText", classmethod(lambda *args: ("test_set", True))
NameInputDialog, "getText", classmethod(lambda *args: ("test_set", True))
)
editor = ProtocolTemplateEditor()
......
......@@ -26,11 +26,9 @@
import os
import re
from PyQt5.QtCore import QRegularExpression
from PyQt5.QtCore import QSortFilterProxyModel
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtGui import QRegularExpressionValidator
from PyQt5.QtWidgets import QButtonGroup
from PyQt5.QtWidgets import QCheckBox
from PyQt5.QtWidgets import QComboBox
......@@ -60,6 +58,8 @@ from .libraries import LibrariesWidget
from .parameterwidget import ParameterWidget
from .scrollwidget import EditorListWidget
from .scrollwidget import ScrollWidget
from .validatedhelpers import NameItemDelegate
from .validatedhelpers import NameLineEdit
ALGORITHM_TYPE = "algorithm_type"
DEFAULT_SCHEMA_VERSION = 2
......@@ -83,8 +83,6 @@ ANALYZER_PROCESS_METHOD_MAP = {
LOOP_VALIDATE_METHOD = "def validate(self, result):"
NAME_REGULAREXPRESSION = QRegularExpression("^[a-zA-Z_][a-zA-Z0-9_-]*$")
def migrate_to_api_v2(asset):
status = asset.type.create_new_version(asset.prefix, asset.name)
......@@ -347,10 +345,9 @@ class ParameterEditor(DeletableEditor):
def __init__(self, parent=None):
super().__init__(parent=None)
self.name_lineedit = QLineEdit()
self.name_lineedit.setValidator(
QRegularExpressionValidator(NAME_REGULAREXPRESSION, self)
)
self.delete_button.setToolTip(self.tr("Remove parameter"))
self.name_lineedit = NameLineEdit()
self.parameter_widget = ParameterWidget()
form_layout = QFormLayout()
......@@ -380,15 +377,14 @@ class ResultEditor(DeletableEditor):
def __init__(self, dataformat_model, parent=None):
super().__init__(parent)
self.delete_button.setToolTip(self.tr("Remove result"))
proxy_model = QSortFilterProxyModel()
proxy_model.setSourceModel(dataformat_model)
proxy_model.setFilterRegExp(
"(^int32$|^float32$|^bool$|^string$|^system/[a-zA-Z0-9_-]+/[0-9]+$|^plot/[a-zA-Z0-9_-]+/[0-9]+$)"
)
self.name_lineedit = QLineEdit()
self.name_lineedit.setValidator(
QRegularExpressionValidator(NAME_REGULAREXPRESSION, self)
)
self.name_lineedit = NameLineEdit()
self.type_combobox = QComboBox()
self.type_combobox.setModel(proxy_model)
self.display_checkbox = QCheckBox()
......@@ -429,12 +425,15 @@ class IOWidget(QGroupBox):
super().__init__(title, parent)
self.dataformat_model = dataformat_model
delegate = AssetItemDelegate(self.dataformat_model)
name_delegate = NameItemDelegate(self)
asset_delegate = AssetItemDelegate(self.dataformat_model, self)
self.tablewidget = QTableWidget(0, 2)
self.tablewidget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.tablewidget.setHorizontalHeaderLabels([self.tr("Name"), self.tr("Type")])
self.tablewidget.setItemDelegateForColumn(1, delegate)
self.tablewidget.setItemDelegateForColumn(0, name_delegate)
self.tablewidget.setItemDelegateForColumn(1, asset_delegate)
self.tablewidget.setMinimumHeight(250)
self.add_button = QPushButton(self.tr("+"))
self.add_button.setFixedSize(30, 30)
......@@ -514,9 +513,15 @@ class GroupEditor(DeletableEditor):
self.dataformat_model = dataformat_model
self.delete_button.setToolTip(self.tr("Remove group"))
self.name_lineedit = QLineEdit()
self.inputs_widget = IOWidget(self.tr("Inputs"), self.dataformat_model)
self.inputs_widget.add_button.setToolTip(self.tr("Add input"))
self.inputs_widget.remove_button.setToolTip(self.tr("Remove input"))
self.outputs_widget = IOWidget(self.tr("Outputs"), self.dataformat_model)
self.outputs_widget.add_button.setToolTip(self.tr("Add output"))
self.outputs_widget.remove_button.setToolTip(self.tr("Remove output"))
self.unsynchronized_checkbox = QCheckBox(self.tr("Unsynchronized"))
self.groupbox = QGroupBox()
self.loop_groupbox = QGroupBox(self.tr("Loop"))
......@@ -612,14 +617,17 @@ class AlgorithmEditor(AbstractAssetEditor):
self.group_list_widget = EditorListWidget()
self.add_group_button = QPushButton(self.tr("+"))
self.add_group_button.setToolTip(self.tr("Add group"))
self.add_group_button.setFixedSize(30, 30)
self.parameter_list_widget = EditorListWidget()
self.add_parameter_button = QPushButton(self.tr("+"))
self.add_parameter_button.setToolTip(self.tr("Add parameter"))
self.add_parameter_button.setFixedSize(30, 30)
self.result_list_widget = EditorListWidget()
self.add_result_button = QPushButton(self.tr("+"))
self.add_result_button.setToolTip(self.tr("Add result"))
self.add_result_button.setFixedSize(30, 30)
self.dataformat_model = DataFormatModel()
......@@ -678,13 +686,13 @@ class AlgorithmEditor(AbstractAssetEditor):
self.add_result_button.clicked.connect(self.__onAddResult)
def __get_free_name(self, widget_list):
name = "Change Me"
name = "Change_Me"
i = 1
restart = True
while restart:
for widget in widget_list:
if widget.name() == name:
name = f"Change Me {i}"
name = f"Change_Me_{i}"
i += 1
break
else:
......@@ -706,8 +714,13 @@ class AlgorithmEditor(AbstractAssetEditor):
last_widget = self.group_list_widget.widget_list[-1]
last_widget.setUnsynchronizedEnabled(True)
self.results_groupbox.setVisible(self.property_editor.isAnalyzer())
self.parameters_groupbox.setVisible(not self.property_editor.isAnalyzer())
is_analyzer = self.property_editor.isAnalyzer()
self.results_groupbox.setVisible(is_analyzer)
self.parameters_groupbox.setVisible(not is_analyzer)
if is_analyzer:
if not self.result_list_widget.widget_list:
self.__onAddResult()
self.dataChanged.emit()
......@@ -785,6 +798,10 @@ class AlgorithmEditor(AbstractAssetEditor):
]:
widget.clear()
results = json_object.get("results", {})
for name, configuration in results.items():
self.__addResult(name, configuration)
groups = json_object.get("groups", [])
for group in groups:
self.__addGroup(group)
......@@ -793,10 +810,6 @@ class AlgorithmEditor(AbstractAssetEditor):
for name, configuration in parameters.items():
self.__addParameter(name, configuration)
results = json_object.get("results", {})
for name, configuration in results.items():
self.__addResult(name, configuration)
self.libraries_widget.set_available_libraries(self.library_model.stringList())
self.libraries_widget.set_used_libraries(json_object.get("uses", {}))
......
......@@ -30,21 +30,20 @@ from PyQt5.QtWidgets import QComboBox
from PyQt5.QtWidgets import QFormLayout
from PyQt5.QtWidgets import QGroupBox
from PyQt5.QtWidgets import QHBoxLayout
from PyQt5.QtWidgets import QInputDialog
from PyQt5.QtWidgets import QLabel
from PyQt5.QtWidgets import QLineEdit
from PyQt5.QtWidgets import QMenu
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtWidgets import QSpinBox
from PyQt5.QtWidgets import QVBoxLayout
from PyQt5.QtWidgets import QWidget
from ..backend.asset import AssetType
from ..backend.assetmodel import DataFormatModel
from ..decorators import frozen
from ..utils import dataformat_basetypes
from .dialogs import NameInputDialog
from .editor import AbstractAssetEditor
from .scrollwidget import ScrollWidget
from .validatedhelpers import NameLineEdit
def create_button_layout(button):
......@@ -95,7 +94,7 @@ def default_object_dataformat():
return {QCoreApplication.translate("Dataformat", "Change_me"): default_dataformat()}
class DataformatBaseWidget(QWidget):
class DataformatBaseWidget(QGroupBox):
"""Base widget to build the various 'sub-editors'"""
nameChanged = pyqtSignal()
......@@ -115,17 +114,18 @@ class DataformatBaseWidget(QWidget):
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 = NameLineEdit()
self.name_layout = QHBoxLayout()
self.name_layout.addWidget(self.name_lineedit, 10)
self.name_layout.addStretch(1)
self.name_layout.addWidget(self.delete_button)
self.name_lineedit = QLineEdit()
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("Name"), self.name_layout)
layout = QVBoxLayout(self)
layout.addLayout(delete_layout)
layout.addLayout(self.form_layout)
self.delete_button.clicked.connect(self.deletionRequested)
......@@ -149,7 +149,7 @@ class DataformatBaseWidget(QWidget):
self.__has_name = has_name
self.name_lineedit.setVisible(self.__has_name)
self.form_layout.labelForField(self.name_lineedit).setVisible(self.__has_name)
self.form_layout.labelForField(self.name_layout).setVisible(self.__has_name)
def hasName(self):
"""Returns whether the entry shown by this widget has a name"""
......@@ -187,6 +187,8 @@ class DataformatWidget(DataformatBaseWidget):
super().__init__(parent)
self.dataformat_model = dataformat_model
self.delete_button.setToolTip(self.tr("Remove format"))
self.dataformat_box = QComboBox()
self.dataformat_box.setModel(self.dataformat_model)
......@@ -228,6 +230,8 @@ class DataformatObjectWidget(DataformatBaseWidget):
super().__init__(parent)
self.dataformat_model = dataformat_model
self.delete_button.setToolTip(self.tr("Remove object"))
self.dataformat_widgets = []
self.dataformat_box = QGroupBox()
......@@ -284,9 +288,7 @@ class DataformatObjectWidget(DataformatBaseWidget):
format_names = [widgets.name() for widgets in self.dataformat_widgets]
while True:
name, ok_pressed = QInputDialog.getText(
self, self.tr("Field name"), self.tr("Name")
)
name, ok_pressed = NameInputDialog.getText(self, self.tr("Field name"))
if not ok_pressed:
break
......@@ -357,6 +359,7 @@ class DimensionWidget(DataformatBaseWidget):
super().__init__(parent)
self.setHasName(False)
self.delete_button.setToolTip(self.tr("Remove dimension"))
self.dimension_spin_box = QSpinBox()
self.form_layout.addRow(self.tr("Dimension"), self.dimension_spin_box)
......@@ -400,11 +403,14 @@ class DataformatArrayWidget(DataformatBaseWidget):
super().__init__(parent)
self.dataformat_model = dataformat_model
self.delete_button.setToolTip(self.tr("Remove array"))
self.dimension_widgets = []
self.dataformat_widget = None
self.dimension_box = QGroupBox(self.tr("Dimensions"))
self.add_dimension_button = QPushButton(self.tr("+"))
self.add_dimension_button.setToolTip(self.tr("Add dimension"))
self.add_dimension_button.setFixedSize(30, 30)
add_dimension_layout = create_button_layout(self.add_dimension_button)
......@@ -609,9 +615,7 @@ class DataformatEditor(AbstractAssetEditor):
format_names = [widgets.name() for widgets in self.dataformat_widgets]
while True:
name, ok_pressed = QInputDialog.getText(
self, self.tr("Field name"), self.tr("Name")
)
name, ok_pressed = NameInputDialog.getText(self, self.tr("Field name"))
if not ok_pressed:
break
......
......@@ -32,13 +32,13 @@ from PyQt5.QtWidgets import QComboBox
from PyQt5.QtWidgets import QDialog
from PyQt5.QtWidgets import QDialogButtonBox
from PyQt5.QtWidgets import QFormLayout
from PyQt5.QtWidgets import QLineEdit
from PyQt5.QtWidgets import QRadioButton
from PyQt5.QtWidgets import QVBoxLayout
from ..backend.asset import AssetType
from ..backend.assetmodel import AssetModel
from ..utils import dataformat_basetypes
from .validatedhelpers import NameLineEdit
class CreationType:
......@@ -87,7 +87,7 @@ class AssetCreationDialog(QDialog, CreationType):
]
# New asset
self.name_lineedit = QLineEdit()
self.name_lineedit = NameLineEdit()
self.new_radio_button = QRadioButton(self.tr("New"))
self.new_radio_button.setChecked(True)
self.toolchain_combobox = None
......@@ -108,7 +108,7 @@ class AssetCreationDialog(QDialog, CreationType):
self.fork_combobox = QComboBox()
self.fork_combobox.addItems(self.fork_asset_list)
self.fork_combobox.setEnabled(False)
self.fork_lineedit = QLineEdit()
self.fork_lineedit = NameLineEdit()
self.fork_lineedit.setEnabled(False)
self.fork_radio_button = QRadioButton(self.tr("Fork"))
......@@ -229,3 +229,33 @@ class AssetCreationDialog(QDialog, CreationType):
return True, dialog.creationType(), dialog.assetInfo()
else:
return False, None, None
class NameInputDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.name_lineedit = NameLineEdit()
self.buttonbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
layout = QFormLayout(self)
layout.addRow(self.tr("Name"), self.name_lineedit)
layout.addWidget(self.buttonbox)
self.buttonbox.accepted.connect(self.accept)
self.buttonbox.rejected.connect(self.reject)
def name(self):
return self.name_lineedit.text()
@staticmethod
def getText(parent, title=""):
dialog = NameInputDialog(parent)
dialog.setWindowTitle(title)
result = dialog.exec_()
if result == QDialog.Accepted:
return dialog.name(), True
else:
return None, False
......@@ -25,9 +25,10 @@
from PyQt5.QtWidgets import QComboBox
from PyQt5.QtWidgets import QGridLayout
from PyQt5.QtWidgets import QLineEdit
from PyQt5.QtWidgets import QWidget
from .validatedhelpers import NameLineEdit
class FieldWidget(QWidget):
"""Class representing a dataformat field"""
......@@ -37,7 +38,7 @@ class FieldWidget(QWidget):
super().__init__(parent)
self.dataformat_name = QLineEdit()
self.dataformat_name = NameLineEdit()
self.dataformat_box = QComboBox()
self.dataformat_box.setModel(dataformat_model)
......
......@@ -65,7 +65,9 @@ class LibrariesWidget(QWidget):
)
self.used_libraries_tablewidget.setSelectionBehavior(QTableView.SelectRows)
self.add_library_button = QPushButton(self.tr(">"))
self.add_library_button.setToolTip(self.tr("Use selected libraries"))
self.remove_library_button = QPushButton(self.tr("<"))
self.remove_library_button.setToolTip(self.tr("Remove selected libraries"))
# Layouts
layout = QHBoxLayout(self)
......
......@@ -30,9 +30,7 @@ from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import QComboBox
from PyQt5.QtWidgets import QFormLayout
from PyQt5.QtWidgets import QHBoxLayout
from PyQt5.QtWidgets import QInputDialog
from PyQt5.QtWidgets import QLabel
from PyQt5.QtWidgets import QLineEdit
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtWidgets import QVBoxLayout
from PyQt5.QtWidgets import QWidget
......@@ -41,10 +39,12 @@ from ..backend.asset import AssetType
from ..backend.assetmodel import AssetModel
from ..decorators import frozen
from .dialogs import AssetCreationDialog
from .dialogs import NameInputDialog
from .editor import AbstractAssetEditor
from .libraries import LibrariesWidget
from .parameterwidget import ParameterWidget
from .scrollwidget import ScrollWidget
from .validatedhelpers import NameLineEdit
class ParameterViewer(QWidget):
......@@ -56,13 +56,14 @@ class ParameterViewer(QWidget):
super().__init__(parent)
self.delete_button = QPushButton(self.tr("-"))
self.delete_button.setToolTip(self.tr("Remove parameter"))
self.delete_button.setFixedSize(30, 30)
delete_layout = QHBoxLayout()
delete_layout.addStretch(1)
delete_layout.addWidget(self.delete_button)
self.name_lineedit = QLineEdit()
self.name_lineedit = NameLineEdit()
self.parameter_widget = ParameterWidget()
form_layout = QFormLayout()
......@@ -120,6 +121,7 @@ class PlotterEditor(AbstractAssetEditor):
self.scroll_widget = ScrollWidget()
self.add_parameter_button = QPushButton(self.tr("+"))
self.add_parameter_button.setToolTip(self.tr("Add parameter"))
self.add_parameter_button.setFixedSize(30, 30)
self.dataformat_combobox = QComboBox(parent)
......@@ -170,9 +172,7 @@ class PlotterEditor(AbstractAssetEditor):
parameter_names = [widget.name() for widget in self.parameter_viewers]
while True:
name, ok_pressed = QInputDialog.getText(
self, self.tr("Parameter name"), self.tr("Name")
)
name, ok_pressed = NameInputDialog.getText(self, self.tr("Parameter name"))
if not ok_pressed:
break
......
......@@ -243,6 +243,7 @@ class PlotterParameterViewer(QWidget):
super().__init__(parent)
self.delete_button = QPushButton(self.tr("-"))
self.delete_button.setToolTip(self.tr("Remove parameter"))
self.delete_button.setFixedSize(30, 30)
delete_layout = QHBoxLayout()
......@@ -318,6 +319,7 @@ class PlotterParametersEditor(AbstractAssetEditor):
self.scroll_widget = ScrollWidget()
self.add_parameter_button = QPushButton(self.tr("+"))
self.add_parameter_button.setToolTip(self.tr("Add parameter"))
self.add_parameter_button.setFixedSize(30, 30)
plotter_label = QLabel(self.tr("Plotter"))
......
......@@ -29,7 +29,6 @@ from PyQt5.QtCore import pyqtSlot
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 QTableWidget
......@@ -41,6 +40,7 @@ from ..backend.asset import AssetType
from ..backend.assetmodel import DataFormatModel
from ..backend.delegates import AssetItemDelegate
from ..decorators import frozen
from .dialogs import NameInputDialog
from .editor import AbstractAssetEditor
from .scrollwidget import ScrollWidget
......@@ -225,9 +225,7 @@ class ProtocolTemplateEditor(AbstractAssetEditor):
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")
)
name, ok_pressed = NameInputDialog.getText(self, self.tr("Set name"))
if not ok_pressed:
break
......
......@@ -28,7 +28,6 @@ from functools import partial
import simplejson as json
from beat.backend.python.algorithm import Algorithm
from PyQt5.QtCore import QFile
from PyQt5.QtCore import QPointF
from PyQt5.QtCore import QRect
......@@ -55,7 +54,6 @@ from PyQt5.QtWidgets import QGraphicsPathItem
from PyQt5.QtWidgets import QGraphicsView
from PyQt5.QtWidgets import QHBoxLayout
from PyQt5.QtWidgets import QLabel
from PyQt5.QtWidgets import QLineEdit
from PyQt5.QtWidgets import QListWidget