Commit 05cf694a authored by Flavio TARSETTI's avatar Flavio TARSETTI

[widgets][toolchaineditor] block add/edition

parent 4ae1dcca
......@@ -26,12 +26,14 @@
import simplejson as json
from enum import Enum
from functools import partial
from PyQt5.QtCore import Qt
from PyQt5.QtCore import QRect
from PyQt5.QtCore import QRectF
from PyQt5.QtCore import QPointF
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtGui import QColor
from PyQt5.QtGui import QBrush
......@@ -43,20 +45,30 @@ from PyQt5.QtGui import QTransform
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QHBoxLayout
from PyQt5.QtWidgets import QFormLayout
from PyQt5.QtWidgets import QWidget
from PyQt5.QtWidgets import QGraphicsView
from PyQt5.QtWidgets import QGraphicsItem
from PyQt5.QtWidgets import QGraphicsPathItem
from PyQt5.QtWidgets import QGraphicsObject
from PyQt5.QtWidgets import QToolBar
from PyQt5.QtWidgets import QAction
from PyQt5.QtWidgets import QLabel
from PyQt5.QtWidgets import QDialog
from PyQt5.QtWidgets import QDialogButtonBox
from PyQt5.QtWidgets import QComboBox
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtWidgets import QMenu
from PyQt5.QtWidgets import QLineEdit
from ..backend.asset import Asset
from ..backend.asset import AssetType
from ..backend.assetmodel import AssetModel
from ..decorators import frozen
from .editor import AbstractAssetEditor
from .drawing_space import DrawingSpace
from ..backend.resourcemodels import AlgorithmResourceModel
from ..backend.resourcemodels import DatasetResourceModel
class BasePin(QGraphicsObject):
......@@ -351,6 +363,84 @@ class Connection(QGraphicsPathItem):
self.end_block.blockMoved.connect(self.set_moved_block_pins_coordinates)
class BlockEditionDialog(QDialog):
"""Dialog to edit a block"""
def __init__(self, block, parent=None):
"""Constructor
:param block: current block
:param parent QWidget: parent widget
"""
super().__init__(parent)
toolchain = block.toolchain
self.setWindowTitle(self.tr("Block Edition"))
self.name_lineedit = QLineEdit(block.name)
self.channel_combobox = QComboBox()
no_channel_label = QLabel(self.tr("No input connections yet!"))
no_dataset_channel_label = QLabel(self.tr("No synchronization for datasets"))
layout = QFormLayout(self)
layout.addRow(self.tr("Name:"), self.name_lineedit)
channels = []
if block.type == BlockType.DATASETS.name:
layout.addRow(self.tr("Channel:"), no_dataset_channel_label)
elif block.synchronized_channel is None:
layout.addRow(self.tr("Channel:"), no_channel_label)
else:
for connection in toolchain.connections:
if connection.end_block == block and connection.channel not in channels:
channels.append(connection.channel)
self.channel_combobox.addItems(channels)
# set current channel if exists
index = self.channel_combobox.findText(
str(block.synchronized_channel), Qt.MatchFixedString
)
if index >= 0:
self.channel_combobox.setCurrentIndex(index)
layout.addRow(self.tr("Channel:"), self.channel_combobox)
# OK and Cancel buttons
self.buttons = QDialogButtonBox(
QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal, self
)
# layout.addWidget(self.buttons)
layout.addRow(self.buttons)
# Signals/Slots connection
self.buttons.accepted.connect(self.accept)
self.buttons.rejected.connect(self.reject)
def value(self):
"""Returns the value selected"""
return {
"name": self.name_lineedit.text(),
"channel": self.channel_combobox.currentText(),
}
@staticmethod
def getNewBlockSettings(block, parent=None):
"""Static method to create the dialog and return qdialog accepted/spinbox value
:param block: current block
:param parent QWidget: parent widget
"""
dialog = BlockEditionDialog(block, parent)
result = dialog.exec_()
value = None
if result == QDialog.Accepted:
value = dialog.value()
return (value, result)
class BlockType(Enum):
"""All possible block types"""
......@@ -403,7 +493,9 @@ class Block(QGraphicsObject):
def load(self, toolchain, block_details):
self.toolchain = toolchain
self.name = block_details["name"]
if "name" in block_details:
self.name = block_details["name"]
if self.type != BlockType.DATASETS.name:
self.inputs = block_details["inputs"]
......@@ -587,6 +679,108 @@ class Block(QGraphicsObject):
self.blockMoved.emit()
self.dataChanged.emit()
def mouseDoubleClickEvent(self, event):
"""Update block information"""
value = None
ok = False
block_updated = False
value, ok = BlockEditionDialog.getNewBlockSettings(self)
old_name = self.name
old_channel = self.synchronized_channel
if ok:
if self.name != value["name"]:
self.name = value["name"]
block_updated = True
if (
self.synchronized_channel != value["channel"]
and self.type != BlockType.DATASETS.name
):
self.synchronized_channel = value["channel"]
block_updated = True
if block_updated:
# Update blocks
block_item = {}
block_item["inputs"] = self.inputs
block_item["outputs"] = self.outputs
block_item["name"] = self.name
block_item["synchronized_channel"] = self.synchronized_channel
self.toolchain.blocks.remove(self)
self.scene().removeItem(self)
self.load(self.toolchain, block_item)
self.dataChanged.connect(self.toolchain.dataChanged)
self.toolchain.blocks.append(self)
self.toolchain.scene.addItem(self)
# if type is dataset: update sync channels everywhere
if self.type == BlockType.DATASETS.name:
for block in self.toolchain.blocks:
if (
block.type != BlockType.DATASETS.name
and block.synchronized_channel == old_name
):
block.synchronized_channel = self.name
# Update connections
for connection in self.toolchain.connections:
# Name update
if connection.start_block_name == old_name:
connection.start_block_name = self.name
if connection.end_block_name == old_name:
connection.end_block_name = self.name
if connection.channel == old_name:
connection.channel = self.name
# Complete toolchain channel update from current block
if old_channel != self.synchronized_channel:
self.toolchain.update_channel_path(
self, old_channel, self.synchronized_channel
)
# Update representation
web_representation = self.toolchain.web_representation
for key, value in web_representation.items():
for sub_key, sub_value in web_representation[key].items():
if sub_key == old_name:
# blocks and channel_colors
new_sub_key = sub_key.replace(old_name, self.name)
web_representation[key][new_sub_key] = web_representation[
key
].pop(sub_key)
elif "/" in sub_key:
# connections
new_sub_key = sub_key
left_part = sub_key.split("/")[0]
right_part = sub_key.split("/")[1]
if left_part.split(".")[0] == old_name:
new_sub_key = (
self.name
+ "."
+ left_part.split(".")[1]
+ "/"
+ right_part
)
if right_part.split(".")[0] == old_name:
new_sub_key = (
left_part
+ "/"
+ self.name
+ "."
+ right_part.split(".")[1]
)
if new_sub_key != sub_key:
web_representation[key][new_sub_key] = web_representation[
key
].pop(sub_key)
self.dataChanged.emit()
def paint(self, painter, option, widget):
"""Paint the block"""
......@@ -715,22 +909,24 @@ class Toolchain(QWidget):
self.toolbar = QToolBar()
dataset_action = QAction(
QIcon("beat/editor/widgets/dataset_icon.png"), "&Dataset", self
)
dataset_action.triggered.connect(lambda: self.add_block(BlockType.DATASETS))
block_action = QAction(
QIcon("beat/editor/widgets/block_icon.png"), "&Block", self
)
block_action.triggered.connect(lambda: self.add_block(BlockType.BLOCKS))
analyzer_action = QAction(
QIcon("beat/editor/widgets/analyzer_icon.png"), "&Analyzer", self
)
analyzer_action.triggered.connect(lambda: self.add_block(BlockType.ANALYZERS))
self.dataset_button = QPushButton()
self.dataset_button.setToolTip("Dataset")
self.dataset_button.setIcon(QIcon("beat/editor/widgets/dataset_icon.png"))
self.dataset_edit_menu = QMenu(self)
self.block_button = QPushButton()
self.block_button.setToolTip("Block")
self.block_button.setIcon(QIcon("beat/editor/widgets/block_icon.png"))
self.block_edit_menu = QMenu(self)
self.toolbar.addAction(dataset_action)
self.toolbar.addAction(block_action)
self.toolbar.addAction(analyzer_action)
self.analyzer_button = QPushButton()
self.analyzer_button.setToolTip("Analyzer")
self.analyzer_button.setIcon(QIcon("beat/editor/widgets/analyzer_icon.png"))
self.analyzer_edit_menu = QMenu(self)
self.toolbar.addWidget(self.dataset_button)
self.toolbar.addWidget(self.block_button)
self.toolbar.addWidget(self.analyzer_button)
self.toolbar.setOrientation(Qt.Vertical)
......@@ -738,11 +934,111 @@ class Toolchain(QWidget):
layout.addWidget(self.toolbar)
layout.addWidget(self.view)
def add_block(self, block_type):
self.new_block = Block(
block_type.name, self.block_config, self.connection_config
)
self.scene.addItem(self.new_block)
def update_channel_path(self, block, old_channel, new_channel):
# check if current block is synchronized on old_channel
for connection in self.connections:
if (
block.name == connection.start_block_name
and connection.channel == old_channel
):
# update connection channel
connection.channel = new_channel
self.scene.removeItem(connection)
# Find the corresponding channel
connection_settings = {}
connection_settings["channel"] = connection.channel
# Create the connection
connection_settings["from"] = (
connection.start_block_name + "." + connection.start_pin_name
)
connection_settings["to"] = (
connection.end_block_name + "." + connection.end_pin_name
)
channel_colors = self.json_object.get("representation", {}).get(
"channel_colors"
)
connection.load(self, connection_settings, channel_colors)
self.scene.addItem(connection)
def button_item_selected(self, block_type, name):
inputs = []
outputs = []
if block_type == BlockType.DATASETS.name:
inputs = None
name_split = name.split("/")
database_name = name_split[0] + "/" + name_split[1]
asset = Asset(self.prefix_path, AssetType.DATABASE, database_name)
for protocol in asset.declaration["protocols"]:
if protocol["name"] == name_split[2]:
for _set in protocol["sets"]:
if _set["name"] == name_split[3]:
for key in _set["outputs"].keys():
outputs.append(key)
else:
if block_type == BlockType.ANALYZERS.name:
outputs = None
asset = Asset(self.prefix_path, AssetType.ALGORITHM, name)
declaration = asset.declaration
for group in declaration["groups"]:
if "inputs" in group:
for key in group["inputs"].keys():
inputs.append(key)
if "outputs" in group:
for key in group["outputs"].keys():
outputs.append(key)
block_item = {}
block_item["inputs"] = inputs
block_item["outputs"] = outputs
init_name_count = 0
init_name = self.tr("CHANGE_ME")
for block in self.blocks:
if block.name.find(init_name) > -1:
init_name_count += 1
block_item["name"] = init_name + "_" + str(init_name_count)
block = Block(block_type, self.block_config, self.connection_config)
block.load(self, block_item)
block.dataChanged.connect(self.dataChanged)
self.blocks.append(block)
self.scene.addItem(block)
def set_prefix_databases_algorithms_lists(
self, prefix_path, dataset_list, algorithm_list, analyzer_list
):
self.prefix_path = prefix_path
for dataset_name in dataset_list:
self.dataset_edit_menu.addAction(
self.tr(dataset_name),
partial(
self.button_item_selected, BlockType.DATASETS.name, dataset_name
),
)
for algorithm_name in algorithm_list:
self.block_edit_menu.addAction(
self.tr(algorithm_name),
partial(
self.button_item_selected, BlockType.BLOCKS.name, algorithm_name
),
)
for analyzer_name in analyzer_list:
self.analyzer_edit_menu.addAction(
self.tr(analyzer_name),
partial(
self.button_item_selected, BlockType.ANALYZERS.name, analyzer_name
),
)
self.dataset_button.setMenu(self.dataset_edit_menu)
self.block_button.setMenu(self.block_edit_menu)
self.analyzer_button.setMenu(self.analyzer_edit_menu)
def clear_space(self):
self.scene.clear()
......@@ -853,7 +1149,35 @@ class ToolchainEditor(AbstractAssetEditor):
self.layout().addWidget(self.toolchain, 2)
self.layout().addStretch()
self.algorithm_list = []
self.analyzer_list = []
self.dataset_list = []
self.toolchain.dataChanged.connect(self.dataChanged)
self.contextChanged.connect(self.__onContextChanged)
@pyqtSlot()
def __onContextChanged(self):
algorithm_model = AlgorithmResourceModel()
algorithm_model.setAnalyzerEnabled(False)
dataset_model = DatasetResourceModel()
self.dataset_list = [
dataset_model.index(i, 0).data() for i in range(dataset_model.rowCount())
]
self.algorithm_list = [
algorithm_model.index(i, 0).data()
for i in range(algorithm_model.rowCount())
]
algorithm_model.setAnalyzerEnabled(True)
self.analyzer_list = [
algorithm_model.index(i, 0).data()
for i in range(algorithm_model.rowCount())
]
self.toolchain.set_prefix_databases_algorithms_lists(
self.prefix_path, self.dataset_list, self.algorithm_list, self.analyzer_list
)
def _load_json(self, json_object):
"""Load the json object passed as parameter"""
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment