GRASS Programmer's Manual  6.4.3(2013)-r
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Macros Pages
gmodeler/frame.py
Go to the documentation of this file.
1 """!
2 @package gmodeler.frame
3 
4 @brief wxGUI Graphical Modeler for creating, editing, and managing models
5 
6 Classes:
7  - frame::ModelFrame
8  - frame::ModelCanvas
9  - frame::ModelEvtHandler
10  - frame::VariablePanel
11  - frame::ItemPanel
12  - frame::PythonPanel
13 
14 (C) 2010-2012 by the GRASS Development Team
15 
16 This program is free software under the GNU General Public License
17 (>=v2). Read the file COPYING that comes with GRASS for details.
18 
19 @author Martin Landa <landa.martin gmail.com>
20 """
21 
22 import os
23 import sys
24 import time
25 import stat
26 import textwrap
27 import tempfile
28 import copy
29 import re
30 import random
31 
32 if __name__ == "__main__":
33  sys.path.append(os.path.join(os.getenv('GISBASE'), 'etc', 'wxpython'))
34 
35 import wx
36 from wx.lib import ogl
37 import wx.lib.flatnotebook as FN
38 
39 from core import globalvar
40 from gui_core.widgets import GNotebook
41 from gui_core.goutput import GMConsole, PyStc
42 from core.debug import Debug
43 from core.gcmd import GMessage, GException, GWarning, GError, RunCommand
44 from gui_core.dialogs import GetImageHandlers
45 from gui_core.preferences import PreferencesBaseDialog
46 from core.settings import UserSettings
47 from core.menudata import MenuData
48 from gui_core.menu import Menu
49 from gmodeler.menudata import ModelerData
50 from gui_core.forms import GUI
51 from gmodeler.preferences import PreferencesDialog, PropertiesDialog
52 from gmodeler.toolbars import ModelerToolbar
53 
54 from gmodeler.model import *
55 from gmodeler.dialogs import *
56 
57 from grass.script import core as grass
58 
59 class ModelFrame(wx.Frame):
60  def __init__(self, parent, id = wx.ID_ANY,
61  title = _("GRASS GIS Graphical Modeler (experimental prototype)"), **kwargs):
62  """!Graphical modeler main window
63 
64  @param parent parent window
65  @param id window id
66  @param title window title
67 
68  @param kwargs wx.Frames' arguments
69  """
70  self.parent = parent
71  self.searchDialog = None # module search dialog
72  self.baseTitle = title
73  self.modelFile = None # loaded model
74  self.modelChanged = False
75  self.randomness = 40 # random layout
76 
77  self.cursors = {
78  "default" : wx.StockCursor(wx.CURSOR_ARROW),
79  "cross" : wx.StockCursor(wx.CURSOR_CROSS),
80  }
81 
82  wx.Frame.__init__(self, parent = parent, id = id, title = title, **kwargs)
83  self.SetName("Modeler")
84  self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
85 
86  self.menubar = Menu(parent = self, data = ModelerData())
87 
88  self.SetMenuBar(self.menubar)
89 
90  self.toolbar = ModelerToolbar(parent = self)
91  self.SetToolBar(self.toolbar)
92 
93  self.statusbar = self.CreateStatusBar(number = 1)
94 
95  self.notebook = GNotebook(parent = self,
96  style = FN.FNB_FANCY_TABS | FN.FNB_BOTTOM |
97  FN.FNB_NO_NAV_BUTTONS | FN.FNB_NO_X_BUTTON)
98 
99  self.canvas = ModelCanvas(self)
100  self.canvas.SetBackgroundColour(wx.WHITE)
101  self.canvas.SetCursor(self.cursors["default"])
102 
103  self.model = Model(self.canvas)
104 
105  self.variablePanel = VariablePanel(parent = self)
106 
107  self.itemPanel = ItemPanel(parent = self)
108 
109  self.pythonPanel = PythonPanel(parent = self)
110 
111  self.goutput = GMConsole(parent = self, notebook = self.notebook)
112 
113  self.notebook.AddPage(page = self.canvas, text=_('Model'), name = 'model')
114  self.notebook.AddPage(page = self.itemPanel, text=_('Items'), name = 'items')
115  self.notebook.AddPage(page = self.variablePanel, text=_('Variables'), name = 'variables')
116  self.notebook.AddPage(page = self.pythonPanel, text=_('Python editor'), name = 'python')
117  self.notebook.AddPage(page = self.goutput, text=_('Command output'), name = 'output')
118  wx.CallAfter(self.notebook.SetSelectionByName, 'model')
119  wx.CallAfter(self.ModelChanged, False)
120 
121  self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
122  self.Bind(wx.EVT_SIZE, self.OnSize)
123  self.notebook.Bind(FN.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.OnPageChanged)
124 
125  self._layout()
126  self.SetMinSize((640, 300))
127  self.SetSize((800, 600))
128 
129  # fix goutput's pane size
130  if self.goutput:
131  self.goutput.SetSashPosition(int(self.GetSize()[1] * .75))
132 
133  def _layout(self):
134  """!Do layout"""
135  sizer = wx.BoxSizer(wx.VERTICAL)
136 
137  sizer.Add(item = self.notebook, proportion = 1,
138  flag = wx.EXPAND)
139 
140  self.SetAutoLayout(True)
141  self.SetSizer(sizer)
142  sizer.Fit(self)
143 
144  self.Layout()
145 
146  def _addEvent(self, item):
147  """!Add event to item"""
148  evthandler = ModelEvtHandler(self.statusbar,
149  self)
150  evthandler.SetShape(item)
151  evthandler.SetPreviousHandler(item.GetEventHandler())
152  item.SetEventHandler(evthandler)
153 
154  def _randomShift(self):
155  """!Returns random value to shift layout"""
156  return random.randint(-self.randomness, self.randomness)
157 
158  def GetCanvas(self):
159  """!Get canvas"""
160  return self.canvas
161 
162  def GetModel(self):
163  """!Get model"""
164  return self.model
165 
166  def ModelChanged(self, changed = True):
167  """!Update window title"""
168  self.modelChanged = changed
169 
170  if self.modelFile:
171  if self.modelChanged:
172  self.SetTitle(self.baseTitle + " - " + os.path.basename(self.modelFile) + '*')
173  else:
174  self.SetTitle(self.baseTitle + " - " + os.path.basename(self.modelFile))
175  else:
176  self.SetTitle(self.baseTitle)
177 
178  def OnPageChanged(self, event):
179  """!Page in notebook changed"""
180  page = event.GetSelection()
181  if page == self.notebook.GetPageIndexByName('python'):
182  if self.pythonPanel.IsEmpty():
183  self.pythonPanel.RefreshScript()
184 
185  if self.pythonPanel.IsModified():
186  self.SetStatusText(_('Python script contains local modifications'), 0)
187  else:
188  self.SetStatusText(_('Python script is up-to-date'), 0)
189 
190  event.Skip()
191 
192  def OnVariables(self, event):
193  """!Switch to variables page"""
194  self.notebook.SetSelectionByName('variables')
195 
196  def OnRemoveItem(self, event):
197  """!Remove shape
198  """
199  self.GetCanvas().RemoveSelected()
200 
201  def OnCanvasRefresh(self, event):
202  """!Refresh canvas"""
203  self.SetStatusText(_("Redrawing model..."), 0)
204  self.GetCanvas().Refresh()
205  self.SetStatusText("", 0)
206 
207  def OnCmdRun(self, event):
208  """!Run command"""
209  try:
210  action = self.GetModel().GetItems()[event.pid]
211  if hasattr(action, "task"):
212  action.Update(running = True)
213  except IndexError:
214  pass
215 
216  def OnCmdPrepare(self, event):
217  """!Prepare for running command"""
218  if not event.userData:
219  return
220 
221  event.onPrepare(item = event.userData['item'],
222  params = event.userData['params'])
223 
224  def OnCmdDone(self, event):
225  """!Command done (or aborted)"""
226  try:
227  action = self.GetModel().GetItems()[event.pid]
228  if hasattr(action, "task"):
229  action.Update(running = True)
230  except IndexError:
231  pass
232 
233  def OnCloseWindow(self, event):
234  """!Close window"""
235  if self.modelChanged and \
236  UserSettings.Get(group='manager', key='askOnQuit', subkey='enabled'):
237  if self.modelFile:
238  message = _("Do you want to save changes in the model?")
239  else:
240  message = _("Do you want to store current model settings "
241  "to model file?")
242 
243  # ask user to save current settings
244  dlg = wx.MessageDialog(self,
245  message = message,
246  caption=_("Quit Graphical Modeler"),
247  style = wx.YES_NO | wx.YES_DEFAULT |
248  wx.CANCEL | wx.ICON_QUESTION | wx.CENTRE)
249  ret = dlg.ShowModal()
250  if ret == wx.ID_YES:
251  if not self.modelFile:
252  self.OnWorkspaceSaveAs()
253  else:
254  self.WriteModelFile(self.modelFile)
255  elif ret == wx.ID_CANCEL:
256  dlg.Destroy()
257  return
258  dlg.Destroy()
259 
260  self.Destroy()
261 
262  def OnSize(self, event):
263  """Window resized, save to the model"""
264  self.ModelChanged()
265  event.Skip()
266 
267  def OnPreferences(self, event):
268  """!Open preferences dialog"""
269  dlg = PreferencesDialog(parent = self)
270  dlg.CenterOnParent()
271 
272  dlg.ShowModal()
273  self.canvas.Refresh()
274 
275  def OnHelp(self, event):
276  """!Show help"""
277  if self.parent and self.parent.GetName() == 'LayerManager':
278  log = self.parent.GetLogWindow()
279  log.RunCmd(['g.manual',
280  'entry=wxGUI.Modeler'])
281  else:
282  RunCommand('g.manual',
283  quiet = True,
284  entry = 'wxGUI.Modeler')
285 
286  def OnModelProperties(self, event):
287  """!Model properties dialog"""
288  dlg = PropertiesDialog(parent = self)
289  dlg.CentreOnParent()
290  properties = self.model.GetProperties()
291  dlg.Init(properties)
292  if dlg.ShowModal() == wx.ID_OK:
293  self.ModelChanged()
294  for key, value in dlg.GetValues().iteritems():
295  properties[key] = value
296  for action in self.model.GetItems(objType = ModelAction):
297  action.GetTask().set_flag('overwrite', properties['overwrite'])
298 
299  dlg.Destroy()
300 
301  def OnDeleteData(self, event):
302  """!Delete intermediate data"""
303  rast, vect, rast3d, msg = self.model.GetIntermediateData()
304 
305  if not rast and not vect and not rast3d:
306  GMessage(parent = self,
307  message = _('No intermediate data to delete.'))
308  return
309 
310  dlg = wx.MessageDialog(parent = self,
311  message= _("Do you want to permanently delete data?%s" % msg),
312  caption=_("Delete intermediate data?"),
313  style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
314 
315  ret = dlg.ShowModal()
316  if ret == wx.ID_YES:
317  dlg.Destroy()
318 
319  if rast:
320  self.goutput.RunCmd(['g.remove', 'rast=%s' %','.join(rast)])
321  if rast3d:
322  self.goutput.RunCmd(['g.remove', 'rast3d=%s' %','.join(rast3d)])
323  if vect:
324  self.goutput.RunCmd(['g.remove', 'vect=%s' %','.join(vect)])
325 
326  self.SetStatusText(_("%d maps deleted from current mapset") % \
327  int(len(rast) + len(rast3d) + len(vect)))
328  return
329 
330  dlg.Destroy()
331 
332  def OnModelNew(self, event):
333  """!Create new model"""
334  Debug.msg(4, "ModelFrame.OnModelNew():")
335 
336  # ask user to save current model
337  if self.modelFile and self.modelChanged:
338  self.OnModelSave()
339  elif self.modelFile is None and \
340  (self.model.GetNumItems() > 0 or len(self.model.GetData()) > 0):
341  dlg = wx.MessageDialog(self, message=_("Current model is not empty. "
342  "Do you want to store current settings "
343  "to model file?"),
344  caption=_("Create new model?"),
345  style=wx.YES_NO | wx.YES_DEFAULT |
346  wx.CANCEL | wx.ICON_QUESTION)
347  ret = dlg.ShowModal()
348  if ret == wx.ID_YES:
349  self.OnModelSaveAs()
350  elif ret == wx.ID_CANCEL:
351  dlg.Destroy()
352  return
353 
354  dlg.Destroy()
355 
356  # delete all items
357  self.canvas.GetDiagram().DeleteAllShapes()
358  self.model.Reset()
359  self.canvas.Refresh()
360  self.itemPanel.Update()
361  self.variablePanel.Reset()
362 
363  # no model file loaded
364  self.modelFile = None
365  self.modelChanged = False
366  self.SetTitle(self.baseTitle)
367 
368  def OnModelOpen(self, event):
369  """!Load model from file"""
370  filename = ''
371  dlg = wx.FileDialog(parent = self, message=_("Choose model file"),
372  defaultDir = os.getcwd(),
373  wildcard=_("GRASS Model File (*.gxm)|*.gxm"))
374  if dlg.ShowModal() == wx.ID_OK:
375  filename = dlg.GetPath()
376 
377  if not filename:
378  return
379 
380  Debug.msg(4, "ModelFrame.OnModelOpen(): filename=%s" % filename)
381 
382  # close current model
383  self.OnModelClose()
384 
385  self.LoadModelFile(filename)
386 
387  self.modelFile = filename
388  self.SetTitle(self.baseTitle + " - " + os.path.basename(self.modelFile))
389  self.SetStatusText(_('%(items)d items (%(actions)d actions) loaded into model') % \
390  { 'items' : self.model.GetNumItems(),
391  'actions' : self.model.GetNumItems(actionOnly = True) }, 0)
392 
393  def OnModelSave(self, event = None):
394  """!Save model to file"""
395  if self.modelFile and self.modelChanged:
396  dlg = wx.MessageDialog(self, message=_("Model file <%s> already exists. "
397  "Do you want to overwrite this file?") % \
398  self.modelFile,
399  caption=_("Save model"),
400  style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
401  if dlg.ShowModal() == wx.ID_NO:
402  dlg.Destroy()
403  else:
404  Debug.msg(4, "ModelFrame.OnModelSave(): filename=%s" % self.modelFile)
405  self.WriteModelFile(self.modelFile)
406  self.SetStatusText(_('File <%s> saved') % self.modelFile, 0)
407  self.SetTitle(self.baseTitle + " - " + os.path.basename(self.modelFile))
408  elif not self.modelFile:
409  self.OnModelSaveAs(None)
410 
411  def OnModelSaveAs(self, event):
412  """!Create model to file as"""
413  filename = ''
414  dlg = wx.FileDialog(parent = self,
415  message = _("Choose file to save current model"),
416  defaultDir = os.getcwd(),
417  wildcard=_("GRASS Model File (*.gxm)|*.gxm"),
418  style=wx.FD_SAVE)
419 
420 
421  if dlg.ShowModal() == wx.ID_OK:
422  filename = dlg.GetPath()
423 
424  if not filename:
425  return
426 
427  # check for extension
428  if filename[-4:] != ".gxm":
429  filename += ".gxm"
430 
431  if os.path.exists(filename):
432  dlg = wx.MessageDialog(parent = self,
433  message=_("Model file <%s> already exists. "
434  "Do you want to overwrite this file?") % filename,
435  caption=_("File already exists"),
436  style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
437  if dlg.ShowModal() != wx.ID_YES:
438  dlg.Destroy()
439  return
440 
441  Debug.msg(4, "GMFrame.OnModelSaveAs(): filename=%s" % filename)
442 
443  self.WriteModelFile(filename)
444  self.modelFile = filename
445  self.SetTitle(self.baseTitle + " - " + os.path.basename(self.modelFile))
446  self.SetStatusText(_('File <%s> saved') % self.modelFile, 0)
447 
448  def OnModelClose(self, event = None):
449  """!Close model file"""
450  Debug.msg(4, "ModelFrame.OnModelClose(): file=%s" % self.modelFile)
451  # ask user to save current model
452  if self.modelFile and self.modelChanged:
453  self.OnModelSave()
454  elif self.modelFile is None and \
455  (self.model.GetNumItems() > 0 or len(self.model.GetData()) > 0):
456  dlg = wx.MessageDialog(self, message=_("Current model is not empty. "
457  "Do you want to store current settings "
458  "to model file?"),
459  caption=_("Create new model?"),
460  style=wx.YES_NO | wx.YES_DEFAULT |
461  wx.CANCEL | wx.ICON_QUESTION)
462  ret = dlg.ShowModal()
463  if ret == wx.ID_YES:
464  self.OnModelSaveAs()
465  elif ret == wx.ID_CANCEL:
466  dlg.Destroy()
467  return
468 
469  dlg.Destroy()
470 
471  self.modelFile = None
472  self.SetTitle(self.baseTitle)
473 
474  self.canvas.GetDiagram().DeleteAllShapes()
475  self.model.Reset()
476 
477  self.canvas.Refresh()
478 
479  def OnRunModel(self, event):
480  """!Run entire model"""
481  self.model.Run(self.goutput, self.OnDone, parent = self)
482 
483  def OnDone(self, cmd, returncode):
484  """!Computation finished"""
485  self.SetStatusText('', 0)
486  # restore original files
487  if hasattr(self.model, "fileInput"):
488  for finput in self.model.fileInput:
489  data = self.model.fileInput[finput]
490  if not data:
491  continue
492 
493  fd = open(finput, "w")
494  try:
495  fd.write(data)
496  finally:
497  fd.close()
498  del self.model.fileInput
499 
500  def OnValidateModel(self, event, showMsg = True):
501  """!Validate entire model"""
502  if self.model.GetNumItems() < 1:
503  GMessage(parent = self,
504  message = _('Model is empty. Nothing to validate.'))
505  return
506 
507 
508  self.SetStatusText(_('Validating model...'), 0)
509  errList = self.model.Validate()
510  self.SetStatusText('', 0)
511 
512  if errList:
513  GWarning(parent = self,
514  message = _('Model is not valid.\n\n%s') % '\n'.join(errList))
515  else:
516  GMessage(parent = self,
517  message = _('Model is valid.'))
518 
519  def OnExportImage(self, event):
520  """!Export model to image (default image)
521  """
522  xminImg = 0
523  xmaxImg = 0
524  yminImg = 0
525  ymaxImg = 0
526  # get current size of canvas
527  for shape in self.canvas.GetDiagram().GetShapeList():
528  w, h = shape.GetBoundingBoxMax()
529  x = shape.GetX()
530  y = shape.GetY()
531  xmin = x - w / 2
532  xmax = x + w / 2
533  ymin = y - h / 2
534  ymax = y + h / 2
535  if xmin < xminImg:
536  xminImg = xmin
537  if xmax > xmaxImg:
538  xmaxImg = xmax
539  if ymin < yminImg:
540  yminImg = ymin
541  if ymax > ymaxImg:
542  ymaxImg = ymax
543  size = wx.Size(int(xmaxImg - xminImg) + 50,
544  int(ymaxImg - yminImg) + 50)
545  bitmap = wx.EmptyBitmap(width = size.width, height = size.height)
546 
547  filetype, ltype = GetImageHandlers(wx.ImageFromBitmap(bitmap))
548 
549  dlg = wx.FileDialog(parent = self,
550  message = _("Choose a file name to save the image (no need to add extension)"),
551  defaultDir = "",
552  defaultFile = "",
553  wildcard = filetype,
554  style=wx.SAVE | wx.FD_OVERWRITE_PROMPT)
555 
556  if dlg.ShowModal() == wx.ID_OK:
557  path = dlg.GetPath()
558  if not path:
559  dlg.Destroy()
560  return
561 
562  base, ext = os.path.splitext(path)
563  fileType = ltype[dlg.GetFilterIndex()]['type']
564  extType = ltype[dlg.GetFilterIndex()]['ext']
565  if ext != extType:
566  path = base + '.' + extType
567 
568  dc = wx.MemoryDC(bitmap)
569  dc.SetBackground(wx.WHITE_BRUSH)
570  dc.SetBackgroundMode(wx.SOLID)
571 
572  dc.BeginDrawing()
573  self.canvas.GetDiagram().Clear(dc)
574  self.canvas.GetDiagram().Redraw(dc)
575  dc.EndDrawing()
576 
577  bitmap.SaveFile(path, fileType)
578  self.SetStatusText(_("Model exported to <%s>") % path)
579 
580  dlg.Destroy()
581 
582  def OnExportPython(self, event = None, text = None):
583  """!Export model to Python script"""
584  filename = self.pythonPanel.SaveAs(force = True)
585  self.SetStatusText(_("Model exported to <%s>") % filename)
586 
587  def OnDefineRelation(self, event):
588  """!Define relation between data and action items"""
589  self.canvas.SetCursor(self.cursors["cross"])
590  self.defineRelation = { 'from' : None,
591  'to' : None }
592 
593  def OnDefineLoop(self, event):
594  """!Define new loop in the model"""
595  self.ModelChanged()
596 
597  width, height = self.canvas.GetSize()
598  loop = ModelLoop(self, x = width/2, y = height/2,
599  id = self.model.GetNumItems() + 1)
600  self.canvas.diagram.AddShape(loop)
601  loop.Show(True)
602 
603  self._addEvent(loop)
604  self.model.AddItem(loop)
605 
606  self.canvas.Refresh()
607 
608  def OnDefineCondition(self, event):
609  """!Define new condition in the model"""
610  self.ModelChanged()
611 
612  width, height = self.canvas.GetSize()
613  cond = ModelCondition(self, x = width/2, y = height/2,
614  id = self.model.GetNumItems() + 1)
615  self.canvas.diagram.AddShape(cond)
616  cond.Show(True)
617 
618  self._addEvent(cond)
619  self.model.AddItem(cond)
620 
621  self.canvas.Refresh()
622 
623  def OnAddAction(self, event):
624  """!Add action to model"""
625  if self.searchDialog is None:
626  self.searchDialog = ModelSearchDialog(self)
627  self.searchDialog.CentreOnParent()
628  else:
629  self.searchDialog.Reset()
630 
631  if self.searchDialog.ShowModal() == wx.ID_CANCEL:
632  self.searchDialog.Hide()
633  return
634 
635  cmd = self.searchDialog.GetCmd()
636  self.searchDialog.Hide()
637 
638  self.ModelChanged()
639 
640  # add action to canvas
641  x, y = self.canvas.GetNewShapePos()
642  action = ModelAction(self.model, cmd = cmd,
643  x = x + self._randomShift(),
644  y = y + self._randomShift(),
645  id = self.model.GetNextId())
646  overwrite = self.model.GetProperties().get('overwrite', None)
647  if overwrite is not None:
648  action.GetTask().set_flag('overwrite', overwrite)
649 
650  self.canvas.diagram.AddShape(action)
651  action.Show(True)
652 
653  self._addEvent(action)
654  self.model.AddItem(action)
655 
656  self.itemPanel.Update()
657  self.canvas.Refresh()
658  time.sleep(.1)
659 
660  # show properties dialog
661  win = action.GetPropDialog()
662  if not win:
663  if action.IsValid():
664  self.GetOptData(dcmd = action.GetLog(string = False), layer = action,
665  params = action.GetParams(), propwin = None)
666  else:
667  GUI(parent = self, show = True).ParseCommand(action.GetLog(string = False),
668  completed = (self.GetOptData, action, action.GetParams()))
669  elif win and not win.IsShown():
670  win.Show()
671 
672  if win:
673  win.Raise()
674 
675  def OnAddData(self, event):
676  """!Add data item to model
677  """
678  # add action to canvas
679  width, height = self.canvas.GetSize()
680  data = ModelData(self, x = width/2 + self._randomShift(),
681  y = height/2 + self._randomShift())
682 
683  dlg = ModelDataDialog(parent = self, shape = data)
684  data.SetPropDialog(dlg)
685  dlg.CentreOnParent()
686  ret = dlg.ShowModal()
687  dlg.Destroy()
688  if ret != wx.ID_OK:
689  return
690 
691  data.Update()
692  self.canvas.diagram.AddShape(data)
693  data.Show(True)
694 
695  self.ModelChanged()
696 
697  self._addEvent(data)
698  self.model.AddItem(data)
699 
700  self.canvas.Refresh()
701 
702 
703  def OnHelp(self, event):
704  """!Display manual page"""
705  grass.run_command('g.manual',
706  entry = 'wxGUI.Modeler')
707 
708  def OnAbout(self, event):
709  """!Display About window"""
710  info = wx.AboutDialogInfo()
711 
712  info.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
713  info.SetName(_('wxGUI Graphical Modeler'))
714  info.SetWebSite('http://grass.osgeo.org')
715  year = grass.version()['date']
716  info.SetDescription(_('(C) 2010-%s by the GRASS Development Team\n\n') % year +
717  '\n'.join(textwrap.wrap(_('This program is free software under the GNU General Public License'
718  '(>=v2). Read the file COPYING that comes with GRASS for details.'), 75)))
719 
720  wx.AboutBox(info)
721 
722  def GetOptData(self, dcmd, layer, params, propwin):
723  """!Process action data"""
724  if params: # add data items
725  width, height = self.canvas.GetSize()
726  x = width/2 - 200 + self._randomShift()
727  y = height/2 + self._randomShift()
728  for p in params['params']:
729  if p.get('prompt', '') in ('raster', 'vector', 'raster3d') and \
730  (p.get('value', None) or \
731  (p.get('age', 'old') != 'old' and p.get('required', 'no') == 'yes')):
732  data = layer.FindData(p.get('name', ''))
733  if data:
734  data.SetValue(p.get('value', ''))
735  data.Update()
736  continue
737 
738  data = self.model.FindData(p.get('value', ''),
739  p.get('prompt', ''))
740  if data:
741  if p.get('age', 'old') == 'old':
742  rel = ModelRelation(parent = self, fromShape = data,
743  toShape = layer, param = p.get('name', ''))
744  else:
745  rel = ModelRelation(parent = self, fromShape = layer,
746  toShape = data, param = p.get('name', ''))
747  layer.AddRelation(rel)
748  data.AddRelation(rel)
749  self.AddLine(rel)
750  data.Update()
751  continue
752 
753  data = ModelData(self, value = p.get('value', ''),
754  prompt = p.get('prompt', ''),
755  x = x, y = y)
756  self._addEvent(data)
757  self.canvas.diagram.AddShape(data)
758  data.Show(True)
759 
760  if p.get('age', 'old') == 'old':
761  rel = ModelRelation(parent = self, fromShape = data,
762  toShape = layer, param = p.get('name', ''))
763  else:
764  rel = ModelRelation(parent = self, fromShape = layer,
765  toShape = data, param = p.get('name', ''))
766  layer.AddRelation(rel)
767  data.AddRelation(rel)
768  self.AddLine(rel)
769  data.Update()
770 
771  # valid / parameterized ?
772  layer.SetValid(params)
773 
774  self.canvas.Refresh()
775 
776  if dcmd:
777  layer.SetProperties(params, propwin)
778 
779  self.SetStatusText(layer.GetLog(), 0)
780 
781  def AddLine(self, rel):
782  """!Add connection between model objects
783 
784  @param rel relation
785  """
786  fromShape = rel.GetFrom()
787  toShape = rel.GetTo()
788 
789  rel.SetCanvas(self)
790  rel.SetPen(wx.BLACK_PEN)
791  rel.SetBrush(wx.BLACK_BRUSH)
792  rel.AddArrow(ogl.ARROW_ARROW)
793  points = rel.GetControlPoints()
794  rel.MakeLineControlPoints(2)
795  if points:
796  for x, y in points:
797  rel.InsertLineControlPoint(point = wx.RealPoint(x, y))
798 
799  self._addEvent(rel)
800  try:
801  fromShape.AddLine(rel, toShape)
802  except TypeError:
803  pass # bug when connecting ModelCondition and ModelLoop - to be fixed
804 
805  self.canvas.diagram.AddShape(rel)
806  rel.Show(True)
807 
808  def LoadModelFile(self, filename):
809  """!Load model definition stored in GRASS Model XML file (gxm)
810  """
811  try:
812  self.model.LoadModel(filename)
813  except GException, e:
814  GError(parent = self,
815  message = _("Reading model file <%s> failed.\n"
816  "Invalid file, unable to parse XML document.") % filename)
817 
818  self.modelFile = filename
819  self.SetTitle(self.baseTitle + " - " + os.path.basename(self.modelFile))
820 
821  self.SetStatusText(_("Please wait, loading model..."), 0)
822 
823  # load actions
824  for item in self.model.GetItems(objType = ModelAction):
825  self._addEvent(item)
826  self.canvas.diagram.AddShape(item)
827  item.Show(True)
828  # relations/data
829  for rel in item.GetRelations():
830  if rel.GetFrom() == item:
831  dataItem = rel.GetTo()
832  else:
833  dataItem = rel.GetFrom()
834  self._addEvent(dataItem)
835  self.canvas.diagram.AddShape(dataItem)
836  self.AddLine(rel)
837  dataItem.Show(True)
838 
839  # load loops
840  for item in self.model.GetItems(objType = ModelLoop):
841  self._addEvent(item)
842  self.canvas.diagram.AddShape(item)
843  item.Show(True)
844 
845  # connect items in the loop
846  self.DefineLoop(item)
847 
848  # load conditions
849  for item in self.model.GetItems(objType = ModelCondition):
850  self._addEvent(item)
851  self.canvas.diagram.AddShape(item)
852  item.Show(True)
853 
854  # connect items in the condition
855  self.DefineCondition(item)
856 
857  # load variables
858  self.variablePanel.Update()
859  self.itemPanel.Update()
860  self.SetStatusText('', 0)
861 
862  # final updates
863  for action in self.model.GetItems(objType = ModelAction):
864  action.SetValid(action.GetParams())
865  action.Update()
866 
867  self.canvas.Refresh(True)
868 
869  def WriteModelFile(self, filename):
870  """!Save model to model file, recover original file on error.
871 
872  @return True on success
873  @return False on failure
874  """
875  self.ModelChanged(False)
876  tmpfile = tempfile.TemporaryFile(mode='w+b')
877  try:
878  WriteModelFile(fd = tmpfile, model = self.model)
879  except StandardError:
880  GError(parent = self,
881  message = _("Writing current settings to model file failed."))
882  return False
883 
884  try:
885  mfile = open(filename, "w")
886  tmpfile.seek(0)
887  for line in tmpfile.readlines():
888  mfile.write(line)
889  except IOError:
890  wx.MessageBox(parent = self,
891  message = _("Unable to open file <%s> for writing.") % filename,
892  caption = _("Error"),
893  style = wx.OK | wx.ICON_ERROR | wx.CENTRE)
894  return False
895 
896  mfile.close()
897 
898  return True
899 
900  def DefineLoop(self, loop):
901  """!Define loop with given list of items"""
902  parent = loop
903  items = loop.GetItems()
904  if not items:
905  return
906 
907  # remove defined relations first
908  for rel in loop.GetRelations():
909  self.canvas.GetDiagram().RemoveShape(rel)
910  loop.Clear()
911 
912  for item in items:
913  rel = ModelRelation(parent = self, fromShape = parent, toShape = item)
914  dx = item.GetX() - parent.GetX()
915  dy = item.GetY() - parent.GetY()
916  loop.AddRelation(rel)
917  if dx != 0:
918  rel.SetControlPoints(((parent.GetX(), parent.GetY() + dy / 2),
919  (parent.GetX() + dx, parent.GetY() + dy / 2)))
920  self.AddLine(rel)
921  parent = item
922 
923  # close loop
924  item = loop.GetItems()[-1]
925  rel = ModelRelation(parent = self, fromShape = item, toShape = loop)
926  loop.AddRelation(rel)
927  self.AddLine(rel)
928  dx = (item.GetX() - loop.GetX()) + loop.GetWidth() / 2 + 50
929  dy = item.GetHeight() / 2 + 50
930  rel.MakeLineControlPoints(0)
931  rel.InsertLineControlPoint(point = wx.RealPoint(loop.GetX() - loop.GetWidth() / 2 ,
932  loop.GetY()))
933  rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX(),
934  item.GetY() + item.GetHeight() / 2))
935  rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX(),
936  item.GetY() + dy))
937  rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX() - dx,
938  item.GetY() + dy))
939  rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX() - dx,
940  loop.GetY()))
941 
942  self.canvas.Refresh()
943 
944  def DefineCondition(self, condition):
945  """!Define if-else statement with given list of items"""
946  parent = condition
947  items = condition.GetItems()
948  if not items['if'] and not items['else']:
949  return
950 
951  # remove defined relations first
952  for rel in condition.GetRelations():
953  self.canvas.GetDiagram().RemoveShape(rel)
954  condition.Clear()
955  dxIf = condition.GetX() + condition.GetWidth() / 2
956  dxElse = condition.GetX() - condition.GetWidth() / 2
957  dy = condition.GetY()
958  for branch in items.keys():
959  for item in items[branch]:
960  rel = ModelRelation(parent = self, fromShape = parent,
961  toShape = item)
962  condition.AddRelation(rel)
963  self.AddLine(rel)
964  rel.MakeLineControlPoints(0)
965  if branch == 'if':
966  rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX() - item.GetWidth() / 2, item.GetY()))
967  rel.InsertLineControlPoint(point = wx.RealPoint(dxIf, dy))
968  else:
969  rel.InsertLineControlPoint(point = wx.RealPoint(dxElse, dy))
970  rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX() - item.GetWidth() / 2, item.GetY()))
971  parent = item
972 
973  self.canvas.Refresh()
974 
975 class ModelCanvas(ogl.ShapeCanvas):
976  """!Canvas where model is drawn"""
977  def __init__(self, parent):
978  self.parent = parent
979  ogl.OGLInitialize()
980  ogl.ShapeCanvas.__init__(self, parent)
981 
982  self.diagram = ogl.Diagram()
983  self.SetDiagram(self.diagram)
984  self.diagram.SetCanvas(self)
985 
986  self.SetScrollbars(20, 20, 2000/20, 2000/20)
987 
988  self.Bind(wx.EVT_CHAR, self.OnChar)
989 
990  def OnChar(self, event):
991  """!Key pressed"""
992  kc = event.GetKeyCode()
993  diagram = self.GetDiagram()
994  if kc == wx.WXK_DELETE:
995  self.RemoveSelected()
996 
997  def RemoveSelected(self):
998  """!Remove selected shapes"""
999  self.parent.ModelChanged()
1000 
1001  diagram = self.GetDiagram()
1002  shapes = [shape for shape in diagram.GetShapeList() if shape.Selected()]
1003  self.RemoveShapes(shapes)
1004 
1005  def RemoveShapes(self, shapes):
1006  """!Removes shapes"""
1007  self.parent.ModelChanged()
1008  diagram = self.GetDiagram()
1009  for shape in shapes:
1010  remList, upList = self.parent.GetModel().RemoveItem(shape)
1011  shape.Select(False)
1012  diagram.RemoveShape(shape)
1013  shape.__del__()
1014  for item in remList:
1015  diagram.RemoveShape(item)
1016  item.__del__()
1017 
1018  for item in upList:
1019  item.Update()
1020 
1021  self.Refresh()
1022 
1023  def GetNewShapePos(self):
1024  """!Determine optimal position for newly added object
1025 
1026  @return x,y
1027  """
1028  xNew, yNew = map(lambda x: x / 2, self.GetSize())
1029  diagram = self.GetDiagram()
1030 
1031  for shape in diagram.GetShapeList():
1032  y = shape.GetY()
1033  yBox = shape.GetBoundingBoxMin()[1] / 2
1034  if yBox > 0 and y < yNew + yBox and y > yNew - yBox:
1035  yNew += yBox * 3
1036 
1037  return xNew, yNew
1038 
1039 class ModelEvtHandler(ogl.ShapeEvtHandler):
1040  """!Model event handler class"""
1041  def __init__(self, log, frame):
1042  ogl.ShapeEvtHandler.__init__(self)
1043  self.log = log
1044  self.frame = frame
1045  self.x = self.y = None
1046 
1047  def OnLeftClick(self, x, y, keys = 0, attachment = 0):
1048  """!Left mouse button pressed -> select item & update statusbar"""
1049  shape = self.GetShape()
1050  canvas = shape.GetCanvas()
1051  dc = wx.ClientDC(canvas)
1052  canvas.PrepareDC(dc)
1053 
1054  if hasattr(self.frame, 'defineRelation'):
1055  drel = self.frame.defineRelation
1056  if drel['from'] is None:
1057  drel['from'] = shape
1058  elif drel['to'] is None:
1059  drel['to'] = shape
1060  rel = ModelRelation(parent = self.frame, fromShape = drel['from'],
1061  toShape = drel['to'])
1062  dlg = ModelRelationDialog(parent = self.frame,
1063  shape = rel)
1064  if dlg.IsValid():
1065  ret = dlg.ShowModal()
1066  if ret == wx.ID_OK:
1067  option = dlg.GetOption()
1068  rel.SetName(option)
1069  drel['from'].AddRelation(rel)
1070  drel['to'].AddRelation(rel)
1071  drel['from'].Update()
1072  params = { 'params' : [{ 'name' : option,
1073  'value' : drel['from'].GetValue()}] }
1074  drel['to'].MergeParams(params)
1075  self.frame.AddLine(rel)
1076 
1077  dlg.Destroy()
1078  del self.frame.defineRelation
1079 
1080  # select object
1081  self._onSelectShape(shape)
1082 
1083  if hasattr(shape, "GetLog"):
1084  self.log.SetStatusText(shape.GetLog(), 0)
1085  else:
1086  self.log.SetStatusText('', 0)
1087 
1088  def OnLeftDoubleClick(self, x, y, keys = 0, attachment = 0):
1089  """!Left mouse button pressed (double-click) -> show properties"""
1090  self.OnProperties()
1091 
1092  def OnProperties(self, event = None):
1093  """!Show properties dialog"""
1094  self.frame.ModelChanged()
1095  shape = self.GetShape()
1096  if isinstance(shape, ModelAction):
1097  module = GUI(parent = self.frame, show = True).ParseCommand(shape.GetLog(string = False),
1098  completed = (self.frame.GetOptData, shape, shape.GetParams()))
1099 
1100  elif isinstance(shape, ModelData):
1101  dlg = ModelDataDialog(parent = self.frame, shape = shape)
1102  shape.SetPropDialog(dlg)
1103  dlg.CentreOnParent()
1104  dlg.Show()
1105 
1106  elif isinstance(shape, ModelLoop):
1107  dlg = ModelLoopDialog(parent = self.frame, shape = shape)
1108  dlg.CentreOnParent()
1109  if dlg.ShowModal() == wx.ID_OK:
1110  shape.SetText(dlg.GetCondition())
1111  alist = list()
1112  ids = dlg.GetItems()
1113  for aId in ids['unchecked']:
1114  action = self.frame.GetModel().GetItem(aId)
1115  action.UnSetBlock(shape)
1116  for aId in ids['checked']:
1117  action = self.frame.GetModel().GetItem(aId)
1118  action.SetBlock(shape)
1119  if action:
1120  alist.append(action)
1121  shape.SetItems(alist)
1122  self.frame.DefineLoop(shape)
1123  self.frame.SetStatusText(shape.GetLog(), 0)
1124  self.frame.GetCanvas().Refresh()
1125 
1126  dlg.Destroy()
1127 
1128  elif isinstance(shape, ModelCondition):
1129  dlg = ModelConditionDialog(parent = self.frame, shape = shape)
1130  dlg.CentreOnParent()
1131  if dlg.ShowModal() == wx.ID_OK:
1132  shape.SetText(dlg.GetCondition())
1133  ids = dlg.GetItems()
1134  for b in ids.keys():
1135  alist = list()
1136  for aId in ids[b]['unchecked']:
1137  action = self.frame.GetModel().GetItem(aId)
1138  action.UnSetBlock(shape)
1139  for aId in ids[b]['checked']:
1140  action = self.frame.GetModel().GetItem(aId)
1141  action.SetBlock(shape)
1142  if action:
1143  alist.append(action)
1144  shape.SetItems(alist, branch = b)
1145  self.frame.DefineCondition(shape)
1146  self.frame.GetCanvas().Refresh()
1147 
1148  dlg.Destroy()
1149 
1150  def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
1151  """!Drag shape (begining)"""
1152  self.frame.ModelChanged()
1153  if self._previousHandler:
1154  self._previousHandler.OnBeginDragLeft(x, y, keys, attachment)
1155 
1156  def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
1157  """!Drag shape (end)"""
1158  if self._previousHandler:
1159  self._previousHandler.OnEndDragLeft(x, y, keys, attachment)
1160 
1161  shape = self.GetShape()
1162  if isinstance(shape, ModelLoop):
1163  self.frame.DefineLoop(shape)
1164  elif isinstance(shape, ModelCondition):
1165  self.frame.DefineCondition(shape)
1166 
1167  for mo in shape.GetBlock():
1168  if isinstance(mo, ModelLoop):
1169  self.frame.DefineLoop(mo)
1170  elif isinstance(mo, ModelCondition):
1171  self.frame.DefineCondition(mo)
1172 
1173  def OnEndSize(self, x, y):
1174  """!Resize shape"""
1175  self.frame.ModelChanged()
1176  if self._previousHandler:
1177  self._previousHandler.OnEndSize(x, y)
1178 
1179  def OnRightClick(self, x, y, keys = 0, attachment = 0):
1180  """!Right click -> pop-up menu"""
1181  if not hasattr (self, "popupID"):
1182  self.popupID = dict()
1183  for key in ('remove', 'enable', 'addPoint',
1184  'delPoint', 'intermediate', 'props', 'id'):
1185  self.popupID[key] = wx.NewId()
1186 
1187  # record coordinates
1188  self.x = x
1189  self.y = y
1190 
1191  # select object
1192  shape = self.GetShape()
1193  self._onSelectShape(shape)
1194 
1195  popupMenu = wx.Menu()
1196  popupMenu.Append(self.popupID['remove'], text=_('Remove'))
1197  self.frame.Bind(wx.EVT_MENU, self.OnRemove, id = self.popupID['remove'])
1198  if isinstance(shape, ModelAction) or isinstance(shape, ModelLoop):
1199  if shape.IsEnabled():
1200  popupMenu.Append(self.popupID['enable'], text=_('Disable'))
1201  self.frame.Bind(wx.EVT_MENU, self.OnDisable, id = self.popupID['enable'])
1202  else:
1203  popupMenu.Append(self.popupID['enable'], text=_('Enable'))
1204  self.frame.Bind(wx.EVT_MENU, self.OnEnable, id = self.popupID['enable'])
1205 
1206  if isinstance(shape, ModelRelation):
1207  popupMenu.AppendSeparator()
1208  popupMenu.Append(self.popupID['addPoint'], text=_('Add control point'))
1209  self.frame.Bind(wx.EVT_MENU, self.OnAddPoint, id = self.popupID['addPoint'])
1210  popupMenu.Append(self.popupID['delPoint'], text=_('Remove control point'))
1211  self.frame.Bind(wx.EVT_MENU, self.OnRemovePoint, id = self.popupID['delPoint'])
1212  if len(shape.GetLineControlPoints()) == 2:
1213  popupMenu.Enable(self.popupID['delPoint'], False)
1214 
1215  if isinstance(shape, ModelData) and '@' not in shape.GetValue():
1216  popupMenu.AppendSeparator()
1217  popupMenu.Append(self.popupID['intermediate'], text=_('Intermediate'),
1218  kind = wx.ITEM_CHECK)
1219  if self.GetShape().IsIntermediate():
1220  popupMenu.Check(self.popupID['intermediate'], True)
1221 
1222  self.frame.Bind(wx.EVT_MENU, self.OnIntermediate, id = self.popupID['intermediate'])
1223 
1224  if isinstance(shape, ModelData) or \
1225  isinstance(shape, ModelAction) or \
1226  isinstance(shape, ModelLoop):
1227  popupMenu.AppendSeparator()
1228  popupMenu.Append(self.popupID['props'], text=_('Properties'))
1229  self.frame.Bind(wx.EVT_MENU, self.OnProperties, id = self.popupID['props'])
1230 
1231  self.frame.PopupMenu(popupMenu)
1232  popupMenu.Destroy()
1233 
1234  def OnDisable(self, event):
1235  """!Disable action"""
1236  self._onEnable(False)
1237 
1238  def OnEnable(self, event):
1239  """!Disable action"""
1240  self._onEnable(True)
1241 
1242  def _onEnable(self, enable):
1243  shape = self.GetShape()
1244  shape.Enable(enable)
1245  self.frame.ModelChanged()
1246  self.frame.canvas.Refresh()
1247 
1248  def _onSelectShape(self, shape):
1249  canvas = shape.GetCanvas()
1250  dc = wx.ClientDC(canvas)
1251 
1252  if shape.Selected():
1253  shape.Select(False, dc)
1254  else:
1255  redraw = False
1256  shapeList = canvas.GetDiagram().GetShapeList()
1257  toUnselect = list()
1258 
1259  for s in shapeList:
1260  if s.Selected():
1261  toUnselect.append(s)
1262 
1263  shape.Select(True, dc)
1264 
1265  for s in toUnselect:
1266  s.Select(False, dc)
1267 
1268  canvas.Refresh(False)
1269 
1270  def OnAddPoint(self, event):
1271  """!Add control point"""
1272  shape = self.GetShape()
1273  shape.InsertLineControlPoint(point = wx.RealPoint(self.x, self.y))
1274  shape.ResetShapes()
1275  shape.Select(True)
1276  self.frame.ModelChanged()
1277  self.frame.canvas.Refresh()
1278 
1279  def OnRemovePoint(self, event):
1280  """!Remove control point"""
1281  shape = self.GetShape()
1282  shape.DeleteLineControlPoint()
1283  shape.Select(False)
1284  shape.Select(True)
1285  self.frame.ModelChanged()
1286  self.frame.canvas.Refresh()
1287 
1288  def OnIntermediate(self, event):
1289  """!Mark data as intermediate"""
1290  self.frame.ModelChanged()
1291  shape = self.GetShape()
1292  shape.SetIntermediate(event.IsChecked())
1293  self.frame.canvas.Refresh()
1294 
1295  def OnRemove(self, event):
1296  """!Remove shape
1297  """
1298  self.frame.GetCanvas().RemoveShapes([self.GetShape()])
1299  self.frame.itemPanel.Update()
1300 
1301 class VariablePanel(wx.Panel):
1302  def __init__(self, parent, id = wx.ID_ANY,
1303  **kwargs):
1304  """!Manage model variables panel
1305  """
1306  self.parent = parent
1307 
1308  wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
1309 
1310  self.listBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
1311  label=" %s " % _("List of variables - right-click to delete"))
1312 
1313  self.list = VariableListCtrl(parent = self,
1314  columns = [_("Name"), _("Data type"),
1315  _("Default value"), _("Description")])
1316 
1317  # add new category
1318  self.addBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
1319  label = " %s " % _("Add new variable"))
1320  self.name = wx.TextCtrl(parent = self, id = wx.ID_ANY)
1321  wx.CallAfter(self.name.SetFocus)
1322  self.type = wx.Choice(parent = self, id = wx.ID_ANY,
1323  choices = [_("integer"),
1324  _("float"),
1325  _("string"),
1326  _("raster"),
1327  _("vector"),
1328  _("mapset"),
1329  _("file")])
1330  self.type.SetSelection(2) # string
1331  self.value = wx.TextCtrl(parent = self, id = wx.ID_ANY)
1332  self.desc = wx.TextCtrl(parent = self, id = wx.ID_ANY)
1333 
1334  # buttons
1335  self.btnAdd = wx.Button(parent = self, id = wx.ID_ADD)
1336  self.btnAdd.SetToolTipString(_("Add new variable to the model"))
1337  self.btnAdd.Enable(False)
1338 
1339  # bindings
1340  self.name.Bind(wx.EVT_TEXT, self.OnText)
1341  self.value.Bind(wx.EVT_TEXT, self.OnText)
1342  self.desc.Bind(wx.EVT_TEXT, self.OnText)
1343  self.btnAdd.Bind(wx.EVT_BUTTON, self.OnAdd)
1344 
1345  self._layout()
1346 
1347  def _layout(self):
1348  """!Layout dialog"""
1349  listSizer = wx.StaticBoxSizer(self.listBox, wx.VERTICAL)
1350  listSizer.Add(item = self.list, proportion = 1,
1351  flag = wx.EXPAND)
1352 
1353  addSizer = wx.StaticBoxSizer(self.addBox, wx.VERTICAL)
1354  gridSizer = wx.GridBagSizer(hgap = 5, vgap = 5)
1355  gridSizer.AddGrowableCol(1)
1356  gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
1357  label = "%s:" % _("Name")),
1358  flag = wx.ALIGN_CENTER_VERTICAL,
1359  pos = (0, 0))
1360  gridSizer.Add(item = self.name,
1361  pos = (0, 1),
1362  flag = wx.EXPAND)
1363  gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
1364  label = "%s:" % _("Data type")),
1365  flag = wx.ALIGN_CENTER_VERTICAL,
1366  pos = (0, 2))
1367  gridSizer.Add(item = self.type,
1368  pos = (0, 3))
1369  gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
1370  label = "%s:" % _("Default value")),
1371  flag = wx.ALIGN_CENTER_VERTICAL,
1372  pos = (1, 0))
1373  gridSizer.Add(item = self.value,
1374  pos = (1, 1), span = (1, 3),
1375  flag = wx.EXPAND)
1376  gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
1377  label = "%s:" % _("Description")),
1378  flag = wx.ALIGN_CENTER_VERTICAL,
1379  pos = (2, 0))
1380  gridSizer.Add(item = self.desc,
1381  pos = (2, 1), span = (1, 3),
1382  flag = wx.EXPAND)
1383  addSizer.Add(item = gridSizer,
1384  flag = wx.EXPAND)
1385  addSizer.Add(item = self.btnAdd, proportion = 0,
1386  flag = wx.TOP | wx.ALIGN_RIGHT, border = 5)
1387 
1388  mainSizer = wx.BoxSizer(wx.VERTICAL)
1389  mainSizer.Add(item = listSizer, proportion = 1,
1390  flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)
1391  mainSizer.Add(item = addSizer, proportion = 0,
1392  flag = wx.EXPAND | wx.ALIGN_CENTER |
1393  wx.LEFT | wx.RIGHT | wx.BOTTOM, border = 5)
1394 
1395  self.SetSizer(mainSizer)
1396  mainSizer.Fit(self)
1397 
1398  def OnText(self, event):
1399  """!Text entered"""
1400  if self.name.GetValue():
1401  self.btnAdd.Enable()
1402  else:
1403  self.btnAdd.Enable(False)
1404 
1405  def OnAdd(self, event):
1406  """!Add new variable to the list"""
1407  msg = self.list.Append(self.name.GetValue(),
1408  self.type.GetStringSelection(),
1409  self.value.GetValue(),
1410  self.desc.GetValue())
1411  self.name.SetValue('')
1412  self.name.SetFocus()
1413 
1414  if msg:
1415  GError(parent = self,
1416  message = msg)
1417  else:
1418  self.type.SetSelection(2) # string
1419  self.value.SetValue('')
1420  self.desc.SetValue('')
1421  self.UpdateModelVariables()
1422 
1424  """!Update model variables"""
1425  variables = dict()
1426  for values in self.list.GetData().itervalues():
1427  name = values[0]
1428  variables[name] = { 'type' : str(values[1]) }
1429  if values[2]:
1430  variables[name]['value'] = values[2]
1431  if values[3]:
1432  variables[name]['description'] = values[3]
1433 
1434  self.parent.GetModel().SetVariables(variables)
1435  self.parent.ModelChanged()
1436 
1437  def Update(self):
1438  """!Reload list of variables"""
1439  self.list.OnReload(None)
1440 
1441  def Reset(self):
1442  """!Remove all variables"""
1443  self.list.DeleteAllItems()
1444  self.parent.GetModel().SetVariables([])
1445 
1446 class ItemPanel(wx.Panel):
1447  def __init__(self, parent, id = wx.ID_ANY,
1448  **kwargs):
1449  """!Manage model items
1450  """
1451  self.parent = parent
1452 
1453  wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
1454 
1455  self.listBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
1456  label=" %s " % _("List of items - right-click to delete"))
1457 
1458  self.list = ItemListCtrl(parent = self,
1459  columns = [_("ID"), _("Name"), _("In block"),
1460  _("Command / Condition")])
1461 
1462  self._layout()
1463 
1464  def _layout(self):
1465  """!Layout dialog"""
1466  listSizer = wx.StaticBoxSizer(self.listBox, wx.VERTICAL)
1467  listSizer.Add(item = self.list, proportion = 1,
1468  flag = wx.EXPAND)
1469 
1470  mainSizer = wx.BoxSizer(wx.VERTICAL)
1471  mainSizer.Add(item = listSizer, proportion = 1,
1472  flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)
1473 
1474  self.SetSizer(mainSizer)
1475  mainSizer.Fit(self)
1476 
1477  def Update(self):
1478  """!Reload list of variables"""
1479  self.list.OnReload(None)
1480 
1481 class PythonPanel(wx.Panel):
1482  def __init__(self, parent, id = wx.ID_ANY,
1483  **kwargs):
1484  """!Model as python script
1485  """
1486  self.parent = parent
1487 
1488  wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
1489 
1490  self.filename = None # temp file to run
1491 
1492  self.bodyBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
1493  label = " %s " % _("Python script"))
1494  self.body = PyStc(parent = self, statusbar = self.parent.GetStatusBar())
1495 
1496  self.btnRun = wx.Button(parent = self, id = wx.ID_ANY, label = _("&Run"))
1497  self.btnRun.SetToolTipString(_("Run python script"))
1498  self.Bind(wx.EVT_BUTTON, self.OnRun, self.btnRun)
1499  self.btnSaveAs = wx.Button(parent = self, id = wx.ID_SAVEAS)
1500  self.btnSaveAs.SetToolTipString(_("Save python script to file"))
1501  self.Bind(wx.EVT_BUTTON, self.OnSaveAs, self.btnSaveAs)
1502  self.btnRefresh = wx.Button(parent = self, id = wx.ID_REFRESH)
1503  self.btnRefresh.SetToolTipString(_("Refresh python script based on the model.\n"
1504  "It will discards all local changes."))
1505  self.Bind(wx.EVT_BUTTON, self.OnRefresh, self.btnRefresh)
1506 
1507  self._layout()
1508 
1509  def _layout(self):
1510  sizer = wx.BoxSizer(wx.VERTICAL)
1511  bodySizer = wx.StaticBoxSizer(self.bodyBox, wx.HORIZONTAL)
1512  btnSizer = wx.BoxSizer(wx.HORIZONTAL)
1513 
1514  bodySizer.Add(item = self.body, proportion = 1,
1515  flag = wx.EXPAND | wx.ALL, border = 3)
1516 
1517  btnSizer.Add(item = self.btnRefresh, proportion = 0,
1518  flag = wx.LEFT | wx.RIGHT, border = 5)
1519  btnSizer.AddStretchSpacer()
1520  btnSizer.Add(item = self.btnSaveAs, proportion = 0,
1521  flag = wx.RIGHT | wx.ALIGN_RIGHT, border = 5)
1522  btnSizer.Add(item = self.btnRun, proportion = 0,
1523  flag = wx.RIGHT | wx.ALIGN_RIGHT, border = 5)
1524 
1525  sizer.Add(item = bodySizer, proportion = 1,
1526  flag = wx.EXPAND | wx.ALL, border = 3)
1527  sizer.Add(item = btnSizer, proportion = 0,
1528  flag = wx.EXPAND | wx.ALL, border = 3)
1529 
1530  sizer.Fit(self)
1531  sizer.SetSizeHints(self)
1532  self.SetSizer(sizer)
1533 
1534  def OnRun(self, event):
1535  """!Run Python script"""
1536  self.filename = grass.tempfile()
1537  try:
1538  fd = open(self.filename, "w")
1539  fd.write(self.body.GetText())
1540  except IOError, e:
1541  GError(_("Unable to launch Python script. %s") % e,
1542  parent = self)
1543  return
1544  finally:
1545  fd.close()
1546  mode = stat.S_IMODE(os.lstat(self.filename)[stat.ST_MODE])
1547  os.chmod(self.filename, mode | stat.S_IXUSR)
1548 
1549  self.parent.goutput.RunCmd([fd.name], switchPage = True,
1550  skipInterface = True, onDone = self.OnDone)
1551 
1552  event.Skip()
1553 
1554  def OnDone(self, cmd, returncode):
1555  """!Python script finished"""
1556  grass.try_remove(self.filename)
1557  self.filename = None
1558 
1559  def SaveAs(self, force = False):
1560  """!Save python script to file
1561 
1562  @return filename
1563  """
1564  filename = ''
1565  dlg = wx.FileDialog(parent = self,
1566  message = _("Choose file to save"),
1567  defaultDir = os.getcwd(),
1568  wildcard = _("Python script (*.py)|*.py"),
1569  style = wx.FD_SAVE)
1570 
1571  if dlg.ShowModal() == wx.ID_OK:
1572  filename = dlg.GetPath()
1573 
1574  if not filename:
1575  return ''
1576 
1577  # check for extension
1578  if filename[-3:] != ".py":
1579  filename += ".py"
1580 
1581  if os.path.exists(filename):
1582  dlg = wx.MessageDialog(self, message=_("File <%s> already exists. "
1583  "Do you want to overwrite this file?") % filename,
1584  caption=_("Save file"),
1585  style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
1586  if dlg.ShowModal() == wx.ID_NO:
1587  dlg.Destroy()
1588  return ''
1589 
1590  dlg.Destroy()
1591 
1592  fd = open(filename, "w")
1593  try:
1594  if force:
1595  WritePythonFile(fd, self.parent.GetModel())
1596  else:
1597  fd.write(self.body.GetText())
1598  finally:
1599  fd.close()
1600 
1601  # executable file
1602  os.chmod(filename, stat.S_IRWXU | stat.S_IWUSR)
1603 
1604  return filename
1605 
1606  def OnSaveAs(self, event):
1607  """!Save python script to file"""
1608  self.SaveAs(force = False)
1609  event.Skip()
1610 
1611  def RefreshScript(self):
1612  """!Refresh Python script
1613 
1614  @return True on refresh
1615  @return False script hasn't been updated
1616  """
1617  if self.body.modified:
1618  dlg = wx.MessageDialog(self,
1619  message = _("Python script is locally modificated. "
1620  "Refresh will discard all changes. "
1621  "Do you really want to continue?"),
1622  caption=_("Update"),
1623  style = wx.YES_NO | wx.NO_DEFAULT |
1624  wx.ICON_QUESTION | wx.CENTRE)
1625  ret = dlg.ShowModal()
1626  dlg.Destroy()
1627  if ret == wx.ID_NO:
1628  return False
1629 
1630  fd = tempfile.TemporaryFile()
1631  WritePythonFile(fd, self.parent.GetModel())
1632  fd.seek(0)
1633  self.body.SetText(fd.read())
1634  fd.close()
1635 
1636  self.body.modified = False
1637 
1638  return True
1639 
1640  def OnRefresh(self, event):
1641  """!Refresh Python script"""
1642  if self.RefreshScript():
1643  self.parent.SetStatusText(_('Python script is up-to-date'), 0)
1644  event.Skip()
1645 
1646  def IsModified(self):
1647  """!Check if python script has been modified"""
1648  return self.body.modified
1649 
1650  def IsEmpty(self):
1651  """!Check if python script is empty"""
1652  return len(self.body.GetText()) == 0
1653 
1654 def main():
1655  import gettext
1656  gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode = True)
1657 
1658  app = wx.PySimpleApp()
1659  wx.InitAllImageHandlers()
1660  frame = ModelFrame(parent = None)
1661  if len(sys.argv) > 1:
1662  frame.LoadModelFile(sys.argv[1])
1663  frame.Show()
1664 
1665  app.MainLoop()
1666 
1667 if __name__ == "__main__":
1668  main()