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()
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 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.
int
Definition: y.tab.c:1344
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:633
def OnIntermediate
Mark data as intermediate.