|
GRASS Programmer's Manual
6.5.svn(2012)-r51648
|
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()