Commit dca011c9 authored by Maxime Perrotin's avatar Maxime Perrotin
Browse files

Add warning if user wants to save with syntax errors

parent 2871eaac
...@@ -56,7 +56,7 @@ from PySide.QtCore import Qt, QPoint, QPointF, QRect, QFile, QObject, Property ...@@ -56,7 +56,7 @@ from PySide.QtCore import Qt, QPoint, QPointF, QRect, QFile, QObject, Property
from PySide.QtGui import(QGraphicsPathItem, QGraphicsPolygonItem, QPainterPath, from PySide.QtGui import(QGraphicsPathItem, QGraphicsPolygonItem, QPainterPath,
QGraphicsItem, QPen, QColor, QMenu, QFileDialog, QGraphicsItem, QPen, QColor, QMenu, QFileDialog,
QPainter, QLineEdit, QTextBlockFormat) QPainter, QLineEdit, QTextBlockFormat, QPolygonF)
from PySide.QtUiTools import QUiLoader from PySide.QtUiTools import QUiLoader
...@@ -868,7 +868,7 @@ class Cornergrabber(QGraphicsPolygonItem, object): ...@@ -868,7 +868,7 @@ class Cornergrabber(QGraphicsPolygonItem, object):
self.prepareGeometryChange() self.prepareGeometryChange()
rect = self.parent.boundingRect() rect = self.parent.boundingRect()
self.setPos(0, 0) self.setPos(0, 0)
self.setPolygon(rect) self.setPolygon(QPolygonF(rect))
self.show() self.show()
def mousePressEvent(self, event): def mousePressEvent(self, event):
......
...@@ -335,65 +335,77 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -335,65 +335,77 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
# Keep a track of highlighted symbols: { symbol: brush } # Keep a track of highlighted symbols: { symbol: brush }
self.highlighted = {} self.highlighted = {}
@property @property
def visible_symb(self): def visible_symb(self):
''' Return the visible items of a scene ''' ''' Return the visible items of a scene '''
return (it for it in self.items() if it.isVisible() and return (it for it in self.items() if it.isVisible() and
isinstance(it, Symbol)) isinstance(it, Symbol))
@property @property
def editable_texts(self): def editable_texts(self):
''' Return all EditableText areas of a scene ''' ''' Return all EditableText areas of a scene '''
return (it for it in self.items() if it.isVisible() and return (it for it in self.items() if it.isVisible() and
isinstance(it, EditableText)) isinstance(it, EditableText))
@property @property
def floating_symb(self): def floating_symb(self):
''' Return the top level floating items of a scene ''' ''' Return the top level floating items of a scene '''
return (it for it in self.visible_symb if not it.hasParent) return (it for it in self.visible_symb if not it.hasParent)
@property @property
def processes(self): def processes(self):
''' Return visible processes components of the scene ''' ''' Return visible processes components of the scene '''
return (it for it in self.visible_symb if isinstance(it, Process) and return (it for it in self.visible_symb if isinstance(it, Process) and
not isinstance(it, Procedure)) not isinstance(it, Procedure))
@property @property
def procedures(self): def procedures(self):
''' Return visible procedures components of the scene ''' ''' Return visible procedures components of the scene '''
return (it for it in self.visible_symb if isinstance(it, Procedure)) return (it for it in self.visible_symb if isinstance(it, Procedure))
@property @property
def states(self): def states(self):
''' Return visible state components of the scene ''' ''' Return visible state components of the scene '''
return (it for it in self.visible_symb if isinstance(it, State)) return (it for it in self.visible_symb if isinstance(it, State))
@property @property
def texts(self): def texts(self):
''' Return visible text areas components of the scene ''' ''' Return visible text areas components of the scene '''
return (it for it in self.visible_symb if isinstance(it, TextSymbol)) return (it for it in self.visible_symb if isinstance(it, TextSymbol))
@property @property
def procs(self): def procs(self):
''' Return visible procedure declaration components of the scene ''' ''' Return visible procedure declaration components of the scene '''
return (it for it in self.visible_symb if isinstance(it, Procedure)) return (it for it in self.visible_symb if isinstance(it, Procedure))
@property @property
def start(self): def start(self):
''' Return visible start components of the scene ''' ''' Return visible start components of the scene '''
return (it for it in self.visible_symb if isinstance(it, Start)) return (it for it in self.visible_symb if isinstance(it, Start))
@property @property
def floating_labels(self): def floating_labels(self):
''' Return visible floating label components of the scene ''' ''' Return visible floating label components of the scene '''
return (it for it in self.floating_symb if isinstance(it, Label)) return (it for it in self.floating_symb if isinstance(it, Label))
@property @property
def returns(self): def returns(self):
''' Return visible return components of the scene ''' ''' Return visible return components of the scene '''
return (it for it in self.visible_symb if isinstance(it, return (it for it in self.visible_symb if isinstance(it,
ProcedureStop)) ProcedureStop))
@property @property
def composite_states(self): def composite_states(self):
''' Return states that contain a composite part ''' ''' Return states that contain a composite part '''
...@@ -405,11 +417,13 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -405,11 +417,13 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
each.nested_scene each.nested_scene
return self._composite_states return self._composite_states
@composite_states.setter @composite_states.setter
def composite_states(self, value): def composite_states(self, value):
''' Attribute setter ''' ''' Attribute setter '''
self._composite_states = value self._composite_states = value
@property @property
def all_nested_scenes(self): def all_nested_scenes(self):
''' Return all nested scenes, recursively ''' ''' Return all nested scenes, recursively '''
...@@ -419,10 +433,12 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -419,10 +433,12 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
for sub in each.nested_scene.all_nested_scenes: for sub in each.nested_scene.all_nested_scenes:
yield sub yield sub
def quit_scene(self): def quit_scene(self):
''' Called in case of scene switch (e.g. UP button) ''' ''' Called in case of scene switch (e.g. UP button) '''
pass pass
def render_everything(self, ast): def render_everything(self, ast):
''' Render a process and its children scenes, recursively ''' ''' Render a process and its children scenes, recursively '''
already_created = [] already_created = []
...@@ -509,6 +525,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -509,6 +525,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
recursive_render(ast, self) recursive_render(ast, self)
def refresh(self): def refresh(self):
''' Refresh the symbols and connections in the scene ''' ''' Refresh the symbols and connections in the scene '''
for symbol in self.visible_symb: for symbol in self.visible_symb:
...@@ -539,6 +556,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -539,6 +556,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
for symbol in self.visible_symb: for symbol in self.visible_symb:
symbol.update_connections() symbol.update_connections()
def set_cursor(self, follower): def set_cursor(self, follower):
''' Set the cursor shape depending on the selected menu item ''' ''' Set the cursor shape depending on the selected menu item '''
for item in self.items(): for item in self.items():
...@@ -551,6 +569,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -551,6 +569,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
# if there are items not having allowed_followers # if there are items not having allowed_followers
pass pass
def reset_cursor(self): def reset_cursor(self):
''' Reset the default cursor of an item ''' ''' Reset the default cursor of an item '''
for item in self.items(): for item in self.items():
...@@ -559,6 +578,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -559,6 +578,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
except AttributeError: except AttributeError:
pass pass
def translate_to_origin(self): def translate_to_origin(self):
''' '''
Translate all items to coordinate system starting at (0,0), Translate all items to coordinate system starting at (0,0),
...@@ -577,6 +597,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -577,6 +597,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
item.pos_y += delta_y item.pos_y += delta_y
return delta_x, delta_y return delta_x, delta_y
def selected_symbols(self): def selected_symbols(self):
''' Generate the list of selected symbols (excluding grabbers) ''' ''' Generate the list of selected symbols (excluding grabbers) '''
for selection in self.selectedItems(): for selection in self.selectedItems():
...@@ -585,16 +606,26 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -585,16 +606,26 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
elif isinstance(selection, Cornergrabber): elif isinstance(selection, Cornergrabber):
yield selection.parent yield selection.parent
def set_selection(self, toolbar): def set_selection(self, toolbar):
''' When the selection has changed, update menu, etc ''' ''' When the selection has changed, update menu, etc '''
toolbar.update_menu(self) toolbar.update_menu(self)
for item in self.selected_symbols(): for item in self.selected_symbols():
item.grabber.display() item.grabber.display()
def raise_syntax_errors(self, errors=None):
''' Display an syntax error pop-up message ''' def syntax_errors(self, symb):
''' Parse a symbol and return a list of syntax errors '''
return symb.check_syntax('\n'.join(Pr.generate(symb, recursive=False)))
def check_syntax(self, symbol):
''' Check syntax of a symbol and display a pop-up in case of errors '''
errors = self.syntax_errors(symbol)
if not errors: if not errors:
return return
for view in self.views(): for view in self.views():
errs = [] errs = []
for error in errors: for error in errors:
...@@ -619,11 +650,18 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -619,11 +650,18 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
msg_box.setDefaultButton(QtGui.QMessageBox.Discard) msg_box.setDefaultButton(QtGui.QMessageBox.Discard)
msg_box.exec_() msg_box.exec_()
def check_syntax(self, symbol):
''' Create PR representation for a symbol and check its syntax ''' def global_syntax_check(self):
pr_text = '\n'.join(Pr.generate(symbol, recursive=False)) ''' Parse each visible symbol in the current scene and its children
errors = symbol.check_syntax(pr_text) and check syntax using the parser '''
self.raise_syntax_errors(errors) errors = []
for each in self.visible_symb:
err = self.syntax_errors(each)
errors.extend(err)
if errors:
return False
return True
def update_completion_list(self, symbol): def update_completion_list(self, symbol):
''' When text has changed on a symbol, update the data dictionnary ''' ''' When text has changed on a symbol, update the data dictionnary '''
...@@ -632,6 +670,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -632,6 +670,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
nextstate=False, cpy=True)) nextstate=False, cpy=True))
symbol.update_completion_list(pr_text=pr_text) symbol.update_completion_list(pr_text=pr_text)
def highlight(self, item): def highlight(self, item):
''' Highlight a symbol ''' ''' Highlight a symbol '''
if item in self.highlighted: if item in self.highlighted:
...@@ -653,6 +692,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -653,6 +692,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
item.setBrush(brush) item.setBrush(brush)
self.highlighted = {} self.highlighted = {}
def find_text(self, pattern): def find_text(self, pattern):
''' Return all symbols with matching text ''' ''' Return all symbols with matching text '''
for item in (symbol for symbol in self.items() for item in (symbol for symbol in self.items()
...@@ -661,6 +701,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -661,6 +701,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
if re.search(pattern, unicode(item), flags=re.IGNORECASE): if re.search(pattern, unicode(item), flags=re.IGNORECASE):
yield item.parentItem() yield item.parentItem()
def search(self, pattern, replace_with=None): def search(self, pattern, replace_with=None):
''' Search and replace function ; get next search result with key n ''' ''' Search and replace function ; get next search result with key n '''
self.clearSelection() self.clearSelection()
...@@ -690,6 +731,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -690,6 +731,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
except StopIteration: except StopIteration:
LOG.info('Pattern not found') LOG.info('Pattern not found')
def delete_selected_symbols(self): def delete_selected_symbols(self):
''' '''
Remove selected symbols from the scene, with proper re-connections Remove selected symbols from the scene, with proper re-connections
...@@ -709,6 +751,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -709,6 +751,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
pass pass
self.undo_stack.endMacro() self.undo_stack.endMacro()
def copy_selected_symbols(self): def copy_selected_symbols(self):
''' '''
Create a copy of selected symbols to a buffer (in AST form) Create a copy of selected symbols to a buffer (in AST form)
...@@ -724,6 +767,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -724,6 +767,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
LOG.error(unicode(error_msg)) LOG.error(unicode(error_msg))
raise raise
def cut_selected_symbols(self): def cut_selected_symbols(self):
''' '''
Create a copy of selected symbols, then delete them Create a copy of selected symbols, then delete them
...@@ -735,6 +779,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -735,6 +779,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
else: else:
self.delete_selected_symbols() self.delete_selected_symbols()
def paste_symbols(self): def paste_symbols(self):
''' '''
Paste previously copied symbols at selection point Paste previously copied symbols at selection point
...@@ -775,6 +820,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -775,6 +820,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
self.undo_stack.endMacro() self.undo_stack.endMacro()
self.refresh() self.refresh()
def sdl_to_statechart(self, basic=False): def sdl_to_statechart(self, basic=False):
''' Create a graphviz representation of the SDL model ''' ''' Create a graphviz representation of the SDL model '''
pr_raw = Pr.parse_scene(self) pr_raw = Pr.parse_scene(self)
...@@ -789,6 +835,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -789,6 +835,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
Helper.flatten(process_ast) Helper.flatten(process_ast)
return Statechart.create_dot_graph(process_ast, basic) return Statechart.create_dot_graph(process_ast, basic)
def export_branch_to_picture(self, symbol, filename, doc_format): def export_branch_to_picture(self, symbol, filename, doc_format):
''' Save a symbol and its followers to a file ''' ''' Save a symbol and its followers to a file '''
temp_scene = SDL_Scene(context=self.context) temp_scene = SDL_Scene(context=self.context)
...@@ -802,6 +849,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -802,6 +849,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
except AttributeError: except AttributeError:
pass pass
def export_img(self, filename=None, doc_format='png', split=False): def export_img(self, filename=None, doc_format='png', split=False):
''' Save the scene as a PNG/SVG or PDF document ''' Save the scene as a PNG/SVG or PDF document
If specified, split the diagram in multiple files, one If specified, split the diagram in multiple files, one
...@@ -870,6 +918,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -870,6 +918,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
if painter.isActive(): if painter.isActive():
painter.end() painter.end()
def clear_focus(self): def clear_focus(self):
''' Clear focus from any item on the scene ''' ''' Clear focus from any item on the scene '''
try: try:
...@@ -878,6 +927,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -878,6 +927,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
# if no focus item # if no focus item
pass pass
def symbol_near(self, pos, dist=5, selectable_only=True): def symbol_near(self, pos, dist=5, selectable_only=True):
''' If any, returns symbol around pos ''' ''' If any, returns symbol around pos '''
items = self.items( items = self.items(
...@@ -888,6 +938,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -888,6 +938,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
or not selectable_only): or not selectable_only):
return item.parent if isinstance(item, Cornergrabber) else item return item.parent if isinstance(item, Cornergrabber) else item
def can_insert(self, pos, item_type): def can_insert(self, pos, item_type):
''' Check if we can add an item type at a given position ''' ''' Check if we can add an item type at a given position '''
parent_item = self.symbol_near(pos) parent_item = self.symbol_near(pos)
...@@ -906,6 +957,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -906,6 +957,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
' symbol cannot be followed by ' + ' symbol cannot be followed by ' +
item_type.__name__) item_type.__name__)
# pylint: disable=C0103 # pylint: disable=C0103
def mousePressEvent(self, event): def mousePressEvent(self, event):
''' '''
...@@ -964,6 +1016,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -964,6 +1016,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
# if not OK, reset and: # if not OK, reset and:
self.mode = 'idle' self.mode = 'idle'
# pylint: disable=C0103 # pylint: disable=C0103
def mouseMoveEvent(self, event): def mouseMoveEvent(self, event):
''' Handle Click + Mouse move, based on the mode ''' ''' Handle Click + Mouse move, based on the mode '''
...@@ -976,6 +1029,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -976,6 +1029,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
# Update the line # Update the line
pass pass
# pylint: disable=C0103 # pylint: disable=C0103
def mouseReleaseEvent(self, event): def mouseReleaseEvent(self, event):
if self.mode == 'select_items': if self.mode == 'select_items':
...@@ -991,6 +1045,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -991,6 +1045,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
self.mode = 'idle' self.mode = 'idle'
super(SDL_Scene, self).mouseReleaseEvent(event) super(SDL_Scene, self).mouseReleaseEvent(event)
# pylint: disable=C0103 # pylint: disable=C0103
def keyPressEvent(self, event): def keyPressEvent(self, event):
''' Handle keyboard: Delete, Undo/Redo ''' ''' Handle keyboard: Delete, Undo/Redo '''
...@@ -1060,6 +1115,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -1060,6 +1115,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
pprint.pprint(selection.__dict__, None, 2, 1) pprint.pprint(selection.__dict__, None, 2, 1)
code.interact('type your command:', local=locals()) code.interact('type your command:', local=locals())
def create_subscene(self, context): def create_subscene(self, context):
''' Create a new SDL scene, e.g. for nested symbols ''' ''' Create a new SDL scene, e.g. for nested symbols '''
subscene = SDL_Scene(context=context) subscene = SDL_Scene(context=context)
...@@ -1067,6 +1123,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -1067,6 +1123,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
subscene.parent_scene = self subscene.parent_scene = self
return subscene return subscene
def place_symbol(self, item_type, parent, pos=None): def place_symbol(self, item_type, parent, pos=None):
''' Draw a symbol on the scene ''' ''' Draw a symbol on the scene '''
item = item_type() item = item_type()
...@@ -1101,6 +1158,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -1101,6 +1158,7 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
view.ensureVisible(item) view.ensureVisible(item)
return item return item
def add_symbol(self, item_type): def add_symbol(self, item_type):
''' Add a symbol, or postpone until a parent symbol is selected ''' ''' Add a symbol, or postpone until a parent symbol is selected '''
try: try:
...@@ -1421,11 +1479,39 @@ class SDL_View(QtGui.QGraphicsView, object): ...@@ -1421,11 +1479,39 @@ class SDL_View(QtGui.QGraphicsView, object):
self, "Save model", ".", "SDL Model (*.pr)")[0] self, "Save model", ".", "SDL Model (*.pr)")[0]
if self.filename and self.filename.split('.')[-1] != 'pr': if self.filename and self.filename.split('.')[-1] != 'pr':
self.filename += ".pr" self.filename += ".pr"
filename = ( filename = ((self.filename or '_opengeode')
(self.filename or '_opengeode') + '.autosave') if autosave else self.filename
+ '.autosave') if autosave else self.filename
# If the current scene is a nested one, save the top parent
if self.parent_scene:
scene = self.parent_scene[0][0]
else:
scene = self.scene()
if not scene:
LOG.info('No scene - nothing to save')
return False
# check syntax and raise a big warning before saving
if not scene.global_syntax_check():
LOG.error('Syntax errors must be fixed NOW '
'or you may not be able to reload the model')
msg_box = QtGui.QMessageBox(self)
msg_box.setIcon(QtGui.QMessageBox.Critical)
msg_box.setWindowTitle('OpenGEODE - Syntax Error')
#msg_box.setInformativeText('\n'.join(errs))
msg_box.setText("Syntax errors were found. It is not advised to "
"save the model now, as you may not be able to "
"open it again. Are you sure you want to save?")
msg_box.setStandardButtons(QtGui.QMessageBox.Save
| QtGui.QMessageBox.Cancel)
res = msg_box.exec_()
if res == QtGui.QMessageBox.Cancel:
return False
if not filename and not autosave: if not filename and not autosave:
return False return False
else: else:
pr_file = QFile(filename) pr_file = QFile(filename)
pr_file.open(QIODevice.WriteOnly | QIODevice.Text) pr_file.open(QIODevice.WriteOnly | QIODevice.Text)
...@@ -1434,15 +1520,6 @@ class SDL_View(QtGui.QGraphicsView, object): ...@@ -1434,15 +1520,6 @@ class SDL_View(QtGui.QGraphicsView, object):
.split(os.path.extsep)[0:-1]).split(os.path.sep)[-1] .split(os.path.extsep)[0:-1]).split(os.path.sep)[-1]