297 lines
13 KiB
Python
297 lines
13 KiB
Python
from __future__ import absolute_import, print_function
|
|
|
|
# Standard modules
|
|
import logging
|
|
from enum import Enum
|
|
|
|
from qtpy import QtGui
|
|
from qtpy.QtWidgets import QMenu, QAction, QDockWidget, QTreeWidget, QTreeWidgetItem
|
|
from qtpy.QtCore import Slot, Signal, QPointF, Qt, QVariant
|
|
|
|
# Custom modules
|
|
from .. import base
|
|
from ...core.base import Element
|
|
from .canvas.flowgraph import FlowgraphScene
|
|
from .canvas import colors
|
|
|
|
# Logging
|
|
log = logging.getLogger(f"grc.application.{__name__}")
|
|
|
|
|
|
class VariableEditorAction(Enum):
|
|
# Actions that are handled by the editor
|
|
ADD_IMPORT = 0
|
|
ADD_VARIABLE = 1
|
|
OPEN_PROPERTIES = 2
|
|
DELETE_BLOCK = 3
|
|
DELETE_CONFIRM = 4
|
|
ENABLE_BLOCK = 5
|
|
DISABLE_BLOCK = 6
|
|
|
|
|
|
class VariableEditor(QDockWidget, base.Component):
|
|
all_editor_actions = Signal([VariableEditorAction])
|
|
|
|
def __init__(self):
|
|
super(VariableEditor, self).__init__()
|
|
|
|
self.qsettings = self.app.qsettings
|
|
|
|
self.setObjectName('variable_editor')
|
|
self.setWindowTitle('Variable Editor')
|
|
|
|
self.right_click_menu = VariableEditorContextMenu(self)
|
|
self.scene = None
|
|
|
|
# GUI Widgets
|
|
self._tree = QTreeWidget()
|
|
self._model = self._tree.model()
|
|
self._tree.setObjectName('variable_editor::tree_widget')
|
|
self._tree.setHeaderLabels(["ID", "Value", ""])
|
|
self.setWidget(self._tree)
|
|
self.currently_rebuilding = True
|
|
self._model.dataChanged.connect(self.handle_change)
|
|
|
|
self.var_add = VariableEditorAction.ADD_VARIABLE
|
|
self.import_add = VariableEditorAction.ADD_IMPORT
|
|
self.del_block = VariableEditorAction.DELETE_BLOCK
|
|
self._tree.itemClicked.connect(self.handle_click)
|
|
|
|
imports = QTreeWidgetItem(self._tree)
|
|
imports.setText(0, "Imports")
|
|
imports.setData(2, Qt.UserRole, self.import_add)
|
|
imports.setIcon(2, QtGui.QIcon.fromTheme("list-add"))
|
|
|
|
variables = QTreeWidgetItem(self._tree)
|
|
variables.setText(0, "Variables")
|
|
variables.setData(2, Qt.UserRole, self.var_add)
|
|
variables.setIcon(2, QtGui.QIcon.fromTheme("list-add"))
|
|
self._tree.expandAll()
|
|
|
|
# TODO: Move to the base controller and set actions as class attributes
|
|
# Automatically create the actions, menus and toolbars.
|
|
# Child controllers need to call the register functions to integrate into the mainwindow
|
|
self.actions = {}
|
|
self.menus = {}
|
|
self.toolbars = {}
|
|
self.createActions(self.actions)
|
|
self.createMenus(self.actions, self.menus)
|
|
self.createToolbars(self.actions, self.toolbars)
|
|
self.connectSlots()
|
|
|
|
# Register the dock widget through the AppController.
|
|
# The AppController then tries to find a saved dock location from the preferences
|
|
# before calling the MainWindow Controller to add the widget.
|
|
self.app.registerDockWidget(self, location=self.settings.window.VARIABLE_EDITOR_DOCK_LOCATION)
|
|
self.currently_rebuilding = False
|
|
if not self.qsettings.value("appearance/display_variable_editor", True, type=bool):
|
|
self.hide()
|
|
|
|
# Actions
|
|
|
|
def createActions(self, actions):
|
|
log.debug("Creating actions")
|
|
|
|
def createMenus(self, actions, menus):
|
|
log.debug("Creating menus")
|
|
|
|
def createToolbars(self, actions, toolbars):
|
|
log.debug("Creating toolbars")
|
|
|
|
def contextMenuEvent(self, event):
|
|
self.right_click_menu.exec_(self.mapToGlobal(event.pos()))
|
|
|
|
def keyPressEvent(self, event):
|
|
super(VariableEditor, self).keyPressEvent(event)
|
|
|
|
def handle_click(self, item, col):
|
|
# we only care about the add/remove icons being clicked
|
|
if (col != 2):
|
|
return
|
|
|
|
action = item.data(2, Qt.UserRole)
|
|
|
|
if action is None:
|
|
log.warn("Item %s does not contain any actions!!", item)
|
|
|
|
self.handle_action(action)
|
|
|
|
def set_scene(self, scene: FlowgraphScene):
|
|
self.scene = scene
|
|
self.update_gui(self.scene.core.blocks)
|
|
self._tree.resizeColumnToContents(0)
|
|
self._tree.resizeColumnToContents(1)
|
|
|
|
# the handler is required by the signal to take two arguments even
|
|
# if we only use one
|
|
def handle_change(self, tl, br):
|
|
if self.currently_rebuilding:
|
|
return
|
|
|
|
c_block = self._tree.model().data(tl, role=Qt.UserRole)
|
|
if not c_block:
|
|
return
|
|
|
|
new_text = self._tree.model().data(tl)
|
|
c_block.old_data = c_block.export_data()
|
|
if tl.column() == 0: # The name (id) changed
|
|
c_block.params['id'].set_value(new_text)
|
|
else: # column == 1, i.e. the value changed
|
|
if c_block.is_import:
|
|
c_block.params['imports'].set_value(new_text)
|
|
else:
|
|
c_block.params['value'].set_value(new_text)
|
|
self.scene.blockPropsChange.emit(c_block)
|
|
self._tree.resizeColumnToContents(0)
|
|
self._tree.resizeColumnToContents(1)
|
|
|
|
def _rebuild(self):
|
|
# TODO: The way we update block params here seems suboptimal
|
|
self.currently_rebuilding = True
|
|
self._tree.clear()
|
|
imports = QTreeWidgetItem(self._tree, 2)
|
|
imports.setText(0, "Imports")
|
|
imports.setForeground(0, self._tree.palette().color(self.palette().WindowText))
|
|
imports.setIcon(2, QtGui.QIcon.fromTheme("list-add"))
|
|
imports.setData(2, Qt.UserRole, self.import_add)
|
|
for block in self._imports:
|
|
import_ = QTreeWidgetItem(imports, 0)
|
|
import_.setText(0, block.name)
|
|
import_.setData(0, Qt.UserRole, block)
|
|
import_.setText(1, block.params['imports'].get_value())
|
|
import_.setData(1, Qt.UserRole, block)
|
|
import_.setIcon(2, QtGui.QIcon.fromTheme("list-remove"))
|
|
import_.setData(2, Qt.UserRole, self.del_block)
|
|
if block.enabled:
|
|
import_.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEditable | Qt.ItemIsEnabled)
|
|
import_.setForeground(0, self._tree.palette().color(self.palette().WindowText))
|
|
import_.setForeground(1, self._tree.palette().color(self.palette().WindowText))
|
|
else:
|
|
import_.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
|
import_.setForeground(0, self._tree.palette().color(self.palette().Disabled, self.palette().WindowText))
|
|
import_.setForeground(1, self._tree.palette().color(self.palette().Disabled, self.palette().WindowText))
|
|
|
|
variables = QTreeWidgetItem(self._tree, 2)
|
|
variables.setText(0, "Variables")
|
|
variables.setForeground(0, self._tree.palette().color(self.palette().WindowText))
|
|
variables.setIcon(2, QtGui.QIcon.fromTheme("list-add"))
|
|
variables.setData(2, Qt.UserRole, self.var_add)
|
|
for block in sorted(self._variables, key=lambda v: v.name):
|
|
variable_ = QTreeWidgetItem(variables, 1)
|
|
variable_.setText(0, block.name)
|
|
variable_.setData(0, Qt.UserRole, block)
|
|
variable_.setData(2, Qt.UserRole, self.del_block)
|
|
if block.key == 'variable':
|
|
variable_.setText(1, block.params['value'].get_value())
|
|
variable_.setData(1, Qt.UserRole, block)
|
|
variable_.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEditable | Qt.ItemIsEnabled)
|
|
if block.enabled:
|
|
variable_.setForeground(0, self._tree.palette().color(self.palette().WindowText))
|
|
variable_.setForeground(1, self._tree.palette().color(self.palette().WindowText))
|
|
else:
|
|
variable_.setForeground(0, self._tree.palette().color(self.palette().Disabled, self.palette().WindowText))
|
|
variable_.setForeground(1, self._tree.palette().color(self.palette().Disabled, self.palette().WindowText))
|
|
else:
|
|
variable_.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
|
if block.enabled:
|
|
try:
|
|
variable_.setText(1, str(block.evaluate(block.value)))
|
|
variable_.setForeground(0, self._tree.palette().color(self.palette().WindowText))
|
|
variable_.setForeground(1, self._tree.palette().color(self.palette().WindowText))
|
|
except Exception:
|
|
log.exception(f'Failed to evaluate variable block {block.name}', exc_info=True)
|
|
variable_.setText(1, '<Open Properties>')
|
|
variable_.setForeground(0, self._tree.palette().color(self.palette().Disabled, self.palette().WindowText))
|
|
variable_.setForeground(1, self._tree.palette().color(self.palette().Disabled, self.palette().WindowText))
|
|
else:
|
|
variable_.setText(1, '<Open Properties>')
|
|
variable_.setForeground(0, self._tree.palette().color(self.palette().Disabled, self.palette().WindowText))
|
|
variable_.setForeground(1, self._tree.palette().color(self.palette().Disabled, self.palette().WindowText))
|
|
variable_.setIcon(2, QtGui.QIcon.fromTheme("list-remove"))
|
|
|
|
self.currently_rebuilding = False
|
|
|
|
def update_gui(self, blocks):
|
|
self._imports = [block for block in blocks if block.is_import]
|
|
self._variables = [block for block in blocks if block.is_variable]
|
|
self._rebuild()
|
|
self._tree.expandAll()
|
|
|
|
Slot(VariableEditorAction)
|
|
|
|
def handle_action(self, action):
|
|
log.debug(f"{action} triggered!")
|
|
"""
|
|
Single handler for the different actions that can be triggered by the context menu,
|
|
key presses or mouse clicks. Also triggers an update of the flow graph and editor.
|
|
"""
|
|
if action == VariableEditorAction.ADD_IMPORT:
|
|
self.all_editor_actions.emit(action)
|
|
return
|
|
elif action == VariableEditorAction.ADD_VARIABLE:
|
|
self.all_editor_actions.emit(action)
|
|
return
|
|
elif action == VariableEditorAction.OPEN_PROPERTIES:
|
|
if self._tree.currentItem().type() == 2: # Import or Variable header line was selected
|
|
return
|
|
self.scene.clearSelection()
|
|
if self._tree.currentItem().type() == 0:
|
|
to_handle = self.scene.core.blocks.index(self._imports[self._tree.currentIndex().row()])
|
|
else:
|
|
to_handle = self.scene.core.blocks.index(self._variables[self._tree.currentIndex().row()])
|
|
self.scene.core.blocks[to_handle].gui.setSelected(True)
|
|
self.all_editor_actions.emit(action)
|
|
return
|
|
|
|
if self._tree.currentItem().type() == 2: # Import or Variable header line was selected
|
|
return
|
|
|
|
self.scene.clearSelection()
|
|
if self._tree.currentItem().type() == 0:
|
|
to_handle = self.scene.core.blocks.index(self._imports[self._tree.currentIndex().row()])
|
|
else:
|
|
to_handle = self.scene.core.blocks.index(self._variables[self._tree.currentIndex().row()])
|
|
self.scene.core.blocks[to_handle].gui.setSelected(True)
|
|
self.all_editor_actions.emit(action)
|
|
|
|
|
|
class VariableEditorContextMenu(QMenu):
|
|
def __init__(self, var_edit: VariableEditor):
|
|
super(QMenu, self).__init__()
|
|
|
|
self.imports = QAction(_("variable_editor_add_import"), self)
|
|
self.imports.triggered.connect(lambda: var_edit.handle_action(VariableEditorAction.ADD_IMPORT))
|
|
self.addAction(self.imports)
|
|
|
|
self.variables = QAction(_("variable_editor_add_variable"), self)
|
|
self.variables.triggered.connect(lambda: var_edit.handle_action(VariableEditorAction.ADD_VARIABLE))
|
|
self.addAction(self.variables)
|
|
|
|
self.addSeparator()
|
|
|
|
self.enable = QAction(_("variable_editor_enable"), self)
|
|
self.enable.triggered.connect(lambda: var_edit.handle_action(VariableEditorAction.ENABLE_BLOCK))
|
|
self.addAction(self.enable)
|
|
|
|
self.disable = QAction(_("variable_editor_disable"), self)
|
|
self.disable.triggered.connect(lambda: var_edit.handle_action(VariableEditorAction.DISABLE_BLOCK))
|
|
self.addAction(self.disable)
|
|
|
|
self.addSeparator()
|
|
|
|
self.delete = QAction(_("variable_editor_delete"), self)
|
|
self.delete.triggered.connect(lambda: var_edit.handle_action(VariableEditorAction.DELETE_BLOCK))
|
|
self.addAction(self.delete)
|
|
|
|
self.addSeparator()
|
|
|
|
self.properties = QAction(_("variable_editor_properties"), self)
|
|
self.properties.triggered.connect(lambda: var_edit.handle_action(VariableEditorAction.OPEN_PROPERTIES))
|
|
self.addAction(self.properties)
|
|
|
|
def update_enabled(self, selected, enabled=False):
|
|
self.delete.setEnabled(selected)
|
|
self.properties.setEnabled(selected)
|
|
self.enable.setEnabled(selected and not enabled)
|
|
self.disable.setEnabled(selected and enabled)
|