Commit 1127e866 authored by Maxime Perrotin's avatar Maxime Perrotin
Browse files

Improved support of nested states

parent a21c962a
......@@ -11,6 +11,29 @@ SDL is the Specification and Description Language (Z100 standard from ITU-T).
This is NOT related to the graphical Simple DirectMedia Layer libraries!
Visit http://sdl-forum.org for more information about SDL.
Visit http://www.pragmadev.com to get a full-featured commercial SDL tool and support
![alt tag](icons/opengeode-screenshot.png)
Features
--------
- Graphical editor of SDL processes and procedures.
- SDL2010 features: FOR loops in task symbols, hierarchical states
- Works on pure PR+CIF files (textual SDL notation) - no fancy proprietary save format
- Supports ASN.1 data types, including the Value notation - check this page to know more about our ASN.1 compiler and tools
- Generates Ada code with ASN.1 types using TASTE ASN.1 "space-certified" (SPARK) compiler
- Extensive syntax and semantic checks
- Automatic conversion to Statechart diagrams
- Save the complete or parts of the model to PNG/SVG/PDF files
- Hyperlinks (link a symbol content to any external document or web page)
- Zoom in, zoom-out
- Context-dependent text auto-completion
- Syntax highlighting
- Undo/Redo, Copy-Paste
- (Limited) VIM mode - You can use :wq or :%s,search,replace,g, and /search pattern
Installation
============
......@@ -18,11 +41,11 @@ Installation
Pre-requisites
--------------
There are three majors dependencies for OpenGEODE:
There are three major dependencies for OpenGEODE:
- Pyside (the Qt bindings for Python) and the ANTLR runtime to be
- Pyside (the Qt bindings for Python)
- Python ANTLR Runtime
- PyGraphviz
- PyGraphviz (Linux only - not available on Windows)
If you use pip to install OpenGEODE, these dependencies should be installed
automatically. However, note that installing PySide from pip requires some
......@@ -32,16 +55,16 @@ If you are using a Linux Debian-based distribution (including Ubuntu),
I would recommended to install PySide using your package manager:
You should also install pygraphviz using the same method, for convenience.
$ sudo apt-get install python-pyside pyside-tools python-pygraphviz
And to install pip:
$ sudo apt-get install pip
```bash
$ sudo apt-get install python-pyside pyside-tools python-pygraphviz pip
```
The Python 2.7 ANTLR 3.1.3 runtime is not part of Debian packages. Install
it with pip (or download and install manually the package):
$ pip install antlr_python_runtime
```bash
$ pip install antlr_python_runtime singledispatch
```
On Windows:
......@@ -57,8 +80,11 @@ Use pip to install the ANTLR runtime (see above)
Automatic installation (recommended)
------------------------------------
$ pip install singledispatch
$ pip install opengeode
To install the application on your machine:
```bash
$ pip install --upgrade opengeode
```
This is sufficient to get opengeode running
......@@ -79,11 +105,20 @@ Installation from source
You can get the source from the TASTE repositories or from GitHub
```bash
$ svn co https://tecsw.estec.esa.int/svn/taste/trunk/misc/opengeode opengeode
```
Or
```bash
$ git clone https://github.com/maxime-esa/opengeode.git opengeode
```
Then as root, type:
Then enter the opengeode directory and as root, type:
```bash
$ make install
```
Information and contact
=======================
......@@ -110,17 +145,21 @@ The fonts are the fonts from Ubuntu, check licence in file FONT-LICENSE.TXT
Changelog
=========
0.99 (28/02/2014)
0.99 (04/2014)
- Refactoring of the backend engine, now using singledispatch
- Support of hierachical states
- Minor bugfixes
0.98 (08/07/2014)
0.98
- Added support for FOR loops
In a task, use "for x in range([start], stop, [range]): ... endfor"
or "for x in sequenceOfvariable: ... endfor"
- Default symbol size is smaller
- Various minor bugfixes
0.97 (07/12/2013)
0.97
- added support for default value when declaring a variable
e.g. DCL myVar myType ::= { x 5, y 2 };
default value must be a ground expression
......
......@@ -101,11 +101,18 @@ def _automaton(ast, scene):
top_level_symbols.append(render(label, scene, ast.states))
# Render floating states
nested_states = {}
for state in ast.states:
# Create only floating states
try:
new_state = render(state, scene=scene, states=ast.states,
terminators=ast.parent.terminators)
if new_state.nested_scene:
if str(new_state).lower() in nested_states.viewkeys():
new_state.nested_scene = None
else:
nested_states[str(new_state).lower()] = \
new_state.nested_scene
except TypeError:
# Discard terminators (see _state function for explanation)
pass
......
......@@ -104,6 +104,8 @@ class Point(genericSymbols.HorizontalSymbol, object):
self.set_shape(node['width'], node['height'])
self.setBrush(QtGui.QBrush(QtCore.Qt.black))
self.graph = graph
# Text is read only
self.text.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
def set_shape(self, width, height):
''' Define the polygon shape from width and height '''
......
......@@ -560,7 +560,8 @@ class Symbol(QObject, QGraphicsPathItem, object):
needs_parent = True
# nested_scene : a symbol may have content that can be visible on demand
# (e.g. a subscene that appears when double-clicking on the item)
nested_scene = None
_allow_nesting = False
_nested_scene = None
# keywords for the syntax highlighter
blackbold = ()
redbold = ()
......@@ -623,7 +624,8 @@ class Symbol(QObject, QGraphicsPathItem, object):
def is_composite(self):
''' Return True if nested scene has something in it '''
try:
return any(item.isVisible() for item in self.nested_scene.items())
return self.allow_nesting and any(item.isVisible()
for item in self._nested_scene.items())
except AttributeError:
return False
......@@ -644,6 +646,23 @@ class Symbol(QObject, QGraphicsPathItem, object):
#LOG.debug(str(followers))
return followers
@property
def allow_nesting(self):
''' Dynamically check if the scene can have a nested subscene
May be redefined in subclasses and checked on double-click
'''
return self._allow_nesting
@property
def nested_scene(self):
''' Return the nested scene. Can be redefined '''
return self._nested_scene
@nested_scene.setter
def nested_scene(self, value):
''' Set the value of the nested scene '''
self._nested_scene = value
def closest_connection_point(self, coord):
'''
Given a position (QPointF), expected in this symbol's
......
......@@ -356,7 +356,8 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
for top_level in Renderer.render(process, scene=self):
G_SYMBOLS.add(top_level)
# Render optional sub-scenes (procedures)
if top_level.nested_scene:
if top_level.nested_scene and not isinstance(
top_level.nested_scene, SDL_Scene):
subscene = SDL_Scene(
context=top_level.__class__.__name__.lower())
subscene.messages_window = self.messages_window
......@@ -1162,8 +1163,7 @@ class SDL_View(QtGui.QGraphicsView, object):
def go_up(self):
'''
When Up button is clicked, go up one scene level
For example to get out of a procedure definition
When Up button is clicked, go up one nested scene level
'''
LOG.debug('GO_UP')
self.scene().clear_focus()
......@@ -1175,6 +1175,16 @@ class SDL_View(QtGui.QGraphicsView, object):
self.up_button.setEnabled(False)
self.refresh()
def go_down(self, scene):
''' Enter a nested diagram (procedure, composite state) '''
self.parent_scene.append(self.scene())
self.scene().clear_focus()
self.setScene(scene)
self.up_button.setEnabled(True)
self.set_toolbar()
self.scene().scene_left.emit()
self.refresh()
# pylint: disable=C0103
def mouseDoubleClickEvent(self, evt):
''' Catch a double click - possibly change the scene '''
......@@ -1182,22 +1192,17 @@ class SDL_View(QtGui.QGraphicsView, object):
if evt.button() == Qt.LeftButton:
item = self.scene().symbol_near(self.mapToScene(evt.pos()))
try:
if item.nested_scene:
if item.allow_nesting:
if not isinstance(item.nested_scene, SDL_Scene):
subscene = SDL_Scene(
context=item.__class__.__name__.lower())
subscene.messages_window = self.messages_window
for top_level in Renderer.render_process(
if item.nested_scene:
for top_level in Renderer.render_process(
subscene, item.nested_scene):
G_SYMBOLS.add(top_level)
G_SYMBOLS.add(top_level)
item.nested_scene = subscene
self.parent_scene.append(self.scene())
self.scene().clear_focus()
self.setScene(item.nested_scene)
self.up_button.setEnabled(True)
self.set_toolbar()
# Refresh to make sure the resizeEvent is emitted
self.refresh()
self.go_down(item.nested_scene)
else:
# Otherwise, double-click edits the item text
item.edit_text(self.mapToScene(evt.pos()))
......
......@@ -869,6 +869,31 @@ class State(VerticalSymbol):
if ast.comment:
Comment(parent=self, ast=ast.comment)
@property
def allow_nesting(self):
''' Redefinition - must be checked according to context '''
result = not any(elem in str(self).lower().strip()
for elem in ('-', ',', '*', ' '))
return result
@property
def nested_scene(self):
''' Redefined - nested scene per state must be unique '''
if not self._nested_scene:
# Check that the nested scene is not already rendered
try:
for each in self.scene().composite_states:
if str(each).lower().strip() == str(self).lower().strip():
return each._nested_scene
except AttributeError:
pass
return self._nested_scene
@nested_scene.setter
def nested_scene(self, value):
''' Set the value of the nested scene '''
self._nested_scene = value
def update_completion_list(self):
''' When text was entered, update state completion list '''
# Get AST for the symbol
......@@ -982,6 +1007,7 @@ class State(VerticalSymbol):
class Procedure(HorizontalSymbol):
''' Procedure declaration symbol '''
_unique_followers = ['Comment']
_allow_nesting = True
common_name = 'procedure'
needs_parent = False
# Define reserved keywords for the syntax highlighter
......
PROCESS challenge;
STATE ON;
STATE on;
SUBSTRUCTURE
in (via_toto);
out (ret0);
......@@ -60,43 +60,51 @@ START;
NEXTSTATE OFF;
/* CIF STATE (-443, 238), (73, 35) */
STATE Safe;
/* CIF INPUT (-487, 293), (70, 35) */
INPUT *;
/* CIF NEXTSTATE (-487, 343), (70, 35) */
NEXTSTATE Off;
/* CIF INPUT (-406, 293), (88, 35) */
INPUT any_one;
/* CIF NEXTSTATE (-397, 343), (70, 35) */
NEXTSTATE on;
ENDSTATE;
/* CIF STATE (-176, 32), (65, 35) */
/* CIF STATE (-141, 32), (65, 35) */
STATE ON;
/* CIF INPUT (16, 87), (88, 35) */
/* CIF INPUT (51, 87), (88, 35) */
INPUT any_one;
/* CIF NEXTSTATE (3, 137), (113, 35) */
NEXTSTATE ON;
/* CIF INPUT (126, 87), (78, 35) */
/* CIF NEXTSTATE (38, 137), (113, 35) */
NEXTSTATE -;
/* CIF INPUT (161, 87), (78, 35) */
INPUT go_off;
/* CIF NEXTSTATE (130, 137), (70, 35) */
/* CIF NEXTSTATE (169, 137), (62, 35) */
NEXTSTATE OFF;
/* CIF CONNECT (-143, 87), (0, 35) */
/* CIF CONNECT (-108, 87), (0, 35) */
CONNECT ret0;
/* CIF PROCEDURECALL (-279, 137), (272, 35) */
/* CIF PROCEDURECALL (-244, 137), (272, 35) */
CALL writeln('Received ret0, going to Safe');
/* CIF NEXTSTATE (-176, 187), (67, 35) */
/* CIF NEXTSTATE (-141, 187), (67, 35) */
NEXTSTATE Safe;
/* CIF CONNECT (-459, 87), (0, 35) */
/* CIF CONNECT (-424, 87), (0, 35) */
CONNECT ;
/* CIF PROCEDURECALL (-629, 137), (340, 35) */
/* CIF PROCEDURECALL (-594, 137), (340, 35) */
CALL writeln('Exit from nested state (no return value)');
/* CIF NEXTSTATE (-494, 187), (70, 35) */
/* CIF NEXTSTATE (-459, 187), (70, 35) */
NEXTSTATE off;
ENDSTATE;
/* CIF STATE (-443, 238), (73, 35) */
STATE Safe;
/* CIF INPUT (-487, 293), (70, 35) */
/* CIF STATE (-674, 38), (70, 35) */
STATE on;
/* CIF INPUT (-674, 93), (70, 35) */
INPUT *;
/* CIF NEXTSTATE (-487, 343), (70, 35) */
NEXTSTATE Off;
/* CIF INPUT (-406, 293), (88, 35) */
INPUT any_one;
/* CIF NEXTSTATE (-397, 343), (70, 35) */
NEXTSTATE on;
/* CIF NEXTSTATE (-674, 143), (70, 35) */
NEXTSTATE -;
ENDSTATE;
/* CIF STATE (-843, 111), (70, 35) */
......
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