瀏覽代碼

wxGUI: run model from menu

git-svn-id: https://svn.osgeo.org/grass/grass/trunk@41886 15284696-431f-4ddb-bdfa-cd5b030d7da7
Martin Landa 15 年之前
父節點
當前提交
2b9946bdf2

+ 17 - 15
gui/wxpython/gui_modules/gcmd.py

@@ -91,27 +91,29 @@ class GMessage:
                                reason, exception),
                                reason, exception),
                           caption = caption,
                           caption = caption,
                           style = style)
                           style = style)
-            
+
+class GError(Exception):
+    def __init__(self, value):
+        self.value = value
+
+    def __str__(self):
+        return str(self.value)
+
 class GException(Exception):
 class GException(Exception):
     """!Generic exception"""
     """!Generic exception"""
-    def __init__(self, message, title=_("Error"), parent=None):
+    def __init__(self, message, title = _("Error"), parent = None):
         self.msg = message
         self.msg = message
         self.parent = parent
         self.parent = parent
         self.title = title
         self.title = title
         
         
     def Show(self):
     def Show(self):
-        dlg = wx.MessageDialog(parent=self.parent,
-                               caption=self.title,
-                               message=self.msg,
-                               style=wx.ICON_ERROR | wx.CENTRE)
-        dlg.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass_error.ico'), wx.BITMAP_TYPE_ICO))
-        if self.parent:
-            dlg.CentreOnParent()
-        else:
-            dlg.CentreOnScreen()
+        GMessage(parent  = self.parent,
+                 message = self.msg,
+                 msgType = 'error')
 
 
-        dlg.ShowModal()
-        
+    def GetMessage(self):
+        return self.msg
+    
     def __str__(self):
     def __str__(self):
         self.Show()
         self.Show()
         
         
@@ -120,9 +122,9 @@ class GException(Exception):
 class GStdError(GException):
 class GStdError(GException):
     """!Generic exception"""
     """!Generic exception"""
 
 
-    def __init__(self, message, title=_("Error"), parent=None):
+    def __init__(self, message, title = _("Error"), parent = None):
         GException.__init__(self, message, title=title, parent=parent)
         GException.__init__(self, message, title=title, parent=parent)
-
+    
 class CmdError(GException):
 class CmdError(GException):
     """!Exception used for GRASS commands.
     """!Exception used for GRASS commands.
 
 

+ 259 - 168
gui/wxpython/gui_modules/gmodeler.py

@@ -4,6 +4,7 @@
 @brief Graphical modeler to create edit, and manage models
 @brief Graphical modeler to create edit, and manage models
 
 
 Classes:
 Classes:
+ - Model
  - ModelFrame
  - ModelFrame
  - ModelCanvas
  - ModelCanvas
  - ModelAction
  - ModelAction
@@ -52,10 +53,185 @@ import goutput
 import gselect
 import gselect
 from   debug import Debug
 from   debug import Debug
 from   gcmd import GMessage
 from   gcmd import GMessage
+from   gcmd import GError
 from   gdialogs import ElementDialog
 from   gdialogs import ElementDialog
 from   gdialogs import GetImageHandlers
 from   gdialogs import GetImageHandlers
 from grass.script import core as grass
 from grass.script import core as grass
 
 
