menuform.py 92 KB

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