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