GRASS Programmer's Manual  6.5.svn(2014)-r66266
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator 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.\n\n%s") % \
817  (filename, e),
818  showTraceback = False)
819  return
820 
821  self.modelFile = filename
822  self.SetTitle(self.baseTitle + " - " + os.path.basename(self.modelFile))
823 
824  self.SetStatusText(_("Please wait, loading model..."), 0)
825 
826  # load actions
827  for item in self.model.GetItems(objType = ModelAction):
828  self._addEvent(item)
829  self.canvas.diagram.AddShape(item)
830  item.Show(True)
831  # relations/data
832  for rel in item.GetRelations():
833  if rel.GetFrom() == item:
834  dataItem = rel.GetTo()
835  else:
836  dataItem = rel.GetFrom()
837  self._addEvent(dataItem)
838  self.canvas.diagram.AddShape(dataItem)
839  self.AddLine(rel)
840  dataItem.Show(True)
841 
842  # load loops
843  for item in self.model.GetItems(objType = ModelLoop):
844  self._addEvent(item)
845  self.canvas.diagram.AddShape(item)
846  item.Show(True)
847 
848  # connect items in the loop
849  self.DefineLoop(item)
850 
851  # load conditions
852  for item in self.model.GetItems(objType = ModelCondition):
853  self._addEvent(item)
854  self.canvas.diagram.AddShape(item)
855  item.Show(True)
856 
857  # connect items in the condition
858  self.DefineCondition(item)
859 
860  # load variables
861  self.variablePanel.Update()
862  self.itemPanel.Update()
863  self.SetStatusText('', 0)
864 
865  # final updates
866  for action in self.model.GetItems(objType = ModelAction):
867  action.SetValid(action.GetParams())
868  action.Update()
869 
870  self.canvas.Refresh(True)
871 
872  def WriteModelFile(self, filename):
873  """!Save model to model file, recover original file on error.
874 
875  @return True on success
876  @return False on failure
877  """
878  self.ModelChanged(False)
879  tmpfile = tempfile.TemporaryFile(mode='w+b')
880  try:
881  WriteModelFile(fd = tmpfile, model = self.model)
882  except StandardError:
883  GError(parent = self,
884  message = _("Writing current settings to model file failed."))
885  return False
886 
887  try:
888  mfile = open(filename, "w")
889  tmpfile.seek(0)
890  for line in tmpfile.readlines():
891  mfile.write(line)
892  except IOError:
893  wx.MessageBox(parent = self,
894  message = _("Unable to open file <%s> for writing.") % filename,
895  caption = _("Error"),
896  style = wx.OK | wx.ICON_ERROR | wx.CENTRE)
897  return False
898 
899  mfile.close()
900 
901  return True
902 
903  def DefineLoop(self, loop):
904  """!Define loop with given list of items"""
905  parent = loop
906  items = loop.GetItems()
907  if not items:
908  return
909 
910  # remove defined relations first
911  for rel in loop.GetRelations():
912  self.canvas.GetDiagram().RemoveShape(rel)
913  loop.Clear()
914 
915  for item in items:
916  rel = ModelRelation(parent = self, fromShape = parent, toShape = item)
917  dx = item.GetX() - parent.GetX()
918  dy = item.GetY() - parent.GetY()
919  loop.AddRelation(rel)
920  if dx != 0:
921  rel.SetControlPoints(((parent.GetX(), parent.GetY() + dy / 2),
922  (parent.GetX() + dx, parent.GetY() + dy / 2)))
923  self.AddLine(rel)
924  parent = item
925 
926  # close loop
927  item = loop.GetItems()[-1]
928  rel = ModelRelation(parent = self, fromShape = item, toShape = loop)
929  loop.AddRelation(rel)
930  self.AddLine(rel)
931  dx = (item.GetX() - loop.GetX()) + loop.GetWidth() / 2 + 50
932  dy = item.GetHeight() / 2 + 50
933  rel.MakeLineControlPoints(0)
934  rel.InsertLineControlPoint(point = wx.RealPoint(loop.GetX() - loop.GetWidth() / 2 ,
935  loop.GetY()))
936  rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX(),
937  item.GetY() + item.GetHeight() / 2))
938  rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX(),
939  item.GetY() + dy))
940  rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX() - dx,
941  item.GetY() + dy))
942  rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX() - dx,
943  loop.GetY()))
944 
945  self.canvas.Refresh()
946 
947  def DefineCondition(self, condition):
948  """!Define if-else statement with given list of items"""
949  parent = condition
950  items = condition.GetItems()
951  if not items['if'] and not items['else']:
952  return
953 
954  # remove defined relations first
955  for rel in condition.GetRelations():
956  self.canvas.GetDiagram().RemoveShape(rel)
957  condition.Clear()
958  dxIf = condition.GetX() + condition.GetWidth() / 2
959  dxElse = condition.GetX() - condition.GetWidth() / 2
960  dy = condition.GetY()
961  for branch in items.keys():
962  for item in items[branch]:
963  rel = ModelRelation(parent = self, fromShape = parent,
964  toShape = item)
965  condition.AddRelation(rel)
966  self.AddLine(rel)
967  rel.MakeLineControlPoints(0)
968  if branch == 'if':
969  rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX() - item.GetWidth() / 2, item.GetY()))
970  rel.InsertLineControlPoint(point = wx.RealPoint(dxIf, dy))
971  else:
972  rel.InsertLineControlPoint(point = wx.RealPoint(dxElse, dy))
973  rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX() - item.GetWidth() / 2, item.GetY()))
974  parent = item
975 
976  self.canvas.Refresh()
977 
978 class ModelCanvas(ogl.ShapeCanvas):
979  """!Canvas where model is drawn"""
980  def __init__(self, parent):
981  self.parent = parent
982  ogl.OGLInitialize()
983  ogl.ShapeCanvas.__init__(self, parent)
984 
985  self.diagram = ogl.Diagram()
986  self.SetDiagram(self.diagram)
987  self.diagram.SetCanvas(self)
988 
989  self.SetScrollbars(20, 20, 2000/20, 2000/20)
990 
991  self.Bind(wx.EVT_CHAR, self.OnChar)
992 
993  def OnChar(self, event):
994  """!Key pressed"""
995  kc = event.GetKeyCode()
996  diagram = self.GetDiagram()
997  if kc == wx.WXK_DELETE:
998  self.RemoveSelected()
999 
1000  def RemoveSelected(self):
1001  """!Remove selected shapes"""
1002  self.parent.ModelChanged()
1003 
1004  diagram = self.GetDiagram()
1005  shapes = [shape for shape in diagram.GetShapeList() if shape.Selected()]
1006  self.RemoveShapes(shapes)
1007 
1008  def RemoveShapes(self, shapes):
1009  """!Removes shapes"""
1010  self.parent.ModelChanged()
1011  diagram = self.GetDiagram()
1012  for shape in shapes:
1013  remList, upList = self.parent.GetModel().RemoveItem(shape)
1014  shape.Select(False)
1015  diagram.RemoveShape(shape)
1016  shape.__del__()
1017  for item in remList:
1018  diagram.RemoveShape(item)
1019  item.__del__()
1020 
1021  for item in upList:
1022  item.Update()
1023 
1024  self.Refresh()
1025 
1026  def GetNewShapePos(self):
1027  """!Determine optimal position for newly added object
1028 
1029  @return x,y
1030  """
1031  xNew, yNew = map(lambda x: x / 2, self.GetSize())
1032  diagram = self.GetDiagram()
1033 
1034  for shape in diagram.GetShapeList():
1035  y = shape.GetY()
1036  yBox = shape.GetBoundingBoxMin()[1] / 2
1037  if yBox > 0 and y < yNew + yBox and y > yNew - yBox:
1038  yNew += yBox * 3
1039 
1040  return xNew, yNew
1041 
1042 class ModelEvtHandler(ogl.ShapeEvtHandler):
1043  """!Model event handler class"""
1044  def __init__(self, log, frame):
1045  ogl.ShapeEvtHandler.__init__(self)
1046  self.log = log
1047  self.frame = frame
1048  self.x = self.y = None
1049 
1050  def OnLeftClick(self, x, y, keys = 0, attachment = 0):
1051  """!Left mouse button pressed -> select item & update statusbar"""
1052  shape = self.GetShape()
1053  canvas = shape.GetCanvas()
1054  dc = wx.ClientDC(canvas)
1055  # probably does nothing, removed from wxPython 2.9
1056  # canvas.PrepareDC(dc)
1057 
1058  if hasattr(self.frame, 'defineRelation'):
1059  drel = self.frame.defineRelation
1060  if drel['from'] is None:
1061  drel['from'] = shape
1062  elif drel['to'] is None:
1063  drel['to'] = shape
1064  rel = ModelRelation(parent = self.frame, fromShape = drel['from'],
1065  toShape = drel['to'])
1066  dlg = ModelRelationDialog(parent = self.frame,
1067  shape = rel)
1068  if dlg.IsValid():
1069  ret = dlg.ShowModal()
1070  if ret == wx.ID_OK:
1071  option = dlg.GetOption()
1072  rel.SetName(option)
1073  drel['from'].AddRelation(rel)
1074  drel['to'].AddRelation(rel)
1075  drel['from'].Update()
1076  params = { 'params' : [{ 'name' : option,
1077  'value' : drel['from'].GetValue()}] }
1078  drel['to'].MergeParams(params)
1079  self.frame.AddLine(rel)
1080 
1081  dlg.Destroy()
1082  del self.frame.defineRelation
1083 
1084  # select object
1085  self._onSelectShape(shape)
1086 
1087  if hasattr(shape, "GetLog"):
1088  self.log.SetStatusText(shape.GetLog(), 0)
1089  else:
1090  self.log.SetStatusText('', 0)
1091 
1092  def OnLeftDoubleClick(self, x, y, keys = 0, attachment = 0):
1093  """!Left mouse button pressed (double-click) -> show properties"""
1094  self.OnProperties()
1095 
1096  def OnProperties(self, event = None):
1097  """!Show properties dialog"""
1098  self.frame.ModelChanged()
1099  shape = self.GetShape()
1100  if isinstance(shape, ModelAction):
1101  module = GUI(parent = self.frame, show = True).ParseCommand(shape.GetLog(string = False),
1102  completed = (self.frame.GetOptData, shape, shape.GetParams()))
1103 
1104  elif isinstance(shape, ModelData):
1105  dlg = ModelDataDialog(parent = self.frame, shape = shape)
1106  shape.SetPropDialog(dlg)
1107  dlg.CentreOnParent()
1108  dlg.Show()
1109 
1110  elif isinstance(shape, ModelLoop):
1111  dlg = ModelLoopDialog(parent = self.frame, shape = shape)
1112  dlg.CentreOnParent()
1113  if dlg.ShowModal() == wx.ID_OK:
1114  shape.SetText(dlg.GetCondition())
1115  alist = list()
1116  ids = dlg.GetItems()
1117  for aId in ids['unchecked']:
1118  action = self.frame.GetModel().GetItem(aId)
1119  action.UnSetBlock(shape)
1120  for aId in ids['checked']:
1121  action = self.frame.GetModel().GetItem(aId)
1122  action.SetBlock(shape)
1123  if action:
1124  alist.append(action)
1125  shape.SetItems(alist)
1126  self.frame.DefineLoop(shape)
1127  self.frame.SetStatusText(shape.GetLog(), 0)
1128  self.frame.GetCanvas().Refresh()
1129 
1130  dlg.Destroy()
1131 
1132  elif isinstance(shape, ModelCondition):
1133  dlg = ModelConditionDialog(parent = self.frame, shape = shape)
1134  dlg.CentreOnParent()
1135  if dlg.ShowModal() == wx.ID_OK:
1136  shape.SetText(dlg.GetCondition())
1137  ids = dlg.GetItems()
1138  for b in ids.keys():
1139  alist = list()
1140  for aId in ids[b]['unchecked']:
1141  action = self.frame.GetModel().GetItem(aId)
1142  action.UnSetBlock(shape)
1143  for aId in ids[b]['checked']:
1144  action = self.frame.GetModel().GetItem(aId)
1145  action.SetBlock(shape)
1146  if action:
1147  alist.append(action)
1148  shape.SetItems(alist, branch = b)
1149  self.frame.DefineCondition(shape)
1150  self.frame.GetCanvas().Refresh()
1151 
1152  dlg.Destroy()
1153 
1154  def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
1155  """!Drag shape (begining)"""
1156  self.frame.ModelChanged()
1157  if self._previousHandler:
1158  self._previousHandler.OnBeginDragLeft(x, y, keys, attachment)
1159 
1160  def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
1161  """!Drag shape (end)"""
1162  if self._previousHandler:
1163  self._previousHandler.OnEndDragLeft(x, y, keys, attachment)
1164 
1165  shape = self.GetShape()
1166  if isinstance(shape, ModelLoop):
1167  self.frame.DefineLoop(shape)
1168  elif isinstance(shape, ModelCondition):
1169  self.frame.DefineCondition(shape)
1170 
1171  for mo in shape.GetBlock():
1172  if isinstance(mo, ModelLoop):
1173  self.frame.DefineLoop(mo)
1174  elif isinstance(mo, ModelCondition):
1175  self.frame.DefineCondition(mo)
1176 
1177  def OnEndSize(self, x, y):
1178  """!Resize shape"""
1179  self.frame.ModelChanged()
1180  if self._previousHandler:
1181  self._previousHandler.OnEndSize(x, y)
1182 
1183  def OnRightClick(self, x, y, keys = 0, attachment = 0):
1184  """!Right click -> pop-up menu"""
1185  if not hasattr (self, "popupID"):
1186  self.popupID = dict()
1187  for key in ('remove', 'enable', 'addPoint',
1188  'delPoint', 'intermediate', 'props', 'id'):
1189  self.popupID[key] = wx.NewId()
1190 
1191  # record coordinates
1192  self.x = x
1193  self.y = y
1194 
1195  # select object
1196  shape = self.GetShape()
1197  self._onSelectShape(shape)
1198 
1199  popupMenu = wx.Menu()
1200  popupMenu.Append(self.popupID['remove'], text=_('Remove'))
1201  self.frame.Bind(wx.EVT_MENU, self.OnRemove, id = self.popupID['remove'])
1202  if isinstance(shape, ModelAction) or isinstance(shape, ModelLoop):
1203  if shape.IsEnabled():
1204  popupMenu.Append(self.popupID['enable'], text=_('Disable'))
1205  self.frame.Bind(wx.EVT_MENU, self.OnDisable, id = self.popupID['enable'])
1206  else:
1207  popupMenu.Append(self.popupID['enable'], text=_('Enable'))
1208  self.frame.Bind(wx.EVT_MENU, self.OnEnable, id = self.popupID['enable'])
1209 
1210  if isinstance(shape, ModelRelation):
1211  popupMenu.AppendSeparator()
1212  popupMenu.Append(self.popupID['addPoint'], text=_('Add control point'))
1213  self.frame.Bind(wx.EVT_MENU, self.OnAddPoint, id = self.popupID['addPoint'])
1214  popupMenu.Append(self.popupID['delPoint'], text=_('Remove control point'))
1215  self.frame.Bind(wx.EVT_MENU, self.OnRemovePoint, id = self.popupID['delPoint'])
1216  if len(shape.GetLineControlPoints()) == 2:
1217  popupMenu.Enable(self.popupID['delPoint'], False)
1218 
1219  if isinstance(shape, ModelData) and '@' not in shape.GetValue():
1220  popupMenu.AppendSeparator()
1221  popupMenu.Append(self.popupID['intermediate'], text=_('Intermediate'),
1222  kind = wx.ITEM_CHECK)
1223  if self.GetShape().IsIntermediate():
1224  popupMenu.Check(self.popupID['intermediate'], True)
1225 
1226  self.frame.Bind(wx.EVT_MENU, self.OnIntermediate, id = self.popupID['intermediate'])
1227 
1228  if isinstance(shape, ModelData) or \
1229  isinstance(shape, ModelAction) or \
1230  isinstance(shape, ModelLoop):
1231  popupMenu.AppendSeparator()
1232  popupMenu.Append(self.popupID['props'], text=_('Properties'))
1233  self.frame.Bind(wx.EVT_MENU, self.OnProperties, id = self.popupID['props'])
1234 
1235  self.frame.PopupMenu(popupMenu)
1236  popupMenu.Destroy()
1237 
1238  def OnDisable(self, event):
1239  """!Disable action"""
1240  self._onEnable(False)
1241 
1242  def OnEnable(self, event):
1243  """!Disable action"""
1244  self._onEnable(True)
1245 
1246  def _onEnable(self, enable):
1247  shape = self.GetShape()
1248  shape.Enable(enable)
1249  self.frame.ModelChanged()
1250  self.frame.canvas.Refresh()
1251 
1252  def _onSelectShape(self, shape):
1253  canvas = shape.GetCanvas()
1254  dc = wx.ClientDC(canvas)
1255 
1256  if shape.Selected():
1257  shape.Select(False, dc)
1258  else:
1259  redraw = False
1260  shapeList = canvas.GetDiagram().GetShapeList()
1261  toUnselect = list()
1262 
1263  for s in shapeList:
1264  if s.Selected():
1265  toUnselect.append(s)
1266 
1267  shape.Select(True, dc)
1268 
1269  for s in toUnselect:
1270  s.Select(False, dc)
1271 
1272  canvas.Refresh(False)
1273 
1274  def OnAddPoint(self, event):
1275  """!Add control point"""
1276  shape = self.GetShape()
1277  shape.InsertLineControlPoint(point = wx.RealPoint(self.x, self.y))
1278  shape.ResetShapes()
1279  shape.Select(True)
1280  self.frame.ModelChanged()
1281  self.frame.canvas.Refresh()
1282 
1283  def OnRemovePoint(self, event):
1284  """!Remove control point"""
1285  shape = self.GetShape()
1286  shape.DeleteLineControlPoint()
1287  shape.Select(False)
1288  shape.Select(True)
1289  self.frame.ModelChanged()
1290  self.frame.canvas.Refresh()
1291 
1292  def OnIntermediate(self, event):
1293  """!Mark data as intermediate"""
1294  self.frame.ModelChanged()
1295  shape = self.GetShape()
1296  shape.SetIntermediate(event.IsChecked())
1297  self.frame.canvas.Refresh()
1298 
1299  def OnRemove(self, event):
1300  """!Remove shape
1301  """
1302  self.frame.GetCanvas().RemoveShapes([self.GetShape()])
1303  self.frame.itemPanel.Update()
1304 
1305 class VariablePanel(wx.Panel):
1306  def __init__(self, parent, id = wx.ID_ANY,
1307  **kwargs):
1308  """!Manage model variables panel
1309  """
1310  self.parent = parent
1311 
1312  wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
1313 
1314  self.listBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
1315  label=" %s " % _("List of variables - right-click to delete"))
1316 
1317  self.list = VariableListCtrl(parent = self,
1318  columns = [_("Name"), _("Data type"),
1319  _("Default value"), _("Description")])
1320 
1321  # add new category
1322  self.addBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
1323  label = " %s " % _("Add new variable"))
1324  self.name = wx.TextCtrl(parent = self, id = wx.ID_ANY)
1325  wx.CallAfter(self.name.SetFocus)
1326  self.type = wx.Choice(parent = self, id = wx.ID_ANY,
1327  choices = [_("integer"),
1328  _("float"),
1329  _("string"),
1330  _("raster"),
1331  _("vector"),
1332  _("mapset"),
1333  _("file")])
1334  self.type.SetSelection(2) # string
1335  self.value = wx.TextCtrl(parent = self, id = wx.ID_ANY)
1336  self.desc = wx.TextCtrl(parent = self, id = wx.ID_ANY)
1337 
1338  # buttons
1339  self.btnAdd = wx.Button(parent = self, id = wx.ID_ADD)
1340  self.btnAdd.SetToolTipString(_("Add new variable to the model"))
1341  self.btnAdd.Enable(False)
1342 
1343  # bindings
1344  self.name.Bind(wx.EVT_TEXT, self.OnText)
1345  self.value.Bind(wx.EVT_TEXT, self.OnText)
1346  self.desc.Bind(wx.EVT_TEXT, self.OnText)
1347  self.btnAdd.Bind(wx.EVT_BUTTON, self.OnAdd)
1348 
1349  self._layout()
1350 
1351  def _layout(self):
1352  """!Layout dialog"""
1353  listSizer = wx.StaticBoxSizer(self.listBox, wx.VERTICAL)
1354  listSizer.Add(item = self.list, proportion = 1,
1355  flag = wx.EXPAND)
1356 
1357  addSizer = wx.StaticBoxSizer(self.addBox, wx.VERTICAL)
1358  gridSizer = wx.GridBagSizer(hgap = 5, vgap = 5)
1359  gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
1360  label = "%s:" % _("Name")),
1361  flag = wx.ALIGN_CENTER_VERTICAL,
1362  pos = (0, 0))
1363  gridSizer.Add(item = self.name,
1364  pos = (0, 1),
1365  flag = wx.EXPAND)
1366  gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
1367  label = "%s:" % _("Data type")),
1368  flag = wx.ALIGN_CENTER_VERTICAL,
1369  pos = (0, 2))
1370  gridSizer.Add(item = self.type,
1371  pos = (0, 3))
1372  gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
1373  label = "%s:" % _("Default value")),
1374  flag = wx.ALIGN_CENTER_VERTICAL,
1375  pos = (1, 0))
1376  gridSizer.Add(item = self.value,
1377  pos = (1, 1), span = (1, 3),
1378  flag = wx.EXPAND)
1379  gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
1380  label = "%s:" % _("Description")),
1381  flag = wx.ALIGN_CENTER_VERTICAL,
1382  pos = (2, 0))
1383  gridSizer.Add(item = self.desc,
1384  pos = (2, 1), span = (1, 3),
1385  flag = wx.EXPAND)
1386  gridSizer.AddGrowableCol(1)
1387  addSizer.Add(item = gridSizer,
1388  flag = wx.EXPAND)
1389  addSizer.Add(item = self.btnAdd, proportion = 0,
1390  flag = wx.TOP | wx.ALIGN_RIGHT, border = 5)
1391 
1392  mainSizer = wx.BoxSizer(wx.VERTICAL)
1393  mainSizer.Add(item = listSizer, proportion = 1,
1394  flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)
1395  mainSizer.Add(item = addSizer, proportion = 0,
1396  flag = wx.EXPAND | wx.ALIGN_CENTER |
1397  wx.LEFT | wx.RIGHT | wx.BOTTOM, border = 5)
1398 
1399  self.SetSizer(mainSizer)
1400  mainSizer.Fit(self)
1401 
1402  def OnText(self, event):
1403  """!Text entered"""
1404  if self.name.GetValue():
1405  self.btnAdd.Enable()
1406  else:
1407  self.btnAdd.Enable(False)
1408 
1409  def OnAdd(self, event):
1410  """!Add new variable to the list"""
1411  msg = self.list.Append(self.name.GetValue(),
1412  self.type.GetStringSelection(),
1413  self.value.GetValue(),
1414  self.desc.GetValue())
1415  self.name.SetValue('')
1416  self.name.SetFocus()
1417 
1418  if msg:
1419  GError(parent = self,
1420  message = msg)
1421  else:
1422  self.type.SetSelection(2) # string
1423  self.value.SetValue('')
1424  self.desc.SetValue('')
1425  self.UpdateModelVariables()
1426 
1428  """!Update model variables"""
1429  variables = dict()
1430  for values in self.list.GetData().itervalues():
1431  name = values[0]
1432  variables[name] = { 'type' : str(values[1]) }
1433  if values[2]:
1434  variables[name]['value'] = values[2]
1435  if values[3]:
1436  variables[name]['description'] = values[3]
1437 
1438  self.parent.GetModel().SetVariables(variables)
1439  self.parent.ModelChanged()
1440 
1441  def Update(self):
1442  """!Reload list of variables"""
1443  self.list.OnReload(None)
1444 
1445  def Reset(self):
1446  """!Remove all variables"""
1447  self.list.DeleteAllItems()
1448  self.parent.GetModel().SetVariables([])
1449 
1450 class ItemPanel(wx.Panel):
1451  def __init__(self, parent, id = wx.ID_ANY,
1452  **kwargs):
1453  """!Manage model items
1454  """
1455  self.parent = parent
1456 
1457  wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
1458 
1459  self.listBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
1460  label=" %s " % _("List of items - right-click to delete"))
1461 
1462  self.list = ItemListCtrl(parent = self,
1463  columns = [_("ID"), _("Name"), _("In block"),
1464  _("Command / Condition")])
1465 
1466  self._layout()
1467 
1468  def _layout(self):
1469  """!Layout dialog"""
1470  listSizer = wx.StaticBoxSizer(self.listBox, wx.VERTICAL)
1471  listSizer.Add(item = self.list, proportion = 1,
1472  flag = wx.EXPAND)
1473 
1474  mainSizer = wx.BoxSizer(wx.VERTICAL)
1475  mainSizer.Add(item = listSizer, proportion = 1,
1476  flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)
1477 
1478  self.SetSizer(mainSizer)
1479  mainSizer.Fit(self)
1480 
1481  def Update(self):
1482  """!Reload list of variables"""
1483  self.list.OnReload(None)
1484 
1485 class PythonPanel(wx.Panel):
1486  def __init__(self, parent, id = wx.ID_ANY,
1487  **kwargs):
1488  """!Model as python script
1489  """
1490  self.parent = parent
1491 
1492  wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
1493 
1494  self.filename = None # temp file to run
1495 
1496  self.bodyBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
1497  label = " %s " % _("Python script"))
1498  self.body = PyStc(parent = self, statusbar = self.parent.GetStatusBar())
1499 
1500  self.btnRun = wx.Button(parent = self, id = wx.ID_ANY, label = _("&Run"))
1501  self.btnRun.SetToolTipString(_("Run python script"))
1502  self.Bind(wx.EVT_BUTTON, self.OnRun, self.btnRun)
1503  self.btnSaveAs = wx.Button(parent = self, id = wx.ID_SAVEAS)
1504  self.btnSaveAs.SetToolTipString(_("Save python script to file"))
1505  self.Bind(wx.EVT_BUTTON, self.OnSaveAs, self.btnSaveAs)
1506  self.btnRefresh = wx.Button(parent = self, id = wx.ID_REFRESH)
1507  self.btnRefresh.SetToolTipString(_("Refresh python script based on the model.\n"
1508  "It will discards all local changes."))
1509  self.Bind(wx.EVT_BUTTON, self.OnRefresh, self.btnRefresh)
1510 
1511  self._layout()
1512 
1513  def _layout(self):
1514  sizer = wx.BoxSizer(wx.VERTICAL)
1515  bodySizer = wx.StaticBoxSizer(self.bodyBox, wx.HORIZONTAL)
1516  btnSizer = wx.BoxSizer(wx.HORIZONTAL)
1517 
1518  bodySizer.Add(item = self.body, proportion = 1,
1519  flag = wx.EXPAND | wx.ALL, border = 3)
1520 
1521  btnSizer.Add(item = self.btnRefresh, proportion = 0,
1522  flag = wx.LEFT | wx.RIGHT, border = 5)
1523  btnSizer.AddStretchSpacer()
1524  btnSizer.Add(item = self.btnSaveAs, proportion = 0,
1525  flag = wx.RIGHT | wx.ALIGN_RIGHT, border = 5)
1526  btnSizer.Add(item = self.btnRun, proportion = 0,
1527  flag = wx.RIGHT | wx.ALIGN_RIGHT, border = 5)
1528 
1529  sizer.Add(item = bodySizer, proportion = 1,
1530  flag = wx.EXPAND | wx.ALL, border = 3)
1531  sizer.Add(item = btnSizer, proportion = 0,
1532  flag = wx.EXPAND | wx.ALL, border = 3)
1533 
1534  sizer.Fit(self)
1535  sizer.SetSizeHints(self)
1536  self.SetSizer(sizer)
1537 
1538  def OnRun(self, event):
1539  """!Run Python script"""
1540  self.filename = grass.tempfile()
1541  try:
1542  fd = open(self.filename, "w")
1543  fd.write(self.body.GetText())
1544  except IOError, e:
1545  GError(_("Unable to launch Python script. %s") % e,
1546  parent = self)
1547  return
1548  finally:
1549  fd.close()
1550  mode = stat.S_IMODE(os.lstat(self.filename)[stat.ST_MODE])
1551  os.chmod(self.filename, mode | stat.S_IXUSR)
1552 
1553  self.parent.goutput.RunCmd([fd.name], switchPage = True,
1554  skipInterface = True, onDone = self.OnDone)
1555 
1556  event.Skip()
1557 
1558  def OnDone(self, cmd, returncode):
1559  """!Python script finished"""
1560  grass.try_remove(self.filename)
1561  self.filename = None
1562 
1563  def SaveAs(self, force = False):
1564  """!Save python script to file
1565 
1566  @return filename
1567  """
1568  filename = ''
1569  dlg = wx.FileDialog(parent = self,
1570  message = _("Choose file to save"),
1571  defaultDir = os.getcwd(),
1572  wildcard = _("Python script (*.py)|*.py"),
1573  style = wx.FD_SAVE)
1574 
1575  if dlg.ShowModal() == wx.ID_OK:
1576  filename = dlg.GetPath()
1577 
1578  if not filename:
1579  return ''
1580 
1581  # check for extension
1582  if filename[-3:] != ".py":
1583  filename += ".py"
1584 
1585  if os.path.exists(filename):
1586  dlg = wx.MessageDialog(self, message=_("File <%s> already exists. "
1587  "Do you want to overwrite this file?") % filename,
1588  caption=_("Save file"),
1589  style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
1590  if dlg.ShowModal() == wx.ID_NO:
1591  dlg.Destroy()
1592  return ''
1593 
1594  dlg.Destroy()
1595 
1596  fd = open(filename, "w")
1597  try:
1598  if force:
1599  WritePythonFile(fd, self.parent.GetModel())
1600  else:
1601  fd.write(self.body.GetText())
1602  finally:
1603  fd.close()
1604 
1605  # executable file
1606  os.chmod(filename, stat.S_IRWXU | stat.S_IWUSR)
1607 
1608  return filename
1609 
1610  def OnSaveAs(self, event):
1611  """!Save python script to file"""
1612  self.SaveAs(force = False)
1613  event.Skip()
1614 
1615  def RefreshScript(self):
1616  """!Refresh Python script
1617 
1618  @return True on refresh
1619  @return False script hasn't been updated
1620  """
1621  if self.body.modified:
1622  dlg = wx.MessageDialog(self,
1623  message = _("Python script is locally modificated. "
1624  "Refresh will discard all changes. "
1625  "Do you really want to continue?"),
1626  caption=_("Update"),
1627  style = wx.YES_NO | wx.NO_DEFAULT |
1628  wx.ICON_QUESTION | wx.CENTRE)
1629  ret = dlg.ShowModal()
1630  dlg.Destroy()
1631  if ret == wx.ID_NO:
1632  return False
1633 
1634  fd = tempfile.TemporaryFile()
1635  WritePythonFile(fd, self.parent.GetModel())
1636  fd.seek(0)
1637  self.body.SetText(fd.read())
1638  fd.close()
1639 
1640  self.body.modified = False
1641 
1642  return True
1643 
1644  def OnRefresh(self, event):
1645  """!Refresh Python script"""
1646  if self.RefreshScript():
1647  self.parent.SetStatusText(_('Python script is up-to-date'), 0)
1648  event.Skip()
1649 
1650  def IsModified(self):
1651  """!Check if python script has been modified"""
1652  return self.body.modified
1653 
1654  def IsEmpty(self):
1655  """!Check if python script is empty"""
1656  return len(self.body.GetText()) == 0
1657 
1658 def main():
1659  import gettext
1660  gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode = True)
1661 
1662  app = wx.PySimpleApp()
1663  wx.InitAllImageHandlers()
1664  frame = ModelFrame(parent = None)
1665  if len(sys.argv) > 1:
1666  frame.LoadModelFile(sys.argv[1])
1667  frame.Show()
1668 
1669  app.MainLoop()
1670 
1671 if __name__ == "__main__":
1672  main()
def LoadModelFile
Load model definition stored in GRASS Model XML file (gxm)
def OnLeftDoubleClick
Left mouse button pressed (double-click) -&gt; show properties.
def OnRefresh
Refresh Python script.
def WriteModelFile
Save model to model file, recover original file on error.
wxGUI Graphical Modeler - dialogs
def OnSize
Window resized, save to the model.
def GetValue
Definition: widgets.py:118
wxGUI command interface
def OnCanvasRefresh
Refresh canvas.
def OnRemoveItem
Remove shape.
def OnBeginDragLeft
Drag shape (begining)
def OnAbout
Display About window.
def OnAdd
Add new variable to the list.
def OnRunModel
Run entire model.
def OnVariables
Switch to variables page.
def OnModelNew
Create new model.
def IsEmpty
Check if python script is empty.
wxGUI Graphical Modeler (base classes &amp; read/write)
wxGUI debugging
def OnText
Text entered.
def RemoveSelected
Remove selected shapes.
def OnProperties
Show properties dialog.
Complex list for menu entries for wxGUI.
def OnDeleteData
Delete intermediate data.
def OnHelp
Show help.
Core GUI widgets.
def OnDisable
Disable action.
def AddLine
Add connection between model objects.
def __init__
Manage model variables panel.
def DefineLoop
Define loop with given list of items.
def _layout
Layout dialog.
wxGUI Graphical Modeler - menu data
wxGUI Graphical Modeler - preferences
Various dialogs used in wxGUI.
def OnRemovePoint
Remove control point.
def OnCloseWindow
Close window.
def OnEndSize
Resize shape.
Menu classes for wxGUI.
def _layout
Layout dialog.
def OnPageChanged
Page in notebook changed.
def DefineCondition
Define if-else statement with given list of items.
def ModelChanged
Update window title.
wxGUI Graphical Modeler toolbars classes
Canvas where model is drawn.
def OnCmdRun
Run command.
def GetOptData
Process action data.
def Update
Reload list of variables.
def UpdateModelVariables
Update model variables.
def OnExportImage
Export model to image (default image)
def OnPreferences
Open preferences dialog.
def OnCmdDone
Command done (or aborted)
def OnModelClose
Close model file.
def GetNewShapePos
Determine optimal position for newly added object.
def OnDone
Computation finished.
def OnValidateModel
Validate entire model.
def OnModelProperties
Model properties dialog.
def OnAddAction
Add action to model.
def OnRemove
Remove shape.
def OnEnable
Disable action.
def __init__
Manage model items.
def GetModel
Get model.
def GetCanvas
Get canvas.
def RefreshScript
Refresh Python script.
def GetImageHandlers
Get list of supported image handlers.
def OnModelSave
Save model to file.
def OnDefineCondition
Define new condition in the model.
def OnAddPoint
Add control point.
Model event handler class.
def OnModelOpen
Load model from file.
def _randomShift
Returns random value to shift layout.
def OnRun
Run Python script.
def OnRightClick
Right click -&gt; pop-up menu.
def _addEvent
Add event to item.
def RemoveShapes
Removes shapes.
def OnDefineLoop
Define new loop in the model.
def OnLeftClick
Left mouse button pressed -&gt; select item &amp; update statusbar.
User preferences dialog.
def SaveAs
Save python script to file.
def __init__
Graphical modeler main window.
def Reset
Remove all variables.
def __init__
Model as python script.
def OnDone
Python script finished.
def OnSaveAs
Save python script to file.
Default GUI settings.
def Update
Reload list of variables.
def OnChar
Key pressed.
def OnCmdPrepare
Prepare for running command.
def OnEndDragLeft
Drag shape (end)
def _layout
Do layout.
def IsModified
Check if python script has been modified.
def OnDefineRelation
Define relation between data and action items.
def OnExportPython
Export model to Python script.
Command output widgets.
def OnAddData
Add data item to model.
def OnModelSaveAs
Create model to file as.
def RunCommand
Run GRASS command.
Definition: gcmd.py:625
def OnIntermediate
Mark data as intermediate.