GRASS Programmer's Manual  6.4.4(2014)-r
forms.py
Go to the documentation of this file.
1 """
2 @package gui_core.forms
3 
4 @brief Construct simple wxPython GUI from a GRASS command interface
5 description.
6 
7 Classes:
8  - forms::UpdateThread
9  - forms::UpdateQThread
10  - forms::TaskFrame
11  - forms::CmdPanel
12  - forms::GUI
13  - forms::GrassGUIApp
14 
15 This program is just a coarse approach to automatically build a GUI
16 from a xml-based GRASS user interface description.
17 
18 You need to have Python 2.4, wxPython 2.8 and python-xml.
19 
20 The XML stream is read from executing the command given in the
21 command line, thus you may call it for instance this way:
22 
23 python <this file.py> r.basins.fill
24 
25 Or you set an alias or wrap the call up in a nice shell script, GUI
26 environment ... please contribute your idea.
27 
28 Updated to wxPython 2.8 syntax and contrib widgets. Methods added to
29 make it callable by gui. Method added to automatically re-run with
30 pythonw on a Mac.
31 
32 @todo
33  - verify option value types
34 
35 Copyright(C) 2000-2012 by the GRASS Development Team
36 
37 This program is free software under the GPL(>=v2) Read the file
38 COPYING coming with GRASS for details.
39 
40 @author Jan-Oliver Wagner <jan@intevation.de>
41 @author Bernhard Reiter <bernhard@intevation.de>
42 @author Michael Barton, Arizona State University
43 @author Daniel Calvelo <dca.gis@gmail.com>
44 @author Martin Landa <landa.martin@gmail.com>
45 @author Luca Delucchi <lucadeluge@gmail.com>
46 """
47 
48 import sys
49 import string
50 import textwrap
51 import os
52 import time
53 import copy
54 import locale
55 from threading import Thread
56 import Queue
57 import re
58 
59 gisbase = os.getenv("GISBASE")
60 if gisbase is None:
61  print >>sys.stderr, "We don't seem to be properly installed, or we are being run outside GRASS. Expect glitches."
62  gisbase = os.path.join(os.path.dirname(sys.argv[0]), os.path.pardir)
63  wxbase = gisbase
64 else:
65  wxbase = os.path.join(gisbase, 'etc', 'wxpython')
66 
67 sys.path.append(wxbase)
68 
69 from core import globalvar
70 import wx
71 try:
72  import wx.lib.agw.flatnotebook as FN
73 except ImportError:
74  import wx.lib.flatnotebook as FN
75 import wx.lib.colourselect as csel
76 import wx.lib.filebrowsebutton as filebrowse
77 from wx.lib.newevent import NewEvent
78 
79 try:
80  import xml.etree.ElementTree as etree
81 except ImportError:
82  import elementtree.ElementTree as etree # Python <= 2.4
83 
84 from grass.script import core as grass
85 from grass.script import task as gtask
86 
87 from gui_core.widgets import StaticWrapText, ScrolledPanel
88 from gui_core.ghelp import HelpPanel
89 from gui_core import gselect
90 from core import gcmd
91 from core import utils
92 from core.settings import UserSettings
93 from gui_core.widgets import FloatValidator, GNotebook
94 
95 wxUpdateDialog, EVT_DIALOG_UPDATE = NewEvent()
96 
97 # From lib/gis/col_str.c, except purple which is mentioned
98 # there but not given RGB values
99 str2rgb = {'aqua': (100, 128, 255),
100  'black': (0, 0, 0),
101  'blue': (0, 0, 255),
102  'brown': (180, 77, 25),
103  'cyan': (0, 255, 255),
104  'gray': (128, 128, 128),
105  'green': (0, 255, 0),
106  'grey': (128, 128, 128),
107  'indigo': (0, 128, 255),
108  'magenta': (255, 0, 255),
109  'orange': (255, 128, 0),
110  'purple': (128, 0, 128),
111  'red': (255, 0, 0),
112  'violet': (128, 0, 255),
113  'white': (255, 255, 255),
114  'yellow': (255, 255, 0)}
115 rgb2str = {}
116 for (s,r) in str2rgb.items():
117  rgb2str[ r ] = s
118 
119 """!Hide some options in the GUI"""
120 _blackList = { 'enabled' : False,
121  'items' : { 'd.legend' : { 'flags' : ['m'] } }
122  }
123 
124 def color_resolve(color):
125  if len(color) > 0 and color[0] in "0123456789":
126  rgb = tuple(map(int, color.split(':')))
127  label = color
128  else:
129  # Convert color names to RGB
130  try:
131  rgb = str2rgb[ color ]
132  label = color
133  except KeyError:
134  rgb = (200,200,200)
135  label = _('Select Color')
136  return (rgb, label)
137 
138 def text_beautify(someString , width = 70):
139  """
140  Make really long texts shorter, clean up whitespace and
141  remove trailing punctuation.
142  """
143  if width > 0:
144  return escape_ampersand(string.strip(
145  os.linesep.join(textwrap.wrap(utils.normalize_whitespace(someString), width)),
146  ".,;:"))
147  else:
148  return escape_ampersand(string.strip(utils.normalize_whitespace(someString), ".,;:"))
149 
151  """!Escapes ampersands with additional ampersand for GUI"""
152  return string.replace(text, "&", "&&")
153 
154 class UpdateThread(Thread):
155  """!Update dialog widgets in the thread"""
156  def __init__(self, parent, event, eventId, task):
157  Thread.__init__(self)
158 
159  self.parent = parent
160  self.event = event
161  self.eventId = eventId
162  self.task = task
163  self.setDaemon(True)
164 
165  # list of functions which updates the dialog
166  self.data = {}
167 
168  def run(self):
169  # get widget id
170  if not self.eventId:
171  for p in self.task.params:
172  if p.get('gisprompt', False) == False:
173  continue
174  prompt = p.get('element', '')
175  if prompt == 'vector':
176  name = p.get('name', '')
177  if name in ('map', 'input'):
178  self.eventId = p['wxId'][0]
179  if self.eventId is None:
180  return
181 
182  p = self.task.get_param(self.eventId, element = 'wxId', raiseError = False)
183  if not p or 'wxId-bind' not in p:
184  return
185 
186  # get widget prompt
187  pType = p.get('prompt', '')
188  if not pType:
189  return
190 
191  # check for map/input parameter
192  pMap = self.task.get_param('map', raiseError = False)
193 
194  if not pMap:
195  pMap = self.task.get_param('input', raiseError = False)
196 
197  if pMap:
198  map = pMap.get('value', '')
199  else:
200  map = None
201 
202  # avoid running db.describe several times
203  cparams = dict()
204  cparams[map] = { 'dbInfo' : None,
205  'layers' : None, }
206 
207  # update reference widgets
208  for uid in p['wxId-bind']:
209  win = self.parent.FindWindowById(uid)
210  if not win:
211  continue
212 
213  name = win.GetName()
214  pBind = self.task.get_param(uid, element = 'wxId', raiseError = False)
215  if pBind:
216  pBind['value'] = ''
217 
218  if name == 'LayerSelect':
219  if map in cparams and not cparams[map]['layers']:
220  win.InsertLayers(vector = map)
221  cparams[map]['layers'] = win.GetItems()
222 
223  elif name == 'TableSelect':
224  pDriver = self.task.get_param('dbdriver', element='prompt', raiseError=False)
225  driver = db = None
226  if pDriver:
227  driver = pDriver['value']
228  pDb = self.task.get_param('dbname', element='prompt', raiseError=False)
229  if pDb:
230  db = pDb['value']
231 
232  self.data[win.InsertTables] = { 'driver' : driver,
233  'database' : db }
234 
235  elif name == 'ColumnSelect':
236  pLayer = self.task.get_param('layer', element='element', raiseError=False)
237  if pLayer:
238  if pLayer.get('value', '') != '':
239  layer = pLayer.get('value', '')
240  else:
241  layer = pLayer.get('default', '')
242  else:
243  layer = 1
244 
245  if map:
246  if map in cparams:
247  if not cparams[map]['dbInfo']:
248  cparams[map]['dbInfo'] = gselect.VectorDBInfo(map)
249  self.data[win.InsertColumns] = { 'vector' : map, 'layer' : layer,
250  'dbInfo' : cparams[map]['dbInfo'] }
251  else: # table
252  driver = db = None
253  pDriver = self.task.get_param('dbdriver', element='prompt', raiseError=False)
254  if pDriver:
255  driver = pDriver.get('value', None)
256  pDb = self.task.get_param('dbname', element='prompt', raiseError=False)
257  if pDb:
258  db = pDb.get('value', None)
259  pTable = self.task.get_param('dbtable', element='element', raiseError=False)
260  if pTable and \
261  pTable.get('value', '') != '':
262  if driver and db:
263  self.data[win.InsertTableColumns] = { 'table' : pTable.get('value'),
264  'driver' : driver,
265  'database' : db }
266  else:
267  self.data[win.InsertTableColumns] = { 'table' : pTable.get('value') }
268 
269  elif name == 'SubGroupSelect':
270  self.data[win.Insert] = { 'group' : p.get('value', '')}
271 
272  elif name == 'LocationSelect':
273  pDbase = self.task.get_param('dbase', element = 'element', raiseError = False)
274  if pDbase:
275  self.data[win.UpdateItems] = { 'dbase' : pDbase.get('value', '')}
276 
277  elif name == 'MapsetSelect':
278  pDbase = self.task.get_param('dbase', element = 'element', raiseError = False)
279  pLocation = self.task.get_param('location', element = 'element', raiseError = False)
280  if pDbase and pLocation:
281  self.data[win.UpdateItems] = { 'dbase' : pDbase.get('value', ''),
282  'location' : pLocation.get('value', '')}
283 
284  elif name == 'ProjSelect':
285  pDbase = self.task.get_param('dbase', element = 'element', raiseError = False)
286  pLocation = self.task.get_param('location', element = 'element', raiseError = False)
287  pMapset = self.task.get_param('mapset', element = 'element', raiseError = False)
288  if pDbase and pLocation and pMapset:
289  self.data[win.UpdateItems] = { 'dbase' : pDbase.get('value', ''),
290  'location' : pLocation.get('value', ''),
291  'mapset' : pMapset.get('value', '')}
292 
293 def UpdateDialog(parent, event, eventId, task):
294  return UpdateThread(parent, event, eventId, task)
295 
296 class UpdateQThread(Thread):
297  """!Update dialog widgets in the thread"""
298  requestId = 0
299  def __init__(self, parent, requestQ, resultQ, **kwds):
300  Thread.__init__(self, **kwds)
301 
302  self.parent = parent # CmdPanel
303  self.setDaemon(True)
304 
305  self.requestQ = requestQ
306  self.resultQ = resultQ
307 
308  self.start()
309 
310  def Update(self, callable, *args, **kwds):
311  UpdateQThread.requestId += 1
312 
313  self.request = None
314  self.requestQ.put((UpdateQThread.requestId, callable, args, kwds))
315 
316  return UpdateQThread.requestId
317 
318  def run(self):
319  while True:
320  requestId, callable, args, kwds = self.requestQ.get()
321 
322  requestTime = time.time()
323 
324  self.request = callable(*args, **kwds)
325 
326  self.resultQ.put((requestId, self.request.run()))
327 
328  if self.request:
329  event = wxUpdateDialog(data = self.request.data)
330  wx.PostEvent(self.parent, event)
331 
332 class TaskFrame(wx.Frame):
333  """!This is the Frame containing the dialog for options input.
334 
335  The dialog is organized in a notebook according to the guisections
336  defined by each GRASS command.
337 
338  If run with a parent, it may Apply, Ok or Cancel; the latter two
339  close the dialog. The former two trigger a callback.
340 
341  If run standalone, it will allow execution of the command.
342 
343  The command is checked and sent to the clipboard when clicking
344  'Copy'.
345  """
346  def __init__(self, parent, ID, task_description,
347  get_dcmd = None, layer = None):
348  self.get_dcmd = get_dcmd
349  self.layer = layer
350  self.task = task_description
351  self.parent = parent # LayerTree | Modeler | None | ...
352  if parent and parent.GetName() == 'Modeler':
353  self.modeler = self.parent
354  else:
355  self.modeler = None
356 
357  # module name + keywords
358  if self.task.name.split('.')[-1] in ('py', 'sh'):
359  title = str(self.task.name.rsplit('.',1)[0])
360  else:
361  title = self.task.name
362  try:
363  if self.task.keywords != ['']:
364  title += " [" + ', '.join(self.task.keywords) + "]"
365  except ValueError:
366  pass
367 
368  wx.Frame.__init__(self, parent = parent, id = ID, title = title,
369  pos = wx.DefaultPosition, style = wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL,
370  name = "MainFrame")
371 
372  self.locale = wx.Locale(language = wx.LANGUAGE_DEFAULT)
373 
374  self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
375 
376  # statusbar
377  self.CreateStatusBar()
378 
379  # icon
380  self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass_dialog.ico'), wx.BITMAP_TYPE_ICO))
381 
382  guisizer = wx.BoxSizer(wx.VERTICAL)
383 
384  # set apropriate output window
385  if self.parent:
386  self.standalone = False
387  else:
388  self.standalone = True
389 
390  # logo + description
391  topsizer = wx.BoxSizer(wx.HORIZONTAL)
392 
393  # GRASS logo
394  self.logo = wx.StaticBitmap(parent = self.panel,
395  bitmap = wx.Bitmap(name = os.path.join(globalvar.ETCIMGDIR,
396  'grass_form.png'),
397  type = wx.BITMAP_TYPE_PNG))
398  topsizer.Add(item = self.logo, proportion = 0, border = 3,
399  flag = wx.ALL | wx.ALIGN_CENTER_VERTICAL)
400 
401  # add module description
402  if self.task.label:
403  module_desc = self.task.label + ' ' + self.task.description
404  else:
405  module_desc = self.task.description
406  self.description = StaticWrapText(parent = self.panel,
407  label = module_desc)
408  topsizer.Add(item = self.description, proportion = 1, border = 5,
409  flag = wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND)
410 
411  guisizer.Add(item = topsizer, proportion = 0, flag = wx.EXPAND)
412 
413  self.panel.SetSizerAndFit(guisizer)
414  self.Layout()
415 
416  # notebooks
417  self.notebookpanel = CmdPanel(parent = self.panel, task = self.task,
418  frame = self)
419  self.goutput = self.notebookpanel.goutput
420  self.notebookpanel.OnUpdateValues = self.updateValuesHook
421  guisizer.Add(item = self.notebookpanel, proportion = 1, flag = wx.EXPAND)
422 
423  # status bar
424  status_text = _("Enter parameters for '") + self.task.name + "'"
425  try:
426  self.task.get_cmd()
427  self.updateValuesHook()
428  except ValueError:
429  self.SetStatusText(status_text)
430 
431  # buttons
432  btnsizer = wx.BoxSizer(orient = wx.HORIZONTAL)
433  # cancel
434  self.btn_cancel = wx.Button(parent = self.panel, id = wx.ID_CLOSE)
435  self.btn_cancel.SetToolTipString(_("Close this window without executing the command (Ctrl+Q)"))
436  btnsizer.Add(item = self.btn_cancel, proportion = 0, flag = wx.ALL | wx.ALIGN_CENTER, border = 10)
437  self.btn_cancel.Bind(wx.EVT_BUTTON, self.OnCancel)
438 
439  if self.get_dcmd is not None: # A callback has been set up
440  btn_apply = wx.Button(parent = self.panel, id = wx.ID_APPLY)
441  btn_ok = wx.Button(parent = self.panel, id = wx.ID_OK)
442  btn_ok.SetDefault()
443 
444  btnsizer.Add(item = btn_apply, proportion = 0,
445  flag = wx.ALL | wx.ALIGN_CENTER,
446  border = 10)
447  btnsizer.Add(item = btn_ok, proportion = 0,
448  flag = wx.ALL | wx.ALIGN_CENTER,
449  border = 10)
450 
451  btn_apply.Bind(wx.EVT_BUTTON, self.OnApply)
452  btn_ok.Bind(wx.EVT_BUTTON, self.OnOK)
453  else: # We're standalone
454  # run
455  self.btn_run = wx.Button(parent = self.panel, id = wx.ID_OK, label = _("&Run"))
456  self.btn_run.SetToolTipString(_("Run the command (Ctrl+R)"))
457  self.btn_run.SetDefault()
458  self.btn_run.SetForegroundColour(wx.Colour(35, 142, 35))
459 
460  # copy
461  self.btn_clipboard = wx.Button(parent = self.panel, id = wx.ID_COPY)
462  self.btn_clipboard.SetToolTipString(_("Copy the current command string to the clipboard (Ctrl+C)"))
463 
464  btnsizer.Add(item = self.btn_run, proportion = 0,
465  flag = wx.ALL | wx.ALIGN_CENTER,
466  border = 10)
467 
468  btnsizer.Add(item = self.btn_clipboard, proportion = 0,
469  flag = wx.ALL | wx.ALIGN_CENTER,
470  border = 10)
471 
472  self.btn_run.Bind(wx.EVT_BUTTON, self.OnRun)
473  self.btn_clipboard.Bind(wx.EVT_BUTTON, self.OnCopy)
474  # help
475  self.btn_help = wx.Button(parent = self.panel, id = wx.ID_HELP)
476  self.btn_help.SetToolTipString(_("Show manual page of the command (Ctrl+H)"))
477  self.btn_help.Bind(wx.EVT_BUTTON, self.OnHelp)
478  if self.notebookpanel.notebook.GetPageIndexByName('manual') < 0:
479  self.btn_help.Hide()
480 
481  # add help button
482  btnsizer.Add(item = self.btn_help, proportion = 0, flag = wx.ALL | wx.ALIGN_CENTER, border = 10)
483 
484  guisizer.Add(item = btnsizer, proportion = 0, flag = wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT,
485  border = 30)
486 
487  if self.parent and not self.modeler:
488  addLayer = False
489  for p in self.task.params:
490  if p.get('age', 'old') == 'new' and \
491  p.get('prompt', '') in ('raster', 'vector', '3d-raster'):
492  addLayer = True
493 
494  if addLayer:
495  # add newly created map into layer tree
496  self.addbox = wx.CheckBox(parent = self.panel,
497  label = _('Add created map(s) into layer tree'), style = wx.NO_BORDER)
498  self.addbox.SetValue(UserSettings.Get(group = 'cmd', key = 'addNewLayer', subkey = 'enabled'))
499  guisizer.Add(item = self.addbox, proportion = 0,
500  flag = wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM,
501  border = 5)
502 
503  hasNew = False
504  for p in self.task.params:
505  if p.get('age', 'old') == 'new':
506  hasNew = True
507  break
508 
509  if self.get_dcmd is None and hasNew:
510  # close dialog when command is terminated
511  self.closebox = wx.CheckBox(parent = self.panel,
512  label = _('Close dialog on finish'), style = wx.NO_BORDER)
513  self.closebox.SetValue(UserSettings.Get(group = 'cmd', key = 'closeDlg', subkey = 'enabled'))
514  self.closebox.SetToolTipString(_("Close dialog when command is successfully finished. "
515  "Change this settings in Preferences dialog ('Command' tab)."))
516  guisizer.Add(item = self.closebox, proportion = 0,
517  flag = wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM,
518  border = 5)
519 
520  self.Bind(wx.EVT_CLOSE, self.OnCancel)
521  self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
522 
523  # do layout
524  # called automatically by SetSizer()
525  self.panel.SetAutoLayout(True)
526  self.panel.SetSizerAndFit(guisizer)
527 
528  sizeFrame = self.GetBestSize()
529  self.SetMinSize(sizeFrame)
530  self.SetSize(wx.Size(sizeFrame[0], sizeFrame[1] + 0.33 * max(self.notebookpanel.panelMinHeight,
531  self.notebookpanel.constrained_size[1])))
532 
533  # thread to update dialog
534  # create queues
535  self.requestQ = Queue.Queue()
536  self.resultQ = Queue.Queue()
538 
539  self.Layout()
540 
541  # keep initial window size limited for small screens
542  width, height = self.GetSizeTuple()
543  self.SetSize(wx.Size(min(width, 650),
544  min(height, 500)))
545 
546  # fix goutput's pane size (required for Mac OSX)
547  if self.goutput:
548  self.goutput.SetSashPosition(int(self.GetSize()[1] * .75))
549 
550  def updateValuesHook(self, event = None):
551  """!Update status bar data"""
552  self.SetStatusText(' '.join(self.notebookpanel.createCmd(ignoreErrors = True)))
553  if event:
554  event.Skip()
555 
556  def OnKeyUp(self, event):
557  """!Key released (check hot-keys)"""
558  try:
559  kc = chr(event.GetKeyCode())
560  except ValueError:
561  event.Skip()
562  return
563 
564  if not event.ControlDown():
565  event.Skip()
566  return
567 
568  if kc == 'Q':
569  self.OnCancel(None)
570  elif kc == 'S':
571  self.OnAbort(None)
572  elif kc == 'H':
573  self.OnHelp(None)
574  elif kc == 'R':
575  self.OnRun(None)
576  elif kc == 'C':
577  self.OnCopy(None)
578 
579  event.Skip()
580 
581  def OnDone(self, cmd, returncode):
582  """!This function is launched from OnRun() when command is
583  finished
584 
585  @param returncode command's return code (0 for success)
586  """
587  if not self.parent or returncode != 0:
588  return
589  if self.parent.GetName() not in ('LayerTree', 'LayerManager'):
590  return
591 
592  if self.parent.GetName() == 'LayerTree':
593  display = self.parent.GetMapDisplay()
594  else: # Layer Manager
595  display = None
596  tree = self.parent.GetLayerTree()
597  if tree:
598  display = tree.GetMapDisplay()
599 
600  if not display or not display.IsAutoRendered():
601  return
602 
603  mapLayers = map(lambda x: x.GetName(),
604  display.GetMap().GetListOfLayers(l_type = 'raster') +
605  display.GetMap().GetListOfLayers(l_type = 'vector'))
606 
607  task = GUI(show = None).ParseCommand(cmd)
608  for p in task.get_options()['params']:
609  if p.get('prompt', '') not in ('raster', 'vector'):
610  continue
611  mapName = p.get('value', '')
612  if '@' not in mapName:
613  mapName = mapName + '@' + grass.gisenv()['MAPSET']
614  if mapName in mapLayers:
615  display.GetWindow().UpdateMap(render = True)
616  return
617 
618  def OnOK(self, event):
619  """!OK button pressed"""
620  cmd = self.OnApply(event)
621  if cmd is not None and self.get_dcmd is not None:
622  self.OnCancel(event)
623 
624  def OnApply(self, event):
625  """!Apply the command"""
626  if self.modeler:
627  cmd = self.createCmd(ignoreErrors = True, ignoreRequired = True)
628  else:
629  cmd = self.createCmd()
630 
631  if cmd is not None and self.get_dcmd is not None:
632  # return d.* command to layer tree for rendering
633  self.get_dcmd(cmd, self.layer, {"params": self.task.params,
634  "flags" : self.task.flags},
635  self)
636  # echo d.* command to output console
637  # self.parent.writeDCommand(cmd)
638 
639  return cmd
640 
641  def OnRun(self, event):
642  """!Run the command"""
643  cmd = self.createCmd()
644 
645  if not cmd or len(cmd) < 1:
646  return
647 
648  if self.standalone or cmd[0][0:2] != "d.":
649  # Send any non-display command to parent window (probably wxgui.py)
650  # put to parents switch to 'Command output'
651  self.notebookpanel.notebook.SetSelectionByName('output')
652 
653  try:
654 
655  self.goutput.RunCmd(cmd, onDone = self.OnDone)
656  except AttributeError, e:
657  print >> sys.stderr, "%s: Probably not running in wxgui.py session?" % (e)
658  print >> sys.stderr, "parent window is: %s" % (str(self.parent))
659  else:
660  gcmd.Command(cmd)
661 
662  # update buttons status
663  for btn in (self.btn_run,
664  self.btn_cancel,
665  self.btn_clipboard,
666  self.btn_help):
667  btn.Enable(False)
668 
669  def OnAbort(self, event):
670  """!Abort running command"""
671  from gui_core.goutput import wxCmdAbort
672  event = wxCmdAbort(aborted = True)
673  wx.PostEvent(self.goutput, event)
674 
675  def OnCopy(self, event):
676  """!Copy the command"""
677  cmddata = wx.TextDataObject()
678  # list -> string
679  cmdstring = ' '.join(self.createCmd(ignoreErrors = True))
680  cmddata.SetText(cmdstring)
681  if wx.TheClipboard.Open():
682 # wx.TheClipboard.UsePrimarySelection(True)
683  wx.TheClipboard.SetData(cmddata)
684  wx.TheClipboard.Close()
685  self.SetStatusText(_("'%s' copied to clipboard") % \
686  (cmdstring))
687 
688  def OnCancel(self, event):
689  """!Cancel button pressed"""
690  self.MakeModal(False)
691 
692  if self.get_dcmd and \
693  self.parent and \
694  self.parent.GetName() in ('LayerTree',
695  'MapWindow'):
696  # display decorations and
697  # pressing OK or cancel after setting layer properties
698  if self.task.name in ['d.barscale','d.legend','d.histogram'] \
699  or len(self.parent.GetPyData(self.layer)[0]['cmd']) >= 1:
700  self.Hide()
701  # canceled layer with nothing set
702  elif len(self.parent.GetPyData(self.layer)[0]['cmd']) < 1:
703  self.parent.Delete(self.layer)
704  self.Destroy()
705  else:
706  # cancel for non-display commands
707  self.Destroy()
708 
709  def OnHelp(self, event):
710  """!Show manual page (switch to the 'Manual' notebook page)"""
711  if self.notebookpanel.notebook.GetPageIndexByName('manual') > -1:
712  self.notebookpanel.notebook.SetSelectionByName('manual')
713  self.notebookpanel.OnPageChange(None)
714 
715  if event:
716  event.Skip()
717 
718  def createCmd(self, ignoreErrors = False, ignoreRequired = False):
719  """!Create command string (python list)"""
720  return self.notebookpanel.createCmd(ignoreErrors = ignoreErrors,
721  ignoreRequired = ignoreRequired)
722 
723 class CmdPanel(wx.Panel):
724  """!A panel containing a notebook dividing in tabs the different
725  guisections of the GRASS cmd.
726  """
727  def __init__(self, parent, task, id = wx.ID_ANY, frame = None, *args, **kwargs):
728  if frame:
729  self.parent = frame
730  else:
731  self.parent = parent
732  self.task = task
733 
734  wx.Panel.__init__(self, parent, id = id, *args, **kwargs)
735 
736  # Determine tab layout
737  sections = []
738  is_section = {}
739  not_hidden = [ p for p in self.task.params + self.task.flags if not p.get('hidden', False) == True ]
740 
741  self.label_id = [] # wrap titles on resize
742 
743  self.Bind(wx.EVT_SIZE, self.OnSize)
744 
745  for task in not_hidden:
746  if task.get('required', False):
747  # All required go into Main, even if they had defined another guisection
748  task['guisection'] = _('Required')
749  if task.get('guisection','') == '':
750  # Undefined guisections end up into Options
751  task['guisection'] = _('Optional')
752  if task['guisection'] not in is_section:
753  # We do it like this to keep the original order, except for Main which goes first
754  is_section[task['guisection']] = 1
755  sections.append(task['guisection'])
756  else:
757  is_section[ task['guisection'] ] += 1
758  del is_section
759 
760  # 'Required' tab goes first, 'Optional' as the last one
761  for (newidx,content) in [ (0,_('Required')), (len(sections)-1,_('Optional')) ]:
762  if content in sections:
763  idx = sections.index(content)
764  sections[idx:idx+1] = []
765  sections[newidx:newidx] = [content]
766 
767  panelsizer = wx.BoxSizer(orient = wx.VERTICAL)
768 
769  # Build notebook
770  self.notebook = GNotebook(self, style = globalvar.FNPageStyle | FN.FNB_NO_X_BUTTON )
771  self.notebook.SetTabAreaColour(globalvar.FNPageColor)
772  self.notebook.Bind(FN.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.OnPageChange)
773 
774  tab = {}
775  tabsizer = {}
776  for section in sections:
777  tab[section] = ScrolledPanel(parent = self.notebook)
778  tab[section].SetScrollRate(10, 10)
779  tabsizer[section] = wx.BoxSizer(orient = wx.VERTICAL)
780  self.notebook.AddPage(page = tab[section], text = section)
781 
782  # are we running from command line?
783  ### add 'command output' tab regardless standalone dialog
784  if self.parent.GetName() == "MainFrame" and self.parent.get_dcmd is None:
785  from gui_core.goutput import GMConsole
786  self.goutput = GMConsole(parent = self, margin = False)
787  self.outpage = self.notebook.AddPage(page = self.goutput, text = _("Command output"), name = 'output')
788  else:
789  self.goutput = None
790 
791  self.manual_tab = HelpPanel(parent = self, grass_command = self.task.name)
792  if not self.manual_tab.GetFile():
793  self.manual_tab.Hide()
794  else:
795  self.notebook.AddPage(page = self.manual_tab, text = _("Manual"), name = 'manual')
796 
797  self.notebook.SetSelection(0)
798 
799  panelsizer.Add(item = self.notebook, proportion = 1, flag = wx.EXPAND)
800 
801  #
802  # flags
803  #
804  text_style = wx.FONTWEIGHT_NORMAL
805  visible_flags = [ f for f in self.task.flags if not f.get('hidden', False) == True ]
806  for f in visible_flags:
807  which_sizer = tabsizer[ f['guisection'] ]
808  which_panel = tab[ f['guisection'] ]
809  # if label is given: description -> tooltip
810  if f.get('label','') != '':
811  title = text_beautify(f['label'])
812  tooltip = text_beautify(f['description'], width = -1)
813  else:
814  title = text_beautify(f['description'])
815  tooltip = None
816  title_sizer = wx.BoxSizer(wx.HORIZONTAL)
817  rtitle_txt = wx.StaticText(parent = which_panel,
818  label = '(' + f['name'] + ')')
819  chk = wx.CheckBox(parent = which_panel, label = title, style = wx.NO_BORDER)
820  self.label_id.append(chk.GetId())
821  if tooltip:
822  chk.SetToolTipString(tooltip)
823  chk.SetValue(f.get('value', False))
824  title_sizer.Add(item = chk, proportion = 1,
825  flag = wx.EXPAND)
826  title_sizer.Add(item = rtitle_txt, proportion = 0,
827  flag = wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL)
828  which_sizer.Add(item = title_sizer, proportion = 0,
829  flag = wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, border = 5)
830  f['wxId'] = [ chk.GetId(), ]
831  chk.Bind(wx.EVT_CHECKBOX, self.OnSetValue)
832 
833  if self.parent.GetName() == 'MainFrame' and self.parent.modeler:
834  parChk = wx.CheckBox(parent = which_panel, id = wx.ID_ANY,
835  label = _("Parameterized in model"))
836  parChk.SetName('ModelParam')
837  parChk.SetValue(f.get('parameterized', False))
838  if 'wxId' in f:
839  f['wxId'].append(parChk.GetId())
840  else:
841  f['wxId'] = [ parChk.GetId() ]
842  parChk.Bind(wx.EVT_CHECKBOX, self.OnSetValue)
843  which_sizer.Add(item = parChk, proportion = 0,
844  flag = wx.LEFT, border = 20)
845 
846  if f['name'] in ('verbose', 'quiet'):
847  chk.Bind(wx.EVT_CHECKBOX, self.OnVerbosity)
848  vq = UserSettings.Get(group = 'cmd', key = 'verbosity', subkey = 'selection')
849  if f['name'] == vq:
850  chk.SetValue(True)
851  f['value'] = True
852  elif f['name'] == 'overwrite' and 'value' not in f:
853  chk.SetValue(UserSettings.Get(group = 'cmd', key = 'overwrite', subkey = 'enabled'))
854  f['value'] = UserSettings.Get(group = 'cmd', key = 'overwrite', subkey = 'enabled')
855 
856  #
857  # parameters
858  #
859  visible_params = [ p for p in self.task.params if not p.get('hidden', False) == True ]
860 
861  try:
862  first_param = visible_params[0]
863  except IndexError:
864  first_param = None
865 
866  for p in visible_params:
867  which_sizer = tabsizer[ p['guisection'] ]
868  which_panel = tab[ p['guisection'] ]
869  # if label is given -> label and description -> tooltip
870  # otherwise description -> lavel
871  if p.get('label','') != '':
872  title = text_beautify(p['label'])
873  tooltip = text_beautify(p['description'], width = -1)
874  else:
875  title = text_beautify(p['description'])
876  tooltip = None
877  txt = None
878 
879  # text style (required -> bold)
880  if not p.get('required', False):
881  text_style = wx.FONTWEIGHT_NORMAL
882  else:
883  text_style = wx.FONTWEIGHT_BOLD
884 
885  # title sizer (description, name, type)
886  if (len(p.get('values', [])) > 0) and \
887  p.get('multiple', False) and \
888  p.get('gisprompt',False) == False and \
889  p.get('type', '') == 'string':
890  title_txt = wx.StaticBox(parent = which_panel, id = wx.ID_ANY)
891  else:
892  title_sizer = wx.BoxSizer(wx.HORIZONTAL)
893  title_txt = wx.StaticText(parent = which_panel)
894  if p['key_desc']:
895  ltype = ','.join(p['key_desc'])
896  else:
897  ltype = p['type']
898  rtitle_txt = wx.StaticText(parent = which_panel,
899  label = '(' + p['name'] + '=' + ltype + ')')
900  title_sizer.Add(item = title_txt, proportion = 1,
901  flag = wx.LEFT | wx.TOP | wx.EXPAND, border = 5)
902  title_sizer.Add(item = rtitle_txt, proportion = 0,
903  flag = wx.ALIGN_RIGHT | wx.RIGHT | wx.TOP, border = 5)
904  which_sizer.Add(item = title_sizer, proportion = 0,
905  flag = wx.EXPAND)
906  self.label_id.append(title_txt.GetId())
907 
908  # title expansion
909  if p.get('multiple', False) and len(p.get('values','')) == 0:
910  title = _("[multiple]") + " " + title
911  if p.get('value','') == '' :
912  p['value'] = p.get('default','')
913 
914  if (len(p.get('values', [])) > 0):
915  valuelist = map(str, p.get('values',[]))
916  valuelist_desc = map(unicode, p.get('values_desc',[]))
917 
918  if p.get('multiple', False) and \
919  p.get('gisprompt',False) == False and \
920  p.get('type', '') == 'string':
921  title_txt.SetLabel(" %s: (%s, %s) " % (title, p['name'], p['type']))
922  if valuelist_desc:
923  hSizer = wx.StaticBoxSizer(box = title_txt, orient = wx.VERTICAL)
924  else:
925  hSizer = wx.StaticBoxSizer(box = title_txt, orient = wx.HORIZONTAL)
926  isEnabled = {}
927  # copy default values
928  if p['value'] == '':
929  p['value'] = p.get('default', '')
930 
931  for defval in p.get('value', '').split(','):
932  isEnabled[ defval ] = 'yes'
933  # for multi checkboxes, this is an array of all wx IDs
934  # for each individual checkbox
935  p['wxId'] = list()
936  idx = 0
937  for val in valuelist:
938  try:
939  label = valuelist_desc[idx]
940  except IndexError:
941  label = val
942 
943  chkbox = wx.CheckBox(parent = which_panel,
944  label = text_beautify(label))
945  p[ 'wxId' ].append(chkbox.GetId())
946  if val in isEnabled:
947  chkbox.SetValue(True)
948  hSizer.Add(item = chkbox, proportion = 0,
949  flag = wx.ADJUST_MINSIZE | wx.ALL, border = 1)
950  chkbox.Bind(wx.EVT_CHECKBOX, self.OnCheckBoxMulti)
951  idx += 1
952 
953  which_sizer.Add(item = hSizer, proportion = 0,
954  flag = wx.EXPAND | wx.TOP | wx.RIGHT | wx.LEFT, border = 5)
955  elif p.get('gisprompt', False) == False:
956  if len(valuelist) == 1: # -> textctrl
957  title_txt.SetLabel("%s (%s %s):" % (title, _('valid range'),
958  str(valuelist[0])))
959 
960  if p.get('type', '') == 'integer' and \
961  not p.get('multiple', False):
962 
963  # for multiple integers use textctrl instead of spinsctrl
964  try:
965  minValue, maxValue = map(int, valuelist[0].split('-'))
966  except ValueError:
967  minValue = -1e6
968  maxValue = 1e6
969  txt2 = wx.SpinCtrl(parent = which_panel, id = wx.ID_ANY, size = globalvar.DIALOG_SPIN_SIZE,
970  min = minValue, max = maxValue)
971  txt2.SetName("SpinCtrl")
972  style = wx.BOTTOM | wx.LEFT
973  else:
974  txt2 = wx.TextCtrl(parent = which_panel, value = p.get('default',''))
975  txt2.SetName("TextCtrl")
976  style = wx.EXPAND | wx.BOTTOM | wx.LEFT
977 
978  value = self._getValue(p)
979  # parameter previously set
980  if value:
981  if txt2.GetName() == "SpinCtrl":
982  txt2.SetValue(int(value))
983  else:
984  txt2.SetValue(value)
985 
986  which_sizer.Add(item = txt2, proportion = 0,
987  flag = style, border = 5)
988 
989  p['wxId'] = [ txt2.GetId(), ]
990  txt2.Bind(wx.EVT_TEXT, self.OnSetValue)
991  else:
992  title_txt.SetLabel(title + ':')
993  value = self._getValue(p)
994 
995  if p['name'] == 'icon': # symbols
996  bitmap = wx.Bitmap(os.path.join(globalvar.ETCSYMBOLDIR, value) + '.png')
997  bb = wx.BitmapButton(parent = which_panel, id = wx.ID_ANY,
998  bitmap = bitmap)
999  iconLabel = wx.StaticText(parent = which_panel, id = wx.ID_ANY)
1000  iconLabel.SetLabel(value)
1001  p['value'] = value
1002  p['wxId'] = [bb.GetId(), iconLabel.GetId()]
1003  bb.Bind(wx.EVT_BUTTON, self.OnSetSymbol)
1004  this_sizer = wx.BoxSizer(wx.HORIZONTAL)
1005  this_sizer.Add(item = bb, proportion = 0,
1006  flag = wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT, border = 5)
1007  this_sizer.Add(item = iconLabel, proportion = 0,
1008  flag = wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT | wx.ALIGN_CENTER_VERTICAL, border = 5)
1009  which_sizer.Add(item = this_sizer, proportion = 0,
1010  flag = wx.ADJUST_MINSIZE, border = 0)
1011  else:
1012  # list of values (combo)
1013  cb = wx.ComboBox(parent = which_panel, id = wx.ID_ANY, value = p.get('default',''),
1014  size = globalvar.DIALOG_COMBOBOX_SIZE,
1015  choices = valuelist, style = wx.CB_DROPDOWN)
1016  if value:
1017  cb.SetValue(value) # parameter previously set
1018  which_sizer.Add(item = cb, proportion = 0,
1019  flag = wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT, border = 5)
1020  p['wxId'] = [cb.GetId(),]
1021  cb.Bind(wx.EVT_COMBOBOX, self.OnSetValue)
1022  cb.Bind(wx.EVT_TEXT, self.OnSetValue)
1023 
1024  # text entry
1025  if (p.get('type','string') in ('string','integer','float')
1026  and len(p.get('values',[])) == 0
1027  and p.get('gisprompt',False) == False
1028  and p.get('prompt','') != 'color'):
1029 
1030  title_txt.SetLabel(title + ':')
1031  if p.get('multiple', False) or \
1032  p.get('type', 'string') == 'string' or \
1033  len(p.get('key_desc', [])) > 1:
1034  txt3 = wx.TextCtrl(parent = which_panel, value = p.get('default',''))
1035 
1036  value = self._getValue(p)
1037  if value:
1038  # parameter previously set
1039  txt3.SetValue(str(value))
1040 
1041  txt3.Bind(wx.EVT_TEXT, self.OnSetValue)
1042  style = wx.EXPAND | wx.BOTTOM | wx.LEFT | wx.RIGHT
1043  else:
1044  minValue = -1e9
1045  maxValue = 1e9
1046  if p.get('type', '') == 'integer':
1047  txt3 = wx.SpinCtrl(parent = which_panel, value = p.get('default',''),
1048  size = globalvar.DIALOG_SPIN_SIZE,
1049  min = minValue, max = maxValue)
1050  style = wx.BOTTOM | wx.LEFT | wx.RIGHT
1051 
1052  value = self._getValue(p)
1053  if value:
1054  txt3.SetValue(int(value)) # parameter previously set
1055 
1056  txt3.Bind(wx.EVT_SPINCTRL, self.OnSetValue)
1057  else:
1058  txt3 = wx.TextCtrl(parent = which_panel, value = p.get('default',''),
1059  validator = FloatValidator())
1060  style = wx.EXPAND | wx.BOTTOM | wx.LEFT | wx.RIGHT
1061 
1062  value = self._getValue(p)
1063  if value:
1064  txt3.SetValue(str(value)) # parameter previously set
1065 
1066  txt3.Bind(wx.EVT_TEXT, self.OnSetValue)
1067 
1068  which_sizer.Add(item = txt3, proportion = 0,
1069  flag = style, border = 5)
1070  p['wxId'] = [ txt3.GetId(), ]
1071 
1072  #
1073  # element selection tree combobox (maps, icons, regions, etc.)
1074  #
1075  if p.get('gisprompt', False) == True:
1076  title_txt.SetLabel(title + ':')
1077  # GIS element entry
1078  if p.get('prompt','') not in ('color',
1079  'color_none',
1080  'subgroup',
1081  'dbdriver',
1082  'dbname',
1083  'dbtable',
1084  'dbcolumn',
1085  'layer',
1086  'layer_all',
1087  'layer_zero',
1088  'location',
1089  'mapset',
1090  'dbase') and \
1091  p.get('element', '') != 'file':
1092  multiple = p.get('multiple', False)
1093  if p.get('age', '') == 'new':
1094  mapsets = [grass.gisenv()['MAPSET'],]
1095  else:
1096  mapsets = None
1097  if self.task.name in ('r.proj', 'v.proj') \
1098  and p.get('name', '') == 'input':
1099  if self.task.name == 'r.proj':
1100  isRaster = True
1101  else:
1102  isRaster = False
1103  selection = gselect.ProjSelect(parent = which_panel,
1104  isRaster = isRaster)
1105  p['wxId'] = [ selection.GetId(), ]
1106  selection.Bind(wx.EVT_COMBOBOX, self.OnSetValue)
1107  selection.Bind(wx.EVT_TEXT, self.OnUpdateSelection)
1108  else:
1109  selection = gselect.Select(parent = which_panel, id = wx.ID_ANY,
1110  size = globalvar.DIALOG_GSELECT_SIZE,
1111  type = p.get('element', ''),
1112  multiple = multiple, mapsets = mapsets,
1113  fullyQualified = p.get('age', 'old') == 'old')
1114 
1115 
1116  # A select.Select is a combobox with two children: a textctl and a popupwindow;
1117  # we target the textctl here
1118  textWin = selection.GetTextCtrl()
1119  p['wxId'] = [ textWin.GetId(), ]
1120  textWin.Bind(wx.EVT_TEXT, self.OnSetValue)
1121 
1122  value = self._getValue(p)
1123  if value:
1124  selection.SetValue(value) # parameter previously set
1125 
1126  which_sizer.Add(item=selection, proportion=0,
1127  flag=wx.ADJUST_MINSIZE| wx.BOTTOM | wx.LEFT | wx.RIGHT, border=5)
1128 
1129  if p.get('prompt', '') in ('vector', 'group'):
1130  selection.Bind(wx.EVT_TEXT, self.OnUpdateSelection)
1131  # subgroup
1132  elif p.get('prompt', '') == 'subgroup':
1133  selection = gselect.SubGroupSelect(parent = which_panel)
1134  p['wxId'] = [ selection.GetId() ]
1135  selection.Bind(wx.EVT_COMBOBOX, self.OnSetValue)
1136  selection.Bind(wx.EVT_TEXT, self.OnSetValue)
1137  which_sizer.Add(item = selection, proportion = 0,
1138  flag = wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT | wx.RIGHT | wx.TOP | wx.ALIGN_CENTER_VERTICAL,
1139  border = 5)
1140 
1141  # layer, dbdriver, dbname, dbcolumn, dbtable entry
1142  elif p.get('prompt', '') in ('dbdriver',
1143  'dbname',
1144  'dbtable',
1145  'dbcolumn',
1146  'layer',
1147  'layer_all',
1148  'layer_zero',
1149  'location',
1150  'mapset',
1151  'dbase'):
1152  if p.get('multiple', 'no') == 'yes':
1153  win = wx.TextCtrl(parent = which_panel, value = p.get('default',''),
1154  size = globalvar.DIALOG_TEXTCTRL_SIZE)
1155  win.Bind(wx.EVT_TEXT, self.OnSetValue)
1156  else:
1157  value = self._getValue(p)
1158 
1159  if p.get('prompt', '') in ('layer',
1160  'layer_all',
1161  'layer_zero'):
1162 
1163  if p.get('age', 'old_layer') == 'old_layer':
1164  initial = list()
1165  if p.get('prompt', '') == 'layer_all':
1166  initial.insert(0, '-1')
1167  elif p.get('prompt', '') == 'layer_zero':
1168  initial.insert(0, '0')
1169  lyrvalue = p.get('default')
1170  if lyrvalue != '':
1171  if lyrvalue not in initial:
1172  initial.append(str(lyrvalue))
1173  lyrvalue = p.get('value')
1174  if lyrvalue != '':
1175  if lyrvalue not in initial:
1176  initial.append(str(lyrvalue))
1177 
1178  win = gselect.LayerSelect(parent = which_panel,
1179  initial = initial,
1180  default = p['default'])
1181  p['wxGetValue'] = win.GetStringSelection
1182  win.Bind(wx.EVT_TEXT, self.OnUpdateSelection)
1183  win.Bind(wx.EVT_TEXT, self.OnSetValue)
1184  win.SetValue(str(value)) # default or previously set value
1185  else:
1186  win = wx.SpinCtrl(parent = which_panel, id = wx.ID_ANY,
1187  min = 1, max = 100, initial = int(p['default']))
1188  win.Bind(wx.EVT_SPINCTRL, self.OnSetValue)
1189  win.SetValue(int(value)) # default or previously set value
1190 
1191  elif p.get('prompt', '') == 'dbdriver':
1192  win = gselect.DriverSelect(parent = which_panel,
1193  choices = p.get('values', []),
1194  value = value)
1195  p['wxGetValue'] = win.GetStringSelection
1196  win.Bind(wx.EVT_COMBOBOX, self.OnUpdateSelection)
1197  win.Bind(wx.EVT_COMBOBOX, self.OnSetValue)
1198  elif p.get('prompt', '') == 'dbname':
1199  win = gselect.DatabaseSelect(parent = which_panel,
1200  value = value)
1201  win.Bind(wx.EVT_TEXT, self.OnUpdateSelection)
1202  win.Bind(wx.EVT_TEXT, self.OnSetValue)
1203 
1204  elif p.get('prompt', '') == 'dbtable':
1205  if p.get('age', 'old_dbtable') == 'old_dbtable':
1206  win = gselect.TableSelect(parent=which_panel)
1207 
1208  p['wxGetValue'] = win.GetStringSelection
1209  win.Bind(wx.EVT_COMBOBOX, self.OnUpdateSelection)
1210  win.Bind(wx.EVT_COMBOBOX, self.OnSetValue)
1211  else:
1212  win = wx.TextCtrl(parent = which_panel, value = p.get('default',''),
1213  size = globalvar.DIALOG_TEXTCTRL_SIZE)
1214  win.Bind(wx.EVT_TEXT, self.OnSetValue)
1215  elif p.get('prompt', '') == 'dbcolumn':
1216  win = gselect.ColumnSelect(parent = which_panel,
1217  value = value,
1218  param = p)
1219  win.Bind(wx.EVT_COMBOBOX, self.OnSetValue)
1220  win.Bind(wx.EVT_TEXT, self.OnSetValue)
1221 
1222  elif p.get('prompt', '') == 'location':
1223  win = gselect.LocationSelect(parent = which_panel,
1224  value = value)
1225  win.Bind(wx.EVT_COMBOBOX, self.OnUpdateSelection)
1226  win.Bind(wx.EVT_COMBOBOX, self.OnSetValue)
1227 
1228  elif p.get('prompt', '') == 'mapset':
1229  win = gselect.MapsetSelect(parent = which_panel,
1230  value = value)
1231  win.Bind(wx.EVT_COMBOBOX, self.OnUpdateSelection)
1232  win.Bind(wx.EVT_COMBOBOX, self.OnSetValue)
1233 
1234  elif p.get('prompt', '') == 'dbase':
1235  win = gselect.DbaseSelect(parent = which_panel,
1236  changeCallback = self.OnSetValue)
1237  win.Bind(wx.EVT_TEXT, self.OnUpdateSelection)
1238  p['wxId'] = [ win.GetChildren()[1].GetId() ]
1239 
1240  if 'wxId' not in p:
1241  try:
1242  p['wxId'] = [ win.GetId(), ]
1243  except AttributeError:
1244  pass
1245 
1246  which_sizer.Add(item = win, proportion = 0,
1247  flag = wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT, border = 5)
1248  # color entry
1249  elif p.get('prompt', '') in ('color',
1250  'color_none'):
1251  default_color = (200,200,200)
1252  label_color = _("Select Color")
1253  if p.get('default','') != '':
1254  default_color, label_color = color_resolve(p['default'])
1255  if p.get('value','') != '' and p.get('value','') != 'none': # parameter previously set
1256  default_color, label_color = color_resolve(p['value'])
1257  if p.get('prompt', '') == 'color_none' or p.get('multiple', False):
1258  this_sizer = wx.BoxSizer(orient = wx.HORIZONTAL)
1259  else:
1260  this_sizer = which_sizer
1261  colorSize = 150
1262  # For color selectors, this is a three-member array, holding the IDs of
1263  # the color picker, the text control for multiple colors (or None),
1264  # and either a "transparent" checkbox or None
1265  p['wxId'] = [None] * 3
1266  if p.get('multiple', False):
1267  txt = wx.TextCtrl(parent = which_panel, id = wx.ID_ANY)
1268  this_sizer.Add(item = txt, proportion = 1,
1269  flag = wx.ADJUST_MINSIZE | wx.LEFT | wx.TOP, border = 5)
1270  txt.Bind(wx.EVT_TEXT, self.OnSetValue)
1271  colorSize = 40
1272  label_color = ''
1273  p['wxId'][1] = txt.GetId()
1274  which_sizer.Add(this_sizer, flag = wx.EXPAND | wx.RIGHT, border = 5)
1275 
1276  btn_colour = csel.ColourSelect(parent = which_panel, id = wx.ID_ANY,
1277  label = label_color, colour = default_color,
1278  pos = wx.DefaultPosition, size = (colorSize,-1))
1279  this_sizer.Add(item = btn_colour, proportion = 0,
1280  flag = wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT, border = 5)
1281  btn_colour.Bind(csel.EVT_COLOURSELECT, self.OnColorChange)
1282  p['wxId'][0] = btn_colour.GetId()
1283 
1284  if p.get('prompt', '') == 'color_none':
1285  none_check = wx.CheckBox(which_panel, wx.ID_ANY, _("Transparent"))
1286  if p.get('value','') != '' and p.get('value',[''])[0] == "none":
1287  none_check.SetValue(True)
1288  else:
1289  none_check.SetValue(False)
1290  this_sizer.Add(item = none_check, proportion = 0,
1291  flag = wx.ADJUST_MINSIZE | wx.LEFT | wx.RIGHT | wx.TOP, border = 5)
1292  which_sizer.Add(this_sizer)
1293  none_check.Bind(wx.EVT_CHECKBOX, self.OnColorChange)
1294  p['wxId'][2] = none_check.GetId()
1295 
1296  # file selector
1297  elif p.get('prompt','') != 'color' and p.get('element', '') == 'file':
1298  if p.get('age', 'new_file') == 'new_file':
1299  fmode = wx.FD_SAVE
1300  else:
1301  fmode = wx.FD_OPEN
1302  fbb = filebrowse.FileBrowseButton(parent = which_panel, id = wx.ID_ANY, fileMask = '*',
1303  size = globalvar.DIALOG_GSELECT_SIZE, labelText = '',
1304  dialogTitle = _('Choose %s') % \
1305  p.get('description',_('File')),
1306  buttonText = _('Browse'),
1307  startDirectory = os.getcwd(), fileMode = fmode,
1308  changeCallback = self.OnSetValue)
1309  value = self._getValue(p)
1310  if value:
1311  fbb.SetValue(value) # parameter previously set
1312  which_sizer.Add(item = fbb, proportion = 0,
1313  flag = wx.EXPAND | wx.RIGHT, border = 5)
1314 
1315  # A file browse button is a combobox with two children:
1316  # a textctl and a button;
1317  # we have to target the button here
1318  p['wxId'] = [ fbb.GetChildren()[1].GetId() ]
1319  if p.get('age', 'new_file') == 'old_file' and \
1320  UserSettings.Get(group='cmd', key='interactiveInput', subkey='enabled'):
1321  # widget for interactive input
1322  ifbb = wx.TextCtrl(parent = which_panel, id = wx.ID_ANY,
1323  style = wx.TE_MULTILINE,
1324  size = (-1, 75))
1325  if p.get('value', '') and os.path.isfile(p['value']):
1326  f = open(p['value'])
1327  ifbb.SetValue(''.join(f.readlines()))
1328  f.close()
1329 
1330  ifbb.Bind(wx.EVT_TEXT, self.OnFileText)
1331 
1332  btnLoad = wx.Button(parent = which_panel, id = wx.ID_ANY, label = _("&Load"))
1333  btnLoad.Bind(wx.EVT_BUTTON, self.OnFileLoad)
1334  btnSave = wx.Button(parent = which_panel, id = wx.ID_SAVEAS)
1335  btnSave.Bind(wx.EVT_BUTTON, self.OnFileSave)
1336 
1337  which_sizer.Add(item = wx.StaticText(parent = which_panel, id = wx.ID_ANY,
1338  label = _('or enter values interactively')),
1339  proportion = 0,
1340  flag = wx.EXPAND | wx.RIGHT | wx.LEFT | wx.BOTTOM, border = 5)
1341  which_sizer.Add(item = ifbb, proportion = 1,
1342  flag = wx.EXPAND | wx.RIGHT | wx.LEFT, border = 5)
1343  btnSizer = wx.BoxSizer(wx.HORIZONTAL)
1344  btnSizer.Add(item = btnLoad, proportion = 0,
1345  flag = wx.ALIGN_RIGHT | wx.RIGHT, border = 10)
1346  btnSizer.Add(item = btnSave, proportion = 0,
1347  flag = wx.ALIGN_RIGHT)
1348  which_sizer.Add(item = btnSizer, proportion = 0,
1349  flag = wx.ALIGN_RIGHT | wx.RIGHT | wx.TOP, border = 5)
1350 
1351  p['wxId'].append(ifbb.GetId())
1352  p['wxId'].append(btnLoad.GetId())
1353  p['wxId'].append(btnSave.GetId())
1354 
1355  if self.parent.GetName() == 'MainFrame' and self.parent.modeler:
1356  parChk = wx.CheckBox(parent = which_panel, id = wx.ID_ANY,
1357  label = _("Parameterized in model"))
1358  parChk.SetName('ModelParam')
1359  parChk.SetValue(p.get('parameterized', False))
1360  if 'wxId' in p:
1361  p['wxId'].append(parChk.GetId())
1362  else:
1363  p['wxId'] = [ parChk.GetId() ]
1364  parChk.Bind(wx.EVT_CHECKBOX, self.OnSetValue)
1365  which_sizer.Add(item = parChk, proportion = 0,
1366  flag = wx.LEFT, border = 20)
1367 
1368  if title_txt is not None:
1369  # create tooltip if given
1370  if len(p['values_desc']) > 0:
1371  if tooltip:
1372  tooltip += 2 * os.linesep
1373  else:
1374  tooltip = ''
1375  if len(p['values']) == len(p['values_desc']):
1376  for i in range(len(p['values'])):
1377  tooltip += p['values'][i] + ': ' + p['values_desc'][i] + os.linesep
1378  tooltip.strip(os.linesep)
1379  if tooltip:
1380  title_txt.SetToolTipString(tooltip)
1381 
1382  if p == first_param:
1383  if 'wxId' in p and len(p['wxId']) > 0:
1384  win = self.FindWindowById(p['wxId'][0])
1385  win.SetFocus()
1386 
1387  #
1388  # set widget relations for OnUpdateSelection
1389  #
1390  pMap = None
1391  pLayer = []
1392  pDriver = None
1393  pDatabase = None
1394  pTable = None
1395  pColumn = []
1396  pGroup = None
1397  pSubGroup = None
1398  pDbase = None
1399  pLocation = None
1400  pMapset = None
1401  for p in self.task.params:
1402  if p.get('gisprompt', False) == False:
1403  continue
1404 
1405  prompt = p.get('element', '')
1406  if prompt in ('cell', 'vector'):
1407  name = p.get('name', '')
1408  if name in ('map', 'input'):
1409  pMap = p
1410  elif prompt == 'layer':
1411  pLayer.append(p)
1412  elif prompt == 'dbcolumn':
1413  pColumn.append(p)
1414  elif prompt == 'dbdriver':
1415  pDriver = p
1416  elif prompt == 'dbname':
1417  pDatabase = p
1418  elif prompt == 'dbtable':
1419  pTable = p
1420  elif prompt == 'group':
1421  pGroup = p
1422  elif prompt == 'subgroup':
1423  pSubGroup = p
1424  elif prompt == 'dbase':
1425  pDbase = p
1426  elif prompt == 'location':
1427  pLocation = p
1428  elif prompt == 'mapset':
1429  pMapset = p
1430 
1431  pColumnIds = []
1432  for p in pColumn:
1433  pColumnIds += p['wxId']
1434  pLayerIds = []
1435  for p in pLayer:
1436  pLayerIds += p['wxId']
1437 
1438  if pMap:
1439  pMap['wxId-bind'] = copy.copy(pColumnIds)
1440  if pLayer:
1441  pMap['wxId-bind'] += pLayerIds
1442  if pLayer:
1443  for p in pLayer:
1444  p['wxId-bind'] = copy.copy(pColumnIds)
1445 
1446  if pDriver and pTable:
1447  pDriver['wxId-bind'] = pTable['wxId']
1448 
1449  if pDatabase and pTable:
1450  pDatabase['wxId-bind'] = pTable['wxId']
1451 
1452  if pTable and pColumnIds:
1453  pTable['wxId-bind'] = pColumnIds
1454 
1455  if pGroup and pSubGroup:
1456  pGroup['wxId-bind'] = pSubGroup['wxId']
1457 
1458  if pDbase and pLocation:
1459  pDbase['wxId-bind'] = pLocation['wxId']
1460 
1461  if pLocation and pMapset:
1462  pLocation['wxId-bind'] = pMapset['wxId']
1463 
1464  if pLocation and pMapset and pMap:
1465  pLocation['wxId-bind'] += pMap['wxId']
1466  pMapset['wxId-bind'] = pMap['wxId']
1467 
1468  #
1469  # determine panel size
1470  #
1471  maxsizes = (0, 0)
1472  for section in sections:
1473  tab[section].SetSizer(tabsizer[section])
1474  tabsizer[section].Fit(tab[section])
1475  tab[section].Layout()
1476  minsecsizes = tabsizer[section].GetSize()
1477  maxsizes = map(lambda x: max(maxsizes[x], minsecsizes[x]), (0, 1))
1478 
1479  # TODO: be less arbitrary with these 600
1480  self.panelMinHeight = 100
1481  self.constrained_size = (min(600, maxsizes[0]) + 25, min(400, maxsizes[1]) + 25)
1482  for section in sections:
1483  tab[section].SetMinSize((self.constrained_size[0], self.panelMinHeight))
1484 
1485  if self.manual_tab.IsLoaded():
1486  self.manual_tab.SetMinSize((self.constrained_size[0], self.panelMinHeight))
1487 
1488  self.SetSizer(panelsizer)
1489  panelsizer.Fit(self.notebook)
1490 
1491  self.Bind(EVT_DIALOG_UPDATE, self.OnUpdateDialog)
1492 
1493  def _getValue(self, p):
1494  """!Get value or default value of given parameter
1495 
1496  @param p parameter directory
1497  """
1498  if p.get('value', '') != '':
1499  return p['value']
1500  return p.get('default', '')
1501 
1502  def OnFileLoad(self, event):
1503  """!Load file to interactive input"""
1504  me = event.GetId()
1505  win = dict()
1506  for p in self.task.params:
1507  if 'wxId' in p and me in p['wxId']:
1508  win['file'] = self.FindWindowById(p['wxId'][0])
1509  win['text'] = self.FindWindowById(p['wxId'][1])
1510  break
1511 
1512  if not win:
1513  return
1514 
1515  path = win['file'].GetValue()
1516  if not path:
1517  gcmd.GMessage(parent = self,
1518  message = _("Nothing to load."))
1519  return
1520 
1521  data = ''
1522  f = open(path, "r")
1523  try:
1524  data = f.read()
1525  finally:
1526  f.close()
1527 
1528  win['text'].SetValue(data)
1529 
1530  def OnFileSave(self, event):
1531  """!Save interactive input to the file"""
1532  wId = event.GetId()
1533  win = {}
1534  for p in self.task.params:
1535  if wId in p.get('wxId', []):
1536  win['file'] = self.FindWindowById(p['wxId'][0])
1537  win['text'] = self.FindWindowById(p['wxId'][1])
1538  break
1539 
1540  if not win:
1541  return
1542 
1543  text = win['text'].GetValue()
1544  if not text:
1545  gcmd.GMessage(parent = self,
1546  message = _("Nothing to save."))
1547  return
1548 
1549  dlg = wx.FileDialog(parent = self,
1550  message = _("Save input as..."),
1551  defaultDir = os.getcwd(),
1552  style = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
1553 
1554  if dlg.ShowModal() == wx.ID_OK:
1555  path = dlg.GetPath()
1556  f = open(path, "w")
1557  try:
1558  f.write(text + os.linesep)
1559  finally:
1560  f.close()
1561 
1562  win['file'].SetValue(path)
1563 
1564  dlg.Destroy()
1565 
1566  def OnFileText(self, event):
1567  """File input interactively entered"""
1568  text = event.GetString()
1569  p = self.task.get_param(value = event.GetId(), element = 'wxId', raiseError = False)
1570  if not p:
1571  return # should not happen
1572  win = self.FindWindowById(p['wxId'][0])
1573  if text:
1574  filename = win.GetValue()
1575  if not filename:
1576  # outFile = tempfile.NamedTemporaryFile(mode = 'w+b')
1577  filename = grass.tempfile()
1578  win.SetValue(filename)
1579 
1580  f = open(filename, "w")
1581  try:
1582  f.write(text)
1583  if text[-1] != os.linesep:
1584  f.write(os.linesep)
1585  finally:
1586  f.close()
1587  else:
1588  win.SetValue('')
1589 
1590  def OnUpdateDialog(self, event):
1591  for fn, kwargs in event.data.iteritems():
1592  fn(**kwargs)
1593 
1594  self.parent.updateValuesHook()
1595 
1596  def OnVerbosity(self, event):
1597  """!Verbosity level changed"""
1598  verbose = self.FindWindowById(self.task.get_flag('verbose')['wxId'][0])
1599  quiet = self.FindWindowById(self.task.get_flag('quiet')['wxId'][0])
1600  if event.IsChecked():
1601  if event.GetId() == verbose.GetId():
1602  if quiet.IsChecked():
1603  quiet.SetValue(False)
1604  self.task.get_flag('quiet')['value'] = False
1605  else:
1606  if verbose.IsChecked():
1607  verbose.SetValue(False)
1608  self.task.get_flag('verbose')['value'] = False
1609 
1610  event.Skip()
1611 
1612  def OnPageChange(self, event):
1613  if not event:
1614  sel = self.notebook.GetSelection()
1615  else:
1616  sel = event.GetSelection()
1617 
1618  idx = self.notebook.GetPageIndexByName('manual')
1619  if idx > -1 and sel == idx:
1620  # calling LoadPage() is strangely time-consuming (only first call)
1621  # FIXME: move to helpPage.__init__()
1622  if not self.manual_tab.IsLoaded():
1623  wx.Yield()
1624  self.manual_tab.LoadPage()
1625 
1626  self.Layout()
1627 
1628  def OnColorChange(self, event):
1629  myId = event.GetId()
1630  for p in self.task.params:
1631  if 'wxId' in p and myId in p['wxId']:
1632  multiple = p['wxId'][1] is not None # multiple colors
1633  hasTransp = p['wxId'][2] is not None
1634  if multiple:
1635  # selected color is added at the end of textCtrl
1636  colorchooser = wx.FindWindowById(p['wxId'][0])
1637  new_color = colorchooser.GetValue()[:]
1638  new_label = rgb2str.get(new_color, ':'.join(map(str, new_color)))
1639  textCtrl = wx.FindWindowById(p['wxId'][1])
1640  val = textCtrl.GetValue()
1641  sep = ','
1642  if val and val[-1] != sep:
1643  val += sep
1644  val += new_label
1645  textCtrl.SetValue(val)
1646  p[ 'value' ] = val
1647  elif hasTransp and wx.FindWindowById(p['wxId'][2]).GetValue():
1648  p[ 'value' ] = 'none'
1649  else:
1650  colorchooser = wx.FindWindowById(p['wxId'][0])
1651  new_color = colorchooser.GetValue()[:]
1652  # This is weird: new_color is a 4-tuple and new_color[:] is a 3-tuple
1653  # under wx2.8.1
1654  new_label = rgb2str.get(new_color, ':'.join(map(str,new_color)))
1655  colorchooser.SetLabel(new_label)
1656  colorchooser.SetColour(new_color)
1657  colorchooser.Refresh()
1658  p[ 'value' ] = colorchooser.GetLabel()
1659  self.OnUpdateValues()
1660 
1661  def OnUpdateValues(self, event = None):
1662  """!If we were part of a richer interface, report back the
1663  current command being built.
1664 
1665  This method should be set by the parent of this panel if
1666  needed. It's a hook, actually. Beware of what is 'self' in
1667  the method def, though. It will be called with no arguments.
1668  """
1669  pass
1670 
1671  def OnCheckBoxMulti(self, event):
1672  """!Fill the values as a ','-separated string according to
1673  current status of the checkboxes.
1674  """
1675  me = event.GetId()
1676  theParam = None
1677  for p in self.task.params:
1678  if 'wxId' in p and me in p['wxId']:
1679  theParam = p
1680  myIndex = p['wxId'].index(me)
1681 
1682  # Unpack current value list
1683  currentValues = {}
1684  for isThere in theParam.get('value', '').split(','):
1685  currentValues[isThere] = 1
1686  theValue = theParam['values'][myIndex]
1687 
1688  if event.Checked():
1689  currentValues[ theValue ] = 1
1690  else:
1691  del currentValues[ theValue ]
1692 
1693  # Keep the original order, so that some defaults may be recovered
1694  currentValueList = []
1695  for v in theParam['values']:
1696  if v in currentValues:
1697  currentValueList.append(v)
1698 
1699  # Pack it back
1700  theParam['value'] = ','.join(currentValueList)
1701 
1702  self.OnUpdateValues()
1703 
1704  def OnSetValue(self, event):
1705  """!Retrieve the widget value and set the task value field
1706  accordingly.
1707 
1708  Use for widgets that have a proper GetValue() method, i.e. not
1709  for selectors.
1710  """
1711  myId = event.GetId()
1712  me = wx.FindWindowById(myId)
1713  name = me.GetName()
1714 
1715  found = False
1716  for porf in self.task.params + self.task.flags:
1717  if 'wxId' not in porf:
1718  continue
1719  if myId in porf['wxId']:
1720  found = True
1721  break
1722 
1723  if not found:
1724  return
1725 
1726  if name in ('DriverSelect', 'TableSelect',
1727  'LocationSelect', 'MapsetSelect', 'ProjSelect'):
1728  porf['value'] = me.GetStringSelection()
1729  elif name == 'GdalSelect':
1730  porf['value'] = event.dsn
1731  elif name == 'ModelParam':
1732  porf['parameterized'] = me.IsChecked()
1733  elif name == 'LayerSelect':
1734  porf['value'] = me.GetValue()
1735  else:
1736  porf['value'] = me.GetValue()
1737 
1738  self.OnUpdateValues(event)
1739 
1740  event.Skip()
1741 
1742  def OnSetSymbol(self, event):
1743  """!Shows dialog for symbol selection"""
1744  myId = event.GetId()
1745 
1746  for p in self.task.params:
1747  if 'wxId' in p and myId in p['wxId']:
1748  from gui_core.dialogs import SymbolDialog
1749  dlg = SymbolDialog(self, symbolPath = globalvar.ETCSYMBOLDIR,
1750  currentSymbol = p['value'])
1751  if dlg.ShowModal() == wx.ID_OK:
1752  img = dlg.GetSelectedSymbolPath()
1753  p['value'] = dlg.GetSelectedSymbolName()
1754 
1755  bitmapButton = wx.FindWindowById(p['wxId'][0])
1756  label = wx.FindWindowById(p['wxId'][1])
1757 
1758  bitmapButton.SetBitmapLabel(wx.Bitmap(img + '.png'))
1759  label.SetLabel(p['value'])
1760 
1761  self.OnUpdateValues(event)
1762 
1763  dlg.Destroy()
1764 
1765  def OnUpdateSelection(self, event):
1766  """!Update dialog (layers, tables, columns, etc.)
1767  """
1768  if not hasattr(self.parent, "updateThread"):
1769  if event:
1770  event.Skip()
1771  return
1772  if event:
1773  self.parent.updateThread.Update(UpdateDialog,
1774  self,
1775  event,
1776  event.GetId(),
1777  self.task)
1778  else:
1779  self.parent.updateThread.Update(UpdateDialog,
1780  self,
1781  None,
1782  None,
1783  self.task)
1784 
1785  def createCmd(self, ignoreErrors = False, ignoreRequired = False):
1786  """!Produce a command line string (list) or feeding into GRASS.
1787 
1788  @param ignoreErrors True then it will return whatever has been
1789  built so far, even though it would not be a correct command
1790  for GRASS
1791  """
1792  try:
1793  cmd = self.task.get_cmd(ignoreErrors = ignoreErrors,
1794  ignoreRequired = ignoreRequired)
1795  except ValueError, err:
1796  dlg = wx.MessageDialog(parent = self,
1797  message = unicode(err),
1798  caption = _("Error in %s") % self.task.name,
1799  style = wx.OK | wx.ICON_ERROR | wx.CENTRE)
1800  dlg.ShowModal()
1801  dlg.Destroy()
1802  cmd = None
1803 
1804  return cmd
1805 
1806  def OnSize(self, event):
1807  width = event.GetSize()[0]
1808  fontsize = self.GetFont().GetPointSize()
1809  text_width = max(width / (fontsize - 3), 70)
1810 
1811  for id in self.label_id:
1812  win = self.FindWindowById(id)
1813  label = win.GetLabel()
1814  label_new = '\n'.join(textwrap.wrap(label, text_width))
1815  win.SetLabel(label_new)
1816 
1817  event.Skip()
1818 
1819 class GUI:
1820  def __init__(self, parent = None, show = True, modal = False,
1821  centreOnParent = False, checkError = False):
1822  """!Parses GRASS commands when module is imported and used from
1823  Layer Manager.
1824  """
1825  self.parent = parent
1826  self.show = show
1827  self.modal = modal
1828  self.centreOnParent = centreOnParent
1829  self.checkError = checkError
1830 
1831  self.grass_task = None
1832  self.cmd = list()
1833 
1834  global _blackList
1835  if self.parent:
1836  _blackList['enabled'] = True
1837  else:
1838  _blackList['enabled'] = False
1839 
1840  def GetCmd(self):
1841  """!Get validated command"""
1842  return self.cmd
1843 
1844  def ParseCommand(self, cmd, gmpath = None, completed = None):
1845  """!Parse command
1846 
1847  Note: cmd is given as list
1848 
1849  If command is given with options, return validated cmd list:
1850  - add key name for first parameter if not given
1851  - change mapname to mapname@mapset
1852  """
1853  start = time.time()
1854  dcmd_params = {}
1855  if completed == None:
1856  get_dcmd = None
1857  layer = None
1858  dcmd_params = None
1859  else:
1860  get_dcmd = completed[0]
1861  layer = completed[1]
1862  if completed[2]:
1863  dcmd_params.update(completed[2])
1864 
1865  # parse the interface decription
1866  try:
1867  global _blackList
1868  self.grass_task = gtask.parse_interface(gcmd.GetRealCmd(cmd[0]),
1869  blackList = _blackList)
1870  except (grass.ScriptError, ValueError), e:
1871  raise gcmd.GException(e.value)
1872 
1873  # if layer parameters previously set, re-insert them into dialog
1874  if completed is not None:
1875  if 'params' in dcmd_params:
1876  self.grass_task.params = dcmd_params['params']
1877  if 'flags' in dcmd_params:
1878  self.grass_task.flags = dcmd_params['flags']
1879 
1880  err = list()
1881  # update parameters if needed && validate command
1882  if len(cmd) > 1:
1883  i = 0
1884  cmd_validated = [cmd[0]]
1885  for option in cmd[1:]:
1886  if option[0] == '-': # flag
1887  if option[1] == '-':
1888  self.grass_task.set_flag(option[2:], True)
1889  else:
1890  self.grass_task.set_flag(option[1], True)
1891  cmd_validated.append(option)
1892  else: # parameter
1893  try:
1894  key, value = option.split('=', 1)
1895  except:
1896  params = self.grass_task.get_options()['params']
1897  if params:
1898  if i == 0: # add key name of first parameter if not given
1899  key = params[0]['name']
1900  value = option
1901  else:
1902  raise gcmd.GException, _("Unable to parse command '%s'") % ' '.join(cmd)
1903  else:
1904  continue
1905 
1906  element = self.grass_task.get_param(key, raiseError = False)
1907  if not element:
1908  err.append(_("%(cmd)s: parameter '%(key)s' not available") % \
1909  { 'cmd' : cmd[0],
1910  'key' : key })
1911  continue
1912  element = element['element']
1913 
1914  if element in ['cell', 'vector']:
1915  # mapname -> mapname@mapset
1916  if '@' not in value:
1917  mapset = grass.find_file(value, element)['mapset']
1918  curr_mapset = grass.gisenv()['MAPSET']
1919  if mapset and mapset != curr_mapset:
1920  value = value + '@' + mapset
1921 
1922  self.grass_task.set_param(key, value)
1923  cmd_validated.append(key + '=' + value)
1924  i += 1
1925 
1926  # update original command list
1927  cmd = cmd_validated
1928 
1929  if self.show is not None:
1930  self.mf = TaskFrame(parent = self.parent, ID = wx.ID_ANY,
1931  task_description = self.grass_task,
1932  get_dcmd = get_dcmd, layer = layer)
1933  else:
1934  self.mf = None
1935 
1936  if get_dcmd is not None:
1937  # update only propwin reference
1938  get_dcmd(dcmd = None, layer = layer, params = None,
1939  propwin = self.mf)
1940 
1941  if self.show is not None:
1942  self.mf.notebookpanel.OnUpdateSelection(None)
1943  if self.show is True:
1944  if self.parent and self.centreOnParent:
1945  self.mf.CentreOnParent()
1946  else:
1947  self.mf.CenterOnScreen()
1948  self.mf.Show(self.show)
1949  self.mf.MakeModal(self.modal)
1950  else:
1951  self.mf.OnApply(None)
1952 
1953  self.cmd = cmd
1954 
1955  if self.checkError:
1956  return self.grass_task, err
1957  else:
1958  return self.grass_task
1959 
1961  """!Get parameter key for input raster/vector map
1962 
1963  @param cmd module name
1964 
1965  @return parameter key
1966  @return None on failure
1967  """
1968  # parse the interface decription
1969  if not self.grass_task:
1970  enc = locale.getdefaultlocale()[1]
1971  if enc and enc.lower() == "cp932":
1972  p = re.compile('encoding="' + enc + '"', re.IGNORECASE)
1973  tree = etree.fromstring(p.sub('encoding="utf-8"',
1974  gtask.get_interface_description(cmd).decode(enc).encode('utf-8')))
1975  else:
1976  tree = etree.fromstring(gtask.get_interface_description(cmd))
1977  self.grass_task = gtask.processTask(tree).get_task()
1978 
1979  for p in self.grass_task.params:
1980  if p.get('name', '') in ('input', 'map'):
1981  age = p.get('age', '')
1982  prompt = p.get('prompt', '')
1983  element = p.get('element', '')
1984  if age == 'old' and \
1985  element in ('cell', 'grid3', 'vector') and \
1986  prompt in ('raster', '3d-raster', 'vector'):
1987  return p.get('name', None)
1988  return None
1989 
1990 class GrassGUIApp(wx.App):
1991  """!Stand-alone GRASS command GUI
1992  """
1993  def __init__(self, grass_task):
1994  self.grass_task = grass_task
1995  wx.App.__init__(self, False)
1996 
1997  def OnInit(self):
1998  msg = self.grass_task.get_error_msg()
1999  if msg:
2000  gcmd.GError(msg + '\n\nTry to set up GRASS_ADDON_PATH variable.')
2001  return True
2002 
2003  self.mf = TaskFrame(parent = None, ID = wx.ID_ANY, task_description = self.grass_task)
2004  self.mf.CentreOnScreen()
2005  self.mf.Show(True)
2006  self.SetTopWindow(self.mf)
2007 
2008  return True
2009 
2010 if __name__ == "__main__":
2011  import gettext
2012  gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode = True)
2013 
2014  if len(sys.argv) == 1:
2015  sys.exit(_("usage: %s <grass command>") % sys.argv[0])
2016 
2017  if sys.argv[1] != 'test':
2018  q = wx.LogNull()
2019  cmd = utils.split(sys.argv[1])
2020  task = gtask.grassTask(gcmd.GetRealCmd(cmd[0]))
2021  task.set_options(cmd[1:])
2022  app = GrassGUIApp(task)
2023  app.MainLoop()
2024  else: #Test
2025  # Test grassTask from within a GRASS session
2026  if os.getenv("GISBASE") is not None:
2027  task = gtask.grassTask("d.vect")
2028  task.get_param('map')['value'] = "map_name"
2029  task.get_flag('v')['value'] = True
2030  task.get_param('layer')['value'] = 1
2031  task.get_param('bcolor')['value'] = "red"
2032  assert ' '.join(task.get_cmd()) == "d.vect -v map = map_name layer = 1 bcolor = red"
2033  # Test interface building with handmade grassTask,
2034  # possibly outside of a GRASS session.
2035  task = gtask.grassTask()
2036  task.name = "TestTask"
2037  task.description = "This is an artificial grassTask() object intended for testing purposes."
2038  task.keywords = ["grass","test","task"]
2039  task.params = [
2040  {
2041  "name" : "text",
2042  "description" : "Descriptions go into tooltips if labels are present, like this one",
2043  "label" : "Enter some text",
2044  },{
2045  "name" : "hidden_text",
2046  "description" : "This text should not appear in the form",
2047  "hidden" : True
2048  },{
2049  "name" : "text_default",
2050  "description" : "Enter text to override the default",
2051  "default" : "default text"
2052  },{
2053  "name" : "text_prefilled",
2054  "description" : "You should see a friendly welcome message here",
2055  "value" : "hello, world"
2056  },{
2057  "name" : "plain_color",
2058  "description" : "This is a plain color, and it is a compulsory parameter",
2059  "required" : False,
2060  "gisprompt" : True,
2061  "prompt" : "color"
2062  },{
2063  "name" : "transparent_color",
2064  "description" : "This color becomes transparent when set to none",
2065  "guisection" : "tab",
2066  "gisprompt" : True,
2067  "prompt" : "color"
2068  },{
2069  "name" : "multi",
2070  "description" : "A multiple selection",
2071  'default': u'red,green,blue',
2072  'gisprompt': False,
2073  'guisection': 'tab',
2074  'multiple': u'yes',
2075  'type': u'string',
2076  'value': '',
2077  'values': ['red', 'green', u'yellow', u'blue', u'purple', u'other']
2078  },{
2079  "name" : "single",
2080  "description" : "A single multiple-choice selection",
2081  'values': ['red', 'green', u'yellow', u'blue', u'purple', u'other'],
2082  "guisection" : "tab"
2083  },{
2084  "name" : "large_multi",
2085  "description" : "A large multiple selection",
2086  "gisprompt" : False,
2087  "multiple" : "yes",
2088  # values must be an array of strings
2089  "values" : str2rgb.keys() + map(str, str2rgb.values())
2090  },{
2091  "name" : "a_file",
2092  "description" : "A file selector",
2093  "gisprompt" : True,
2094  "element" : "file"
2095  }
2096  ]
2097  task.flags = [
2098  {
2099  "name" : "a",
2100  "description" : "Some flag, will appear in Main since it is required",
2101  "required" : True
2102  },{
2103  "name" : "b",
2104  "description" : "pre-filled flag, will appear in options since it is not required",
2105  "value" : True
2106  },{
2107  "name" : "hidden_flag",
2108  "description" : "hidden flag, should not be changeable",
2109  "hidden" : "yes",
2110  "value" : True
2111  }
2112  ]
2113  q = wx.LogNull()
2114  GrassGUIApp(task).MainLoop()
2115 
def OnDone(self, cmd, returncode)
This function is launched from OnRun() when command is finished.
Definition: forms.py:581
def OnAbort(self, event)
Abort running command.
Definition: forms.py:669
def OnUpdateSelection(self, event)
Update dialog (layers, tables, columns, etc.)
Definition: forms.py:1765
def createCmd
Produce a command line string (list) or feeding into GRASS.
Definition: forms.py:1785
def OnSetValue(self, event)
Retrieve the widget value and set the task value field accordingly.
Definition: forms.py:1704
def OnOK(self, event)
OK button pressed.
Definition: forms.py:618
def OnFileText(self, event)
Definition: forms.py:1566
def __init__
Definition: forms.py:347
def OnSetSymbol(self, event)
Shows dialog for symbol selection.
Definition: forms.py:1742
def run(self)
Definition: forms.py:168
def createCmd
Create command string (python list)
Definition: forms.py:718
#define min(x, y)
Definition: draw2.c:68
Creates combo box for selecting data layers defined for vector.
Definition: gselect.py:646
def SetValue(self, value)
Definition: widgets.py:115
def ParseCommand
Parse command.
Definition: forms.py:1844
def OnFileSave(self, event)
Save interactive input to the file.
Definition: forms.py:1530
Core GUI widgets.
def GetCmd(self)
Get validated command.
Definition: forms.py:1840
def OnFileLoad(self, event)
Load file to interactive input.
Definition: forms.py:1502
def OnColorChange(self, event)
Definition: forms.py:1628
grass_task
Definition: forms.py:1831
centreOnParent
Definition: forms.py:1828
checkError
Definition: forms.py:1829
#define max(x, y)
Definition: draw2.c:69
def text_beautify
Definition: forms.py:138
def __init__
Parses GRASS commands when module is imported and used from Layer Manager.
Definition: forms.py:1821
def OnUpdateDialog(self, event)
Definition: forms.py:1590
def OnCopy(self, event)
Copy the command.
Definition: forms.py:675
Various dialogs used in wxGUI.
def Update(self, callable, args, kwds)
Definition: forms.py:310
def __init__(self, parent, requestQ, resultQ, kwds)
Definition: forms.py:299
Widget for selecting input raster/vector map used by r.proj/v.proj modules.
Definition: gselect.py:1776
def OnCheckBoxMulti(self, event)
Fill the values as a ','-separated string according to current status of the checkboxes.
Definition: forms.py:1671
Creates combo box for selecting attribute tables from the database.
Definition: gselect.py:738
def split(s)
Platform spefic shlex.split.
Definition: core/utils.py:37
Widget for selecting GRASS Database.
Definition: gselect.py:871
def __init__(self, parent, task, id=wx.ID_ANY, frame=None, args, kwargs)
Definition: forms.py:727
def GetValue(self)
Definition: widgets.py:118
Creates combo box for selecting database driver.
Definition: gselect.py:713
Widget for selecting GRASS location.
Definition: gselect.py:881
def __init__(self, grass_task)
Definition: forms.py:1993
Run command in separate thread.
Definition: gcmd.py:311
def GetRealCmd(cmd)
Return real command name - only for MS Windows.
Definition: gcmd.py:59
Update dialog widgets in the thread.
Definition: forms.py:296
def _getValue(self, p)
Get value or default value of given parameter.
Definition: forms.py:1493
def OnCancel(self, event)
Cancel button pressed.
Definition: forms.py:688
def __init__(self, parent, event, eventId, task)
Definition: forms.py:156
def escape_ampersand(text)
Escapes ampersands with additional ampersand for GUI.
Definition: forms.py:150
Stand-alone GRASS command GUI.
Definition: forms.py:1990
Help window.
def OnHelp(self, event)
Show manual page (switch to the 'Manual' notebook page)
Definition: forms.py:709
Widget for selecting GRASS mapset.
Definition: gselect.py:907
Widget for selecting subgroups.
Definition: gselect.py:962
def GetCommandInputMapParamKey(self, cmd)
Get parameter key for input raster/vector map.
Definition: forms.py:1960
goutput
add 'command output' tab regardless standalone dialog
Definition: forms.py:786
def OnPageChange(self, event)
Definition: forms.py:1612
Creates combo box for selecting columns in the attribute table for a vector map.
Definition: gselect.py:777
def updateValuesHook
Update status bar data.
Definition: forms.py:550
def OnRun(self, event)
Run the command.
Definition: forms.py:641
def OnKeyUp(self, event)
Key released (check hot-keys)
Definition: forms.py:556
Creates combo box for selecting database driver.
Definition: gselect.py:727
def OnInit(self)
Definition: forms.py:1997
def OnUpdateValues
If we were part of a richer interface, report back the current command being built.
Definition: forms.py:1661
def run(self)
Definition: forms.py:318
def normalize_whitespace(text)
Remove redundant whitespace from a string.
Definition: core/utils.py:33
def OnApply(self, event)
Apply the command.
Definition: forms.py:624
Update dialog widgets in the thread.
Definition: forms.py:154
def UpdateDialog(parent, event, eventId, task)
Definition: forms.py:293
def color_resolve(color)
Definition: forms.py:124
def OnVerbosity(self, event)
Verbosity level changed.
Definition: forms.py:1596
Default GUI settings.
tuple range
Definition: tools.py:1406
Class providing information about attribute tables linked to a vector map.
Definition: gselect.py:534
tuple fn
if textDict['border'] != 'none' and not rot: units = UnitConversion(self) borderWidth = units...
def OnSize(self, event)
Definition: forms.py:1806
Command output widgets.
def decode(string)
Definition: core.py:80
This is the Frame containing the dialog for options input.
Definition: forms.py:332
A panel containing a notebook dividing in tabs the different guisections of the GRASS cmd...
Definition: forms.py:723