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

Allow configuration of statecharts

parent 28cb9527
......@@ -22,6 +22,7 @@
<file>icons/texture.png</file>
<file>opengeode.ui</file>
<file>hyperlink.ui</file>
<file>statechart_cfg.ui</file>
<file>fonts/Ubuntu-B.ttf</file>
<file>fonts/Ubuntu-BI.ttf</file>
<file>fonts/Ubuntu-R.ttf</file>
......
......@@ -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
try:
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]
else:
valid_inputs.append(each)
elif each:
valid_inputs.add(each.lower())
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:
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():
# 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):
fixedsize='true',
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)
inputs_to_save.add(label)
inputs_to_save.add(label.lower())
source = str(diamond)
label = ''
diamond += 1
......@@ -678,29 +735,34 @@ def create_dot_graph(root_ast, basic=False):
else:
target = term.inputString.lower() or ' '
if basic:
target_states[target].add(label)
elif valid_inputs is None or label in valid_inputs or not label:
graph.add_edge(source, target, label=label)
inputs_to_save.add(label)
target_states[target] |= set(label.split(','))
else:
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
valid_inputs]
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('statechart.dot', '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')
ui_file.open(QFile.ReadOnly)
self.hyperlink_dialog = loader.load(ui_file)
ui_file.close()
......
This diff is collapsed.
......@@ -1027,6 +1027,8 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
ast, _, _ = ogParser.parse_pr(string=pr_data)
try:
process_ast, = ast.processes
process_ast.input_signals = \
sdlSymbols.CONTEXT.processes[0].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
self.setWindowState(Qt.WindowMaximized)
......@@ -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()
try:
graph = scene.sdl_to_statechart()
......@@ -2314,7 +2320,11 @@ class OG_MainWindow(QtGui.QMainWindow, object):
self.statechart_scene.itemsBoundingRect(),
Qt.KeepAspectRatioByExpanding)
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)
......
# $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">
<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>
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