GRASS Programmer's Manual  6.5.svn(2012)-r51648
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
gmodeler/frame.py
Go to the documentation of this file.
00001 """!
00002 @package gmodeler.frame
00003 
00004 @brief wxGUI Graphical Modeler for creating, editing, and managing models
00005 
00006 Classes:
00007  - frame::ModelFrame
00008  - frame::ModelCanvas
00009  - frame::ModelEvtHandler
00010  - frame::VariablePanel
00011  - frame::ItemPanel
00012  - frame::PythonPanel
00013 
00014 (C) 2010-2012 by the GRASS Development Team
00015 
00016 This program is free software under the GNU General Public License
00017 (>=v2). Read the file COPYING that comes with GRASS for details.
00018 
00019 @author Martin Landa <landa.martin gmail.com>
00020 """
00021 
00022 import os
00023 import sys
00024 import time
00025 import stat
00026 import textwrap
00027 import tempfile
00028 import copy
00029 import re
00030 
00031 if __name__ == "__main__":
00032     sys.path.append(os.path.join(os.getenv('GISBASE'), 'etc', 'wxpython'))
00033 from core                 import globalvar
00034 import wx
00035 from wx.lib import ogl
00036 import wx.lib.flatnotebook    as FN
00037 
00038 from gui_core.widgets     import GNotebook
00039 from gui_core.goutput     import GMConsole, PyStc
00040 from core.debug           import Debug
00041 from core.gcmd            import GMessage, GException, GWarning, GError, RunCommand
00042 from gui_core.dialogs     import GetImageHandlers
00043 from gui_core.preferences import PreferencesBaseDialog
00044 from core.settings        import UserSettings
00045 from core.menudata        import MenuData
00046 from gui_core.menu        import Menu
00047 from gmodeler.menudata    import ModelerData
00048 from gui_core.forms       import GUI
00049 from gmodeler.preferences import PreferencesDialog, PropertiesDialog
00050 from gmodeler.toolbars    import ModelerToolbar
00051 
00052 from gmodeler.model       import *
00053 from gmodeler.dialogs     import *
00054 
00055 from grass.script import core as grass
00056 
00057 class ModelFrame(wx.Frame):
00058     def __init__(self, parent, id = wx.ID_ANY,
00059                  title = _("GRASS GIS Graphical Modeler (experimental prototype)"), **kwargs):
00060         """!Graphical modeler main window
00061         
00062         @param parent parent window
00063         @param id window id
00064         @param title window title
00065 
00066         @param kwargs wx.Frames' arguments
00067         """
00068         self.parent = parent
00069         self.searchDialog = None # module search dialog
00070         self.baseTitle = title
00071         self.modelFile = None    # loaded model
00072         self.modelChanged = False
00073         
00074         self.cursors = {
00075             "default" : wx.StockCursor(wx.CURSOR_ARROW),
00076             "cross"   : wx.StockCursor(wx.CURSOR_CROSS),
00077             }
00078         
00079         wx.Frame.__init__(self, parent = parent, id = id, title = title, **kwargs)
00080         self.SetName("Modeler")
00081         self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
00082         
00083         self.menubar = Menu(parent = self, data = ModelerData())
00084         
00085         self.SetMenuBar(self.menubar)
00086         
00087         self.toolbar = ModelerToolbar(parent = self)
00088         self.SetToolBar(self.toolbar)
00089         
00090         self.statusbar = self.CreateStatusBar(number = 1)
00091         
00092         self.notebook = GNotebook(parent = self,
00093                                   style = FN.FNB_FANCY_TABS | FN.FNB_BOTTOM |
00094                                   FN.FNB_NO_NAV_BUTTONS | FN.FNB_NO_X_BUTTON)
00095         
00096         self.canvas = ModelCanvas(self)
00097         self.canvas.SetBackgroundColour(wx.WHITE)
00098         self.canvas.SetCursor(self.cursors["default"])
00099         
00100         self.model = Model(self.canvas)
00101         
00102         self.variablePanel = VariablePanel(parent = self)
00103         
00104         self.itemPanel = ItemPanel(parent = self)
00105         
00106         self.pythonPanel = PythonPanel(parent = self)
00107         
00108         self.goutput = GMConsole(parent = self, notebook = self.notebook)
00109         
00110         self.notebook.AddPage(page = self.canvas, text=_('Model'), name = 'model')
00111         self.notebook.AddPage(page = self.itemPanel, text=_('Items'), name = 'items')
00112         self.notebook.AddPage(page = self.variablePanel, text=_('Variables'), name = 'variables')
00113         self.notebook.AddPage(page = self.pythonPanel, text=_('Python editor'), name = 'python')
00114         self.notebook.AddPage(page = self.goutput, text=_('Command output'), name = 'output')
00115         wx.CallAfter(self.notebook.SetSelectionByName, 'model')
00116         wx.CallAfter(self.ModelChanged, False)
00117 
00118         self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
00119         self.Bind(wx.EVT_SIZE, self.OnSize)
00120         self.notebook.Bind(FN.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.OnPageChanged)
00121         
00122         self._layout()
00123         self.SetMinSize((475, 300))
00124         self.SetSize((640, 480))
00125         
00126         # fix goutput's pane size
00127         if self.goutput:
00128             self.goutput.SetSashPosition(int(self.GetSize()[1] * .75))
00129         
00130     def _layout(self):
00131         """!Do layout"""
00132         sizer = wx.BoxSizer(wx.VERTICAL)
00133 
00134         sizer.Add(item = self.notebook, proportion = 1,
00135                   flag = wx.EXPAND)
00136         
00137         self.SetAutoLayout(True)
00138         self.SetSizer(sizer)
00139         sizer.Fit(self)
00140         
00141         self.Layout()
00142 
00143     def _addEvent(self, item):
00144         """!Add event to item"""
00145         evthandler = ModelEvtHandler(self.statusbar,
00146                                      self)
00147         evthandler.SetShape(item)
00148         evthandler.SetPreviousHandler(item.GetEventHandler())
00149         item.SetEventHandler(evthandler)
00150 
00151     def GetCanvas(self):
00152         """!Get canvas"""
00153         return self.canvas
00154     
00155     def GetModel(self):
00156         """!Get model"""
00157         return self.model
00158     
00159     def ModelChanged(self, changed = True):
00160         """!Update window title"""
00161         self.modelChanged = changed
00162         
00163         if self.modelFile:
00164             if self.modelChanged:
00165                 self.SetTitle(self.baseTitle + " - " +  os.path.basename(self.modelFile) + '*')
00166             else:
00167                 self.SetTitle(self.baseTitle + " - " +  os.path.basename(self.modelFile))
00168         else:
00169             self.SetTitle(self.baseTitle)
00170 
00171     def OnPageChanged(self, event):
00172         """!Page in notebook changed"""
00173         page = event.GetSelection()
00174         if page == self.notebook.GetPageIndexByName('python'):
00175             if self.pythonPanel.IsEmpty():
00176                 self.pythonPanel.RefreshScript()
00177             
00178             if self.pythonPanel.IsModified():
00179                 self.SetStatusText(_('Python script contains local modifications'), 0)
00180             else:
00181                 self.SetStatusText(_('Python script is up-to-date'), 0)
00182         
00183         event.Skip()
00184 
00185     def OnVariables(self, event):
00186         """!Switch to variables page"""
00187         self.notebook.SetSelectionByName('variables')
00188         
00189     def OnRemoveItem(self, event):
00190         """!Remove shape
00191         """
00192         self.GetCanvas().RemoveSelected()
00193         
00194     def OnCanvasRefresh(self, event):
00195         """!Refresh canvas"""
00196         self.SetStatusText(_("Redrawing model..."), 0)
00197         self.GetCanvas().Refresh()
00198         self.SetStatusText("", 0)
00199 
00200     def OnCmdRun(self, event):
00201         """!Run command"""
00202         try:
00203             action = self.GetModel().GetItems()[event.pid]
00204             if hasattr(action, "task"):
00205                 action.Update(running = True)
00206         except IndexError:
00207             pass
00208         
00209     def OnCmdPrepare(self, event):
00210         """!Prepare for running command"""
00211         if not event.userData:
00212             return
00213         
00214         event.onPrepare(item = event.userData['item'],
00215                         params = event.userData['params'])
00216         
00217     def OnCmdDone(self, event):
00218         """!Command done (or aborted)"""
00219         try:
00220             action = self.GetModel().GetItems()[event.pid]
00221             if hasattr(action, "task"):
00222                 action.Update(running = True)
00223         except IndexError:
00224             pass
00225         
00226     def OnCloseWindow(self, event):
00227         """!Close window"""
00228         if self.modelChanged and \
00229                 UserSettings.Get(group='manager', key='askOnQuit', subkey='enabled'):
00230             if self.modelFile:
00231                 message = _("Do you want to save changes in the model?")
00232             else:
00233                 message = _("Do you want to store current model settings "
00234                             "to model file?")
00235             
00236             # ask user to save current settings
00237             dlg = wx.MessageDialog(self,
00238                                    message = message,
00239                                    caption=_("Quit Graphical Modeler"),
00240                                    style = wx.YES_NO | wx.YES_DEFAULT |
00241                                    wx.CANCEL | wx.ICON_QUESTION | wx.CENTRE)
00242             ret = dlg.ShowModal()
00243             if ret == wx.ID_YES:
00244                 if not self.modelFile:
00245                         self.OnWorkspaceSaveAs()
00246                 else:
00247                     self.WriteModelFile(self.modelFile)
00248             elif ret == wx.ID_CANCEL:
00249                 dlg.Destroy()
00250                 return
00251             dlg.Destroy()
00252         
00253         self.Destroy()
00254 
00255     def OnSize(self, event):
00256         """Window resized, save to the model"""
00257         self.ModelChanged()
00258         event.Skip()
00259         
00260     def OnPreferences(self, event):
00261         """!Open preferences dialog"""
00262         dlg = PreferencesDialog(parent = self)
00263         dlg.CenterOnParent()
00264         
00265         dlg.ShowModal()
00266         self.canvas.Refresh()
00267         
00268     def OnHelp(self, event):
00269         """!Show help"""
00270         if self.parent and self.parent.GetName() == 'LayerManager':
00271             log = self.parent.GetLogWindow()
00272             log.RunCmd(['g.manual',
00273                         'entry=wxGUI.Modeler'])
00274         else:
00275             RunCommand('g.manual',
00276                        quiet = True,
00277                        entry = 'wxGUI.Modeler')
00278         
00279     def OnModelProperties(self, event):
00280         """!Model properties dialog"""
00281         dlg = PropertiesDialog(parent = self)
00282         dlg.CentreOnParent()
00283         properties = self.model.GetProperties()
00284         dlg.Init(properties)
00285         if dlg.ShowModal() == wx.ID_OK:
00286             self.ModelChanged()
00287             for key, value in dlg.GetValues().iteritems():
00288                 properties[key] = value
00289             for action in self.model.GetItems(objType = ModelAction):
00290                 action.GetTask().set_flag('overwrite', properties['overwrite'])
00291         
00292         dlg.Destroy()
00293         
00294     def OnDeleteData(self, event):
00295         """!Delete intermediate data"""
00296         rast, vect, rast3d, msg = self.model.GetIntermediateData()
00297         
00298         if not rast and not vect and not rast3d:
00299             GMessage(parent = self,
00300                      message = _('No intermediate data to delete.'))
00301             return
00302         
00303         dlg = wx.MessageDialog(parent = self,
00304                                message= _("Do you want to permanently delete data?%s" % msg),
00305                                caption=_("Delete intermediate data?"),
00306                                style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
00307         
00308         ret = dlg.ShowModal()
00309         if ret == wx.ID_YES:
00310             dlg.Destroy()
00311             
00312             if rast:
00313                 self.goutput.RunCmd(['g.remove', 'rast=%s' %','.join(rast)])
00314             if rast3d:
00315                 self.goutput.RunCmd(['g.remove', 'rast3d=%s' %','.join(rast3d)])
00316             if vect:
00317                 self.goutput.RunCmd(['g.remove', 'vect=%s' %','.join(vect)])
00318             
00319             self.SetStatusText(_("%d maps deleted from current mapset") % \
00320                                  int(len(rast) + len(rast3d) + len(vect)))
00321             return
00322         
00323         dlg.Destroy()
00324                 
00325     def OnModelNew(self, event):
00326         """!Create new model"""
00327         Debug.msg(4, "ModelFrame.OnModelNew():")
00328         
00329         # ask user to save current model
00330         if self.modelFile and self.modelChanged:
00331             self.OnModelSave()
00332         elif self.modelFile is None and \
00333                 (self.model.GetNumItems() > 0 or len(self.model.GetData()) > 0):
00334             dlg = wx.MessageDialog(self, message=_("Current model is not empty. "
00335                                                    "Do you want to store current settings "
00336                                                    "to model file?"),
00337                                    caption=_("Create new model?"),
00338                                    style=wx.YES_NO | wx.YES_DEFAULT |
00339                                    wx.CANCEL | wx.ICON_QUESTION)
00340             ret = dlg.ShowModal()
00341             if ret == wx.ID_YES:
00342                 self.OnModelSaveAs()
00343             elif ret == wx.ID_CANCEL:
00344                 dlg.Destroy()
00345                 return
00346             
00347             dlg.Destroy()
00348         
00349         # delete all items
00350         self.canvas.GetDiagram().DeleteAllShapes()
00351         self.model.Reset()
00352         self.canvas.Refresh()
00353         self.itemPanel.Update()
00354         self.variablePanel.Reset()
00355         
00356         # no model file loaded
00357         self.modelFile = None
00358         self.modelChanged = False
00359         self.SetTitle(self.baseTitle)
00360         
00361     def OnModelOpen(self, event):
00362         """!Load model from file"""
00363         filename = ''
00364         dlg = wx.FileDialog(parent = self, message=_("Choose model file"),
00365                             defaultDir = os.getcwd(),
00366                             wildcard=_("GRASS Model File (*.gxm)|*.gxm"))
00367         if dlg.ShowModal() == wx.ID_OK:
00368             filename = dlg.GetPath()
00369                     
00370         if not filename:
00371             return
00372         
00373         Debug.msg(4, "ModelFrame.OnModelOpen(): filename=%s" % filename)
00374         
00375         # close current model
00376         self.OnModelClose()
00377         
00378         self.LoadModelFile(filename)
00379         
00380         self.modelFile = filename
00381         self.SetTitle(self.baseTitle + " - " +  os.path.basename(self.modelFile))
00382         self.SetStatusText(_('%(items)d items (%(actions)d actions) loaded into model') % \
00383                                { 'items' : self.model.GetNumItems(),
00384                                  'actions' : self.model.GetNumItems(actionOnly = True) }, 0)
00385         
00386     def OnModelSave(self, event = None):
00387         """!Save model to file"""
00388         if self.modelFile and self.modelChanged:
00389             dlg = wx.MessageDialog(self, message=_("Model file <%s> already exists. "
00390                                                    "Do you want to overwrite this file?") % \
00391                                        self.modelFile,
00392                                    caption=_("Save model"),
00393                                    style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
00394             if dlg.ShowModal() == wx.ID_NO:
00395                 dlg.Destroy()
00396             else:
00397                 Debug.msg(4, "ModelFrame.OnModelSave(): filename=%s" % self.modelFile)
00398                 self.WriteModelFile(self.modelFile)
00399                 self.SetStatusText(_('File <%s> saved') % self.modelFile, 0)
00400                 self.SetTitle(self.baseTitle + " - " +  os.path.basename(self.modelFile))
00401         elif not self.modelFile:
00402             self.OnModelSaveAs(None)
00403         
00404     def OnModelSaveAs(self, event):
00405         """!Create model to file as"""
00406         filename = ''
00407         dlg = wx.FileDialog(parent = self,
00408                             message = _("Choose file to save current model"),
00409                             defaultDir = os.getcwd(),
00410                             wildcard=_("GRASS Model File (*.gxm)|*.gxm"),
00411                             style=wx.FD_SAVE)
00412         
00413         
00414         if dlg.ShowModal() == wx.ID_OK:
00415             filename = dlg.GetPath()
00416         
00417         if not filename:
00418             return
00419         
00420         # check for extension
00421         if filename[-4:] != ".gxm":
00422             filename += ".gxm"
00423         
00424         if os.path.exists(filename):
00425             dlg = wx.MessageDialog(parent = self,
00426                                    message=_("Model file <%s> already exists. "
00427                                              "Do you want to overwrite this file?") % filename,
00428                                    caption=_("File already exists"),
00429                                    style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
00430             if dlg.ShowModal() != wx.ID_YES:
00431                 dlg.Destroy()
00432                 return
00433         
00434         Debug.msg(4, "GMFrame.OnModelSaveAs(): filename=%s" % filename)
00435         
00436         self.WriteModelFile(filename)
00437         self.modelFile = filename
00438         self.SetTitle(self.baseTitle + " - " + os.path.basename(self.modelFile))
00439         self.SetStatusText(_('File <%s> saved') % self.modelFile, 0)
00440 
00441     def OnModelClose(self, event = None):
00442         """!Close model file"""
00443         Debug.msg(4, "ModelFrame.OnModelClose(): file=%s" % self.modelFile)
00444         # ask user to save current model
00445         if self.modelFile and self.modelChanged:
00446             self.OnModelSave()
00447         elif self.modelFile is None and \
00448                 (self.model.GetNumItems() > 0 or len(self.model.GetData()) > 0):
00449             dlg = wx.MessageDialog(self, message=_("Current model is not empty. "
00450                                                    "Do you want to store current settings "
00451                                                    "to model file?"),
00452                                    caption=_("Create new model?"),
00453                                    style=wx.YES_NO | wx.YES_DEFAULT |
00454                                    wx.CANCEL | wx.ICON_QUESTION)
00455             ret = dlg.ShowModal()
00456             if ret == wx.ID_YES:
00457                 self.OnModelSaveAs()
00458             elif ret == wx.ID_CANCEL:
00459                 dlg.Destroy()
00460                 return
00461             
00462             dlg.Destroy()
00463         
00464         self.modelFile = None
00465         self.SetTitle(self.baseTitle)
00466         
00467         self.canvas.GetDiagram().DeleteAllShapes()
00468         self.model.Reset()
00469         
00470         self.canvas.Refresh()
00471         
00472     def OnRunModel(self, event):
00473         """!Run entire model"""
00474         self.model.Run(self.goutput, self.OnDone, parent = self)
00475         
00476     def OnDone(self, cmd, returncode):
00477         """!Computation finished"""
00478         self.SetStatusText('', 0)
00479         # restore original files
00480         if hasattr(self.model, "fileInput"):
00481             for finput in self.model.fileInput:
00482                 data = self.model.fileInput[finput]
00483                 if not data:
00484                     continue
00485                 
00486                 fd = open(finput, "w")
00487                 try:
00488                     fd.write(data)
00489                 finally:
00490                     fd.close()
00491             del self.model.fileInput
00492         
00493     def OnValidateModel(self, event, showMsg = True):
00494         """!Validate entire model"""
00495         if self.model.GetNumItems() < 1:
00496             GMessage(parent = self, 
00497                      message = _('Model is empty. Nothing to validate.'))
00498             return
00499         
00500         
00501         self.SetStatusText(_('Validating model...'), 0)
00502         errList = self.model.Validate()
00503         self.SetStatusText('', 0)
00504         
00505         if errList:
00506             GWarning(parent = self,
00507                      message = _('Model is not valid.\n\n%s') % '\n'.join(errList))
00508         else:
00509             GMessage(parent = self,
00510                      message = _('Model is valid.'))
00511     
00512     def OnExportImage(self, event):
00513         """!Export model to image (default image)
00514         """
00515         xminImg = 0
00516         xmaxImg = 0
00517         yminImg = 0
00518         ymaxImg = 0
00519         # get current size of canvas
00520         for shape in self.canvas.GetDiagram().GetShapeList():
00521             w, h = shape.GetBoundingBoxMax()
00522             x    = shape.GetX()
00523             y    = shape.GetY()
00524             xmin = x - w / 2
00525             xmax = x + w / 2
00526             ymin = y - h / 2
00527             ymax = y + h / 2
00528             if xmin < xminImg:
00529                 xminImg = xmin
00530             if xmax > xmaxImg:
00531                 xmaxImg = xmax
00532             if ymin < yminImg:
00533                 yminImg = ymin
00534             if ymax > ymaxImg:
00535                 ymaxImg = ymax
00536         size = wx.Size(int(xmaxImg - xminImg) + 50,
00537                        int(ymaxImg - yminImg) + 50)
00538         bitmap = wx.EmptyBitmap(width = size.width, height = size.height)
00539         
00540         filetype, ltype = GetImageHandlers(wx.ImageFromBitmap(bitmap))
00541         
00542         dlg = wx.FileDialog(parent = self,
00543                             message = _("Choose a file name to save the image (no need to add extension)"),
00544                             defaultDir = "",
00545                             defaultFile = "",
00546                             wildcard = filetype,
00547                             style=wx.SAVE | wx.FD_OVERWRITE_PROMPT)
00548         
00549         if dlg.ShowModal() == wx.ID_OK:
00550             path = dlg.GetPath()
00551             if not path:
00552                 dlg.Destroy()
00553                 return
00554             
00555             base, ext = os.path.splitext(path)
00556             fileType = ltype[dlg.GetFilterIndex()]['type']
00557             extType  = ltype[dlg.GetFilterIndex()]['ext']
00558             if ext != extType:
00559                 path = base + '.' + extType
00560             
00561             dc = wx.MemoryDC(bitmap)
00562             dc.SetBackground(wx.WHITE_BRUSH)
00563             dc.SetBackgroundMode(wx.SOLID)
00564             
00565             dc.BeginDrawing()
00566             self.canvas.GetDiagram().Clear(dc)
00567             self.canvas.GetDiagram().Redraw(dc)
00568             dc.EndDrawing()
00569             
00570             bitmap.SaveFile(path, fileType)
00571             self.SetStatusText(_("Model exported to <%s>") % path)
00572         
00573         dlg.Destroy()
00574         
00575     def OnExportPython(self, event = None, text = None):
00576         """!Export model to Python script"""
00577         filename = self.pythonPanel.SaveAs(force = True)
00578         self.SetStatusText(_("Model exported to <%s>") % filename)
00579 
00580     def OnDefineRelation(self, event):
00581         """!Define relation between data and action items"""
00582         self.canvas.SetCursor(self.cursors["cross"])
00583         self.defineRelation = { 'from' : None,
00584                                 'to'   : None }
00585         
00586     def OnDefineLoop(self, event):
00587         """!Define new loop in the model"""
00588         self.ModelChanged()
00589         
00590         width, height = self.canvas.GetSize()
00591         loop = ModelLoop(self, x = width/2, y = height/2,
00592                          id = self.model.GetNumItems() + 1)
00593         self.canvas.diagram.AddShape(loop)
00594         loop.Show(True)
00595         
00596         self._addEvent(loop)
00597         self.model.AddItem(loop)
00598         
00599         self.canvas.Refresh()
00600         
00601     def OnDefineCondition(self, event):
00602         """!Define new condition in the model"""
00603         self.ModelChanged()
00604         
00605         width, height = self.canvas.GetSize()
00606         cond = ModelCondition(self, x = width/2, y = height/2,
00607                               id = self.model.GetNumItems() + 1)
00608         self.canvas.diagram.AddShape(cond)
00609         cond.Show(True)
00610         
00611         self._addEvent(cond)
00612         self.model.AddItem(cond)
00613         
00614         self.canvas.Refresh()
00615     
00616     def OnAddAction(self, event):
00617         """!Add action to model"""
00618         if self.searchDialog is None:
00619             self.searchDialog = ModelSearchDialog(self)
00620             self.searchDialog.CentreOnParent()
00621         else:
00622             self.searchDialog.Reset()
00623             
00624         if self.searchDialog.ShowModal() == wx.ID_CANCEL:
00625             self.searchDialog.Hide()
00626             return
00627             
00628         cmd = self.searchDialog.GetCmd()
00629         self.searchDialog.Hide()
00630         
00631         self.ModelChanged()
00632         
00633         # add action to canvas
00634         width, height = self.canvas.GetSize()
00635         
00636         action = ModelAction(self.model, cmd = cmd, x = width/2, y = height/2,
00637                              id = self.model.GetNextId())
00638         overwrite = self.model.GetProperties().get('overwrite', None)
00639         if overwrite is not None:
00640             action.GetTask().set_flag('overwrite', overwrite)
00641         
00642         self.canvas.diagram.AddShape(action)
00643         action.Show(True)
00644 
00645         self._addEvent(action)
00646         self.model.AddItem(action)
00647         
00648         self.itemPanel.Update()
00649         self.canvas.Refresh()
00650         time.sleep(.1)
00651         
00652         # show properties dialog
00653         win = action.GetPropDialog()
00654         if not win:
00655             if action.IsValid():
00656                 self.GetOptData(dcmd = action.GetLog(string = False), layer = action,
00657                                 params = action.GetParams(), propwin = None)
00658             else:
00659                 GUI(parent = self, show = True).ParseCommand(action.GetLog(string = False),
00660                                                              completed = (self.GetOptData, action, action.GetParams()))
00661         elif win and not win.IsShown():
00662             win.Show()
00663         
00664         if win:
00665             win.Raise()
00666         
00667     def OnAddData(self, event):
00668         """!Add data item to model
00669         """
00670         # add action to canvas
00671         width, height = self.canvas.GetSize()
00672         data = ModelData(self, x = width/2, y = height/2)
00673        
00674         dlg = ModelDataDialog(parent = self, shape = data)
00675         data.SetPropDialog(dlg)
00676         dlg.CentreOnParent()
00677         ret = dlg.ShowModal()
00678         dlg.Destroy()
00679         if ret != wx.ID_OK:
00680             return
00681         
00682         data.Update()
00683         self.canvas.diagram.AddShape(data)
00684         data.Show(True)
00685         
00686         self.ModelChanged()
00687         
00688         self._addEvent(data)
00689         self.model.AddItem(data)
00690         
00691         self.canvas.Refresh()
00692         
00693         
00694     def OnHelp(self, event):
00695         """!Display manual page"""
00696         grass.run_command('g.manual',
00697                           entry = 'wxGUI.Modeler')
00698 
00699     def OnAbout(self, event):
00700         """!Display About window"""
00701         info = wx.AboutDialogInfo()
00702 
00703         info.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
00704         info.SetName(_('wxGUI Graphical Modeler'))
00705         info.SetWebSite('http://grass.osgeo.org')
00706         year = grass.version()['date']
00707         info.SetDescription(_('(C) 2010-%s by the GRASS Development Team\n\n') % year + 
00708                             '\n'.join(textwrap.wrap(_('This program is free software under the GNU General Public License'
00709                                                       '(>=v2). Read the file COPYING that comes with GRASS for details.'), 75)))
00710         
00711         wx.AboutBox(info)
00712         
00713     def GetOptData(self, dcmd, layer, params, propwin):
00714         """!Process action data"""
00715         if params: # add data items
00716             width, height = self.canvas.GetSize()
00717             x = [width/2 + 200, width/2 - 200]
00718             for p in params['params']:
00719                 if p.get('prompt', '') in ('raster', 'vector', 'raster3d') and \
00720                         (p.get('value', None) or \
00721                              (p.get('age', 'old') != 'old' and p.get('required', 'no') == 'yes')):
00722                     data = layer.FindData(p.get('name', ''))
00723                     if data:
00724                         data.SetValue(p.get('value', ''))
00725                         data.Update()
00726                         continue
00727                     
00728                     data = self.model.FindData(p.get('value', ''),
00729                                                p.get('prompt', ''))
00730                     if data:
00731                         if p.get('age', 'old') == 'old':
00732                             rel = ModelRelation(parent = self, fromShape = data,
00733                                                 toShape = layer, param = p.get('name', ''))
00734                         else:
00735                             rel = ModelRelation(parent = self, fromShape = layer,
00736                                                 toShape = data, param = p.get('name', ''))
00737                         layer.AddRelation(rel)
00738                         data.AddRelation(rel)
00739                         self.AddLine(rel)
00740                         data.Update()
00741                         continue
00742                     
00743                     data = ModelData(self, value = p.get('value', ''),
00744                                      prompt = p.get('prompt', ''),
00745                                      x = x.pop(), y = height/2)
00746                     self._addEvent(data)
00747                     self.canvas.diagram.AddShape(data)
00748                     data.Show(True)
00749                                                             
00750                     if p.get('age', 'old') == 'old':
00751                         rel = ModelRelation(parent = self, fromShape = data,
00752                                             toShape = layer, param = p.get('name', ''))
00753                     else:
00754                         rel = ModelRelation(parent = self, fromShape = layer,
00755                                             toShape = data, param = p.get('name', ''))
00756                     layer.AddRelation(rel)
00757                     data.AddRelation(rel)
00758                     self.AddLine(rel)
00759                     data.Update()
00760             
00761             # valid / parameterized ?
00762             layer.SetValid(params)
00763             
00764             self.canvas.Refresh()
00765         
00766         if dcmd:
00767             layer.SetProperties(params, propwin)
00768             
00769         self.SetStatusText(layer.GetLog(), 0)
00770         
00771     def AddLine(self, rel):
00772         """!Add connection between model objects
00773         
00774         @param rel relation
00775         """
00776         fromShape = rel.GetFrom()
00777         toShape   = rel.GetTo()
00778         
00779         rel.SetCanvas(self)
00780         rel.SetPen(wx.BLACK_PEN)
00781         rel.SetBrush(wx.BLACK_BRUSH)
00782         rel.AddArrow(ogl.ARROW_ARROW)
00783         points = rel.GetControlPoints()
00784         rel.MakeLineControlPoints(2)
00785         if points:
00786             for x, y in points:
00787                 rel.InsertLineControlPoint(point = wx.RealPoint(x, y))
00788         
00789         self._addEvent(rel)
00790         try:
00791             fromShape.AddLine(rel, toShape)
00792         except TypeError:
00793             pass # bug when connecting ModelCondition and ModelLoop - to be fixed
00794         
00795         self.canvas.diagram.AddShape(rel)
00796         rel.Show(True)
00797         
00798     def LoadModelFile(self, filename):
00799         """!Load model definition stored in GRASS Model XML file (gxm)
00800         """
00801         try:
00802             self.model.LoadModel(filename)
00803         except GException, e:
00804             GError(parent = self,
00805                    message = _("Reading model file <%s> failed.\n"
00806                                "Invalid file, unable to parse XML document.") % filename)
00807         
00808         self.modelFile = filename
00809         self.SetTitle(self.baseTitle + " - " +  os.path.basename(self.modelFile))
00810         
00811         self.SetStatusText(_("Please wait, loading model..."), 0)
00812         
00813         # load actions
00814         for item in self.model.GetItems(objType = ModelAction):
00815             self._addEvent(item)
00816             self.canvas.diagram.AddShape(item)
00817             item.Show(True)
00818             # relations/data
00819             for rel in item.GetRelations():
00820                 if rel.GetFrom() == item:
00821                     dataItem = rel.GetTo()
00822                 else:
00823                     dataItem = rel.GetFrom()
00824                 self._addEvent(dataItem)
00825                 self.canvas.diagram.AddShape(dataItem)
00826                 self.AddLine(rel)
00827                 dataItem.Show(True)
00828         
00829         # load loops
00830         for item in self.model.GetItems(objType = ModelLoop):
00831             self._addEvent(item)
00832             self.canvas.diagram.AddShape(item)
00833             item.Show(True)
00834             
00835             # connect items in the loop
00836             self.DefineLoop(item)
00837 
00838         # load conditions
00839         for item in self.model.GetItems(objType = ModelCondition):
00840             self._addEvent(item)
00841             self.canvas.diagram.AddShape(item)
00842             item.Show(True)
00843             
00844             # connect items in the condition
00845             self.DefineCondition(item)
00846         
00847         # load variables
00848         self.variablePanel.Update()
00849         self.itemPanel.Update()
00850         self.SetStatusText('', 0)
00851         
00852         # final updates
00853         for action in self.model.GetItems(objType = ModelAction):
00854             action.SetValid(action.GetParams())
00855             action.Update()
00856         
00857         self.canvas.Refresh(True)
00858         
00859     def WriteModelFile(self, filename):
00860         """!Save model to model file, recover original file on error.
00861         
00862         @return True on success
00863         @return False on failure
00864         """
00865         self.ModelChanged(False)
00866         tmpfile = tempfile.TemporaryFile(mode='w+b')
00867         try:
00868             WriteModelFile(fd = tmpfile, model = self.model)
00869         except StandardError:
00870             GError(parent = self,
00871                    message = _("Writing current settings to model file failed."))
00872             return False
00873         
00874         try:
00875             mfile = open(filename, "w")
00876             tmpfile.seek(0)
00877             for line in tmpfile.readlines():
00878                 mfile.write(line)
00879         except IOError:
00880             wx.MessageBox(parent = self,
00881                           message = _("Unable to open file <%s> for writing.") % filename,
00882                           caption = _("Error"),
00883                           style = wx.OK | wx.ICON_ERROR | wx.CENTRE)
00884             return False
00885         
00886         mfile.close()
00887         
00888         return True
00889     
00890     def DefineLoop(self, loop):
00891         """!Define loop with given list of items"""
00892         parent = loop
00893         items = loop.GetItems()
00894         if not items:
00895             return
00896         
00897         # remove defined relations first
00898         for rel in loop.GetRelations():
00899             self.canvas.GetDiagram().RemoveShape(rel)
00900         loop.Clear()
00901         
00902         for item in items:
00903             rel = ModelRelation(parent = self, fromShape = parent, toShape = item)
00904             dx = item.GetX() - parent.GetX()
00905             dy = item.GetY() - parent.GetY()
00906             loop.AddRelation(rel)
00907             if dx != 0:
00908                 rel.SetControlPoints(((parent.GetX(), parent.GetY() + dy / 2),
00909                                       (parent.GetX() + dx, parent.GetY() + dy / 2)))
00910             self.AddLine(rel)
00911             parent = item
00912         
00913         # close loop
00914         item = loop.GetItems()[-1]
00915         rel = ModelRelation(parent = self, fromShape = item, toShape = loop)
00916         loop.AddRelation(rel)
00917         self.AddLine(rel)
00918         dx = (item.GetX() - loop.GetX()) + loop.GetWidth() / 2 + 50
00919         dy = item.GetHeight() / 2 + 50
00920         rel.MakeLineControlPoints(0)
00921         rel.InsertLineControlPoint(point = wx.RealPoint(loop.GetX() - loop.GetWidth() / 2 ,
00922                                                         loop.GetY()))
00923         rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX(),
00924                                                         item.GetY() + item.GetHeight() / 2))
00925         rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX(),
00926                                                         item.GetY() + dy))
00927         rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX() - dx,
00928                                                         item.GetY() + dy))
00929         rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX() - dx,
00930                                                         loop.GetY()))
00931         
00932         self.canvas.Refresh()
00933 
00934     def DefineCondition(self, condition):
00935         """!Define if-else statement with given list of items"""
00936         parent = condition
00937         items = condition.GetItems()
00938         if not items['if'] and not items['else']:
00939             return
00940         
00941         # remove defined relations first
00942         for rel in condition.GetRelations():
00943             self.canvas.GetDiagram().RemoveShape(rel)
00944         condition.Clear()
00945         dxIf   = condition.GetX() + condition.GetWidth() / 2
00946         dxElse = condition.GetX() - condition.GetWidth() / 2
00947         dy     = condition.GetY()
00948         for branch in items.keys():
00949             for item in items[branch]:
00950                 rel = ModelRelation(parent = self, fromShape = parent,
00951                                     toShape = item)
00952                 condition.AddRelation(rel)
00953                 self.AddLine(rel)
00954                 rel.MakeLineControlPoints(0)
00955                 if branch == 'if':
00956                     rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX() - item.GetWidth() / 2, item.GetY()))
00957                     rel.InsertLineControlPoint(point = wx.RealPoint(dxIf, dy))
00958                 else:
00959                     rel.InsertLineControlPoint(point = wx.RealPoint(dxElse, dy))
00960                     rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX() - item.GetWidth() / 2, item.GetY()))
00961                 parent = item
00962         
00963         self.canvas.Refresh()
00964         
00965 class ModelCanvas(ogl.ShapeCanvas):
00966     """!Canvas where model is drawn"""
00967     def __init__(self, parent):
00968         self.parent = parent
00969         ogl.OGLInitialize()
00970         ogl.ShapeCanvas.__init__(self, parent)
00971         
00972         self.diagram = ogl.Diagram()
00973         self.SetDiagram(self.diagram)
00974         self.diagram.SetCanvas(self)
00975         
00976         self.SetScrollbars(20, 20, 1000/20, 1000/20)
00977         
00978         self.Bind(wx.EVT_CHAR,  self.OnChar)
00979         
00980     def OnChar(self, event):
00981         """!Key pressed"""
00982         kc = event.GetKeyCode()
00983         diagram = self.GetDiagram()
00984         if kc == wx.WXK_DELETE:
00985             self.RemoveSelected()
00986         
00987     def RemoveSelected(self):
00988         """!Remove selected shapes"""
00989         self.parent.ModelChanged()
00990         
00991         diagram = self.GetDiagram()
00992         for shape in diagram.GetShapeList():
00993             if not shape.Selected():
00994                 continue
00995             remList, upList = self.parent.GetModel().RemoveItem(shape)
00996             shape.Select(False)
00997             diagram.RemoveShape(shape)
00998             shape.__del__()
00999             for item in remList:
01000                 diagram.RemoveShape(item)
01001                 item.__del__()
01002             
01003             for item in upList:
01004                 item.Update()
01005         
01006         self.Refresh()
01007 
01008 class ModelEvtHandler(ogl.ShapeEvtHandler):
01009     """!Model event handler class"""
01010     def __init__(self, log, frame):
01011         ogl.ShapeEvtHandler.__init__(self)
01012         self.log = log
01013         self.frame = frame
01014         self.x = self.y = None
01015         
01016     def OnLeftClick(self, x, y, keys = 0, attachment = 0):
01017         """!Left mouse button pressed -> select item & update statusbar"""
01018         shape = self.GetShape()
01019         canvas = shape.GetCanvas()
01020         dc = wx.ClientDC(canvas)
01021         canvas.PrepareDC(dc)
01022         
01023         if hasattr(self.frame, 'defineRelation'):
01024             drel = self.frame.defineRelation
01025             if drel['from'] is None:
01026                 drel['from'] = shape
01027             elif drel['to'] is None:
01028                 drel['to'] = shape
01029                 rel = ModelRelation(parent = self.frame, fromShape = drel['from'],
01030                                     toShape = drel['to'])
01031                 dlg = ModelRelationDialog(parent = self.frame,
01032                                           shape = rel)
01033                 if dlg.IsValid():
01034                     ret = dlg.ShowModal()
01035                     if ret == wx.ID_OK:
01036                         option = dlg.GetOption()
01037                         rel.SetName(option)
01038                         drel['from'].AddRelation(rel)
01039                         drel['to'].AddRelation(rel)
01040                         drel['from'].Update()
01041                         params = { 'params' : [{ 'name' : option,
01042                                                  'value' : drel['from'].GetValue()}] }
01043                         drel['to'].MergeParams(params)
01044                         self.frame.AddLine(rel)
01045                 
01046                     dlg.Destroy()
01047                 del self.frame.defineRelation
01048         
01049         if shape.Selected():
01050             shape.Select(False, dc)
01051         else:
01052             redraw = False
01053             shapeList = canvas.GetDiagram().GetShapeList()
01054             toUnselect = list()
01055             
01056             for s in shapeList:
01057                 if s.Selected():
01058                     toUnselect.append(s)
01059             
01060             shape.Select(True, dc)
01061             
01062             for s in toUnselect:
01063                 s.Select(False, dc)
01064                 
01065         canvas.Refresh(False)
01066 
01067         if hasattr(shape, "GetLog"):
01068             self.log.SetStatusText(shape.GetLog(), 0)
01069         else:
01070             self.log.SetStatusText('', 0)
01071         
01072     def OnLeftDoubleClick(self, x, y, keys = 0, attachment = 0):
01073         """!Left mouse button pressed (double-click) -> show properties"""
01074         self.OnProperties()
01075         
01076     def OnProperties(self, event = None):
01077         """!Show properties dialog"""
01078         self.frame.ModelChanged()
01079         shape = self.GetShape()
01080         if isinstance(shape, ModelAction):
01081             module = GUI(parent = self.frame, show = True).ParseCommand(shape.GetLog(string = False),
01082                                                                         completed = (self.frame.GetOptData, shape, shape.GetParams()))
01083         
01084         elif isinstance(shape, ModelData):
01085             dlg = ModelDataDialog(parent = self.frame, shape = shape)
01086             shape.SetPropDialog(dlg)
01087             dlg.CentreOnParent()
01088             dlg.Show()
01089         
01090         elif isinstance(shape, ModelLoop):
01091             dlg = ModelLoopDialog(parent = self.frame, shape = shape)
01092             dlg.CentreOnParent()
01093             if dlg.ShowModal() == wx.ID_OK:
01094                 shape.SetText(dlg.GetCondition())
01095                 alist = list()
01096                 ids = dlg.GetItems()
01097                 for aId in ids['unchecked']:
01098                     action = self.frame.GetModel().GetItem(aId)
01099                     action.UnSetBlock(shape)
01100                 for aId in ids['checked']:
01101                     action = self.frame.GetModel().GetItem(aId)
01102                     action.SetBlock(shape)
01103                     if action:
01104                         alist.append(action)
01105                 shape.SetItems(alist)
01106                 self.frame.DefineLoop(shape)
01107                 self.frame.SetStatusText(shape.GetLog(), 0)
01108             self.frame.GetCanvas().Refresh()
01109             
01110             dlg.Destroy()
01111         
01112         elif isinstance(shape, ModelCondition):
01113             dlg = ModelConditionDialog(parent = self.frame, shape = shape)
01114             dlg.CentreOnParent()
01115             if dlg.ShowModal() == wx.ID_OK:
01116                 shape.SetText(dlg.GetCondition())
01117                 ids = dlg.GetItems()
01118                 for b in ids.keys():
01119                     alist = list()
01120                     for aId in ids[b]['unchecked']:
01121                         action = self.frame.GetModel().GetItem(aId)
01122                         action.UnSetBlock(shape)
01123                     for aId in ids[b]['checked']:
01124                         action = self.frame.GetModel().GetItem(aId)
01125                         action.SetBlock(shape)
01126                         if action:
01127                             alist.append(action)
01128                     shape.SetItems(alist, branch = b)
01129                 self.frame.DefineCondition(shape)
01130             self.frame.GetCanvas().Refresh()
01131             
01132             dlg.Destroy()
01133                    
01134     def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
01135         """!Drag shape (begining)"""
01136         self.frame.ModelChanged()
01137         if self._previousHandler:
01138             self._previousHandler.OnBeginDragLeft(x, y, keys, attachment)
01139         
01140     def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
01141         """!Drag shape (end)"""
01142         if self._previousHandler:
01143             self._previousHandler.OnEndDragLeft(x, y, keys, attachment)
01144         
01145         shape = self.GetShape()
01146         if isinstance(shape, ModelLoop):
01147             self.frame.DefineLoop(shape)
01148         elif isinstance(shape, ModelCondition):
01149             self.frame.DefineCondition(shape)
01150         
01151         for mo in shape.GetBlock():
01152             if isinstance(mo, ModelLoop):
01153                 self.frame.DefineLoop(mo)
01154             elif isinstance(mo, ModelCondition):
01155                 self.frame.DefineCondition(mo)
01156         
01157     def OnEndSize(self, x, y):
01158         """!Resize shape"""
01159         self.frame.ModelChanged()
01160         if self._previousHandler:
01161             self._previousHandler.OnEndSize(x, y)
01162         
01163     def OnRightClick(self, x, y, keys = 0, attachment = 0):
01164         """!Right click -> pop-up menu"""
01165         if not hasattr (self, "popupID"):
01166             self.popupID = dict()
01167             for key in ('remove', 'enable', 'addPoint',
01168                         'delPoint', 'intermediate', 'props', 'id'):
01169                 self.popupID[key] = wx.NewId()
01170         
01171         # record coordinates
01172         self.x = x
01173         self.y = y
01174         
01175         shape = self.GetShape()
01176         popupMenu = wx.Menu()
01177         popupMenu.Append(self.popupID['remove'], text=_('Remove'))
01178         self.frame.Bind(wx.EVT_MENU, self.OnRemove, id = self.popupID['remove'])
01179         if isinstance(shape, ModelAction) or isinstance(shape, ModelLoop):
01180             if shape.IsEnabled():
01181                 popupMenu.Append(self.popupID['enable'], text=_('Disable'))
01182                 self.frame.Bind(wx.EVT_MENU, self.OnDisable, id = self.popupID['enable'])
01183             else:
01184                 popupMenu.Append(self.popupID['enable'], text=_('Enable'))
01185                 self.frame.Bind(wx.EVT_MENU, self.OnEnable, id = self.popupID['enable'])
01186         
01187         if isinstance(shape, ModelRelation):
01188             popupMenu.AppendSeparator()
01189             popupMenu.Append(self.popupID['addPoint'], text=_('Add control point'))
01190             self.frame.Bind(wx.EVT_MENU, self.OnAddPoint, id = self.popupID['addPoint'])
01191             popupMenu.Append(self.popupID['delPoint'], text=_('Remove control point'))
01192             self.frame.Bind(wx.EVT_MENU, self.OnRemovePoint, id = self.popupID['delPoint'])
01193             if len(shape.GetLineControlPoints()) == 2:
01194                 popupMenu.Enable(self.popupID['delPoint'], False)
01195         
01196         if isinstance(shape, ModelData) and '@' not in shape.GetValue():
01197             popupMenu.AppendSeparator()
01198             popupMenu.Append(self.popupID['intermediate'], text=_('Intermediate'),
01199                              kind = wx.ITEM_CHECK)
01200             if self.GetShape().IsIntermediate():
01201                 popupMenu.Check(self.popupID['intermediate'], True)
01202             
01203             self.frame.Bind(wx.EVT_MENU, self.OnIntermediate, id = self.popupID['intermediate'])
01204             
01205         if isinstance(shape, ModelData) or \
01206                 isinstance(shape, ModelAction) or \
01207                 isinstance(shape, ModelLoop):
01208             popupMenu.AppendSeparator()
01209             popupMenu.Append(self.popupID['props'], text=_('Properties'))
01210             self.frame.Bind(wx.EVT_MENU, self.OnProperties, id = self.popupID['props'])
01211         
01212         self.frame.PopupMenu(popupMenu)
01213         popupMenu.Destroy()
01214 
01215     def OnDisable(self, event):
01216         """!Disable action"""
01217         self._onEnable(False)
01218         
01219     def OnEnable(self, event):
01220         """!Disable action"""
01221         self._onEnable(True)
01222         
01223     def _onEnable(self, enable):
01224         shape = self.GetShape()
01225         shape.Enable(enable)
01226         self.frame.ModelChanged()
01227         self.frame.canvas.Refresh()
01228         
01229     def OnAddPoint(self, event):
01230         """!Add control point"""
01231         shape = self.GetShape()
01232         shape.InsertLineControlPoint(point = wx.RealPoint(self.x, self.y))
01233         shape.ResetShapes()
01234         shape.Select(True)
01235         self.frame.ModelChanged()
01236         self.frame.canvas.Refresh()
01237         
01238     def OnRemovePoint(self, event):
01239         """!Remove control point"""
01240         shape = self.GetShape()
01241         shape.DeleteLineControlPoint()
01242         shape.Select(False)
01243         shape.Select(True)
01244         self.frame.ModelChanged()
01245         self.frame.canvas.Refresh()
01246         
01247     def OnIntermediate(self, event):
01248         """!Mark data as intermediate"""
01249         self.frame.ModelChanged()
01250         shape = self.GetShape()
01251         shape.SetIntermediate(event.IsChecked())
01252         self.frame.canvas.Refresh()
01253 
01254     def OnRemove(self, event):
01255         """!Remove shape
01256         """
01257         self.frame.GetCanvas().RemoveSelected()
01258         self.frame.itemPanel.Update()
01259         
01260 class VariablePanel(wx.Panel):
01261     def __init__(self, parent, id = wx.ID_ANY,
01262                  **kwargs):
01263         """!Manage model variables panel
01264         """
01265         self.parent = parent
01266         
01267         wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
01268         
01269         self.listBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
01270                                     label=" %s " % _("List of variables - right-click to delete"))
01271         
01272         self.list = VariableListCtrl(parent = self,
01273                                      columns = [_("Name"), _("Data type"),
01274                                                 _("Default value"), _("Description")])
01275         
01276         # add new category
01277         self.addBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
01278                                    label = " %s " % _("Add new variable"))
01279         self.name = wx.TextCtrl(parent = self, id = wx.ID_ANY)
01280         wx.CallAfter(self.name.SetFocus)
01281         self.type = wx.Choice(parent = self, id = wx.ID_ANY,
01282                               choices = [_("integer"),
01283                                          _("float"),
01284                                          _("string"),
01285                                          _("raster"),
01286                                          _("vector"),
01287                                          _("mapset"),
01288                                          _("file")])
01289         self.type.SetSelection(2) # string
01290         self.value = wx.TextCtrl(parent = self, id = wx.ID_ANY)
01291         self.desc = wx.TextCtrl(parent = self, id = wx.ID_ANY)
01292         
01293         # buttons
01294         self.btnAdd = wx.Button(parent = self, id = wx.ID_ADD)
01295         self.btnAdd.SetToolTipString(_("Add new variable to the model"))
01296         self.btnAdd.Enable(False)
01297         
01298         # bindings
01299         self.name.Bind(wx.EVT_TEXT, self.OnText)
01300         self.value.Bind(wx.EVT_TEXT, self.OnText)
01301         self.desc.Bind(wx.EVT_TEXT, self.OnText)
01302         self.btnAdd.Bind(wx.EVT_BUTTON, self.OnAdd)
01303         
01304         self._layout()
01305 
01306     def _layout(self):
01307         """!Layout dialog"""
01308         listSizer = wx.StaticBoxSizer(self.listBox, wx.VERTICAL)
01309         listSizer.Add(item = self.list, proportion = 1,
01310                       flag = wx.EXPAND)
01311         
01312         addSizer = wx.StaticBoxSizer(self.addBox, wx.VERTICAL)
01313         gridSizer = wx.GridBagSizer(hgap = 5, vgap = 5)
01314         gridSizer.AddGrowableCol(1)
01315         gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
01316                                            label = "%s:" % _("Name")),
01317                       flag = wx.ALIGN_CENTER_VERTICAL,
01318                       pos = (0, 0))
01319         gridSizer.Add(item = self.name,
01320                       pos = (0, 1),
01321                       flag = wx.EXPAND)
01322         gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
01323                                            label = "%s:" % _("Data type")),
01324                       flag = wx.ALIGN_CENTER_VERTICAL,
01325                       pos = (0, 2))
01326         gridSizer.Add(item = self.type,
01327                       pos = (0, 3))
01328         gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
01329                                            label = "%s:" % _("Default value")),
01330                       flag = wx.ALIGN_CENTER_VERTICAL,
01331                       pos = (1, 0))
01332         gridSizer.Add(item = self.value,
01333                       pos = (1, 1), span = (1, 3),
01334                       flag = wx.EXPAND)
01335         gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
01336                                            label = "%s:" % _("Description")),
01337                       flag = wx.ALIGN_CENTER_VERTICAL,
01338                       pos = (2, 0))
01339         gridSizer.Add(item = self.desc,
01340                       pos = (2, 1), span = (1, 3),
01341                       flag = wx.EXPAND)
01342         addSizer.Add(item = gridSizer,
01343                      flag = wx.EXPAND)
01344         addSizer.Add(item = self.btnAdd, proportion = 0,
01345                      flag = wx.TOP | wx.ALIGN_RIGHT, border = 5)
01346         
01347         mainSizer = wx.BoxSizer(wx.VERTICAL)
01348         mainSizer.Add(item = listSizer, proportion = 1,
01349                       flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)
01350         mainSizer.Add(item = addSizer, proportion = 0,
01351                       flag = wx.EXPAND | wx.ALIGN_CENTER |
01352                       wx.LEFT | wx.RIGHT | wx.BOTTOM, border = 5)
01353         
01354         self.SetSizer(mainSizer)
01355         mainSizer.Fit(self)
01356         
01357     def OnText(self, event):
01358         """!Text entered"""
01359         if self.name.GetValue():
01360             self.btnAdd.Enable()
01361         else:
01362             self.btnAdd.Enable(False)
01363     
01364     def OnAdd(self, event):
01365         """!Add new variable to the list"""
01366         msg = self.list.Append(self.name.GetValue(),
01367                                self.type.GetStringSelection(),
01368                                self.value.GetValue(),
01369                                self.desc.GetValue())
01370         self.name.SetValue('')
01371         self.name.SetFocus()
01372         
01373         if msg:
01374             GError(parent = self,
01375                    message = msg)
01376         else:
01377             self.type.SetSelection(2) # string
01378             self.value.SetValue('')
01379             self.desc.SetValue('')
01380             self.UpdateModelVariables()
01381         
01382     def UpdateModelVariables(self):
01383         """!Update model variables"""
01384         variables = dict()
01385         for values in self.list.GetData().itervalues():
01386             name = values[0]
01387             variables[name] = { 'type' : str(values[1]) }
01388             if values[2]:
01389                 variables[name]['value'] = values[2]
01390             if values[3]:
01391                 variables[name]['description'] = values[3]
01392         
01393         self.parent.GetModel().SetVariables(variables)
01394         self.parent.ModelChanged()
01395 
01396     def Update(self):
01397         """!Reload list of variables"""
01398         self.list.OnReload(None)
01399         
01400     def Reset(self):
01401         """!Remove all variables"""
01402         self.list.DeleteAllItems()
01403         self.parent.GetModel().SetVariables([])
01404         
01405 class ItemPanel(wx.Panel):
01406     def __init__(self, parent, id = wx.ID_ANY,
01407                  **kwargs):
01408         """!Manage model items
01409         """
01410         self.parent = parent
01411         
01412         wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
01413         
01414         self.listBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
01415                                     label=" %s " % _("List of items - right-click to delete"))
01416         
01417         self.list = ItemListCtrl(parent = self,
01418                                  columns = [_("ID"), _("Name"), _("In block"),
01419                                             _("Command / Condition")])
01420         
01421         self._layout()
01422 
01423     def _layout(self):
01424         """!Layout dialog"""
01425         listSizer = wx.StaticBoxSizer(self.listBox, wx.VERTICAL)
01426         listSizer.Add(item = self.list, proportion = 1,
01427                       flag = wx.EXPAND)
01428         
01429         mainSizer = wx.BoxSizer(wx.VERTICAL)
01430         mainSizer.Add(item = listSizer, proportion = 1,
01431                       flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)
01432         
01433         self.SetSizer(mainSizer)
01434         mainSizer.Fit(self)
01435         
01436     def Update(self):
01437         """!Reload list of variables"""
01438         self.list.OnReload(None)
01439 
01440 class PythonPanel(wx.Panel):
01441     def __init__(self, parent, id = wx.ID_ANY,
01442                  **kwargs):
01443         """!Model as python script
01444         """
01445         self.parent = parent
01446         
01447         wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
01448 
01449         self.filename = None # temp file to run
01450         
01451         self.bodyBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
01452                                     label = " %s " % _("Python script"))
01453         self.body = PyStc(parent = self, statusbar = self.parent.GetStatusBar())
01454 
01455         self.btnRun = wx.Button(parent = self, id = wx.ID_ANY, label = _("&Run"))
01456         self.btnRun.SetToolTipString(_("Run python script"))
01457         self.Bind(wx.EVT_BUTTON, self.OnRun, self.btnRun)
01458         self.btnSaveAs = wx.Button(parent = self, id = wx.ID_SAVEAS)
01459         self.btnSaveAs.SetToolTipString(_("Save python script to file"))
01460         self.Bind(wx.EVT_BUTTON, self.OnSaveAs, self.btnSaveAs)
01461         self.btnRefresh = wx.Button(parent = self, id = wx.ID_REFRESH)
01462         self.btnRefresh.SetToolTipString(_("Refresh python script based on the model.\n"
01463                                            "It will discards all local changes."))
01464         self.Bind(wx.EVT_BUTTON, self.OnRefresh, self.btnRefresh)
01465         
01466         self._layout()
01467         
01468     def _layout(self):
01469         sizer = wx.BoxSizer(wx.VERTICAL)
01470         bodySizer = wx.StaticBoxSizer(self.bodyBox, wx.HORIZONTAL)
01471         btnSizer = wx.BoxSizer(wx.HORIZONTAL)
01472         
01473         bodySizer.Add(item = self.body, proportion = 1,
01474                       flag = wx.EXPAND | wx.ALL, border = 3)
01475         
01476         btnSizer.Add(item = self.btnRefresh, proportion = 0,
01477                      flag = wx.LEFT | wx.RIGHT, border = 5)
01478         btnSizer.AddStretchSpacer()
01479         btnSizer.Add(item = self.btnSaveAs, proportion = 0,
01480                      flag = wx.RIGHT | wx.ALIGN_RIGHT, border = 5)
01481         btnSizer.Add(item = self.btnRun, proportion = 0,
01482                      flag = wx.RIGHT | wx.ALIGN_RIGHT, border = 5)
01483         
01484         sizer.Add(item = bodySizer, proportion = 1,
01485                   flag = wx.EXPAND | wx.ALL, border = 3)
01486         sizer.Add(item = btnSizer, proportion = 0,
01487                   flag = wx.EXPAND | wx.ALL, border = 3)
01488         
01489         sizer.Fit(self)
01490         sizer.SetSizeHints(self)
01491         self.SetSizer(sizer)
01492 
01493     def OnRun(self, event):
01494         """!Run Python script"""
01495         self.filename = grass.tempfile()
01496         try:
01497             fd = open(self.filename, "w")
01498             fd.write(self.body.GetText())
01499         except IOError, e:
01500             GError(_("Unable to launch Python script. %s") % e,
01501                    parent = self)
01502             return
01503         finally:
01504             fd.close()
01505             mode = stat.S_IMODE(os.lstat(self.filename)[stat.ST_MODE])
01506             os.chmod(self.filename, mode | stat.S_IXUSR)
01507         
01508         self.parent.goutput.RunCmd([fd.name], switchPage = True,
01509                                    skipInterface = True, onDone = self.OnDone)
01510         
01511         event.Skip()
01512 
01513     def OnDone(self, cmd, returncode):
01514         """!Python script finished"""
01515         grass.try_remove(self.filename)
01516         self.filename = None
01517         
01518     def SaveAs(self, force = False):
01519         """!Save python script to file
01520 
01521         @return filename
01522         """
01523         filename = ''
01524         dlg = wx.FileDialog(parent = self,
01525                             message = _("Choose file to save"),
01526                             defaultDir = os.getcwd(),
01527                             wildcard = _("Python script (*.py)|*.py"),
01528                             style = wx.FD_SAVE)
01529         
01530         if dlg.ShowModal() == wx.ID_OK:
01531             filename = dlg.GetPath()
01532         
01533         if not filename:
01534             return ''
01535         
01536         # check for extension
01537         if filename[-3:] != ".py":
01538             filename += ".py"
01539         
01540         if os.path.exists(filename):
01541             dlg = wx.MessageDialog(self, message=_("File <%s> already exists. "
01542                                                    "Do you want to overwrite this file?") % filename,
01543                                    caption=_("Save file"),
01544                                    style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
01545             if dlg.ShowModal() == wx.ID_NO:
01546                 dlg.Destroy()
01547                 return ''
01548             
01549             dlg.Destroy()
01550         
01551         fd = open(filename, "w")
01552         try:
01553             if force:
01554                 WritePythonFile(fd, self.parent.GetModel())
01555             else:
01556                 fd.write(self.body.GetText())
01557         finally:
01558             fd.close()
01559         
01560         # executable file
01561         os.chmod(filename, stat.S_IRWXU | stat.S_IWUSR)
01562         
01563         return filename
01564     
01565     def OnSaveAs(self, event):
01566         """!Save python script to file"""
01567         self.SaveAs(force = False)
01568         event.Skip()
01569         
01570     def RefreshScript(self):
01571         """!Refresh Python script
01572 
01573         @return True on refresh
01574         @return False script hasn't been updated
01575         """
01576         if self.body.modified:
01577             dlg = wx.MessageDialog(self,
01578                                    message = _("Python script is locally modificated. "
01579                                                "Refresh will discard all changes. "
01580                                                "Do you really want to continue?"),
01581                                    caption=_("Update"),
01582                                    style = wx.YES_NO | wx.NO_DEFAULT |
01583                                    wx.ICON_QUESTION | wx.CENTRE)
01584             ret = dlg.ShowModal()
01585             dlg.Destroy()
01586             if ret == wx.ID_NO:
01587                 return False
01588         
01589         fd = tempfile.TemporaryFile()
01590         WritePythonFile(fd, self.parent.GetModel())
01591         fd.seek(0)
01592         self.body.SetText(fd.read())
01593         fd.close()
01594         
01595         self.body.modified = False
01596         
01597         return True
01598     
01599     def OnRefresh(self, event):
01600         """!Refresh Python script"""
01601         if self.RefreshScript():
01602             self.parent.SetStatusText(_('Python script is up-to-date'), 0)
01603         event.Skip()
01604         
01605     def IsModified(self):
01606         """!Check if python script has been modified"""
01607         return self.body.modified
01608     
01609     def IsEmpty(self):
01610         """!Check if python script is empty"""
01611         return len(self.body.GetText()) == 0
01612         
01613 def main():
01614     import gettext
01615     gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode = True)
01616     
01617     app = wx.PySimpleApp()
01618     wx.InitAllImageHandlers()
01619     frame = ModelFrame(parent = None)
01620     if len(sys.argv) > 1:
01621         frame.LoadModelFile(sys.argv[1])
01622     frame.Show()
01623     
01624     app.MainLoop()
01625     
01626 if __name__ == "__main__":
01627     main()