+class Model(object):
+    """!Class representing the model"""
+    def __init__(self, canvas = None):
+        self.actions = list()    # list of recorded actions
+        self.data    = list()    # list of recorded data items
+        self.canvas  = canvas
+        
+    def GetCanvas(self):
+        """!Get canvas or None"""
+        return self.canvas
+    
+    def GetActions(self):
+        """!Return list of actions"""
+        return self.actions
+
+    def GetData(self):
+        """!Return list of data"""
+        return self.data
+
+    def Reset(self):
+        """!Reset model"""
+        self.actions = list()
+        self.data    = list()
+        
+    def AddAction(self, item):
+        """!Add action to the model"""
+        self.actions.append(item)
+        
+    def AddData(self, item):
+        """!Add data to the model"""
+        self.data.append(item)
+
+    def FindAction(self, id):
+        """!Find action by id"""
+        for action in self.actions:
+            if action.GetId() == id:
+                return action
+        
+        return None
+
+    def FindData(self, value, prompt):
+        """!Find data by value, and prompt"""
+        for data in self.data:
+            if data.GetValue() == value and \
+                    data.GetPrompt() == prompt:
+                return data
+        
+        return None
+    
+    def LoadModel(self, filename):
+        """!Load model definition stored in GRASS Model XML file (gxm)
+        
+        @todo Validate against DTD
+        
+        Raise exception on error.
+        """
+        dtdFilename = os.path.join(globalvar.ETCWXDIR, "xml", "grass-gxm.dtd")
+        
+        # parse workspace file
+        try:
+            gxmXml = ProcessModelFile(etree.parse(filename))
+        except StandardError, e:
+            raise GError(e)
+        
+        # load model.GetActions()
+        for action in gxmXml.actions:
+            actionItem = ModelAction(parent = self, 
+                                     x = action['pos'][0],
+                                     y = action['pos'][1],
+                                     width = action['size'][0],
+                                     height = action['size'][1],
+                                     cmd = action['cmd'])
+            actionItem.SetId(action['id'])
+
+            self.actions.append(actionItem)
+            
+            task = menuform.GUI().ParseCommand(cmd = actionItem.GetLog(string = False),
+                                               show = None)
+            valid = True
+            for p in task.get_options()['params']:
+                if p.get('value', '') == '' and \
+                        p.get('default', '') == '':
+                    valid = False
+                    break
+            actionItem.SetValid(valid)
+        
+        # load data & connections
+        for data in gxmXml.data:
+            dataItem = ModelData(parent = self, 
+                                 x = data['pos'][0],
+                                 y = data['pos'][1],
+                                 width = data['size'][0],
+                                 height = data['size'][1],
+                                 name = data['name'],
+                                 prompt = data['prompt'],
+                                 value = data['value'])
+            dataItem.SetIntermediate(data['intermediate'])
+            
+            for idx in range(len(data['id'])):
+                actionItem = self.FindAction(data['id'][idx])
+                if data['from'][idx] is True:
+                    dataItem.AddAction(actionItem, direction = 'from')
+                elif data['from'][idx] is False:
+                    dataItem.AddAction(actionItem, direction = 'to')
+            
+            self.data.append(dataItem)
+            
+            actionItem.AddData(dataItem)
+        
+    def IsValid(self):
+        """Return True if model is valid"""
+        if self.Validate():
+            return False
+        
+        return True
+    
+    def Validate(self):
+        """!Validate model, return None if model is valid otherwise
+        error string"""
+        errList = list()
+        for action in self.actions:
+            task = menuform.GUI().ParseCommand(cmd = action.GetLog(string = False),
+                                               show = None)
+            errList += task.getCmdError()
+
+        return errList
+
+    def Run(self, log, onDone):
+        """!Run model"""
+        for action in self.actions:
+            log.RunCmd(command = action.GetLog(string = False),
+                       onDone = onDone)
+    
+    def DeleteIntermediateData(self, log):
+        """!Detele intermediate data"""
+        rast, vect, rast3d, msg = self.GetIntermediateData()
+        
+        if rast:
+            log.RunCmd(['g.remove', 'rast=%s' %','.join(rast)])
+        if rast3d:
+            log.RunCmd(['g.remove', 'rast3d=%s' %','.join(rast3d)])
+        if vect:
+            log.RunCmd(['g.remove', 'vect=%s' %','.join(vect)])
+        
+    def GetIntermediateData(self):
+        """!Get info about intermediate data"""
+        rast = list()
+        rast3d = list()
+        vect = list()
+        for data in self.data:
+            if not data.IsIntermediate():
+                continue
+            name = data.GetValue()
+            prompt = data.GetPrompt()
+            if prompt == 'raster':
+                rast.append(name)
+            elif prompt == 'vector':
+                vect.append(name)
+            elif prompt == 'rast3d':
+                rast3d.append(name)
+        
+        msg = ''
+        if rast:
+            msg += '\n\n%s: ' % _('Raster maps')
+            msg += ', '.join(rast)
+        if rast3d:
+            msg += '\n\n%s: ' % _('3D raster maps')
+            msg += ', '.join(rast3d)
+        if vect:
+            msg += '\n\n%s: ' % _('Vector maps')
+            msg += ', '.join(vect)
+        
+        return rast, vect, rast3d, msg
+    
 class ModelFrame(wx.Frame):
 class ModelFrame(wx.Frame):
     def __init__(self, parent, id = wx.ID_ANY, title = _("Graphical modeler (under development)"), **kwargs):
     def __init__(self, parent, id = wx.ID_ANY, title = _("Graphical modeler (under development)"), **kwargs):
         """!Graphical modeler main window
         """!Graphical modeler main window
