forms.py 100 KB

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