Commit 48af1941 authored by Maxime Perrotin's avatar Maxime Perrotin
Browse files

Allow configuration of statecharts

parent 28cb9527
......@@ -22,6 +22,7 @@
......@@ -22,10 +22,17 @@
import os
import logging
from collections import defaultdict
from functools import partial
from itertools import chain
import re
from PySide import QtGui, QtCore
# import resource file to get the configuration widget
from PySide.QtUiTools import QUiLoader
import icons
g_statechart_lock = False
import pygraphviz as dotgraph
except ImportError:
......@@ -494,10 +501,6 @@ def render_statechart(scene, graphtree=None, keep_pos=False, dump_gfx=''):
if dump_gfx.split('.')[-1].lower() != 'png':
dump_gfx += '.png'
# graph.layout(prog='neato', args='-Nfontsize=12, -Efontsize=8 '
# '-Gsplines=curved -Gsep=0.3 -Gdpi=72 '
# '-Gstart=random10 -Goverlap=scale '
# '-Nstyle=rounded -Nshape=record -Elen=1 {kp} {dump}'
graph.layout(prog='neato', args='{cfg} {kp} {dump}'
.format(cfg=config, kp='-n1' if keep_pos else '',
dump=('-Tpng -o' + dump_gfx) if dump_gfx else ''))
......@@ -551,7 +554,22 @@ def render_statechart(scene, graphtree=None, keep_pos=False, dump_gfx=''):
each.setZValue(each.zValue() + symb.zValue() + 1)
def create_dot_graph(root_ast, basic=False):
def lock():
''' Prevent multiple callers to render at the same time '''
global g_statechart_lock
g_statechart_lock = True
def unlock():
''' Prevent multiple callers to render at the same time '''
global g_statechart_lock
g_statechart_lock = False
def locked():
''' Return the lock status '''
return g_statechart_lock
def create_dot_graph(root_ast, basic=False, scene=None):
''' Return a dot.AGraph item, from an ogAST.Process or child entry
Set basic=True to generate a simple graph with at most one edge
between two states and no diamond nodes
......@@ -559,10 +577,14 @@ def create_dot_graph(root_ast, basic=False):
graph = dotgraph.AGraph(strict=False, directed=True)
ret = {'graph': graph, 'children': {}, 'config': {}}
diamond = 0
input_signals = {sig['name'].lower() for sig in root_ast.input_signals}
# XXX misses the timers
# valid_inputs: list of messages to be displayed in the statecharts
# user can remove them from the file to make cleaner diagrams
# config_params can be set to tune the call to graphviz
valid_inputs = []
valid_inputs = set()
config_params = {}
inputs_to_save = set()
identifier = getattr(root_ast, "statename", root_ast.processName)
......@@ -573,10 +595,10 @@ def create_dot_graph(root_ast, basic=False):
split = each.split()
if len(split) == 3 and split[0] == "cfg":
config_params[split[1]] = split[2]
elif each:
except IOError:
valid_inputs = None
valid_inputs = input_signals
config_params = {"-Nfontsize" : "12",
"-Efontsize" : "8",
"-Gsplines" : "curved",
......@@ -590,6 +612,41 @@ def create_dot_graph(root_ast, basic=False):
else: ("Statechart settings read from configuration file")
if scene:
# Load and display a table for the user to filter out messages that
# are not relevant to display on the statechart - and make it lighter
# Repeat for substates, too.
def right(leftList, rightList):
for each in leftList.selectedItems():
item = leftList.takeItem(leftList.row(each))
def left(leftList, rightList):
for each in rightList.selectedItems():
item = rightList.takeItem(rightList.row(each))
loader = QUiLoader()
ui_file = QtCore.QFile(":/statechart_cfg.ui")
dialog = loader.load(ui_file)
dialog.setParent (scene.views()[0], QtCore.Qt.Dialog)
okButton = dialog.findChild(QtGui.QPushButton, "okButton")
rightButton = dialog.findChild(QtGui.QToolButton, "toRight")
leftButton = dialog.findChild(QtGui.QToolButton, "toLeft")
rightList = dialog.findChild(QtGui.QListWidget, "rightList")
leftList = dialog.findChild(QtGui.QListWidget, "leftList")
rightButton.pressed.connect(partial(right, leftList, rightList))
leftButton.pressed.connect(partial(left, leftList, rightList))
leftList.addItems(list(input_signals - valid_inputs))
go = dialog.exec_()
for idx in xrange(rightList.count()):
for state in root_ast.mapping.viewkeys():
# create a new node for each state (including nested states)
if state.endswith('START'):
......@@ -666,9 +723,9 @@ def create_dot_graph(root_ast, basic=False):
width=15.0 / 72.0,
height=15.0 / 72.0, label='')
if valid_inputs is None or label in valid_inputs or not label:
if label.lower() in valid_inputs or not label.strip():
graph.add_edge(source, str(diamond), label=label)
source = str(diamond)
label = ''
diamond += 1
......@@ -678,29 +735,34 @@ def create_dot_graph(root_ast, basic=False):
target = term.inputString.lower() or ' '
if basic:
elif valid_inputs is None or label in valid_inputs or not label:
graph.add_edge(source, target, label=label)
target_states[target] |= set(label.split(','))
labs = set(lab.strip() for lab in label.split(',') if
lab.strip().lower() in valid_inputs | {""})
actual = ',\n'.join(labs)
graph.add_edge(source, target, label=actual)
inputs_to_save |= set(lab.lower() for lab in labs)
for target, labels in target_states.viewitems():
sublab = [lab for lab in labels if valid_inputs is None or label in
sublab = [lab.strip() for lab in labels if
lab.strip().lower() in valid_inputs | {""}]
# Basic mode
if sublab:
graph.add_edge(source, target, label=',\n'.join(sublab))
inputs_to_save |= set(sublab)
inputs_to_save |= set(lab.lower() for lab in sublab)
# with open('', 'w') as output:
# output.write(graph.to_string())
#return graph
if valid_inputs is None:
with open(identifier + ".cfg", "w") as cfg_file:
for name, value in config_params.viewitems():
cfg_file.write("cfg {} {}\n".format(name, value))
for each in inputs_to_save:
cfg_file.write(each + "\n")
with open(identifier + ".cfg", "w") as cfg_file:
for name, value in config_params.viewitems():
cfg_file.write("cfg {} {}\n".format(name, value))
for each in inputs_to_save:
cfg_file.write(each + "\n")
ret['config'] = config_params
for each in root_ast.composite_states:
ret['children'][each.statename] = create_dot_graph(each, basic)
# Recursively generate the graphs for nested states
# Inherit from the list of signals from the higer level state
each.input_signals = root_ast.input_signals
ret['children'][each.statename] = create_dot_graph(each, basic, scene)
return ret
......@@ -444,7 +444,7 @@ class Symbol(QObject, QGraphicsPathItem, object):
def loadHyperlinkDialog(self):
''' Load dialog from ui file for defining hyperlink '''
loader = QUiLoader()
ui_file = QFile(':/hyperlink.ui') # UI_DIALOG_FILE)
ui_file = QFile(':/hyperlink.ui')
self.hyperlink_dialog = loader.load(ui_file)
This diff is collapsed.
......@@ -1027,6 +1027,8 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
ast, _, _ = ogParser.parse_pr(string=pr_data)
process_ast, = ast.processes
process_ast.input_signals = \
except ValueError:
LOG.debug('No statechart to render')
return None
......@@ -1034,7 +1036,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
# dot supports only vertically-aligned states, and fdp does not
# support curved edges and is buggy with pygraphviz anyway)
# Helper.flatten(process_ast)
return Statechart.create_dot_graph(process_ast, basic)
return Statechart.create_dot_graph(process_ast, basic, scene=self)
def export_branch_to_picture(self, symbol, filename, doc_format):
......@@ -2149,6 +2151,7 @@ class OG_MainWindow(QtGui.QMainWindow, object):
self.mdi_area = None
self.sub_mdi = None
self.statechart_mdi = None
self.current_window = None
self.datadict = None
......@@ -2303,7 +2306,10 @@ class OG_MainWindow(QtGui.QMainWindow, object):
''' Signal sent by Qt when the MDI area tab changes
Here we check if the Statechart tab is selected, and we draw/refresh
the statechart automatically in that case '''
if mdi == self.statechart_mdi:
if(mdi == self.statechart_mdi and
mdi != self.current_window and not Statechart.locked()):
# this signal is executed even when model windows are open
# so the lock is necessary to prevent recursive execution
scene = self.view.top_scene()
graph = scene.sdl_to_statechart()
......@@ -2314,7 +2320,11 @@ class OG_MainWindow(QtGui.QMainWindow, object):
except (AttributeError, IOError, TypeError) as err:
LOG.debug("Statechart error: " + str(err))
if mdi is not None:
# When leaving the focus, this signal is received with mdi == None
# but the window is not changed, so don't update current_window
self.current_window = mdi
@QtCore.Slot(QtGui.QTreeWidgetItem, int)
# $ANTLR 3.1.3 Mar 17, 2009 19:23:44 sdl92.g 2016-11-29 10:37:54
# $ANTLR 3.1.3 Mar 17, 2009 19:23:44 sdl92.g 2017-02-19 22:16:11
import sys
from antlr3 import *
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<widget class="QDialog" name="Dialog">
<property name="windowModality">
<property name="geometry">
<property name="windowTitle">
<string>Configure statechart</string>
<property name="modal">
<layout class="QVBoxLayout" name="verticalLayout">
<layout class="QVBoxLayout" name="verticalLayout_3">
<widget class="QLabel" name="label">
<property name="text">
<string>Select the messages you want to view in the statechart</string>
<layout class="QHBoxLayout" name="horizontalLayout">
<widget class="QListWidget" name="leftList">
<property name="editTriggers">
<property name="alternatingRowColors">
<property name="selectionMode">
<property name="selectionRectVisible">
<property name="sortingEnabled">
<layout class="QVBoxLayout" name="verticalLayout_2">
<widget class="QToolButton" name="toRight">
<property name="text">
<property name="arrowType">
<widget class="QToolButton" name="toLeft">
<property name="text">
<property name="arrowType">
<widget class="QListWidget" name="rightList">
<property name="editTriggers">
<property name="alternatingRowColors">
<property name="selectionMode">
<widget class="QPushButton" name="okButton">
<property name="text">
<string notr="true">OK</string>
Supports Markdown
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