GRASS Programmer's Manual  6.4.4(2014)-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.FD_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  # probably does nothing, removed from wxPython 2.9
1053  # canvas.PrepareDC(dc)
1054 
1055  if hasattr(self.frame, 'defineRelation'):
1056  drel = self.frame.defineRelation
1057  if drel['from'] is None:
1058  drel['from'] = shape
1059  elif drel['to'] is None:
1060  drel['to'] = shape
1061  rel = ModelRelation(parent = self.frame, fromShape = drel['from'],
1062  toShape = drel['to'])
1063  dlg = ModelRelationDialog(parent = self.frame,
1064  shape = rel)
1065  if dlg.IsValid():
1066  ret = dlg.ShowModal()
1067  if ret == wx.ID_OK:
1068  option = dlg.GetOption()
1069  rel.SetName(option)
1070  drel['from'].AddRelation(rel)
1071  drel['to'].AddRelation(rel)
1072  drel['from'].Update()
1073  params = { 'params' : [{ 'name' : option,
1074  'value' : drel['from'].GetValue()}] }
1075  drel['to'].MergeParams(params)
1076  self.frame.AddLine(rel)
1077 
1078  dlg.Destroy()
1079  del self.frame.defineRelation
1080 
1081  # select object
1082  self._onSelectShape(shape)
1083 
1084  if hasattr(shape, "GetLog"):
1085  self.log.SetStatusText(shape.GetLog(), 0)
1086  else:
1087  self.log.SetStatusText('', 0)
1088 
1089  def OnLeftDoubleClick(self, x, y, keys = 0, attachment = 0):
1090  """!Left mouse button pressed (double-click) -> show properties"""
1091  self.OnProperties()
1092 
1093  def OnProperties(self, event = None):
1094  """!Show properties dialog"""
1095  self.frame.ModelChanged()
1096  shape = self.GetShape()
1097  if isinstance(shape, ModelAction):
1098  module = GUI(parent = self.frame, show = True).ParseCommand(shape.GetLog(string = False),
1099  completed = (self.frame.GetOptData, shape, shape.GetParams()))
1100 
1101  elif isinstance(shape, ModelData):
1102  dlg = ModelDataDialog(parent = self.frame, shape = shape)
1103  shape.SetPropDialog(dlg)
1104  dlg.CentreOnParent()
1105  dlg.Show()
1106 
1107  elif isinstance(shape, ModelLoop):
1108  dlg = ModelLoopDialog(parent = self.frame, shape = shape)
1109  dlg.CentreOnParent()
1110  if dlg.ShowModal() == wx.ID_OK:
1111  shape.SetText(dlg.GetCondition())
1112  alist = list()
1113  ids = dlg.GetItems()
1114  for aId in ids['unchecked']:
1115  action = self.frame.GetModel().GetItem(aId)
1116  action.UnSetBlock(shape)
1117  for aId in ids['checked']:
1118  action = self.frame.GetModel().GetItem(aId)
1119  action.SetBlock(shape)
1120  if action:
1121  alist.append(action)
1122  shape.SetItems(alist)
1123  self.frame.DefineLoop(shape)
1124  self.frame.SetStatusText(shape.GetLog(), 0)
1125  self.frame.GetCanvas().Refresh()
1126 
1127  dlg.Destroy()
1128 
1129  elif isinstance(shape, ModelCondition):
1130  dlg = ModelConditionDialog(parent = self.frame, shape = shape)
1131  dlg.CentreOnParent()
1132  if dlg.ShowModal() == wx.ID_OK:
1133  shape.SetText(dlg.GetCondition())
1134  ids = dlg.GetItems()
1135  for b in ids.keys():
1136  alist = list()
1137  for aId in ids[b]['unchecked']:
1138  action = self.frame.GetModel().GetItem(aId)
1139  action.UnSetBlock(shape)
1140  for aId in ids[b]['checked']:
1141  action = self.frame.GetModel().GetItem(aId)
1142  action.SetBlock(shape)
1143  if action:
1144  alist.append(action)
1145  shape.SetItems(alist, branch = b)
1146  self.frame.DefineCondition(shape)
1147  self.frame.GetCanvas().Refresh()
1148 
1149  dlg.Destroy()
1150 
1151  def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
1152  """!Drag shape (begining)"""
1153  self.frame.ModelChanged()
1154  if self._previousHandler:
1155  self._previousHandler.OnBeginDragLeft(x, y, keys, attachment)
1156 
1157  def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
1158  """!Drag shape (end)"""
1159  if self._previousHandler:
1160  self._previousHandler.OnEndDragLeft(x, y, keys, attachment)
1161 
1162  shape = self.GetShape()
1163  if isinstance(shape, ModelLoop):
1164  self.frame.DefineLoop(shape)
1165  elif isinstance(shape, ModelCondition):
1166  self.frame.DefineCondition(shape)
1167 
1168  for mo in shape.GetBlock():
1169  if isinstance(mo, ModelLoop):
1170  self.frame.DefineLoop(mo)
1171  elif isinstance(mo, ModelCondition):
1172  self.frame.DefineCondition(mo)
1173 
1174  def OnEndSize(self, x, y):
1175  """!Resize shape"""
1176  self.frame.ModelChanged()
1177  if self._previousHandler:
1178  self._previousHandler.OnEndSize(x, y)
1179 
1180  def OnRightClick(self, x, y, keys = 0, attachment = 0):
1181  """!Right click -> pop-up menu"""
1182  if not hasattr (self, "popupID"):
1183  self.popupID = dict()
1184  for key in ('remove', 'enable', 'addPoint',
1185  'delPoint', 'intermediate', 'props', 'id'):
1186  self.popupID[key] = wx.NewId()
1187 
1188  # record coordinates
1189  self.x = x
1190  self.y = y
1191 
1192  # select object
1193  shape = self.GetShape()
1194  self._onSelectShape(shape)
1195 
1196  popupMenu = wx.Menu()
1197  popupMenu.Append(self.popupID['remove'], text=_('Remove'))
1198  self.frame.Bind(wx.EVT_MENU, self.OnRemove, id = self.popupID['remove'])
1199  if isinstance(shape, ModelAction) or isinstance(shape, ModelLoop):
1200  if shape.IsEnabled():
1201  popupMenu.Append(self.popupID['enable'], text=_('Disable'))
1202  self.frame.Bind(wx.EVT_MENU, self.OnDisable, id = self.popupID['enable'])
1203  else:
1204  popupMenu.Append(self.popupID['enable'], text=_('Enable'))
1205  self.frame.Bind(wx.EVT_MENU, self.OnEnable, id = self.popupID['enable'])
1206 
1207  if isinstance(shape, ModelRelation):
1208  popupMenu.AppendSeparator()
1209  popupMenu.Append(self.popupID['addPoint'], text=_('Add control point'))
1210  self.frame.Bind(wx.EVT_MENU, self.OnAddPoint, id = self.popupID['addPoint'])
1211  popupMenu.Append(self.popupID['delPoint'], text=_('Remove control point'))
1212  self.frame.Bind(wx.EVT_MENU, self.OnRemovePoint, id = self.popupID['delPoint'])
1213  if len(shape.GetLineControlPoints()) == 2:
1214  popupMenu.Enable(self.popupID['delPoint'], False)
1215 
1216  if isinstance(shape, ModelData) and '@' not in shape.GetValue():
1217  popupMenu.AppendSeparator()
1218  popupMenu.Append(self.popupID['intermediate'], text=_('Intermediate'),
1219  kind = wx.ITEM_CHECK)
1220  if self.GetShape().IsIntermediate():
1221  popupMenu.Check(self.popupID['intermediate'], True)
1222 
1223  self.frame.Bind(wx.EVT_MENU, self.OnIntermediate, id = self.popupID['intermediate'])
1224 
1225  if isinstance(shape, ModelData) or \
1226  isinstance(shape, ModelAction) or \
1227  isinstance(shape, ModelLoop):
1228  popupMenu.AppendSeparator()
1229  popupMenu.Append(self.popupID['props'], text=_('Properties'))
1230  self.frame.Bind(wx.EVT_MENU, self.OnProperties, id = self.popupID['props'])
1231 
1232  self.frame.PopupMenu(popupMenu)
1233  popupMenu.Destroy()
1234 
1235  def OnDisable(self, event):
1236  """!Disable action"""
1237  self._onEnable(False)
1238 
1239  def OnEnable(self, event):
1240  """!Disable action"""
1241  self._onEnable(True)
1242 
1243  def _onEnable(self, enable):
1244  shape = self.GetShape()
1245  shape.Enable(enable)
1246  self.frame.ModelChanged()
1247  self.frame.canvas.Refresh()
1248 
1249  def _onSelectShape(self, shape):
1250  canvas = shape.GetCanvas()
1251  dc = wx.ClientDC(canvas)
1252 
1253  if shape.Selected():
1254  shape.Select(False, dc)
1255  else:
1256  redraw = False
1257  shapeList = canvas.GetDiagram().GetShapeList()
1258  toUnselect = list()
1259 
1260  for s in shapeList:
1261  if s.Selected():
1262  toUnselect.append(s)
1263 
1264  shape.Select(True, dc)
1265 
1266  for s in toUnselect:
1267  s.Select(False, dc)
1268 
1269  canvas.Refresh(False)
1270 
1271  def OnAddPoint(self, event):
1272  """!Add control point"""
1273  shape = self.GetShape()
1274  shape.InsertLineControlPoint(point = wx.RealPoint(self.x, self.y))
1275  shape.ResetShapes()
1276  shape.Select(True)
1277  self.frame.ModelChanged()
1278  self.frame.canvas.Refresh()
1279 
1280  def OnRemovePoint(self, event):
1281  """!Remove control point"""
1282  shape = self.GetShape()
1283  shape.DeleteLineControlPoint()
1284  shape.Select(False)
1285  shape.Select(True)
1286  self.frame.ModelChanged()
1287  self.frame.canvas.Refresh()
1288 
1289  def OnIntermediate(self, event):
1290  """!Mark data as intermediate"""
1291  self.frame.ModelChanged()
1292  shape = self.GetShape()
1293  shape.SetIntermediate(event.IsChecked())
1294  self.frame.canvas.Refresh()
1295 
1296  def OnRemove(self, event):
1297  """!Remove shape
1298  """
1299  self.frame.GetCanvas().RemoveShapes([self.GetShape()])
1300  self.frame.itemPanel.Update()
1301 
1302 class VariablePanel(wx.Panel):
1303  def __init__(self, parent, id = wx.ID_ANY,
1304  **kwargs):
1305  """!Manage model variables panel
1306  """
1307  self.parent = parent
1308 
1309  wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
1310 
1311  self.listBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
1312  label=" %s " % _("List of variables - right-click to delete"))
1313 
1314  self.list = VariableListCtrl(parent = self,
1315  columns = [_("Name"), _("Data type"),
1316  _("Default value"), _("Description")])
1317 
1318  # add new category
1319  self.addBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
1320  label = " %s " % _("Add new variable"))
1321  self.name = wx.TextCtrl(parent = self, id = wx.ID_ANY)
1322  wx.CallAfter(self.name.SetFocus)
1323  self.type = wx.Choice(parent = self, id = wx.ID_ANY,
1324  choices = [_("integer"),
1325  _("float"),
1326  _("string"),
1327  _("raster"),
1328  _("vector"),
1329  _("mapset"),
1330  _("file")])
1331  self.type.SetSelection(2) # string
1332  self.value = wx.TextCtrl(parent = self, id = wx.ID_ANY)
1333  self.desc = wx.TextCtrl(parent = self, id = wx.ID_ANY)
1334 
1335  # buttons
1336  self.btnAdd = wx.Button(parent = self, id = wx.ID_ADD)
1337  self.btnAdd.SetToolTipString(_("Add new variable to the model"))
1338  self.btnAdd.Enable(False)
1339 
1340  # bindings
1341  self.name.Bind(wx.EVT_TEXT, self.OnText)
1342  self.value.Bind(wx.EVT_TEXT, self.OnText)
1343  self.desc.Bind(wx.EVT_TEXT, self.OnText)
1344  self.btnAdd.Bind(wx.EVT_BUTTON, self.OnAdd)
1345 
1346  self._layout()
1347 
1348  def _layout(self):
1349  """!Layout dialog"""
1350  listSizer = wx.StaticBoxSizer(self.listBox, wx.VERTICAL)
1351  listSizer.Add(item = self.list, proportion = 1,
1352  flag = wx.EXPAND)
1353 
1354  addSizer = wx.StaticBoxSizer(self.addBox, wx.VERTICAL)
1355  gridSizer = wx.GridBagSizer(hgap = 5, vgap = 5)
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  gridSizer.AddGrowableCol(1)
1384  addSizer.Add(item = gridSizer,
1385  flag = wx.EXPAND)
1386  addSizer.Add(item = self.btnAdd, proportion = 0,
1387  flag = wx.TOP | wx.ALIGN_RIGHT, border = 5)
1388 
1389  mainSizer = wx.BoxSizer(wx.VERTICAL)
1390  mainSizer.Add(item = listSizer, proportion = 1,
1391  flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)
1392  mainSizer.Add(item = addSizer, proportion = 0,
1393  flag = wx.EXPAND | wx.ALIGN_CENTER |
1394  wx.LEFT | wx.RIGHT | wx.BOTTOM, border = 5)
1395 
1396  self.SetSizer(mainSizer)
1397  mainSizer.Fit(self)
1398 
1399  def OnText(self, event):
1400  """!Text entered"""
1401  if self.name.GetValue():
1402  self.btnAdd.Enable()
1403  else:
1404  self.btnAdd.Enable(False)
1405 
1406  def OnAdd(self, event):
1407  """!Add new variable to the list"""
1408  msg = self.list.Append(self.name.GetValue(),
1409  self.type.GetStringSelection(),
1410  self.value.GetValue(),
1411  self.desc.GetValue())
1412  self.name.SetValue('')
1413  self.name.SetFocus()
1414 
1415  if msg:
1416  GError(parent = self,
1417  message = msg)
1418  else:
1419  self.type.SetSelection(2) # string
1420  self.value.SetValue('')
1421  self.desc.SetValue('')
1422  self.UpdateModelVariables()
1423 
1425  """!Update model variables"""
1426  variables = dict()
1427  for values in self.list.GetData().itervalues():
1428  name = values[0]
1429  variables[name] = { 'type' : str(values[1]) }
1430  if values[2]:
1431  variables[name]['value'] = values[2]
1432  if values[3]:
1433  variables[name]['description'] = values[3]
1434 
1435  self.parent.GetModel().SetVariables(variables)
1436  self.parent.ModelChanged()
1437 
1438  def Update(self):
1439  """!Reload list of variables"""
1440  self.list.OnReload(None)
1441 
1442  def Reset(self):
1443  """!Remove all variables"""
1444  self.list.DeleteAllItems()
1445  self.parent.GetModel().SetVariables([])
1446 
1447 class ItemPanel(wx.Panel):
1448  def __init__(self, parent, id = wx.ID_ANY,
1449  **kwargs):
1450  """!Manage model items
1451  """
1452  self.parent = parent
1453 
1454  wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
1455 
1456  self.listBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
1457  label=" %s " % _("List of items - right-click to delete"))
1458 
1459  self.list = ItemListCtrl(parent = self,
1460  columns = [_("ID"), _("Name"), _("In block"),
1461  _("Command / Condition")])
1462 
1463  self._layout()
1464 
1465  def _layout(self):
1466  """!Layout dialog"""
1467  listSizer = wx.StaticBoxSizer(self.listBox, wx.VERTICAL)
1468  listSizer.Add(item = self.list, proportion = 1,
1469  flag = wx.EXPAND)
1470 
1471  mainSizer = wx.BoxSizer(wx.VERTICAL)
1472  mainSizer.Add(item = listSizer, proportion = 1,
1473  flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)
1474 
1475  self.SetSizer(mainSizer)
1476  mainSizer.Fit(self)
1477 
1478  def Update(self):
1479  """!Reload list of variables"""
1480  self.list.OnReload(None)
1481 
1482 class PythonPanel(wx.Panel):
1483  def __init__(self, parent, id = wx.ID_ANY,
1484  **kwargs):
1485  """!Model as python script
1486  """
1487  self.parent = parent
1488 
1489  wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
1490 
1491  self.filename = None # temp file to run
1492 
1493  self.bodyBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
1494  label = " %s " % _("Python script"))
1495  self.body = PyStc(parent = self, statusbar = self.parent.GetStatusBar())
1496 
1497  self.btnRun = wx.Button(parent = self, id = wx.ID_ANY, label = _("&Run"))
1498  self.btnRun.SetToolTipString(_("Run python script"))
1499  self.Bind(wx.EVT_BUTTON, self.OnRun, self.btnRun)
1500  self.btnSaveAs = wx.Button(parent = self, id = wx.ID_SAVEAS)
1501  self.btnSaveAs.SetToolTipString(_("Save python script to file"))
1502  self.Bind(wx.EVT_BUTTON, self.OnSaveAs, self.btnSaveAs)
1503  self.btnRefresh = wx.Button(parent = self, id = wx.ID_REFRESH)
1504  self.btnRefresh.SetToolTipString(_("Refresh python script based on the model.\n"
1505  "It will discards all local changes."))
1506  self.Bind(wx.EVT_BUTTON, self.OnRefresh, self.btnRefresh)
1507 
1508  self._layout()
1509 
1510  def _layout(self):
1511  sizer = wx.BoxSizer(wx.VERTICAL)
1512  bodySizer = wx.StaticBoxSizer(self.bodyBox, wx.HORIZONTAL)
1513  btnSizer = wx.BoxSizer(wx.HORIZONTAL)
1514 
1515  bodySizer.Add(item = self.body, proportion = 1,
1516  flag = wx.EXPAND | wx.ALL, border = 3)
1517 
1518  btnSizer.Add(item = self.btnRefresh, proportion = 0,
1519  flag = wx.LEFT | wx.RIGHT, border = 5)
1520  btnSizer.AddStretchSpacer()
1521  btnSizer.Add(item = self.btnSaveAs, proportion = 0,
1522  flag = wx.RIGHT | wx.ALIGN_RIGHT, border = 5)
1523  btnSizer.Add(item = self.btnRun, proportion = 0,
1524  flag = wx.RIGHT | wx.ALIGN_RIGHT, border = 5)
1525 
1526  sizer.Add(item = bodySizer, proportion = 1,
1527  flag = wx.EXPAND | wx.ALL, border = 3)
1528  sizer.Add(item = btnSizer, proportion = 0,
1529  flag = wx.EXPAND | wx.ALL, border = 3)
1530 
1531  sizer.Fit(self)
1532  sizer.SetSizeHints(self)
1533  self.SetSizer(sizer)
1534 
1535  def OnRun(self, event):
1536  """!Run Python script"""
1537  self.filename = grass.tempfile()
1538  try:
1539  fd = open(self.filename, "w")
1540  fd.write(self.body.GetText())
1541  except IOError, e:
1542  GError(_("Unable to launch Python script. %s") % e,
1543  parent = self)
1544  return
1545  finally:
1546  fd.close()
1547  mode = stat.S_IMODE(os.lstat(self.filename)[stat.ST_MODE])
1548  os.chmod(self.filename, mode | stat.S_IXUSR)
1549 
1550  self.parent.goutput.RunCmd([fd.name], switchPage = True,
1551  skipInterface = True, onDone = self.OnDone)
1552 
1553  event.Skip()
1554 
1555  def OnDone(self, cmd, returncode):
1556  """!Python script finished"""
1557  grass.try_remove(self.filename)
1558  self.filename = None
1559 
1560  def SaveAs(self, force = False):
1561  """!Save python script to file
1562 
1563  @return filename
1564  """
1565  filename = ''
1566  dlg = wx.FileDialog(parent = self,
1567  message = _("Choose file to save"),
1568  defaultDir = os.getcwd(),
1569  wildcard = _("Python script (*.py)|*.py"),
1570  style = wx.FD_SAVE)
1571 
1572  if dlg.ShowModal() == wx.ID_OK:
1573  filename = dlg.GetPath()
1574 
1575  if not filename:
1576  return ''
1577 
1578  # check for extension
1579  if filename[-3:] != ".py":
1580  filename += ".py"
1581 
1582  if os.path.exists(filename):
1583  dlg = wx.MessageDialog(self, message=_("File <%s> already exists. "
1584  "Do you want to overwrite this file?") % filename,
1585  caption=_("Save file"),
1586  style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
1587  if dlg.ShowModal() == wx.ID_NO:
1588  dlg.Destroy()
1589  return ''
1590 
1591  dlg.Destroy()
1592 
1593  fd = open(filename, "w")
1594  try:
1595  if force:
1596  WritePythonFile(fd, self.parent.GetModel())
1597  else:
1598  fd.write(self.body.GetText())
1599  finally:
1600  fd.close()
1601 
1602  # executable file
1603  os.chmod(filename, stat.S_IRWXU | stat.S_IWUSR)
1604 
1605  return filename
1606 
1607  def OnSaveAs(self, event):
1608  """!Save python script to file"""
1609  self.SaveAs(force = False)
1610  event.Skip()
1611 
1612  def RefreshScript(self):
1613  """!Refresh Python script
1614 
1615  @return True on refresh
1616  @return False script hasn't been updated
1617  """
1618  if self.body.modified:
1619  dlg = wx.MessageDialog(self,
1620  message = _("Python script is locally modificated. "
1621  "Refresh will discard all changes. "
1622  "Do you really want to continue?"),
1623  caption=_("Update"),
1624  style = wx.YES_NO | wx.NO_DEFAULT |
1625  wx.ICON_QUESTION | wx.CENTRE)
1626  ret = dlg.ShowModal()
1627  dlg.Destroy()
1628  if ret == wx.ID_NO:
1629  return False
1630 
1631  fd = tempfile.TemporaryFile()
1632  WritePythonFile(fd, self.parent.GetModel())
1633  fd.seek(0)
1634  self.body.SetText(fd.read())
1635  fd.close()
1636 
1637  self.body.modified = False
1638 
1639  return True
1640 
1641  def OnRefresh(self, event):
1642  """!Refresh Python script"""
1643  if self.RefreshScript():
1644  self.parent.SetStatusText(_('Python script is up-to-date'), 0)
1645  event.Skip()
1646 
1647  def IsModified(self):
1648  """!Check if python script has been modified"""
1649  return self.body.modified
1650 
1651  def IsEmpty(self):
1652  """!Check if python script is empty"""
1653  return len(self.body.GetText()) == 0
1654 
1655 def main():
1656  import gettext
1657  gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode = True)
1658 
1659  app = wx.PySimpleApp()
1660  if not globalvar.CheckWxVersion([2, 9]):
1661  wx.InitAllImageHandlers()
1662  frame = ModelFrame(parent = None)
1663  if len(sys.argv) > 1:
1664  frame.LoadModelFile(sys.argv[1])
1665  frame.Show()
1666 
1667  app.MainLoop()
1668 
1669 if __name__ == "__main__":
1670  main()
def LoadModelFile
Load model definition stored in GRASS Model XML file (gxm)
def OnLeftDoubleClick
Left mouse button pressed (double-click) -> 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 CheckWxVersion
Check wx version.
Definition: globalvar.py:33
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 & 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 -> 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 -> select item & 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.