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

Allow configuration of statecharts

parent 28cb9527
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
<file>icons/texture.png</file> <file>icons/texture.png</file>
<file>opengeode.ui</file> <file>opengeode.ui</file>
<file>hyperlink.ui</file> <file>hyperlink.ui</file>
<file>statechart_cfg.ui</file>
<file>fonts/Ubuntu-B.ttf</file> <file>fonts/Ubuntu-B.ttf</file>
<file>fonts/Ubuntu-BI.ttf</file> <file>fonts/Ubuntu-BI.ttf</file>
<file>fonts/Ubuntu-R.ttf</file> <file>fonts/Ubuntu-R.ttf</file>
......
...@@ -22,10 +22,17 @@ ...@@ -22,10 +22,17 @@
import os import os
import logging import logging
from collections import defaultdict from collections import defaultdict
from functools import partial
from itertools import chain from itertools import chain
import re import re
from PySide import QtGui, QtCore from PySide import QtGui, QtCore
# import resource file to get the configuration widget
from PySide.QtUiTools import QUiLoader
import icons
g_statechart_lock = False
try: try:
import pygraphviz as dotgraph import pygraphviz as dotgraph
except ImportError: except ImportError:
...@@ -494,10 +501,6 @@ def render_statechart(scene, graphtree=None, keep_pos=False, dump_gfx=''): ...@@ -494,10 +501,6 @@ def render_statechart(scene, graphtree=None, keep_pos=False, dump_gfx=''):
if dump_gfx.split('.')[-1].lower() != 'png': if dump_gfx.split('.')[-1].lower() != 'png':
dump_gfx += '.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}' graph.layout(prog='neato', args='{cfg} {kp} {dump}'
.format(cfg=config, kp='-n1' if keep_pos else '', .format(cfg=config, kp='-n1' if keep_pos else '',
dump=('-Tpng -o' + dump_gfx) if dump_gfx 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=''): ...@@ -551,7 +554,22 @@ def render_statechart(scene, graphtree=None, keep_pos=False, dump_gfx=''):
each.setZValue(each.zValue() + symb.zValue() + 1) 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 ''' 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 Set basic=True to generate a simple graph with at most one edge
between two states and no diamond nodes between two states and no diamond nodes
...@@ -559,10 +577,14 @@ def create_dot_graph(root_ast, basic=False): ...@@ -559,10 +577,14 @@ def create_dot_graph(root_ast, basic=False):
graph = dotgraph.AGraph(strict=False, directed=True) graph = dotgraph.AGraph(strict=False, directed=True)
ret = {'graph': graph, 'children': {}, 'config': {}} ret = {'graph': graph, 'children': {}, 'config': {}}
diamond = 0 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 # valid_inputs: list of messages to be displayed in the statecharts
# user can remove them from the file to make cleaner diagrams # user can remove them from the file to make cleaner diagrams
# config_params can be set to tune the call to graphviz # config_params can be set to tune the call to graphviz
valid_inputs = [] valid_inputs = set()
config_params = {} config_params = {}
inputs_to_save = set() inputs_to_save = set()
identifier = getattr(root_ast, "statename", root_ast.processName) identifier = getattr(root_ast, "statename", root_ast.processName)
...@@ -573,10 +595,10 @@ def create_dot_graph(root_ast, basic=False): ...@@ -573,10 +595,10 @@ def create_dot_graph(root_ast, basic=False):
split = each.split() split = each.split()
if len(split) == 3 and split[0] == "cfg": if len(split) == 3 and split[0] == "cfg":
config_params[split[1]] = split[2] config_params[split[1]] = split[2]
else: elif each:
valid_inputs.append(each) valid_inputs.add(each.lower())
except IOError: except IOError:
valid_inputs = None valid_inputs = input_signals
config_params = {"-Nfontsize" : "12", config_params = {"-Nfontsize" : "12",
"-Efontsize" : "8", "-Efontsize" : "8",
"-Gsplines" : "curved", "-Gsplines" : "curved",
...@@ -590,6 +612,41 @@ def create_dot_graph(root_ast, basic=False): ...@@ -590,6 +612,41 @@ def create_dot_graph(root_ast, basic=False):
else: else:
LOG.info ("Statechart settings read from configuration file") LOG.info ("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.
lock()
def right(leftList, rightList):
for each in leftList.selectedItems():
item = leftList.takeItem(leftList.row(each))
rightList.addItem(item)
def left(leftList, rightList):
for each in rightList.selectedItems():
item = rightList.takeItem(rightList.row(each))
leftList.addItem(item)
loader = QUiLoader()
ui_file = QtCore.QFile(":/statechart_cfg.ui")
ui_file.open(QtCore.QFile.ReadOnly)
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")
okButton.pressed.connect(dialog.accept)
rightButton.pressed.connect(partial(right, leftList, rightList))
leftButton.pressed.connect(partial(left, leftList, rightList))
ui_file.close()
rightList.addItems(list(valid_inputs))
leftList.addItems(list(input_signals - valid_inputs))
go = dialog.exec_()
valid_inputs.clear()
for idx in xrange(rightList.count()):
valid_inputs.add(rightList.item(idx).text())
unlock()
for state in root_ast.mapping.viewkeys(): for state in root_ast.mapping.viewkeys():
# create a new node for each state (including nested states) # create a new node for each state (including nested states)
if state.endswith('START'): if state.endswith('START'):
...@@ -666,9 +723,9 @@ def create_dot_graph(root_ast, basic=False): ...@@ -666,9 +723,9 @@ def create_dot_graph(root_ast, basic=False):
fixedsize='true', fixedsize='true',
width=15.0 / 72.0, width=15.0 / 72.0,
height=15.0 / 72.0, label='') 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) graph.add_edge(source, str(diamond), label=label)
inputs_to_save.add(label) inputs_to_save.add(label.lower())
source = str(diamond) source = str(diamond)
label = '' label = ''
diamond += 1 diamond += 1
...@@ -678,29 +735,34 @@ def create_dot_graph(root_ast, basic=False): ...@@ -678,29 +735,34 @@ def create_dot_graph(root_ast, basic=False):
else: else:
target = term.inputString.lower() or ' ' target = term.inputString.lower() or ' '
if basic: if basic:
target_states[target].add(label) target_states[target] |= set(label.split(','))
elif valid_inputs is None or label in valid_inputs or not label: else:
graph.add_edge(source, target, label=label) labs = set(lab.strip() for lab in label.split(',') if
inputs_to_save.add(label) 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(): 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
valid_inputs] lab.strip().lower() in valid_inputs | {""}]
# Basic mode # Basic mode
if sublab: if sublab:
graph.add_edge(source, target, label=',\n'.join(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('statechart.dot', 'w') as output: # with open('statechart.dot', 'w') as output:
# output.write(graph.to_string()) # output.write(graph.to_string())
#return graph #return graph
if valid_inputs is None: with open(identifier + ".cfg", "w") as cfg_file:
with open(identifier + ".cfg", "w") as cfg_file: for name, value in config_params.viewitems():
for name, value in config_params.viewitems(): cfg_file.write("cfg {} {}\n".format(name, value))
cfg_file.write("cfg {} {}\n".format(name, value)) for each in inputs_to_save:
for each in inputs_to_save: cfg_file.write(each + "\n")
cfg_file.write(each + "\n")
ret['config'] = config_params ret['config'] = config_params
for each in root_ast.composite_states: 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 return ret
......
...@@ -444,7 +444,7 @@ class Symbol(QObject, QGraphicsPathItem, object): ...@@ -444,7 +444,7 @@ class Symbol(QObject, QGraphicsPathItem, object):
def loadHyperlinkDialog(self): def loadHyperlinkDialog(self):
''' Load dialog from ui file for defining hyperlink ''' ''' Load dialog from ui file for defining hyperlink '''
loader = QUiLoader() loader = QUiLoader()
ui_file = QFile(':/hyperlink.ui') # UI_DIALOG_FILE) ui_file = QFile(':/hyperlink.ui')
ui_file.open(QFile.ReadOnly) ui_file.open(QFile.ReadOnly)
self.hyperlink_dialog = loader.load(ui_file) self.hyperlink_dialog = loader.load(ui_file)
ui_file.close() ui_file.close()
......
This diff is collapsed.
...@@ -1027,6 +1027,8 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -1027,6 +1027,8 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
ast, _, _ = ogParser.parse_pr(string=pr_data) ast, _, _ = ogParser.parse_pr(string=pr_data)
try: try:
process_ast, = ast.processes process_ast, = ast.processes
process_ast.input_signals = \
sdlSymbols.CONTEXT.processes[0].input_signals
except ValueError: except ValueError:
LOG.debug('No statechart to render') LOG.debug('No statechart to render')
return None return None
...@@ -1034,7 +1036,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -1034,7 +1036,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
# dot supports only vertically-aligned states, and fdp does not # dot supports only vertically-aligned states, and fdp does not
# support curved edges and is buggy with pygraphviz anyway) # support curved edges and is buggy with pygraphviz anyway)
# Helper.flatten(process_ast) # 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): def export_branch_to_picture(self, symbol, filename, doc_format):
...@@ -2149,6 +2151,7 @@ class OG_MainWindow(QtGui.QMainWindow, object): ...@@ -2149,6 +2151,7 @@ class OG_MainWindow(QtGui.QMainWindow, object):
self.mdi_area = None self.mdi_area = None
self.sub_mdi = None self.sub_mdi = None
self.statechart_mdi = None self.statechart_mdi = None
self.current_window = None
self.datadict = None self.datadict = None
self.setWindowState(Qt.WindowMaximized) self.setWindowState(Qt.WindowMaximized)
...@@ -2303,7 +2306,10 @@ class OG_MainWindow(QtGui.QMainWindow, object): ...@@ -2303,7 +2306,10 @@ class OG_MainWindow(QtGui.QMainWindow, object):
''' Signal sent by Qt when the MDI area tab changes ''' Signal sent by Qt when the MDI area tab changes
Here we check if the Statechart tab is selected, and we draw/refresh Here we check if the Statechart tab is selected, and we draw/refresh
the statechart automatically in that case ''' 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() scene = self.view.top_scene()
try: try:
graph = scene.sdl_to_statechart() graph = scene.sdl_to_statechart()
...@@ -2314,7 +2320,11 @@ class OG_MainWindow(QtGui.QMainWindow, object): ...@@ -2314,7 +2320,11 @@ class OG_MainWindow(QtGui.QMainWindow, object):
self.statechart_scene.itemsBoundingRect(), self.statechart_scene.itemsBoundingRect(),
Qt.KeepAspectRatioByExpanding) Qt.KeepAspectRatioByExpanding)
except (AttributeError, IOError, TypeError) as err: except (AttributeError, IOError, TypeError) as err:
LOG.debug(str(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) @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 import sys
from antlr3 import * from antlr3 import *
......
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="windowModality">
<enum>Qt::ApplicationModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>436</width>
<height>334</height>
</rect>
</property>
<property name="windowTitle">
<string>Configure statechart</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Select the messages you want to view in the statechart</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QListWidget" name="leftList">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::MultiSelection</enum>
</property>
<property name="selectionRectVisible">
<bool>true</bool>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QToolButton" name="toRight">
<property name="text">
<string>...</string>
</property>
<property name="arrowType">
<enum>Qt::RightArrow</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="toLeft">
<property name="text">
<string>...</string>
</property>
<property name="arrowType">
<enum>Qt::LeftArrow</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QListWidget" name="rightList">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::MultiSelection</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="okButton">
<property name="text">
<string notr="true">OK</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
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