@@ -68,8 +244,6 @@ class ModelFrame(wx.Frame):
         """
         """
         self.parent = parent
         self.parent = parent
         self.searchDialog = None # module search dialog
         self.searchDialog = None # module search dialog
-        self.actions = list()    # list of recorded actions
-        self.data    = list()    # list of recorded data items
         self.baseTitle = title
         self.baseTitle = title
         self.modelFile = None    # loaded model
         self.modelFile = None    # loaded model
         self.modelChanged = False
         self.modelChanged = False
@@ -89,9 +263,9 @@ class ModelFrame(wx.Frame):
         
         
         self.toolbar = toolbars.ModelToolbar(parent = self)
         self.toolbar = toolbars.ModelToolbar(parent = self)
         self.SetToolBar(self.toolbar)
         self.SetToolBar(self.toolbar)
-
+        
         self.statusbar = self.CreateStatusBar(number = 1)
         self.statusbar = self.CreateStatusBar(number = 1)
-
+        
         self.notebook = FN.FlatNotebook(parent = self, id = wx.ID_ANY,
         self.notebook = FN.FlatNotebook(parent = self, id = wx.ID_ANY,
                                         style = FN.FNB_FANCY_TABS | FN.FNB_BOTTOM |
                                         style = FN.FNB_FANCY_TABS | FN.FNB_BOTTOM |
                                         FN.FNB_NO_NAV_BUTTONS | FN.FNB_NO_X_BUTTON)
                                         FN.FNB_NO_NAV_BUTTONS | FN.FNB_NO_X_BUTTON)
@@ -100,6 +274,8 @@ class ModelFrame(wx.Frame):
         self.canvas.SetBackgroundColour(wx.WHITE)
         self.canvas.SetBackgroundColour(wx.WHITE)
         self.canvas.SetCursor(self.cursors["default"])
         self.canvas.SetCursor(self.cursors["default"])
         
         
+        self.model = Model(self.canvas)
+        
         self.goutput = goutput.GMConsole(parent = self, pageid = 1)
         self.goutput = goutput.GMConsole(parent = self, pageid = 1)
                 
                 
         self.modelPage   = self.notebook.AddPage(self.canvas, text=_('Model'))
         self.modelPage   = self.notebook.AddPage(self.canvas, text=_('Model'))
@@ -136,24 +312,7 @@ class ModelFrame(wx.Frame):
         evthandler.SetShape(item)
         evthandler.SetShape(item)
         evthandler.SetPreviousHandler(item.GetEventHandler())
         evthandler.SetPreviousHandler(item.GetEventHandler())
         item.SetEventHandler(evthandler)
         item.SetEventHandler(evthandler)
-
-    def FindAction(self, id):
-        """!Find action by id"""
-        for action in self.actions:
-            if action.GetId() == id:
-                return action
-        
-        return None
-
-    def FindData(self, value, prompt):
-        """!Find data by value, and prompt"""
-        for data in self.data:
-            if data.GetValue() == value and \
-                    data.GetPrompt() == prompt:
-                return data
         
         
-        return None
-            
     def ModelChanged(self):
     def ModelChanged(self):
         """!Update window title"""
         """!Update window title"""
         if not self.modelChanged:
         if not self.modelChanged:
@@ -168,43 +327,19 @@ class ModelFrame(wx.Frame):
 
 
     def OnDeleteData(self, event):
     def OnDeleteData(self, event):
         """!Delete intermediate data"""
         """!Delete intermediate data"""
-        rast = list()
-        rast3d = list()
-        vect = list()
-        for data in self.data:
-            if not data.IsIntermediate():
-                continue
-            name = data.GetValue()
-            prompt = data.GetPrompt()
-            if prompt == 'raster':
-                rast.append(name)
-            elif prompt == 'vector':
-                vect.append(name)
-            elif prompt == 'rast3d':
-                rast3d.append(name)
-            
+        rast, vect, rast3d, msg = self.model.GetIntermediateData()
+        
         if not rast and not vect and not rast3d:
         if not rast and not vect and not rast3d:
             GMessage(parent = self,
             GMessage(parent = self,
                      message = _('Nothing to delete.'),
                      message = _('Nothing to delete.'),
                      msgType = 'info')
                      msgType = 'info')
             return
             return
-            
-        msg = ''
-        if rast:
-            msg += '\n\n%s: ' % _('Raster maps')
-            msg += ', '.join(rast)
-        if rast3d:
-            msg += '\n\n%s: ' % _('3D raster maps')
-            msg += ', '.join(rast3d)
-        if vect:
-            msg += '\n\n%s: ' % _('Vector maps')
-            msg += ', '.join(vect)
         
         
         dlg = wx.MessageDialog(parent = self,
         dlg = wx.MessageDialog(parent = self,
                                message= _("Do you want to permanently delete data?%s" % msg),
                                message= _("Do you want to permanently delete data?%s" % msg),
                                caption=_("Delete intermediate data?"),
                                caption=_("Delete intermediate data?"),
                                style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
                                style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
-
+        
         ret = dlg.ShowModal()
         ret = dlg.ShowModal()
         if ret == wx.ID_YES:
         if ret == wx.ID_YES:
             dlg.Destroy()
             dlg.Destroy()
@@ -230,7 +365,7 @@ class ModelFrame(wx.Frame):
         if self.modelFile and self.modelChanged:
         if self.modelFile and self.modelChanged:
             self.OnModelSave()
             self.OnModelSave()
         elif self.modelFile is None and \
         elif self.modelFile is None and \
-                (len(self.actions) > 0 or len(self.data) > 0):
+                (len(self.model.GetActions()) > 0 or len(self.model.GetData()) > 0):
             dlg = wx.MessageDialog(self, message=_("Current model is not empty. "
             dlg = wx.MessageDialog(self, message=_("Current model is not empty. "
                                                    "Do you want to store current settings "
                                                    "Do you want to store current settings "
                                                    "to model file?"),
                                                    "to model file?"),
@@ -248,8 +383,7 @@ class ModelFrame(wx.Frame):
         
         
         # delete all items
         # delete all items
         self.canvas.GetDiagram().DeleteAllShapes()
         self.canvas.GetDiagram().DeleteAllShapes()
-        self.actions = list()
-        self.data = list()
+        self.model.Reset()
         self.canvas.Refresh()
         self.canvas.Refresh()
         
         
         # no model file loaded
         # no model file loaded
@@ -278,7 +412,7 @@ class ModelFrame(wx.Frame):
         
         
         self.modelFile = filename
         self.modelFile = filename
         self.SetTitle(self.baseTitle + " - " +  os.path.basename(self.modelFile))
         self.SetTitle(self.baseTitle + " - " +  os.path.basename(self.modelFile))
-        self.SetStatusText(_('%d actions loaded into model') % len(self.actions), 0)
+        self.SetStatusText(_('%d actions loaded into model') % len(self.model.GetActions()), 0)
         
         
     def OnModelSave(self, event = None):
     def OnModelSave(self, event = None):
         """!Save model to file"""
         """!Save model to file"""
@@ -341,7 +475,7 @@ class ModelFrame(wx.Frame):
         if self.modelFile and self.modelChanged:
         if self.modelFile and self.modelChanged:
             self.OnModelSave()
             self.OnModelSave()
         elif self.modelFile is None and \
         elif self.modelFile is None and \
-                (len(self.actions) > 0 or len(self.data) > 0):
+                (len(self.model.GetActions()) > 0 or len(self.model.GetData()) > 0):
             dlg = wx.MessageDialog(self, message=_("Current model is not empty. "
             dlg = wx.MessageDialog(self, message=_("Current model is not empty. "
                                                    "Do you want to store current settings "
                                                    "Do you want to store current settings "
                                                    "to model file?"),
                                                    "to model file?"),
@@ -361,14 +495,13 @@ class ModelFrame(wx.Frame):
         self.SetTitle(self.baseTitle)
         self.SetTitle(self.baseTitle)
         
         
         self.canvas.GetDiagram().DeleteAllShapes()
         self.canvas.GetDiagram().DeleteAllShapes()
-        self.actions = list()
-        self.data    = list()
+        self.model.Reset()
         
         
         self.canvas.Refresh()
         self.canvas.Refresh()
         
         
     def OnRunModel(self, event):
     def OnRunModel(self, event):
         """!Run entire model"""
         """!Run entire model"""
-        if len(self.actions) < 1:
+        if len(self.model.GetActions()) < 1:
             GMessage(parent = self, 
             GMessage(parent = self, 
                      message = _('Model is empty. Nothing to run.'),
                      message = _('Model is empty. Nothing to run.'),
                      msgType = 'info')
                      msgType = 'info')
@@ -386,8 +519,8 @@ class ModelFrame(wx.Frame):
             if ret != wx.ID_YES:
             if ret != wx.ID_YES:
                 return
                 return
         
         
-        for action in self.actions:
-            self.SetStatusText(_('Running model...'), 0)
+        for action in self.model.GetActions():
+            self.SetStatusText(_('Running model...'), 0)        
             self.goutput.RunCmd(command = action.GetLog(string = False),
             self.goutput.RunCmd(command = action.GetLog(string = False),
                                 onDone = self.OnDone)
                                 onDone = self.OnDone)
         
         
@@ -397,7 +530,7 @@ class ModelFrame(wx.Frame):
         
         
     def OnValidateModel(self, event, showMsg = True):
     def OnValidateModel(self, event, showMsg = True):
         """!Validate entire model"""
         """!Validate entire model"""
-        if len(self.actions) < 1:
+        if len(self.model.GetActions()) < 1:
             GMessage(parent = self, 
             GMessage(parent = self, 
                      message = _('Model is empty. Nothing to validate.'),
                      message = _('Model is empty. Nothing to validate.'),
                      msgType = 'info')
                      msgType = 'info')
@@ -552,7 +685,7 @@ def cleanup():
 """)
 """)
 
 
         fd.write("\ndef main():\n")
         fd.write("\ndef main():\n")
-        for action in self.actions:
+        for action in self.model.GetActions():
             task = menuform.GUI().ParseCommand(cmd = action.GetLog(string = False),
             task = menuform.GUI().ParseCommand(cmd = action.GetLog(string = False),
                                                show = None)
                                                show = None)
             opts = task.get_options()
             opts = task.get_options()
@@ -596,11 +729,8 @@ if __name__ == "__main__":
     def _validateModel(self):
     def _validateModel(self):
         """!Validate model"""
         """!Validate model"""
         self.SetStatusText(_('Validating model...'), 0)
         self.SetStatusText(_('Validating model...'), 0)
-        errList = list()
-        for action in self.actions:
-            task = menuform.GUI().ParseCommand(cmd = action.GetLog(string = False),
-                                               show = None)
-            errList += task.getCmdError()
+        
+        errList = self.model.Validate()
         
         
         self.SetStatusText('', 0)
         self.SetStatusText('', 0)
         
         
@@ -638,7 +768,7 @@ if __name__ == "__main__":
         action.Show(True)
         action.Show(True)
 
 
         self._addEvent(action)
         self._addEvent(action)
-        self.actions.append(action)
+        self.model.AddAction(action)
         
         
         self.canvas.Refresh()
         self.canvas.Refresh()
         time.sleep(.1)
         time.sleep(.1)
@@ -663,7 +793,7 @@ if __name__ == "__main__":
         data.Show(True)
         data.Show(True)
         
         
         self._addEvent(data)
         self._addEvent(data)
-        self.data.append(data)
+        self.model.AddData(data)
         
         
         self.canvas.Refresh()
         self.canvas.Refresh()
         
         
@@ -736,7 +866,7 @@ if __name__ == "__main__":
                     data.Show(True)
                     data.Show(True)
                     
                     
                     self._addEvent(data)
                     self._addEvent(data)
-                    self.data.append(data)
+                    self.model.AddData(data)
                     
                     
                     if p.get('age', 'old') == 'old':
                     if p.get('age', 'old') == 'old':
                         self._addLine(data, layer)
                         self._addLine(data, layer)
@@ -783,21 +913,13 @@ if __name__ == "__main__":
         
         
     def LoadModelFile(self, filename):
     def LoadModelFile(self, filename):
         """!Load model definition stored in GRASS Model XML file (gxm)
         """!Load model definition stored in GRASS Model XML file (gxm)
-
-        @todo Validate against DTD
-
-        Raise exception on error.
         """
         """
-        ### dtdFilename = os.path.join(globalvar.ETCWXDIR, "xml", "grass-gxm.dtd")
-        
-        # parse workspace file
         try:
         try:
-            gxmXml = ProcessModelFile(etree.parse(filename))
-        except:
+            self.model.LoadModel(filename)
+        except GError, e:
             GMessage(parent = self,
             GMessage(parent = self,
                      message = _("Reading model file <%s> failed.\n"
                      message = _("Reading model file <%s> failed.\n"
                                  "Invalid file, unable to parse XML document.") % filename)
                                  "Invalid file, unable to parse XML document.") % filename)
-            return
         
         
         self.modelFile = filename
         self.modelFile = filename
         self.SetTitle(self.baseTitle + " - " +  os.path.basename(self.modelFile))
         self.SetTitle(self.baseTitle + " - " +  os.path.basename(self.modelFile))
@@ -805,58 +927,21 @@ if __name__ == "__main__":
         self.SetStatusText(_("Please wait, loading model..."), 0)
         self.SetStatusText(_("Please wait, loading model..."), 0)
         
         
         # load actions
         # load actions
-        for action in gxmXml.actions:
-            actionShape = ModelAction(parent = self, 
-                                      x = action['pos'][0],
-                                      y = action['pos'][1],
-                                      width = action['size'][0],
-                                      height = action['size'][1],
-                                      cmd = action['cmd'])
-            actionShape.SetId(action['id'])
-            self.canvas.diagram.AddShape(actionShape)
-            actionShape.Show(True)
-            
-            self._addEvent(actionShape)
-            self.actions.append(actionShape)
-            
-            task = menuform.GUI().ParseCommand(cmd = actionShape.GetLog(string = False),
-                                               show = None)
-            valid = True
-            for p in task.get_options()['params']:
-                if p.get('value', '') == '' and \
-                        p.get('default', '') == '':
-                    valid = False
-                    break
-            actionShape.SetValid(valid)
+        for action in self.model.GetActions():
+            self._addEvent(action)
+            self.canvas.diagram.AddShape(action)
+            action.Show(True)
         
         
-        # load data & connections
-        for data in gxmXml.data:
-            dataShape = ModelData(parent = self, 
-                                  x = data['pos'][0],
-                                  y = data['pos'][1],
-                                  width = data['size'][0],
-                                  height = data['size'][1],
-                                  name = data['name'],
-                                  prompt = data['prompt'],
-                                  value = data['value'])
-            dataShape.SetIntermediate(data['intermediate'])
-            
-            self.canvas.diagram.AddShape(dataShape)
-            dataShape.Show(True)
-            
-            self._addEvent(dataShape)
-            self.data.append(dataShape)
+        # load data & relations
+        for data in self.model.GetData():
+            self._addEvent(data)
+            self.canvas.diagram.AddShape(data)
+            data.Show(True)
 
 
-            for idx in range(len(data['id'])):
-                actionShape = self.FindAction(data['id'][idx])
-                if data['from'][idx] is True:
-                    self._addLine(dataShape, actionShape)
-                    dataShape.AddAction(actionShape, direction = 'from')
-                elif data['from'][idx] is False:
-                    self._addLine(actionShape, dataShape)
-                    dataShape.AddAction(actionShape, direction = 'to')
-            
-            actionShape.AddData(dataShape)
+            for action in data.GetActions('from'):
+                self._addLine(data, action)
+            for action in data.GetActions('to'):
+                self._addLine(action, data)
         
         
         self.SetStatusText('', 0)
         self.SetStatusText('', 0)
         
         
@@ -878,7 +963,7 @@ if __name__ == "__main__":
             return False
             return False
         
         
         try:
         try:
-            WriteModelFile(fd = file, actions = self.actions, data = self.data)
+            WriteModelFile(fd = file, actions = self.model.GetActions(), data = self.model.GetData())
         except StandardError:
         except StandardError:
             file.close()
             file.close()
             
             
@@ -924,19 +1009,19 @@ class ModelAction(ogl.RectangleShape):
         self.colors['valid'] = wx.LIGHT_GREY_BRUSH
         self.colors['valid'] = wx.LIGHT_GREY_BRUSH
         self.colors['invalid'] = wx.WHITE_BRUSH
         self.colors['invalid'] = wx.WHITE_BRUSH
         
         
-        ogl.RectangleShape.__init__(self, width, height)
+        if self.parent.GetCanvas():
+            ogl.RectangleShape.__init__(self, width, height)
+            
+            self.SetCanvas(self.parent)
+            self.SetX(x)
+            self.SetY(y)
+            self.SetPen(wx.BLACK_PEN)
+            self.SetBrush(self.colors['invalid'])
+            if self.cmd and len(self.cmd) > 0:
+                self.AddText(self.cmd[0])
+            else:
+                self.AddText('<<module>>')
         
         
-        # self.Draggable(True)
-        self.SetCanvas(self.parent)
-        self.SetX(x)
-        self.SetY(y)
-        self.SetPen(wx.BLACK_PEN)
-        self.SetBrush(self.colors['invalid'])
-        if self.cmd and len(self.cmd) > 0:
-            self.AddText(self.cmd[0])
-        else:
-            self.AddText('<<module>>')
-
     def GetId(self):
     def GetId(self):
         """!Get id"""
         """!Get id"""
         return self.id
         return self.id
@@ -1012,31 +1097,31 @@ class ModelData(ogl.EllipseShape):
         self.propWin = None
         self.propWin = None
         
         
         self.actions = { 'from' : list(), 'to' : list() }
         self.actions = { 'from' : list(), 'to' : list() }
+
+        if self.parent.GetCanvas():
+            ogl.EllipseShape.__init__(self, width, height)
+            
+            self.SetCanvas(self.parent)
+            self.SetX(x)
+            self.SetY(y)
+            self.SetPen(wx.BLACK_PEN)
+            if self.prompt == 'raster':
+                self.SetBrush(wx.Brush(wx.Colour(215, 215, 248)))
+            elif self.prompt == 'vector':
+                self.SetBrush(wx.Brush(wx.Colour(248, 215, 215)))
+            else:
+                self.SetBrush(wx.LIGHT_GREY_BRUSH)
         
         
-        ogl.EllipseShape.__init__(self, width, height)
-        
-        # self.Draggable(True)
-        self.SetCanvas(self.parent)
-        self.SetX(x)
-        self.SetY(y)
-        self.SetPen(wx.BLACK_PEN)
-        if self.prompt == 'raster':
-            self.SetBrush(wx.Brush(wx.Colour(215, 215, 248)))
-        elif self.prompt == 'vector':
-            self.SetBrush(wx.Brush(wx.Colour(248, 215, 215)))
-        else:
-            self.SetBrush(wx.LIGHT_GREY_BRUSH)
+            if name:
+                self.AddText(name)
+            else:
+                self.AddText(_('unknown'))
         
         
-        if name:
-            self.AddText(name)
-        else:
-            self.AddText(_('unknown'))
+            if value:
+                self.AddText(value)
+            else:
+                self.AddText('\n')
         
         
-        if value:
-            self.AddText(value)
-        else:
-            self.AddText('\n')
-
     def IsIntermediate(self):
     def IsIntermediate(self):
         """!Checks if data item is intermediate"""
         """!Checks if data item is intermediate"""
         return self.intermediate
         return self.intermediate
@@ -1263,15 +1348,21 @@ class ModelEvtHandler(ogl.ShapeEvtHandler):
         self.frame.canvas.Refresh()
         self.frame.canvas.Refresh()
 
 
     def OnRemove(self, event):
     def OnRemove(self, event):
-        """!Remove shape"""
+        """!Remove shape
+
+        @todo complex remove
+        """
         self.frame.ModelChanged()
         self.frame.ModelChanged()
         shapes = [self.GetShape()]
         shapes = [self.GetShape()]
-        if isinstance(shape, ModelAction):
-            pass
-        if isinstance(shape, ModelData):
-            pass
         for shape in shapes:
         for shape in shapes:
+            if isinstance(shape, ModelAction):
+                pass
+            if isinstance(shape, ModelData):
+                pass
+
+            shape.Select(False)            
             self.frame.canvas.GetDiagram().RemoveShape(shape)
             self.frame.canvas.GetDiagram().RemoveShape(shape)
+        
         self.frame.canvas.Refresh()
         self.frame.canvas.Refresh()
         
         
 class ModelSearchDialog(wx.Dialog):
 class ModelSearchDialog(wx.Dialog):

+ 1 - 0
gui/wxpython/gui_modules/goutput.py

@@ -686,6 +686,7 @@ class GMConsole(wx.SplitterWindow):
                 pass
                 pass
             
             
             self.btn_abort.Enable(False)
             self.btn_abort.Enable(False)
+        
         if event.onDone:
         if event.onDone:
             event.onDone(returncode = event.returncode)
             event.onDone(returncode = event.returncode)
         
         

+ 38 - 0
gui/wxpython/wxgui.py

@@ -266,6 +266,44 @@ class GMFrame(wx.Frame):
         
         
         win.Show()
         win.Show()
         
         
+    def OnDone(self, returncode):
+        """Command execution finised"""
+        if hasattr(self, "model"):
+            self.model.DeleteIntermediateData(log = self.goutput)
+            del self.model
+        self.SetStatusText('')
+        
+    def OnRunModel(self, event):
+        """!Run model"""
+        filename = ''
+        dlg = wx.FileDialog(parent = self, message=_("Choose model to run"),
+                            defaultDir = os.getcwd(),
+                            wildcard=_("GRASS Model File (*.gxm)|*.gxm"))
+        if dlg.ShowModal() == wx.ID_OK:
+            filename = dlg.GetPath()
+        
+        if not filename:
+            return
+        
+        self.model = gmodeler.Model()
+        self.model.LoadModel(filename)
+        self.SetStatusText(_('Validating model...'), 0)
+        result =  self.model.Validate()
+        if result:
+            dlg = wx.MessageDialog(parent = self,
+                                   message = _('Model is not valid. Do you want to '
+                                               'run the model anyway?\n\n%s') % '\n'.join(errList),
+                                   caption=_("Run model?"),
+                                   style = wx.YES_NO | wx.NO_DEFAULT |
+                                   wx.ICON_QUESTION | wx.CENTRE)
+            ret = dlg.ShowModal()
+            if ret != wx.ID_YES:
+                return
+        
+        self.SetStatusText(_('Running model...'), 0)
+        self.model.Run(log = self.goutput,
+                       onDone = self.OnDone)
+        
     def OnMapsets(self, event):
     def OnMapsets(self, event):
         """
         """
         Launch mapset access dialog
         Launch mapset access dialog

+ 5 - 0
gui/wxpython/xml/menudata.xml

@@ -624,6 +624,11 @@
 	  <help>Launch Graphical modeler</help>
 	  <help>Launch Graphical modeler</help>
 	  <handler>OnGModeler</handler>
 	  <handler>OnGModeler</handler>
 	</menuitem>
 	</menuitem>
+	<menuitem>
+	  <label>Run model</label>
+	  <help>Run model prepared by Graphical modeler</help>
+	  <handler>OnRunModel</handler>
+	</menuitem>
 	<separator />
 	<separator />
 	<menuitem>
 	<menuitem>
 	  <label>NVIZ (requires Tcl/Tk)</label>
 	  <label>NVIZ (requires Tcl/Tk)</label>