forms.py 128 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388
  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-2015 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. from __future__ import print_function
  37. import sys
  38. import textwrap
  39. import os
  40. import copy
  41. import locale
  42. import six
  43. if sys.version_info.major == 2:
  44. import Queue
  45. else:
  46. import queue as Queue
  47. unicode = str
  48. import codecs
  49. from threading import Thread
  50. import wx
  51. import wx.lib.colourselect as csel
  52. import wx.lib.filebrowsebutton as filebrowse
  53. from wx.lib.newevent import NewEvent
  54. try:
  55. import xml.etree.ElementTree as etree
  56. except ImportError:
  57. import elementtree.ElementTree as etree # Python <= 2.4
  58. # needed when started from command line and for testing
  59. if __name__ == "__main__":
  60. if os.getenv("GISBASE") is None:
  61. # intentionally not translatable
  62. sys.exit(
  63. "Failed to start. GRASS GIS is not running"
  64. " or the installation is broken."
  65. )
  66. from grass.script.setup import set_gui_path
  67. set_gui_path()
  68. from grass.pydispatch.signal import Signal
  69. from grass.script import core as grass
  70. from grass.script import task as gtask
  71. from core import globalvar
  72. from gui_core.widgets import (
  73. StaticWrapText,
  74. ScrolledPanel,
  75. ColorTablesComboBox,
  76. BarscalesComboBox,
  77. NArrowsComboBox,
  78. )
  79. from gui_core.ghelp import HelpPanel
  80. from gui_core import gselect
  81. from core import gcmd
  82. from core import utils
  83. from core.settings import UserSettings
  84. from gui_core.widgets import (
  85. FloatValidator,
  86. FormListbook,
  87. FormNotebook,
  88. PlacementValidator,
  89. )
  90. from core.giface import Notification, StandaloneGrassInterface
  91. from gui_core.widgets import LayersList
  92. from gui_core.wrap import (
  93. BitmapFromImage,
  94. Button,
  95. CloseButton,
  96. StaticText,
  97. StaticBox,
  98. SpinCtrl,
  99. CheckBox,
  100. BitmapButton,
  101. TextCtrl,
  102. NewId,
  103. )
  104. from core.debug import Debug
  105. wxUpdateDialog, EVT_DIALOG_UPDATE = NewEvent()
  106. """Hide some options in the GUI"""
  107. # _blackList = { 'enabled' : False,
  108. # 'items' : { 'r.buffer' : {'params' : ['input', 'output'],
  109. # 'flags' : ['z', 'overwrite']}}}
  110. _blackList = {"enabled": False, "items": {}}
  111. def text_beautify(someString, width=70):
  112. """Make really long texts shorter, clean up whitespace and remove
  113. trailing punctuation.
  114. """
  115. if width > 0:
  116. return escape_ampersand(
  117. os.linesep.join(
  118. textwrap.wrap(utils.normalize_whitespace(someString), width)
  119. ).strip(".,;:")
  120. )
  121. else:
  122. return escape_ampersand(utils.normalize_whitespace(someString).strip(".,;:"))
  123. def escape_ampersand(text):
  124. """Escapes ampersands with additional ampersand for GUI"""
  125. return text.replace("&", "&&")
  126. class UpdateThread(Thread):
  127. """Update dialog widgets in the thread"""
  128. def __init__(self, parent, event, eventId, task):
  129. Thread.__init__(self)
  130. self.parent = parent
  131. self.event = event
  132. self.eventId = eventId
  133. self.task = task
  134. self.setDaemon(True)
  135. # list of functions which updates the dialog
  136. self.data = {}
  137. def run(self):
  138. # get widget id
  139. if not self.eventId:
  140. for p in self.task.params:
  141. if p.get("gisprompt", False) is False:
  142. continue
  143. prompt = p.get("element", "")
  144. if prompt == "vector":
  145. name = p.get("name", "")
  146. if name in ("map", "input"):
  147. self.eventId = p["wxId"][0]
  148. if self.eventId is None:
  149. return
  150. p = self.task.get_param(self.eventId, element="wxId", raiseError=False)
  151. if not p or "wxId-bind" not in p:
  152. return
  153. # is this check necessary?
  154. # get widget prompt
  155. # pType = p.get('prompt', '')
  156. # if not pType:
  157. # return
  158. # check for map/input parameter
  159. pMap = self.task.get_param("map", raiseError=False)
  160. if not pMap:
  161. pMap = self.task.get_param("input", raiseError=False)
  162. if pMap:
  163. map = pMap.get("value", "")
  164. else:
  165. map = None
  166. # avoid running db.describe several times
  167. cparams = dict()
  168. cparams[map] = {
  169. "dbInfo": None,
  170. "layers": None,
  171. }
  172. # update reference widgets
  173. for uid in p["wxId-bind"]:
  174. win = self.parent.FindWindowById(uid)
  175. if not win:
  176. continue
  177. name = win.GetName()
  178. # @todo: replace name by isinstance() and signals
  179. pBind = self.task.get_param(uid, element="wxId", raiseError=False)
  180. if pBind:
  181. pBind["value"] = ""
  182. # set appropriate types in t.* modules and g.list/remove element
  183. # selections
  184. if name == "Select":
  185. type_param = self.task.get_param(
  186. "type", element="name", raiseError=False
  187. )
  188. if "all" in type_param.get("value"):
  189. etype = type_param.get("values")[:]
  190. if "all" in etype:
  191. etype.remove("all")
  192. etype = ",".join(etype)
  193. else:
  194. etype = type_param.get("value")
  195. if globalvar.CheckWxVersion([3]):
  196. self.data[win.SetElementList] = {"type": etype}
  197. else:
  198. self.data[win.GetParent().SetElementList] = {"type": etype}
  199. # t.(un)register has one type for 'input', 'maps'
  200. maps_param = self.task.get_param(
  201. "maps", element="name", raiseError=False
  202. )
  203. if self.task.get_name().startswith("t") and maps_param is not None:
  204. if maps_param["wxId"][0] != uid:
  205. element_dict = {
  206. "raster": "strds",
  207. "vector": "stvds",
  208. "raster_3d": "str3ds",
  209. }
  210. self.data[win.GetParent().SetType] = {
  211. "etype": element_dict[type_param.get("value")]
  212. }
  213. map = layer = None
  214. driver = db = None
  215. if name in ("LayerSelect", "ColumnSelect", "SqlWhereSelect"):
  216. if p.get("element", "") == "vector": # -> vector
  217. # get map name
  218. map = p.get("value", "")
  219. # get layer
  220. for bid in p["wxId-bind"]:
  221. p = self.task.get_param(bid, element="wxId", raiseError=False)
  222. if not p:
  223. continue
  224. if p.get("element", "") in ["layer", "layer_all"]:
  225. layer = p.get("value", "")
  226. if layer != "":
  227. layer = p.get("value", "")
  228. else:
  229. layer = p.get("default", "")
  230. break
  231. elif p.get("element", "") in ["layer", "layer_all"]: # -> layer
  232. # get layer
  233. layer = p.get("value", "")
  234. if layer != "":
  235. layer = p.get("value", "")
  236. else:
  237. layer = p.get("default", "")
  238. # get map name
  239. pMapL = self.task.get_param(
  240. p["wxId"][0], element="wxId-bind", raiseError=False
  241. )
  242. if pMapL:
  243. gui_deps = pMapL.get("guidependency", None)
  244. if gui_deps:
  245. gui_deps = gui_deps.split(",")
  246. if not gui_deps or (gui_deps and p.get("name", "") in gui_deps):
  247. map = pMapL.get("value", "")
  248. if name == "TableSelect" or (name == "ColumnSelect" and not map):
  249. pDriver = self.task.get_param(
  250. "dbdriver", element="prompt", raiseError=False
  251. )
  252. if pDriver:
  253. driver = pDriver.get("value", "")
  254. pDb = self.task.get_param("dbname", element="prompt", raiseError=False)
  255. if pDb:
  256. db = pDb.get("value", "")
  257. if name == "ColumnSelect":
  258. pTable = self.task.get_param(
  259. "dbtable", element="element", raiseError=False
  260. )
  261. if pTable:
  262. table = pTable.get("value", "")
  263. if name == "LayerSelect":
  264. # determine format
  265. native = True
  266. if pMap:
  267. for id in pMap["wxId"]:
  268. winVec = self.parent.FindWindowById(id)
  269. if (
  270. winVec.GetName() == "VectorFormat"
  271. and winVec.GetSelection() != 0
  272. ):
  273. native = False
  274. break
  275. # TODO: update only if needed
  276. if native:
  277. if map:
  278. self.data[win.InsertLayers] = {"vector": map}
  279. else:
  280. self.data[win.InsertLayers] = {}
  281. else:
  282. if map:
  283. self.data[win.InsertLayers] = {"dsn": map.rstrip("@OGR")}
  284. else:
  285. self.data[win.InsertLayers] = {}
  286. elif name == "TableSelect":
  287. self.data[win.InsertTables] = {"driver": driver, "database": db}
  288. elif name == "ColumnSelect":
  289. if map:
  290. if map not in cparams:
  291. cparams[map] = {
  292. "dbInfo": None,
  293. "layers": None,
  294. }
  295. if not cparams[map]["dbInfo"]:
  296. cparams[map]["dbInfo"] = gselect.VectorDBInfo(map)
  297. self.data[win.GetParent().InsertColumns] = {
  298. "vector": map,
  299. "layer": layer,
  300. "dbInfo": cparams[map]["dbInfo"],
  301. }
  302. else: # table
  303. if driver and db:
  304. self.data[win.GetParent().InsertTableColumns] = {
  305. "table": pTable.get("value"),
  306. "driver": driver,
  307. "database": db,
  308. }
  309. elif pTable:
  310. self.data[win.GetParent().InsertTableColumns] = {
  311. "table": pTable.get("value")
  312. }
  313. elif name == "SubGroupSelect":
  314. self.data[win.Insert] = {"group": p.get("value", "")}
  315. elif name == "LocationSelect":
  316. pDbase = self.task.get_param(
  317. "dbase", element="element", raiseError=False
  318. )
  319. if pDbase:
  320. self.data[win.UpdateItems] = {"dbase": pDbase.get("value", "")}
  321. elif name == "MapsetSelect":
  322. pDbase = self.task.get_param(
  323. "dbase", element="element", raiseError=False
  324. )
  325. pLocation = self.task.get_param(
  326. "location", element="element", raiseError=False
  327. )
  328. if pDbase and pLocation:
  329. self.data[win.UpdateItems] = {
  330. "dbase": pDbase.get("value", ""),
  331. "location": pLocation.get("value", ""),
  332. }
  333. elif name == "ProjSelect":
  334. pDbase = self.task.get_param(
  335. "dbase", element="element", raiseError=False
  336. )
  337. pLocation = self.task.get_param(
  338. "location", element="element", raiseError=False
  339. )
  340. pMapset = self.task.get_param(
  341. "mapset", element="element", raiseError=False
  342. )
  343. if pDbase and pLocation and pMapset:
  344. self.data[win.UpdateItems] = {
  345. "dbase": pDbase.get("value", ""),
  346. "location": pLocation.get("value", ""),
  347. "mapset": pMapset.get("value", ""),
  348. }
  349. elif name == "SqlWhereSelect":
  350. if map:
  351. self.data[win.GetParent().SetData] = {"vector": map, "layer": layer}
  352. # TODO: table?
  353. def UpdateDialog(parent, event, eventId, task):
  354. return UpdateThread(parent, event, eventId, task)
  355. class UpdateQThread(Thread):
  356. """Update dialog widgets in the thread"""
  357. requestId = 0
  358. def __init__(self, parent, requestQ, resultQ, **kwds):
  359. Thread.__init__(self, **kwds)
  360. self.parent = parent # cmdPanel
  361. self.setDaemon(True)
  362. self.requestQ = requestQ
  363. self.resultQ = resultQ
  364. self.start()
  365. def Update(self, callable, *args, **kwds):
  366. UpdateQThread.requestId += 1
  367. self.request = None
  368. self.requestQ.put((UpdateQThread.requestId, callable, args, kwds))
  369. return UpdateQThread.requestId
  370. def run(self):
  371. while True:
  372. requestId, callable, args, kwds = self.requestQ.get()
  373. self.request = callable(*args, **kwds)
  374. self.resultQ.put((requestId, self.request.run()))
  375. if self.request:
  376. event = wxUpdateDialog(data=self.request.data)
  377. wx.PostEvent(self.parent, event)
  378. class TaskFrame(wx.Frame):
  379. """This is the Frame containing the dialog for options input.
  380. The dialog is organized in a notebook according to the guisections
  381. defined by each GRASS command.
  382. If run with a parent, it may Apply, Ok or Cancel; the latter two
  383. close the dialog. The former two trigger a callback.
  384. If run standalone, it will allow execution of the command.
  385. The command is checked and sent to the clipboard when clicking
  386. 'Copy'.
  387. """
  388. def __init__(
  389. self,
  390. parent,
  391. giface,
  392. task_description,
  393. id=wx.ID_ANY,
  394. get_dcmd=None,
  395. layer=None,
  396. title=None,
  397. style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL,
  398. **kwargs,
  399. ):
  400. self.get_dcmd = get_dcmd
  401. self.layer = layer
  402. self.task = task_description
  403. self.parent = parent # LayerTree | Modeler | None | ...
  404. self._giface = giface
  405. self.dialogClosing = Signal("TaskFrame.dialogClosing")
  406. # Module name as title by default
  407. if not title:
  408. title = self.task.get_name()
  409. wx.Frame.__init__(
  410. self,
  411. parent=parent,
  412. id=id,
  413. title=title,
  414. name="MainFrame",
  415. style=style,
  416. **kwargs,
  417. )
  418. self.locale = wx.Locale(language=wx.LANGUAGE_DEFAULT)
  419. self.panel = wx.Panel(parent=self, id=wx.ID_ANY)
  420. # statusbar
  421. self.CreateStatusBar()
  422. # icon
  423. self.SetIcon(
  424. wx.Icon(
  425. os.path.join(globalvar.ICONDIR, "grass_dialog.ico"), wx.BITMAP_TYPE_ICO
  426. )
  427. )
  428. guisizer = wx.BoxSizer(wx.VERTICAL)
  429. # set appropriate output window
  430. if self.parent:
  431. self.standalone = False
  432. else:
  433. self.standalone = True
  434. # logo + description
  435. topsizer = wx.BoxSizer(wx.HORIZONTAL)
  436. # GRASS logo
  437. self.logo = wx.StaticBitmap(
  438. self.panel,
  439. -1,
  440. wx.Bitmap(
  441. name=os.path.join(globalvar.IMGDIR, "grass_form.png"),
  442. type=wx.BITMAP_TYPE_PNG,
  443. ),
  444. )
  445. topsizer.Add(
  446. self.logo, proportion=0, border=3, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL
  447. )
  448. # add module description
  449. if self.task.label:
  450. module_desc = self.task.label + " " + self.task.description
  451. else:
  452. module_desc = self.task.description
  453. self.description = StaticWrapText(parent=self.panel, label=module_desc)
  454. topsizer.Add(self.description, proportion=1, border=5, flag=wx.ALL | wx.EXPAND)
  455. guisizer.Add(topsizer, proportion=0, flag=wx.EXPAND)
  456. self.panel.SetSizerAndFit(guisizer)
  457. self.Layout()
  458. # notebooks
  459. self.notebookpanel = CmdPanel(
  460. parent=self.panel, giface=self._giface, task=self.task, frame=self
  461. )
  462. self._gconsole = self.notebookpanel._gconsole
  463. if self._gconsole:
  464. self._gconsole.mapCreated.connect(self.OnMapCreated)
  465. self._gconsole.updateMap.connect(lambda: self._giface.updateMap.emit())
  466. self.goutput = self.notebookpanel.goutput
  467. if self.goutput:
  468. self.goutput.showNotification.connect(
  469. lambda message: self.SetStatusText(message)
  470. )
  471. self.notebookpanel.OnUpdateValues = self.updateValuesHook
  472. guisizer.Add(self.notebookpanel, proportion=1, flag=wx.EXPAND)
  473. # status bar
  474. status_text = _("Enter parameters for '") + self.task.name + "'"
  475. try:
  476. self.task.get_cmd()
  477. self.updateValuesHook()
  478. except ValueError:
  479. self.SetStatusText(status_text)
  480. # buttons
  481. btnsizer = wx.BoxSizer(orient=wx.HORIZONTAL)
  482. # cancel
  483. self.btn_cancel = CloseButton(parent=self.panel)
  484. self.btn_cancel.SetToolTip(
  485. _("Close this window without executing the command (Ctrl+Q)")
  486. )
  487. btnsizer.Add(
  488. self.btn_cancel, proportion=0, flag=wx.ALL | wx.ALIGN_CENTER, border=10
  489. )
  490. self.btn_cancel.Bind(wx.EVT_BUTTON, self.OnCancel)
  491. # bind closing to ESC and CTRL+Q
  492. self.Bind(wx.EVT_MENU, self.OnCancel, id=wx.ID_CANCEL)
  493. accelTableList = [(wx.ACCEL_NORMAL, wx.WXK_ESCAPE, wx.ID_CANCEL)]
  494. accelTableList.append((wx.ACCEL_CTRL, ord("Q"), wx.ID_CANCEL))
  495. # TODO: bind Ctrl-t for tile windows here (trac #2004)
  496. if self.get_dcmd is not None: # A callback has been set up
  497. btn_apply = Button(parent=self.panel, id=wx.ID_APPLY)
  498. btn_ok = Button(parent=self.panel, id=wx.ID_OK)
  499. btn_ok.SetDefault()
  500. btnsizer.Add(
  501. btn_apply, proportion=0, flag=wx.ALL | wx.ALIGN_CENTER, border=10
  502. )
  503. btnsizer.Add(btn_ok, proportion=0, flag=wx.ALL | wx.ALIGN_CENTER, border=10)
  504. btn_apply.Bind(wx.EVT_BUTTON, self.OnApply)
  505. btn_ok.Bind(wx.EVT_BUTTON, self.OnOK)
  506. else: # We're standalone
  507. # run
  508. self.btn_run = Button(parent=self.panel, id=wx.ID_OK, label=_("&Run"))
  509. self.btn_run.SetToolTip(_("Run the command (Ctrl+R)"))
  510. self.btn_run.SetDefault()
  511. btnsizer.Add(
  512. self.btn_run, proportion=0, flag=wx.ALL | wx.ALIGN_CENTER, border=10
  513. )
  514. self.btn_run.Bind(wx.EVT_BUTTON, self.OnRun)
  515. self.Bind(wx.EVT_MENU, self.OnRun, id=wx.ID_OK)
  516. accelTableList.append((wx.ACCEL_CTRL, ord("R"), wx.ID_OK))
  517. # copy
  518. self.btn_clipboard = Button(parent=self.panel, id=wx.ID_ANY, label=_("Copy"))
  519. self.btn_clipboard.SetToolTip(
  520. _("Copy the current command string to the clipboard")
  521. )
  522. btnsizer.Add(
  523. self.btn_clipboard, proportion=0, flag=wx.ALL | wx.ALIGN_CENTER, border=10
  524. )
  525. self.btn_clipboard.Bind(wx.EVT_BUTTON, self.OnCopyCommand)
  526. # help
  527. self.btn_help = Button(parent=self.panel, id=wx.ID_HELP)
  528. self.btn_help.SetToolTip(_("Show manual page of the command (Ctrl+H)"))
  529. self.btn_help.Bind(wx.EVT_BUTTON, self.OnHelp)
  530. self.Bind(wx.EVT_MENU, self.OnHelp, id=wx.ID_HELP)
  531. accelTableList.append((wx.ACCEL_CTRL, ord("H"), wx.ID_HELP))
  532. if self.notebookpanel.notebook.GetPageIndexByName("manual") < 0:
  533. self.btn_help.Hide()
  534. # add help button
  535. btnsizer.Add(
  536. self.btn_help, proportion=0, flag=wx.ALL | wx.ALIGN_CENTER, border=10
  537. )
  538. guisizer.Add(
  539. btnsizer, proportion=0, flag=wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT, border=30
  540. )
  541. # abort key bindings
  542. abortId = NewId()
  543. self.Bind(wx.EVT_MENU, self.OnAbort, id=abortId)
  544. accelTableList.append((wx.ACCEL_CTRL, ord("S"), abortId))
  545. # set accelerator table
  546. accelTable = wx.AcceleratorTable(accelTableList)
  547. self.SetAcceleratorTable(accelTable)
  548. if self._giface and self._giface.GetLayerTree():
  549. addLayer = False
  550. for p in self.task.params:
  551. if p.get("age", "old") == "new" and p.get("prompt", "") in (
  552. "raster",
  553. "vector",
  554. "raster_3d",
  555. ):
  556. addLayer = True
  557. if addLayer:
  558. # add newly created map into layer tree
  559. self.addbox = wx.CheckBox(
  560. parent=self.panel,
  561. label=_("Add created map(s) into layer tree"),
  562. style=wx.NO_BORDER,
  563. )
  564. self.addbox.SetValue(
  565. UserSettings.Get(group="cmd", key="addNewLayer", subkey="enabled")
  566. )
  567. guisizer.Add(
  568. self.addbox,
  569. proportion=0,
  570. flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM,
  571. border=5,
  572. )
  573. hasNew = False
  574. for p in self.task.params:
  575. if p.get("age", "old") == "new":
  576. hasNew = True
  577. break
  578. if self.get_dcmd is None and hasNew:
  579. # close dialog when command is terminated
  580. self.closebox = CheckBox(
  581. parent=self.panel, label=_("Close dialog on finish"), style=wx.NO_BORDER
  582. )
  583. self.closebox.SetValue(
  584. UserSettings.Get(group="cmd", key="closeDlg", subkey="enabled")
  585. )
  586. self.closebox.SetToolTip(
  587. _(
  588. "Close dialog when command is successfully finished. "
  589. "Change this settings in Preferences dialog ('Command' tab)."
  590. )
  591. )
  592. guisizer.Add(
  593. self.closebox,
  594. proportion=0,
  595. flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM,
  596. border=5,
  597. )
  598. # bindings
  599. self.Bind(wx.EVT_CLOSE, self.OnCancel)
  600. # do layout
  601. # called automatically by SetSizer()
  602. self.panel.SetAutoLayout(True)
  603. self.panel.SetSizerAndFit(guisizer)
  604. sizeFrame = self.GetBestSize()
  605. self.SetMinSize(sizeFrame)
  606. if hasattr(self, "closebox"):
  607. scale = 0.33
  608. else:
  609. scale = 0.50
  610. self.SetSize(
  611. wx.Size(
  612. round(sizeFrame[0]),
  613. round(
  614. sizeFrame[1]
  615. + scale
  616. * max(
  617. self.notebookpanel.panelMinHeight,
  618. self.notebookpanel.constrained_size[1],
  619. )
  620. ),
  621. )
  622. )
  623. # thread to update dialog
  624. # create queues
  625. self.requestQ = Queue.Queue()
  626. self.resultQ = Queue.Queue()
  627. self.updateThread = UpdateQThread(
  628. self.notebookpanel, self.requestQ, self.resultQ
  629. )
  630. self.Layout()
  631. # keep initial window size limited for small screens
  632. width, height = self.GetSize()
  633. self.SetSize(wx.Size(min(width, 650), min(height, 500)))
  634. # fix goutput's pane size (required for Mac OSX)
  635. if self.goutput:
  636. self.goutput.SetSashPosition(int(self.GetSize()[1] * 0.75))
  637. def MakeModal(self, modal=True):
  638. if globalvar.wxPythonPhoenix:
  639. if modal and not hasattr(self, "_disabler"):
  640. self._disabler = wx.WindowDisabler(self)
  641. if not modal and hasattr(self, "_disabler"):
  642. del self._disabler
  643. else:
  644. super(TaskFrame, self).MakeModal(modal)
  645. def updateValuesHook(self, event=None):
  646. """Update status bar data"""
  647. self.SetStatusText(
  648. " ".join(
  649. [
  650. gcmd.DecodeString(each) if isinstance(each, str) else each
  651. for each in self.notebookpanel.createCmd(ignoreErrors=True)
  652. ]
  653. )
  654. )
  655. if event:
  656. event.Skip()
  657. def OnDone(self, event):
  658. """This function is launched from OnRun() when command is
  659. finished
  660. """
  661. if hasattr(self, "btn_cancel"):
  662. self.btn_cancel.Enable(True)
  663. if hasattr(self, "btn_clipboard"):
  664. self.btn_clipboard.Enable(True)
  665. if hasattr(self, "btn_help"):
  666. self.btn_help.Enable(True)
  667. if hasattr(self, "btn_run"):
  668. self.btn_run.Enable(True)
  669. if (
  670. hasattr(self, "get_dcmd")
  671. and self.get_dcmd is None
  672. and hasattr(self, "closebox")
  673. and self.closebox.IsChecked()
  674. and (event.returncode == 0)
  675. ):
  676. # was closed also when aborted but better is leave it open
  677. wx.CallLater(2000, self.Close)
  678. def OnMapCreated(self, name, ltype):
  679. """Map created or changed
  680. :param name: map name
  681. :param ltype: layer type (prompt value)
  682. """
  683. if hasattr(self, "addbox") and self.addbox.IsChecked():
  684. add = True
  685. else:
  686. add = False
  687. if self._giface:
  688. self._giface.mapCreated.emit(name=name, ltype=ltype, add=add)
  689. def OnOK(self, event):
  690. """OK button pressed"""
  691. cmd = self.OnApply(event)
  692. if cmd is not None and self.get_dcmd is not None:
  693. self.OnCancel(event)
  694. def OnApply(self, event):
  695. """Apply the command"""
  696. if self._giface and hasattr(self._giface, "_model"):
  697. cmd = self.createCmd(ignoreErrors=True, ignoreRequired=True)
  698. else:
  699. cmd = self.createCmd()
  700. if cmd is not None and self.get_dcmd is not None:
  701. # return d.* command to layer tree for rendering
  702. self.get_dcmd(
  703. cmd,
  704. self.layer,
  705. {"params": self.task.params, "flags": self.task.flags},
  706. self,
  707. )
  708. # echo d.* command to output console
  709. # self.parent.writeDCommand(cmd)
  710. return cmd
  711. def OnRun(self, event):
  712. """Run the command"""
  713. cmd = self.createCmd()
  714. if not cmd or len(cmd) < 1:
  715. return
  716. ret = 0
  717. if self.standalone or cmd[0][0:2] != "d.":
  718. # Send any non-display command to parent window (probably wxgui.py)
  719. # put to parents switch to 'Command output'
  720. self.notebookpanel.notebook.SetSelectionByName("output")
  721. try:
  722. if self.task.path:
  723. cmd[0] = self.task.path # full path
  724. ret = self._gconsole.RunCmd(cmd, onDone=self.OnDone)
  725. except AttributeError as e:
  726. print(
  727. "%s: Probably not running in wxgui.py session?" % (e),
  728. file=sys.stderr,
  729. )
  730. print("parent window is: %s" % (str(self.parent)), file=sys.stderr)
  731. else:
  732. gcmd.Command(cmd)
  733. if ret != 0:
  734. self.notebookpanel.notebook.SetSelection(0)
  735. return
  736. # update buttons status
  737. for btn in (self.btn_run, self.btn_cancel, self.btn_clipboard, self.btn_help):
  738. btn.Enable(False)
  739. def OnAbort(self, event):
  740. """Abort running command"""
  741. from core.gconsole import wxCmdAbort
  742. event = wxCmdAbort(aborted=True)
  743. wx.PostEvent(self._gconsole, event)
  744. def OnCopyCommand(self, event):
  745. """Copy the command"""
  746. cmddata = wx.TextDataObject()
  747. # list -> string
  748. cmdlist = self.createCmd(ignoreErrors=True)
  749. # TODO: better protect whitespace with quotes
  750. for i in range(1, len(cmdlist)):
  751. if " " in cmdlist[i]:
  752. optname, val = cmdlist[i].split("=", 1)
  753. cmdlist[i] = '%s="%s"' % (optname, val)
  754. cmdstring = " ".join(cmdlist)
  755. cmddata.SetText(cmdstring)
  756. if wx.TheClipboard.Open():
  757. # wx.TheClipboard.UsePrimarySelection(True)
  758. wx.TheClipboard.SetData(cmddata)
  759. wx.TheClipboard.Close()
  760. self.SetStatusText(_("'%s' copied to clipboard") % (cmdstring))
  761. def OnCancel(self, event):
  762. """Cancel button pressed"""
  763. self.MakeModal(False)
  764. self.dialogClosing.emit()
  765. if (
  766. self.get_dcmd
  767. and self.parent
  768. and self.parent.GetName() in ("LayerTree", "MapWindow")
  769. ):
  770. Debug.msg(1, "TaskFrame.OnCancel(): known parent")
  771. # display decorations and
  772. # pressing OK or cancel after setting layer properties
  773. if (
  774. self.task.name
  775. in [
  776. "d.barscale",
  777. "d.legend",
  778. "d.northarrow",
  779. "d.histogram",
  780. "d.text",
  781. "d.legend.vect",
  782. ]
  783. or len(self.parent.GetLayerInfo(self.layer, key="cmd")) >= 1
  784. ):
  785. # TODO: do this through policy
  786. self.Hide()
  787. # canceled layer with nothing set
  788. elif len(self.parent.GetLayerInfo(self.layer, key="cmd")) < 1:
  789. # TODO: do this through callback or signal
  790. try:
  791. self.parent.Delete(self.layer)
  792. except ValueError:
  793. # happens when closing dialog of a new layer which was
  794. # removed from tree
  795. pass
  796. self._Destroy()
  797. else:
  798. Debug.msg(1, "TaskFrame.OnCancel(): no parent")
  799. # cancel for non-display commands
  800. self._Destroy()
  801. def OnHelp(self, event):
  802. """Show manual page (switch to the 'Manual' notebook page)"""
  803. if self.notebookpanel.notebook.GetPageIndexByName("manual") > -1:
  804. self.notebookpanel.notebook.SetSelectionByName("manual")
  805. self.notebookpanel.OnPageChange(None)
  806. if event:
  807. event.Skip()
  808. def createCmd(self, ignoreErrors=False, ignoreRequired=False):
  809. """Create command string (python list)"""
  810. return self.notebookpanel.createCmd(
  811. ignoreErrors=ignoreErrors, ignoreRequired=ignoreRequired
  812. )
  813. def _Destroy(self):
  814. """Destroy Frame"""
  815. self.notebookpanel.notebook.Unbind(wx.EVT_NOTEBOOK_PAGE_CHANGED)
  816. self.notebookpanel.notebook.widget.Unbind(wx.EVT_NOTEBOOK_PAGE_CHANGED)
  817. self.Destroy()
  818. class CmdPanel(wx.Panel):
  819. """A panel containing a notebook dividing in tabs the different
  820. guisections of the GRASS cmd.
  821. """
  822. def __init__(self, parent, giface, task, id=wx.ID_ANY, frame=None, *args, **kwargs):
  823. if frame:
  824. self.parent = frame
  825. else:
  826. self.parent = parent
  827. self.task = task
  828. self._giface = giface
  829. wx.Panel.__init__(self, parent, id=id, *args, **kwargs)
  830. self.mapCreated = Signal
  831. self.updateMap = Signal
  832. # Determine tab layout
  833. sections = []
  834. is_section = {}
  835. not_hidden = [
  836. p
  837. for p in self.task.params + self.task.flags
  838. if not p.get("hidden", False) is True
  839. ]
  840. self.label_id = [] # wrap titles on resize
  841. self.Bind(wx.EVT_SIZE, self.OnSize)
  842. for task in not_hidden:
  843. if task.get("required", False) and not task.get("guisection", ""):
  844. # All required go into Main, even if they had defined another
  845. # guisection
  846. task["guisection"] = _("Required")
  847. if task.get("guisection", "") == "":
  848. # Undefined guisections end up into Options
  849. task["guisection"] = _("Optional")
  850. if task["guisection"] not in is_section:
  851. # We do it like this to keep the original order, except for
  852. # Main which goes first
  853. is_section[task["guisection"]] = 1
  854. sections.append(task["guisection"])
  855. else:
  856. is_section[task["guisection"]] += 1
  857. del is_section
  858. # 'Required' tab goes first, 'Optional' as the last one
  859. for (newidx, content) in [
  860. (0, _("Required")),
  861. (len(sections) - 1, _("Optional")),
  862. ]:
  863. if content in sections:
  864. idx = sections.index(content)
  865. sections[idx : idx + 1] = []
  866. sections[newidx:newidx] = [content]
  867. panelsizer = wx.BoxSizer(orient=wx.VERTICAL)
  868. # build notebook
  869. style = UserSettings.Get(
  870. group="appearance", key="commandNotebook", subkey="selection"
  871. )
  872. if style == 0: # basic top
  873. self.notebook = FormNotebook(self, style=wx.BK_TOP)
  874. self.notebook.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.OnPageChange)
  875. elif style == 1: # basic left
  876. self.notebook = FormNotebook(self, style=wx.BK_LEFT)
  877. self.notebook.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.OnPageChange)
  878. elif style == 2: # list left
  879. self.notebook = FormListbook(self, style=wx.BK_LEFT)
  880. self.notebook.Bind(wx.EVT_LISTBOOK_PAGE_CHANGED, self.OnPageChange)
  881. self.notebook.Refresh()
  882. tab = {}
  883. tabsizer = {}
  884. for section in sections:
  885. tab[section] = ScrolledPanel(parent=self.notebook)
  886. tab[section].SetScrollRate(10, 10)
  887. tabsizer[section] = wx.BoxSizer(orient=wx.VERTICAL)
  888. #
  889. # flags
  890. #
  891. visible_flags = [
  892. f for f in self.task.flags if not f.get("hidden", False) is True
  893. ]
  894. for f in visible_flags:
  895. # we don't want another help (checkbox appeared in r58783)
  896. if f["name"] == "help":
  897. continue
  898. which_sizer = tabsizer[f["guisection"]]
  899. which_panel = tab[f["guisection"]]
  900. # if label is given: description -> tooltip
  901. if f.get("label", "") != "":
  902. title = text_beautify(f["label"])
  903. tooltip = text_beautify(f["description"], width=-1)
  904. else:
  905. title = text_beautify(f["description"])
  906. tooltip = None
  907. title_sizer = wx.BoxSizer(wx.HORIZONTAL)
  908. rtitle_txt = StaticText(parent=which_panel, label="(" + f["name"] + ")")
  909. chk = CheckBox(parent=which_panel, label=title, style=wx.NO_BORDER)
  910. self.label_id.append(chk.GetId())
  911. if tooltip:
  912. chk.SetToolTip(tooltip)
  913. chk.SetValue(f.get("value", False))
  914. title_sizer.Add(chk, proportion=1, flag=wx.EXPAND)
  915. title_sizer.Add(rtitle_txt, proportion=0, flag=wx.ALIGN_CENTER_VERTICAL)
  916. which_sizer.Add(
  917. title_sizer,
  918. proportion=0,
  919. flag=wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT,
  920. border=5,
  921. )
  922. f["wxId"] = [
  923. chk.GetId(),
  924. ]
  925. chk.Bind(wx.EVT_CHECKBOX, self.OnSetValue)
  926. if self.parent.GetName() == "MainFrame" and (
  927. self._giface and hasattr(self._giface, "_model")
  928. ):
  929. parChk = wx.CheckBox(
  930. parent=which_panel, id=wx.ID_ANY, label=_("Parameterized in model")
  931. )
  932. parChk.SetName("ModelParam")
  933. parChk.SetValue(f.get("parameterized", False))
  934. if "wxId" in f:
  935. f["wxId"].append(parChk.GetId())
  936. else:
  937. f["wxId"] = [parChk.GetId()]
  938. parChk.Bind(wx.EVT_CHECKBOX, self.OnSetValue)
  939. which_sizer.Add(parChk, proportion=0, flag=wx.LEFT, border=20)
  940. if f["name"] in ("verbose", "quiet"):
  941. chk.Bind(wx.EVT_CHECKBOX, self.OnVerbosity)
  942. vq = UserSettings.Get(group="cmd", key="verbosity", subkey="selection")
  943. if f["name"] == vq:
  944. chk.SetValue(True)
  945. f["value"] = True
  946. if f["name"] == "overwrite":
  947. value = UserSettings.Get(group="cmd", key="overwrite", subkey="enabled")
  948. if value: # override only when enabled
  949. f["value"] = value
  950. chk.SetValue(f["value"])
  951. #
  952. # parameters
  953. #
  954. visible_params = [
  955. p for p in self.task.params if not p.get("hidden", False) is True
  956. ]
  957. try:
  958. first_param = visible_params[0]
  959. except IndexError:
  960. first_param = None
  961. for p in visible_params:
  962. which_sizer = tabsizer[p["guisection"]]
  963. which_panel = tab[p["guisection"]]
  964. # if label is given -> label and description -> tooltip
  965. # otherwise description -> lavel
  966. if p.get("label", "") != "":
  967. title = text_beautify(p["label"])
  968. tooltip = text_beautify(p["description"], width=-1)
  969. else:
  970. title = text_beautify(p["description"])
  971. tooltip = None
  972. prompt = p.get("prompt", "")
  973. # title sizer (description, name, type)
  974. if (
  975. (len(p.get("values", [])) > 0)
  976. and p.get("multiple", False)
  977. and p.get("gisprompt", False) is False
  978. and p.get("type", "") == "string"
  979. ):
  980. title_txt = StaticBox(parent=which_panel, id=wx.ID_ANY)
  981. else:
  982. title_sizer = wx.BoxSizer(wx.HORIZONTAL)
  983. title_txt = StaticText(parent=which_panel)
  984. if p["key_desc"]:
  985. ltype = ",".join(p["key_desc"])
  986. else:
  987. ltype = p["type"]
  988. # red star for required options
  989. if p.get("required", False):
  990. required_txt = StaticText(parent=which_panel, label="*")
  991. required_txt.SetForegroundColour(wx.RED)
  992. required_txt.SetToolTip(_("This option is required"))
  993. else:
  994. required_txt = StaticText(parent=which_panel, label="")
  995. rtitle_txt = StaticText(
  996. parent=which_panel, label="(" + p["name"] + "=" + ltype + ")"
  997. )
  998. title_sizer.Add(
  999. title_txt, proportion=0, flag=wx.LEFT | wx.TOP | wx.EXPAND, border=5
  1000. )
  1001. title_sizer.Add(required_txt, proportion=1, flag=wx.EXPAND, border=0)
  1002. title_sizer.Add(
  1003. rtitle_txt, proportion=0, flag=wx.RIGHT | wx.TOP, border=5
  1004. )
  1005. which_sizer.Add(title_sizer, proportion=0, flag=wx.EXPAND)
  1006. self.label_id.append(title_txt.GetId())
  1007. # title expansion
  1008. if p.get("multiple", False) and len(p.get("values", "")) == 0:
  1009. title = _("[multiple]") + " " + title
  1010. if p.get("value", "") == "":
  1011. p["value"] = p.get("default", "")
  1012. if len(p.get("values", [])) > 0:
  1013. valuelist = list(map(str, p.get("values", [])))
  1014. valuelist_desc = list(map(unicode, p.get("values_desc", [])))
  1015. required_text = "*" if p.get("required", False) else ""
  1016. if (
  1017. p.get("multiple", False)
  1018. and p.get("gisprompt", False) is False
  1019. and p.get("type", "") == "string"
  1020. ):
  1021. title_txt.SetLabel(
  1022. " %s:%s (%s=%s) "
  1023. % (title, required_text, p["name"], p["type"])
  1024. )
  1025. stSizer = wx.StaticBoxSizer(box=title_txt, orient=wx.VERTICAL)
  1026. if valuelist_desc:
  1027. hSizer = wx.FlexGridSizer(cols=1, vgap=1, hgap=1)
  1028. else:
  1029. hSizer = wx.FlexGridSizer(cols=6, vgap=1, hgap=1)
  1030. isEnabled = {}
  1031. # copy default values
  1032. if p["value"] == "":
  1033. p["value"] = p.get("default", "")
  1034. for defval in p.get("value", "").split(","):
  1035. isEnabled[defval] = "yes"
  1036. # for multi checkboxes, this is an array of all wx IDs
  1037. # for each individual checkbox
  1038. p["wxId"] = list()
  1039. idx = 0
  1040. for val in valuelist:
  1041. try:
  1042. label = valuelist_desc[idx]
  1043. except IndexError:
  1044. label = val
  1045. chkbox = wx.CheckBox(
  1046. parent=which_panel, label=text_beautify(label)
  1047. )
  1048. p["wxId"].append(chkbox.GetId())
  1049. if val in isEnabled:
  1050. chkbox.SetValue(True)
  1051. hSizer.Add(chkbox, proportion=0)
  1052. chkbox.Bind(wx.EVT_CHECKBOX, self.OnUpdateSelection)
  1053. chkbox.Bind(wx.EVT_CHECKBOX, self.OnCheckBoxMulti)
  1054. idx += 1
  1055. stSizer.Add(
  1056. hSizer, proportion=0, flag=wx.ADJUST_MINSIZE | wx.ALL, border=1
  1057. )
  1058. which_sizer.Add(
  1059. stSizer,
  1060. proportion=0,
  1061. flag=wx.EXPAND | wx.TOP | wx.RIGHT | wx.LEFT,
  1062. border=5,
  1063. )
  1064. elif p.get("gisprompt", False) is False:
  1065. if len(valuelist) == 1: # -> textctrl
  1066. title_txt.SetLabel(
  1067. "%s (%s %s):" % (title, _("valid range"), str(valuelist[0]))
  1068. )
  1069. if p.get("type", "") == "integer" and not p.get(
  1070. "multiple", False
  1071. ):
  1072. # for multiple integers use textctrl instead of
  1073. # spinsctrl
  1074. try:
  1075. minValue, maxValue = list(
  1076. map(int, valuelist[0].rsplit("-", 1))
  1077. )
  1078. except ValueError:
  1079. minValue = -1e6
  1080. maxValue = 1e6
  1081. txt2 = SpinCtrl(
  1082. parent=which_panel,
  1083. id=wx.ID_ANY,
  1084. size=globalvar.DIALOG_SPIN_SIZE,
  1085. min=minValue,
  1086. max=maxValue,
  1087. )
  1088. style = wx.BOTTOM | wx.LEFT
  1089. else:
  1090. if p["name"] in ("at"):
  1091. txt2 = TextCtrl(
  1092. parent=which_panel,
  1093. value=p.get("default", ""),
  1094. validator=PlacementValidator(
  1095. num_of_params=len(p["key_desc"])
  1096. ),
  1097. )
  1098. else:
  1099. txt2 = TextCtrl(
  1100. parent=which_panel, value=p.get("default", "")
  1101. )
  1102. style = wx.EXPAND | wx.BOTTOM | wx.LEFT
  1103. value = self._getValue(p)
  1104. # parameter previously set
  1105. if value:
  1106. if isinstance(txt2, SpinCtrl):
  1107. txt2.SetValue(int(value))
  1108. else:
  1109. txt2.SetValue(value)
  1110. which_sizer.Add(txt2, proportion=0, flag=style, border=5)
  1111. p["wxId"] = [
  1112. txt2.GetId(),
  1113. ]
  1114. txt2.Bind(wx.EVT_TEXT, self.OnSetValue)
  1115. else:
  1116. title_txt.SetLabel(title + ":")
  1117. value = self._getValue(p)
  1118. if p["name"] in ("icon", "icon_area", "icon_line"): # symbols
  1119. bitmap = wx.Bitmap(
  1120. os.path.join(globalvar.SYMBDIR, value) + ".png"
  1121. )
  1122. bb = BitmapButton(
  1123. parent=which_panel, id=wx.ID_ANY, bitmap=bitmap
  1124. )
  1125. iconLabel = StaticText(parent=which_panel, id=wx.ID_ANY)
  1126. iconLabel.SetLabel(value)
  1127. p["value"] = value
  1128. p["wxId"] = [bb.GetId(), iconLabel.GetId()]
  1129. bb.Bind(wx.EVT_BUTTON, self.OnSetSymbol)
  1130. this_sizer = wx.BoxSizer(wx.HORIZONTAL)
  1131. this_sizer.Add(
  1132. bb,
  1133. proportion=0,
  1134. flag=wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT,
  1135. border=5,
  1136. )
  1137. this_sizer.Add(
  1138. iconLabel,
  1139. proportion=0,
  1140. flag=wx.ADJUST_MINSIZE
  1141. | wx.BOTTOM
  1142. | wx.LEFT
  1143. | wx.ALIGN_CENTER_VERTICAL,
  1144. border=5,
  1145. )
  1146. which_sizer.Add(
  1147. this_sizer,
  1148. proportion=0,
  1149. flag=wx.ADJUST_MINSIZE,
  1150. border=0,
  1151. )
  1152. else:
  1153. # list of values (combo)
  1154. cb = wx.ComboBox(
  1155. parent=which_panel,
  1156. id=wx.ID_ANY,
  1157. value=p.get("default", ""),
  1158. size=globalvar.DIALOG_COMBOBOX_SIZE,
  1159. choices=valuelist,
  1160. style=wx.CB_DROPDOWN,
  1161. )
  1162. if value:
  1163. cb.SetValue(value) # parameter previously set
  1164. which_sizer.Add(
  1165. cb,
  1166. proportion=0,
  1167. flag=wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT,
  1168. border=5,
  1169. )
  1170. p["wxId"] = [
  1171. cb.GetId(),
  1172. ]
  1173. cb.Bind(wx.EVT_COMBOBOX, self.OnSetValue)
  1174. cb.Bind(wx.EVT_TEXT, self.OnSetValue)
  1175. if p.get("guidependency", ""):
  1176. cb.Bind(wx.EVT_COMBOBOX, self.OnUpdateSelection)
  1177. # text entry
  1178. if (
  1179. p.get("type", "string") in ("string", "integer", "float")
  1180. and len(p.get("values", [])) == 0
  1181. and p.get("gisprompt", False) is False
  1182. and p.get("prompt", "") != "color"
  1183. ):
  1184. title_txt.SetLabel(title + ":")
  1185. p["wxId"] = []
  1186. if (
  1187. p.get("multiple", False)
  1188. or p.get("type", "string") == "string"
  1189. or len(p.get("key_desc", [])) > 1
  1190. ):
  1191. if p["name"] in ("at"):
  1192. win = TextCtrl(
  1193. parent=which_panel,
  1194. value=p.get("default", ""),
  1195. validator=PlacementValidator(
  1196. num_of_params=len(p["key_desc"])
  1197. ),
  1198. )
  1199. else:
  1200. win = TextCtrl(parent=which_panel, value=p.get("default", ""))
  1201. value = self._getValue(p)
  1202. if value:
  1203. # parameter previously set
  1204. win.SetValue(
  1205. value if p.get("type", "string") == "string" else str(value)
  1206. )
  1207. win.Bind(wx.EVT_TEXT, self.OnSetValue)
  1208. style = wx.EXPAND | wx.BOTTOM | wx.LEFT | wx.RIGHT
  1209. if p.get("name", "") == "font":
  1210. font_btn = Button(parent=which_panel, label=_("Select font"))
  1211. font_btn.Bind(wx.EVT_BUTTON, self.OnSelectFont)
  1212. font_sizer = wx.BoxSizer(wx.HORIZONTAL)
  1213. font_sizer.Add(win, proportion=1, flag=style, border=5)
  1214. font_sizer.Add(font_btn, proportion=0, flag=style, border=5)
  1215. which_sizer.Add(font_sizer, proportion=0, flag=style, border=5)
  1216. p["wxId"].append(font_btn.GetId())
  1217. else:
  1218. which_sizer.Add(win, proportion=0, flag=style, border=5)
  1219. elif p.get("type", "") == "integer":
  1220. minValue = int(-1e9)
  1221. maxValue = int(1e9)
  1222. value = self._getValue(p)
  1223. win = SpinCtrl(
  1224. parent=which_panel,
  1225. value=p.get("default", ""),
  1226. size=globalvar.DIALOG_SPIN_SIZE,
  1227. min=minValue,
  1228. max=maxValue,
  1229. )
  1230. if value:
  1231. win.SetValue(int(value)) # parameter previously set
  1232. win.Bind(wx.EVT_SPINCTRL, self.OnSetValue)
  1233. style = wx.BOTTOM | wx.LEFT | wx.RIGHT
  1234. which_sizer.Add(win, proportion=0, flag=style, border=5)
  1235. else: # float
  1236. win = TextCtrl(
  1237. parent=which_panel,
  1238. value=p.get("default", ""),
  1239. validator=FloatValidator(),
  1240. )
  1241. style = wx.EXPAND | wx.BOTTOM | wx.LEFT | wx.RIGHT
  1242. which_sizer.Add(win, proportion=0, flag=style, border=5)
  1243. value = self._getValue(p)
  1244. if value:
  1245. win.SetValue(str(value)) # parameter previously set
  1246. win.Bind(wx.EVT_TEXT, self.OnSetValue)
  1247. p["wxId"].append(win.GetId())
  1248. #
  1249. # element selection tree combobox (maps, icons, regions, etc.)
  1250. #
  1251. if p.get("gisprompt", False):
  1252. title_txt.SetLabel(title + ":")
  1253. # GIS element entry
  1254. if p.get("prompt", "") not in (
  1255. "color",
  1256. "cat",
  1257. "cats",
  1258. "subgroup",
  1259. "sigfile",
  1260. "separator",
  1261. "dbdriver",
  1262. "dbname",
  1263. "dbtable",
  1264. "dbcolumn",
  1265. "layer",
  1266. "location",
  1267. "mapset",
  1268. "dbase",
  1269. "coords",
  1270. "file",
  1271. "dir",
  1272. "colortable",
  1273. "barscale",
  1274. "northarrow",
  1275. "datasource",
  1276. "datasource_layer",
  1277. "sql_query",
  1278. ):
  1279. multiple = p.get("multiple", False)
  1280. if p.get("age", "") == "new":
  1281. mapsets = [
  1282. grass.gisenv()["MAPSET"],
  1283. ]
  1284. else:
  1285. mapsets = None
  1286. if (
  1287. self.task.name in ("r.proj", "v.proj")
  1288. and p.get("name", "") == "input"
  1289. ):
  1290. selection = gselect.ProjSelect(
  1291. parent=which_panel, isRaster=self.task.name == "r.proj"
  1292. )
  1293. p["wxId"] = [
  1294. selection.GetId(),
  1295. ]
  1296. selection.Bind(wx.EVT_COMBOBOX, self.OnSetValue)
  1297. selection.Bind(wx.EVT_TEXT, self.OnUpdateSelection)
  1298. else:
  1299. elem = p.get("element", None)
  1300. # hack for t.* modules
  1301. if elem in ("stds", "map"):
  1302. orig_elem = elem
  1303. type_param = self.task.get_param(
  1304. "type", element="name", raiseError=False
  1305. )
  1306. if type_param:
  1307. elem = type_param.get("default", None)
  1308. # for t.(un)register:
  1309. maps_param = self.task.get_param(
  1310. "maps", element="name", raiseError=False
  1311. )
  1312. if maps_param and orig_elem == "stds":
  1313. element_dict = {
  1314. "raster": "strds",
  1315. "vector": "stvds",
  1316. "raster_3d": "str3ds",
  1317. }
  1318. elem = element_dict[type_param.get("default")]
  1319. extraItems = None
  1320. if self._giface:
  1321. if hasattr(self._giface, "_model"):
  1322. extraItems = {
  1323. _("Graphical Modeler"): self._giface.GetLayerList(
  1324. p.get("prompt")
  1325. )
  1326. }
  1327. else:
  1328. layers = self._giface.GetLayerList()
  1329. if len(layers) > 0:
  1330. mapList = []
  1331. extraItems = {_("Map Display"): mapList}
  1332. for layer in layers:
  1333. if layer.type != p.get("prompt"):
  1334. continue
  1335. if str(layer):
  1336. mapList.append(str(layer))
  1337. selection = gselect.Select(
  1338. parent=which_panel,
  1339. id=wx.ID_ANY,
  1340. size=globalvar.DIALOG_GSELECT_SIZE,
  1341. type=elem,
  1342. multiple=multiple,
  1343. nmaps=len(p.get("key_desc", [])),
  1344. mapsets=mapsets,
  1345. fullyQualified=p.get("age", "old") == "old",
  1346. extraItems=extraItems,
  1347. )
  1348. value = self._getValue(p)
  1349. if value:
  1350. selection.SetValue(value)
  1351. formatSelector = True
  1352. # A gselect.Select is a combobox with two children: a textctl and a popupwindow;
  1353. # we target the textctl here
  1354. textWin = selection.GetTextCtrl()
  1355. if globalvar.CheckWxVersion([3]):
  1356. p["wxId"] = [
  1357. selection.GetId(),
  1358. ]
  1359. else:
  1360. p["wxId"] = [
  1361. textWin.GetId(),
  1362. ]
  1363. if prompt != "vector":
  1364. self.FindWindowById(p["wxId"][0]).Bind(
  1365. wx.EVT_TEXT, self.OnSetValue
  1366. )
  1367. if prompt == "vector":
  1368. win = self.FindWindowById(p["wxId"][0])
  1369. # handlers should be bound in this order
  1370. # OnUpdateSelection depends on calling OnSetValue first
  1371. # which is bad
  1372. win.Bind(wx.EVT_TEXT, self.OnUpdateSelection)
  1373. win.Bind(wx.EVT_TEXT, self.OnSetValue)
  1374. # if formatSelector and p.get('age', 'old') == 'old':
  1375. # # OGR supported (read-only)
  1376. # self.hsizer = wx.BoxSizer(wx.HORIZONTAL)
  1377. # self.hsizer.Add(item = selection,
  1378. # flag = wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT | wx.RIGHT | wx.TOP | wx.ALIGN_TOP,
  1379. # border = 5)
  1380. # # format (native / ogr)
  1381. # rbox = wx.RadioBox(parent = which_panel, id = wx.ID_ANY,
  1382. # label = " %s " % _("Format"),
  1383. # style = wx.RA_SPECIFY_ROWS,
  1384. # choices = [_("Native / Linked OGR"), _("Direct OGR")])
  1385. # if p.get('value', '').lower().rfind('@ogr') > -1:
  1386. # rbox.SetSelection(1)
  1387. # rbox.SetName('VectorFormat')
  1388. # rbox.Bind(wx.EVT_RADIOBOX, self.OnVectorFormat)
  1389. # self.hsizer.Add(item = rbox,
  1390. # flag = wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT |
  1391. # wx.RIGHT | wx.ALIGN_TOP,
  1392. # border = 5)
  1393. # ogrSelection = gselect.GdalSelect(parent = self, panel = which_panel, ogr = True,
  1394. # default = 'dir',
  1395. # exclude = ['file'])
  1396. # self.Bind(gselect.EVT_GDALSELECT, self.OnUpdateSelection)
  1397. # self.Bind(gselect.EVT_GDALSELECT, self.OnSetValue)
  1398. # ogrSelection.SetName('OgrSelect')
  1399. # ogrSelection.Hide()
  1400. # which_sizer.Add(item = self.hsizer, proportion = 0)
  1401. # p['wxId'].append(rbox.GetId())
  1402. # p['wxId'].append(ogrSelection.GetId())
  1403. # for win in ogrSelection.GetDsnWin():
  1404. # p['wxId'].append(win.GetId())
  1405. # else:
  1406. which_sizer.Add(
  1407. selection,
  1408. proportion=0,
  1409. flag=wx.ADJUST_MINSIZE
  1410. | wx.BOTTOM
  1411. | wx.LEFT
  1412. | wx.RIGHT
  1413. | wx.TOP,
  1414. border=5,
  1415. )
  1416. elif prompt == "group":
  1417. win = self.FindWindowById(p["wxId"][0])
  1418. win.Bind(wx.EVT_TEXT, self.OnUpdateSelection)
  1419. win.Bind(wx.EVT_TEXT, self.OnSetValue)
  1420. which_sizer.Add(
  1421. selection,
  1422. proportion=0,
  1423. flag=wx.ADJUST_MINSIZE
  1424. | wx.BOTTOM
  1425. | wx.LEFT
  1426. | wx.RIGHT
  1427. | wx.TOP,
  1428. border=5,
  1429. )
  1430. else:
  1431. if prompt in ("stds", "strds", "stvds", "str3ds"):
  1432. showButton = True
  1433. try:
  1434. # if matplotlib is there
  1435. from timeline import frame
  1436. showButton = True
  1437. except ImportError:
  1438. showButton = False
  1439. else:
  1440. showButton = False
  1441. if showButton:
  1442. iconTheme = UserSettings.Get(
  1443. group="appearance", key="iconTheme", subkey="type"
  1444. )
  1445. bitmap = wx.Bitmap(
  1446. os.path.join(
  1447. globalvar.ICONDIR, iconTheme, "map-info.png"
  1448. )
  1449. )
  1450. bb = BitmapButton(parent=which_panel, bitmap=bitmap)
  1451. bb.Bind(wx.EVT_BUTTON, self.OnTimelineTool)
  1452. bb.SetToolTip(
  1453. _(
  1454. "Show graphical representation of temporal extent of dataset(s) ."
  1455. )
  1456. )
  1457. p["wxId"].append(bb.GetId())
  1458. hSizer = wx.BoxSizer(wx.HORIZONTAL)
  1459. hSizer.Add(
  1460. selection,
  1461. proportion=0,
  1462. flag=wx.ADJUST_MINSIZE
  1463. | wx.BOTTOM
  1464. | wx.LEFT
  1465. | wx.RIGHT
  1466. | wx.TOP,
  1467. border=5,
  1468. )
  1469. hSizer.Add(
  1470. bb,
  1471. proportion=0,
  1472. flag=wx.EXPAND | wx.BOTTOM | wx.RIGHT | wx.TOP,
  1473. border=5,
  1474. )
  1475. which_sizer.Add(hSizer)
  1476. else:
  1477. which_sizer.Add(
  1478. selection,
  1479. proportion=0,
  1480. flag=wx.ADJUST_MINSIZE
  1481. | wx.BOTTOM
  1482. | wx.LEFT
  1483. | wx.RIGHT
  1484. | wx.TOP,
  1485. border=5,
  1486. )
  1487. # subgroup
  1488. elif prompt == "subgroup":
  1489. selection = gselect.SubGroupSelect(parent=which_panel)
  1490. p["wxId"] = [selection.GetId()]
  1491. selection.Bind(wx.EVT_TEXT, self.OnUpdateSelection)
  1492. selection.Bind(wx.EVT_TEXT, self.OnSetValue)
  1493. which_sizer.Add(
  1494. selection,
  1495. proportion=0,
  1496. flag=wx.ADJUST_MINSIZE
  1497. | wx.BOTTOM
  1498. | wx.LEFT
  1499. | wx.RIGHT
  1500. | wx.TOP,
  1501. border=5,
  1502. )
  1503. # sigrature file
  1504. elif prompt == "sigfile":
  1505. if p.get("age", "") == "new":
  1506. mapsets = [
  1507. grass.gisenv()["MAPSET"],
  1508. ]
  1509. else:
  1510. mapsets = None
  1511. selection = gselect.SignatureSelect(
  1512. parent=which_panel,
  1513. element=p.get("element", "sig"),
  1514. mapsets=mapsets,
  1515. )
  1516. p["wxId"] = [selection.GetId()]
  1517. selection.Bind(wx.EVT_TEXT, self.OnSetValue)
  1518. selection.Bind(wx.EVT_COMBOBOX, self.OnSetValue)
  1519. which_sizer.Add(
  1520. selection,
  1521. proportion=0,
  1522. flag=wx.ADJUST_MINSIZE
  1523. | wx.BOTTOM
  1524. | wx.LEFT
  1525. | wx.RIGHT
  1526. | wx.TOP,
  1527. border=5,
  1528. )
  1529. # separator
  1530. elif prompt == "separator":
  1531. win = gselect.SeparatorSelect(parent=which_panel)
  1532. value = self._getValue(p)
  1533. win.SetValue(value)
  1534. p["wxId"] = [win.GetId()]
  1535. win.Bind(wx.EVT_TEXT, self.OnSetValue)
  1536. win.Bind(wx.EVT_COMBOBOX, self.OnSetValue)
  1537. which_sizer.Add(
  1538. win,
  1539. proportion=0,
  1540. flag=wx.ADJUST_MINSIZE
  1541. | wx.BOTTOM
  1542. | wx.LEFT
  1543. | wx.RIGHT
  1544. | wx.TOP,
  1545. border=5,
  1546. )
  1547. # layer, dbdriver, dbname, dbcolumn, dbtable entry
  1548. elif prompt in (
  1549. "dbdriver",
  1550. "dbname",
  1551. "dbtable",
  1552. "dbcolumn",
  1553. "layer",
  1554. "location",
  1555. "mapset",
  1556. "dbase",
  1557. ):
  1558. if p.get("multiple", "no") == "yes":
  1559. win = TextCtrl(
  1560. parent=which_panel,
  1561. value=p.get("default", ""),
  1562. size=globalvar.DIALOG_TEXTCTRL_SIZE,
  1563. )
  1564. win.Bind(wx.EVT_TEXT, self.OnSetValue)
  1565. else:
  1566. value = self._getValue(p)
  1567. if prompt == "layer":
  1568. if p.get("element", "layer") == "layer_all":
  1569. all = True
  1570. else:
  1571. all = False
  1572. if p.get("age", "old") == "old":
  1573. win = gselect.LayerSelect(
  1574. parent=which_panel, all=all, default=p["default"]
  1575. )
  1576. win.Bind(wx.EVT_TEXT, self.OnUpdateSelection)
  1577. win.Bind(wx.EVT_TEXT, self.OnSetValue)
  1578. win.SetValue(
  1579. str(value)
  1580. ) # default or previously set value
  1581. else:
  1582. win = SpinCtrl(
  1583. parent=which_panel,
  1584. id=wx.ID_ANY,
  1585. min=1,
  1586. max=100,
  1587. initial=int(p["default"]),
  1588. )
  1589. win.Bind(wx.EVT_SPINCTRL, self.OnSetValue)
  1590. win.SetValue(
  1591. int(value)
  1592. ) # default or previously set value
  1593. p["wxId"] = [win.GetId()]
  1594. elif prompt == "dbdriver":
  1595. win = gselect.DriverSelect(
  1596. parent=which_panel,
  1597. choices=p.get("values", []),
  1598. value=value,
  1599. )
  1600. win.Bind(wx.EVT_COMBOBOX, self.OnUpdateSelection)
  1601. win.Bind(wx.EVT_COMBOBOX, self.OnSetValue)
  1602. elif prompt == "dbname":
  1603. win = gselect.DatabaseSelect(
  1604. parent=which_panel, value=value
  1605. )
  1606. win.Bind(wx.EVT_TEXT, self.OnUpdateSelection)
  1607. win.Bind(wx.EVT_TEXT, self.OnSetValue)
  1608. elif prompt == "dbtable":
  1609. if p.get("age", "old") == "old":
  1610. win = gselect.TableSelect(parent=which_panel)
  1611. win.Bind(wx.EVT_COMBOBOX, self.OnUpdateSelection)
  1612. win.Bind(wx.EVT_COMBOBOX, self.OnSetValue)
  1613. else:
  1614. win = TextCtrl(
  1615. parent=which_panel,
  1616. value=p.get("default", ""),
  1617. size=globalvar.DIALOG_TEXTCTRL_SIZE,
  1618. )
  1619. win.Bind(wx.EVT_TEXT, self.OnSetValue)
  1620. elif prompt == "dbcolumn":
  1621. win = gselect.ColumnSelect(
  1622. parent=which_panel,
  1623. value=value,
  1624. param=p,
  1625. multiple=p.get("multiple", False),
  1626. )
  1627. # A gselect.ColumnSelect is a combobox
  1628. # with two children: a textctl and a
  1629. # popupwindow; we target the textctl here
  1630. textWin = win.GetTextCtrl()
  1631. p["wxId"] = [
  1632. textWin.GetId(),
  1633. ]
  1634. textWin.Bind(wx.EVT_TEXT, self.OnSetValue)
  1635. win.Bind(wx.EVT_TEXT, self.OnUpdateSelection)
  1636. elif prompt == "location":
  1637. win = gselect.LocationSelect(
  1638. parent=which_panel, value=value
  1639. )
  1640. win.Bind(wx.EVT_TEXT, self.OnUpdateSelection)
  1641. win.Bind(wx.EVT_COMBOBOX, self.OnUpdateSelection)
  1642. win.Bind(wx.EVT_TEXT, self.OnSetValue)
  1643. win.Bind(wx.EVT_COMBOBOX, self.OnSetValue)
  1644. elif prompt == "mapset":
  1645. if p.get("age", "old") == "old":
  1646. new = False
  1647. else:
  1648. new = True
  1649. win = gselect.MapsetSelect(
  1650. parent=which_panel,
  1651. value=value,
  1652. new=new,
  1653. multiple=p.get("multiple", False),
  1654. )
  1655. win.Bind(wx.EVT_TEXT, self.OnUpdateSelection)
  1656. win.Bind(wx.EVT_COMBOBOX, self.OnUpdateSelection)
  1657. win.Bind(wx.EVT_TEXT, self.OnSetValue)
  1658. win.Bind(wx.EVT_COMBOBOX, self.OnSetValue)
  1659. elif prompt == "dbase":
  1660. win = gselect.DbaseSelect(
  1661. parent=which_panel, changeCallback=self.OnSetValue
  1662. )
  1663. win.Bind(wx.EVT_TEXT, self.OnUpdateSelection)
  1664. p["wxId"] = [win.GetChildren()[1].GetId()]
  1665. if "wxId" not in p:
  1666. try:
  1667. p["wxId"] = [
  1668. win.GetId(),
  1669. ]
  1670. except AttributeError:
  1671. pass
  1672. flags = wx.BOTTOM | wx.LEFT | wx.RIGHT
  1673. if prompt == "dbname":
  1674. flags |= wx.EXPAND
  1675. which_sizer.Add(win, proportion=0, flag=flags, border=5)
  1676. # color entry
  1677. elif prompt == "color":
  1678. default_color = (200, 200, 200)
  1679. label_color = _("Select Color")
  1680. if p.get("default", "") != "":
  1681. default_color, label_color = utils.color_resolve(p["default"])
  1682. if (
  1683. p.get("value", "") != "" and p.get("value", "") != "none"
  1684. ): # parameter previously set
  1685. if not p.get("multiple", False):
  1686. default_color, label_color = utils.color_resolve(p["value"])
  1687. if p.get("element", "") == "color_none" or p.get("multiple", False):
  1688. this_sizer = wx.BoxSizer(orient=wx.HORIZONTAL)
  1689. else:
  1690. this_sizer = which_sizer
  1691. colorSize = 150
  1692. # For color selectors, this is a three-member array, holding the IDs of
  1693. # the color picker, the text control for multiple colors (or None),
  1694. # and either a "transparent" checkbox or None
  1695. p["wxId"] = [None] * 3
  1696. if p.get("multiple", False):
  1697. txt = TextCtrl(parent=which_panel, id=wx.ID_ANY)
  1698. this_sizer.Add(
  1699. txt,
  1700. proportion=1,
  1701. flag=wx.ADJUST_MINSIZE | wx.LEFT | wx.TOP,
  1702. border=5,
  1703. )
  1704. txt.Bind(wx.EVT_TEXT, self.OnSetValue)
  1705. if p.get("value", ""):
  1706. txt.SetValue(p["value"])
  1707. colorSize = 40
  1708. label_color = ""
  1709. p["wxId"][1] = txt.GetId()
  1710. which_sizer.Add(this_sizer, flag=wx.EXPAND | wx.RIGHT, border=5)
  1711. btn_colour = csel.ColourSelect(
  1712. parent=which_panel,
  1713. id=wx.ID_ANY,
  1714. label=label_color,
  1715. colour=default_color,
  1716. pos=wx.DefaultPosition,
  1717. size=(colorSize, 32),
  1718. )
  1719. this_sizer.Add(
  1720. btn_colour,
  1721. proportion=0,
  1722. flag=wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT,
  1723. border=5,
  1724. )
  1725. btn_colour.Bind(csel.EVT_COLOURSELECT, self.OnColorChange)
  1726. p["wxId"][0] = btn_colour.GetId()
  1727. if p.get("element", "") == "color_none":
  1728. none_check = wx.CheckBox(
  1729. which_panel, wx.ID_ANY, _("Transparent")
  1730. )
  1731. if p.get("value", "") == "none":
  1732. none_check.SetValue(True)
  1733. else:
  1734. none_check.SetValue(False)
  1735. this_sizer.Add(
  1736. none_check,
  1737. proportion=0,
  1738. flag=wx.ADJUST_MINSIZE | wx.LEFT | wx.RIGHT | wx.TOP,
  1739. border=5,
  1740. )
  1741. which_sizer.Add(this_sizer)
  1742. none_check.Bind(wx.EVT_CHECKBOX, self.OnColorChange)
  1743. p["wxId"][2] = none_check.GetId()
  1744. # file selector
  1745. elif p.get("prompt", "") != "color" and p.get("prompt", "") == "file":
  1746. if p.get("age", "new") == "new":
  1747. fmode = wx.FD_SAVE
  1748. else:
  1749. fmode = wx.FD_OPEN
  1750. # check wildcard
  1751. try:
  1752. fExt = os.path.splitext(p.get("key_desc", ["*.*"])[0])[1]
  1753. except:
  1754. fExt = None
  1755. if not fExt:
  1756. fMask = "*"
  1757. else:
  1758. fMask = "%s files (*%s)|*%s|Files (*)|*" % (
  1759. fExt[1:].upper(),
  1760. fExt,
  1761. fExt,
  1762. )
  1763. fbb = filebrowse.FileBrowseButton(
  1764. parent=which_panel,
  1765. id=wx.ID_ANY,
  1766. fileMask=fMask,
  1767. size=globalvar.DIALOG_GSELECT_SIZE,
  1768. labelText="",
  1769. dialogTitle=_("Choose %s")
  1770. % p.get("description", _("file")).lower(),
  1771. buttonText=_("Browse"),
  1772. startDirectory=os.getcwd(),
  1773. fileMode=fmode,
  1774. changeCallback=self.OnSetValue,
  1775. )
  1776. value = self._getValue(p)
  1777. if value:
  1778. fbb.SetValue(value) # parameter previously set
  1779. which_sizer.Add(
  1780. fbb, proportion=0, flag=wx.EXPAND | wx.RIGHT, border=5
  1781. )
  1782. # A file browse button is a combobox with two children:
  1783. # a textctl and a button;
  1784. # we have to target the button here
  1785. p["wxId"] = [fbb.GetChildren()[1].GetId()]
  1786. if (
  1787. p.get("age", "new") == "old"
  1788. and p.get("prompt", "") == "file"
  1789. and p.get("element", "") == "file"
  1790. and UserSettings.Get(
  1791. group="cmd", key="interactiveInput", subkey="enabled"
  1792. )
  1793. ):
  1794. # widget for interactive input
  1795. ifbb = TextCtrl(
  1796. parent=which_panel,
  1797. id=wx.ID_ANY,
  1798. style=wx.TE_MULTILINE,
  1799. size=(-1, 75),
  1800. )
  1801. if p.get("value", "") and os.path.isfile(p["value"]):
  1802. ifbb.Clear()
  1803. enc = locale.getdefaultlocale()[1]
  1804. with codecs.open(
  1805. p["value"], encoding=enc, errors="ignore"
  1806. ) as f:
  1807. nonascii = bytearray(range(0x80, 0x100))
  1808. for line in f.readlines():
  1809. try:
  1810. ifbb.AppendText(line)
  1811. except UnicodeDecodeError:
  1812. # remove non-ascii characters on encoding mismatch (file vs OS)
  1813. ifbb.AppendText(line.translate(None, nonascii))
  1814. ifbb.SetInsertionPoint(0)
  1815. ifbb.Bind(wx.EVT_TEXT, self.OnFileText)
  1816. btnLoad = Button(
  1817. parent=which_panel, id=wx.ID_ANY, label=_("&Load")
  1818. )
  1819. btnLoad.SetToolTip(_("Load and edit content of a file"))
  1820. btnLoad.Bind(wx.EVT_BUTTON, self.OnFileLoad)
  1821. btnSave = Button(
  1822. parent=which_panel, id=wx.ID_ANY, label=_("&Save as")
  1823. )
  1824. btnSave.SetToolTip(_("Save content to a file for further use"))
  1825. btnSave.Bind(wx.EVT_BUTTON, self.OnFileSave)
  1826. fileContentLabel = StaticText(
  1827. parent=which_panel,
  1828. id=wx.ID_ANY,
  1829. label=_("or enter values directly:"),
  1830. )
  1831. fileContentLabel.SetToolTip(
  1832. _(
  1833. "Enter file content directly instead of specifying"
  1834. " a file."
  1835. " Temporary file will be automatically created."
  1836. )
  1837. )
  1838. which_sizer.Add(
  1839. fileContentLabel,
  1840. proportion=0,
  1841. flag=wx.EXPAND | wx.RIGHT | wx.LEFT | wx.BOTTOM,
  1842. border=5,
  1843. )
  1844. which_sizer.Add(
  1845. ifbb,
  1846. proportion=1,
  1847. flag=wx.EXPAND | wx.RIGHT | wx.LEFT,
  1848. border=5,
  1849. )
  1850. btnSizer = wx.BoxSizer(wx.HORIZONTAL)
  1851. btnSizer.Add(btnLoad, proportion=0, flag=wx.RIGHT, border=10)
  1852. btnSizer.Add(btnSave, proportion=0)
  1853. which_sizer.Add(
  1854. btnSizer,
  1855. proportion=0,
  1856. flag=wx.ALIGN_RIGHT | wx.RIGHT | wx.TOP,
  1857. border=5,
  1858. )
  1859. p["wxId"].append(ifbb.GetId())
  1860. p["wxId"].append(btnLoad.GetId())
  1861. p["wxId"].append(btnSave.GetId())
  1862. # directory selector
  1863. elif p.get("prompt", "") != "color" and p.get("prompt", "") == "dir":
  1864. fbb = filebrowse.DirBrowseButton(
  1865. parent=which_panel,
  1866. id=wx.ID_ANY,
  1867. size=globalvar.DIALOG_GSELECT_SIZE,
  1868. labelText="",
  1869. dialogTitle=_("Choose %s")
  1870. % p.get("description", _("Directory")),
  1871. buttonText=_("Browse"),
  1872. startDirectory=os.getcwd(),
  1873. newDirectory=True,
  1874. changeCallback=self.OnSetValue,
  1875. )
  1876. value = self._getValue(p)
  1877. if value:
  1878. fbb.SetValue(value) # parameter previously set
  1879. which_sizer.Add(
  1880. fbb, proportion=0, flag=wx.EXPAND | wx.RIGHT, border=5
  1881. )
  1882. # A file browse button is a combobox with two children:
  1883. # a textctl and a button;
  1884. # we have to target the button here
  1885. p["wxId"] = [fbb.GetChildren()[1].GetId()]
  1886. # interactive inserting of coordinates from map window
  1887. elif prompt == "coords":
  1888. # interactive inserting if layer manager is accessible
  1889. if self._giface:
  1890. win = gselect.CoordinatesSelect(
  1891. parent=which_panel,
  1892. giface=self._giface,
  1893. multiple=p.get("multiple", False),
  1894. param=p,
  1895. )
  1896. p["wxId"] = [win.GetTextWin().GetId()]
  1897. win.GetTextWin().Bind(wx.EVT_TEXT, self.OnSetValue)
  1898. # bind closing event because destructor is not working
  1899. # properly
  1900. if hasattr(self.parent, "dialogClosing"):
  1901. self.parent.dialogClosing.connect(win.OnClose)
  1902. # normal text field
  1903. else:
  1904. win = TextCtrl(parent=which_panel)
  1905. p["wxId"] = [win.GetId()]
  1906. win.Bind(wx.EVT_TEXT, self.OnSetValue)
  1907. which_sizer.Add(
  1908. win,
  1909. proportion=0,
  1910. flag=wx.EXPAND | wx.BOTTOM | wx.LEFT | wx.RIGHT,
  1911. border=5,
  1912. )
  1913. elif prompt in ("cat", "cats"):
  1914. # interactive selection of vector categories if layer
  1915. # manager is accessible
  1916. if self._giface:
  1917. win = gselect.VectorCategorySelect(
  1918. parent=which_panel, giface=self._giface, task=self.task
  1919. )
  1920. p["wxId"] = [win.GetTextWin().GetId()]
  1921. win.GetTextWin().Bind(wx.EVT_TEXT, self.OnSetValue)
  1922. # bind closing event because destructor is not working
  1923. # properly
  1924. if hasattr(self.parent, "dialogClosing"):
  1925. self.parent.dialogClosing.connect(win.OnClose)
  1926. # normal text field
  1927. else:
  1928. win = TextCtrl(parent=which_panel)
  1929. value = self._getValue(p)
  1930. win.SetValue(value)
  1931. p["wxId"] = [win.GetId()]
  1932. win.Bind(wx.EVT_TEXT, self.OnSetValue)
  1933. which_sizer.Add(
  1934. win,
  1935. proportion=0,
  1936. flag=wx.EXPAND | wx.BOTTOM | wx.LEFT | wx.RIGHT,
  1937. border=5,
  1938. )
  1939. elif prompt in ("colortable", "barscale", "northarrow"):
  1940. if prompt == "colortable":
  1941. cb = ColorTablesComboBox(
  1942. parent=which_panel,
  1943. value=p.get("default", ""),
  1944. size=globalvar.DIALOG_COMBOBOX_SIZE,
  1945. choices=valuelist,
  1946. )
  1947. elif prompt == "barscale":
  1948. cb = BarscalesComboBox(
  1949. parent=which_panel,
  1950. value=p.get("default", ""),
  1951. size=globalvar.DIALOG_COMBOBOX_SIZE,
  1952. choices=valuelist,
  1953. )
  1954. elif prompt == "northarrow":
  1955. cb = NArrowsComboBox(
  1956. parent=which_panel,
  1957. value=p.get("default", ""),
  1958. size=globalvar.DIALOG_COMBOBOX_SIZE,
  1959. choices=valuelist,
  1960. )
  1961. value = self._getValue(p)
  1962. if value:
  1963. cb.SetValue(value) # parameter previously set
  1964. which_sizer.Add(
  1965. cb,
  1966. proportion=0,
  1967. flag=wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT,
  1968. border=5,
  1969. )
  1970. p["wxId"] = [cb.GetId(), cb.GetTextCtrl().GetId()]
  1971. cb.Bind(wx.EVT_COMBOBOX, self.OnSetValue)
  1972. cb.GetTextCtrl().Bind(wx.EVT_TEXT, self.OnSetValue)
  1973. if p.get("guidependency", ""):
  1974. cb.Bind(wx.EVT_COMBOBOX, self.OnUpdateSelection)
  1975. elif prompt == "datasource":
  1976. win = gselect.GdalSelect(parent=parent, panel=which_panel, ogr=True)
  1977. win.Bind(wx.EVT_TEXT, self.OnSetValue)
  1978. win.Bind(wx.EVT_CHOICE, self.OnSetValue)
  1979. p["wxId"] = [
  1980. win.GetId(),
  1981. win.fileWidgets["browse"].GetChildren()[1].GetId(),
  1982. win.dirWidgets["browse"].GetChildren()[1].GetId(),
  1983. win.dbWidgets["choice"].GetId(),
  1984. ]
  1985. value = self._getValue(p)
  1986. if value:
  1987. win.fileWidgets["browse"].GetChildren()[1].SetValue(
  1988. value
  1989. ) # parameter previously set
  1990. which_sizer.Add(win, proportion=0, flag=wx.EXPAND)
  1991. elif prompt == "datasource_layer":
  1992. self.win1 = LayersList(
  1993. parent=which_panel,
  1994. columns=[
  1995. _("Layer id"),
  1996. _("Layer name"),
  1997. _("Feature type"),
  1998. _("Projection match"),
  1999. ],
  2000. )
  2001. which_sizer.Add(
  2002. self.win1, proportion=0, flag=wx.EXPAND | wx.ALL, border=3
  2003. )
  2004. porf = self.task.get_param(
  2005. "input", element="name", raiseError=False
  2006. )
  2007. if porf and "wxId" in porf:
  2008. winDataSource = self.FindWindowById(porf["wxId"][0])
  2009. winDataSource.reloadDataRequired.connect(
  2010. lambda listData: self.win1.LoadData(listData, False)
  2011. )
  2012. p["wxId"] = [self.win1.GetId()]
  2013. def OnCheckItem(index, flag):
  2014. layers = list()
  2015. geometry = None
  2016. for layer, match, listId in self.win1.GetLayers():
  2017. if "|" in layer:
  2018. layer, geometry = layer.split("|", 1)
  2019. layers.append(layer)
  2020. porf = self.task.get_param(
  2021. "layer", element="name", raiseError=False
  2022. )
  2023. porf["value"] = ",".join(layers)
  2024. # geometry is currently discarded
  2025. # TODO: v.import has no geometry option
  2026. self.OnUpdateValues() # TODO: replace by signal
  2027. self.win1.OnCheckItem = OnCheckItem
  2028. elif prompt == "sql_query":
  2029. win = gselect.SqlWhereSelect(parent=which_panel, param=p)
  2030. p["wxId"] = [win.GetTextWin().GetId()]
  2031. win.GetTextWin().Bind(wx.EVT_TEXT, self.OnSetValue)
  2032. which_sizer.Add(
  2033. win,
  2034. proportion=0,
  2035. flag=wx.EXPAND | wx.BOTTOM | wx.LEFT | wx.RIGHT,
  2036. border=5,
  2037. )
  2038. if self.parent.GetName() == "MainFrame" and (
  2039. self._giface and hasattr(self._giface, "_model")
  2040. ):
  2041. parChk = wx.CheckBox(
  2042. parent=which_panel, id=wx.ID_ANY, label=_("Parameterized in model")
  2043. )
  2044. parChk.SetName("ModelParam")
  2045. parChk.SetValue(p.get("parameterized", False))
  2046. if "wxId" in p:
  2047. p["wxId"].append(parChk.GetId())
  2048. else:
  2049. p["wxId"] = [parChk.GetId()]
  2050. parChk.Bind(wx.EVT_CHECKBOX, self.OnSetValue)
  2051. which_sizer.Add(parChk, proportion=0, flag=wx.LEFT, border=20)
  2052. if title_txt is not None:
  2053. # create tooltip if given
  2054. if len(p["values_desc"]) > 0:
  2055. if tooltip:
  2056. tooltip += 2 * os.linesep
  2057. else:
  2058. tooltip = ""
  2059. if len(p["values"]) == len(p["values_desc"]):
  2060. for i in range(len(p["values"])):
  2061. tooltip += (
  2062. p["values"][i] + ": " + p["values_desc"][i] + os.linesep
  2063. )
  2064. tooltip.strip(os.linesep)
  2065. if tooltip:
  2066. title_txt.SetToolTip(tooltip)
  2067. if p == first_param:
  2068. if "wxId" in p and len(p["wxId"]) > 0:
  2069. win = self.FindWindowById(p["wxId"][0])
  2070. win.SetFocus()
  2071. #
  2072. # set widget relations for OnUpdateSelection
  2073. #
  2074. pMap = None
  2075. pLayer = []
  2076. pDriver = None
  2077. pDatabase = None
  2078. pTable = None
  2079. pColumn = []
  2080. pGroup = None
  2081. pSubGroup = None
  2082. pDbase = None
  2083. pLocation = None
  2084. pMapset = None
  2085. pSqlWhere = []
  2086. for p in self.task.params:
  2087. if (
  2088. self.task.blackList["enabled"]
  2089. and self.task.get_name() in self.task.blackList["items"]
  2090. and p.get("name", "")
  2091. in self.task.blackList["items"][self.task.get_name()]["params"]
  2092. ):
  2093. continue
  2094. guidep = p.get("guidependency", "")
  2095. if guidep:
  2096. # fixed options dependency defined
  2097. options = guidep.split(",")
  2098. for opt in options:
  2099. pOpt = self.task.get_param(opt, element="name", raiseError=False)
  2100. if pOpt and id:
  2101. if "wxId-bind" not in p:
  2102. p["wxId-bind"] = list()
  2103. p["wxId-bind"] += pOpt["wxId"]
  2104. continue
  2105. if p.get("gisprompt", False) is False:
  2106. continue
  2107. prompt = p.get("prompt", "")
  2108. if prompt in ("raster", "vector"):
  2109. name = p.get("name", "")
  2110. if name in ("map", "input"):
  2111. pMap = p
  2112. elif prompt == "layer":
  2113. pLayer.append(p)
  2114. elif prompt == "dbcolumn":
  2115. pColumn.append(p)
  2116. elif prompt == "dbdriver":
  2117. pDriver = p
  2118. elif prompt == "dbname":
  2119. pDatabase = p
  2120. elif prompt == "dbtable":
  2121. pTable = p
  2122. elif prompt == "group":
  2123. pGroup = p
  2124. elif prompt == "subgroup":
  2125. pSubGroup = p
  2126. elif prompt == "dbase":
  2127. pDbase = p
  2128. elif prompt == "location":
  2129. pLocation = p
  2130. elif prompt == "mapset":
  2131. pMapset = p
  2132. elif prompt == "sql_query":
  2133. pSqlWhere.append(p)
  2134. # collect ids
  2135. pColumnIds = []
  2136. for p in pColumn:
  2137. pColumnIds += p["wxId"]
  2138. pLayerIds = []
  2139. for p in pLayer:
  2140. pLayerIds += p["wxId"]
  2141. pSqlWhereIds = []
  2142. for p in pSqlWhere:
  2143. pSqlWhereIds += p["wxId"]
  2144. # set wxId-bindings
  2145. if pMap:
  2146. pMap["wxId-bind"] = []
  2147. if pLayer:
  2148. pMap["wxId-bind"] += pLayerIds
  2149. pMap["wxId-bind"] += copy.copy(pColumnIds)
  2150. pMap["wxId-bind"] += copy.copy(pSqlWhereIds)
  2151. if pLayer:
  2152. for p in pLayer:
  2153. p["wxId-bind"] = copy.copy(pColumnIds)
  2154. p["wxId-bind"] += copy.copy(pSqlWhereIds)
  2155. if pDriver and pTable:
  2156. pDriver["wxId-bind"] = pTable["wxId"]
  2157. if pDatabase and pTable:
  2158. pDatabase["wxId-bind"] = pTable["wxId"]
  2159. if pTable and pColumnIds:
  2160. pTable["wxId-bind"] = pColumnIds
  2161. if pGroup and pSubGroup:
  2162. pGroup["wxId-bind"] = pSubGroup["wxId"]
  2163. if pDbase and pLocation:
  2164. pDbase["wxId-bind"] = pLocation["wxId"]
  2165. if pLocation and pMapset:
  2166. pLocation["wxId-bind"] = pMapset["wxId"]
  2167. if pLocation and pMapset and pMap:
  2168. # pLocation['wxId-bind'] += pMap['wxId']
  2169. pMapset["wxId-bind"] = pMap["wxId"]
  2170. #
  2171. # determine panel size
  2172. #
  2173. maxsizes = (0, 0)
  2174. for section in sections:
  2175. tab[section].SetSizer(tabsizer[section])
  2176. tab[section].SetupScrolling(True, True, 10, 10)
  2177. tab[section].Layout()
  2178. minsecsizes = tabsizer[section].GetSize()
  2179. maxsizes = list(map(lambda x: max(maxsizes[x], minsecsizes[x]), (0, 1)))
  2180. # TODO: be less arbitrary with these 600
  2181. self.panelMinHeight = 100
  2182. self.constrained_size = (min(600, maxsizes[0]) + 25, min(400, maxsizes[1]) + 25)
  2183. for section in sections:
  2184. tab[section].SetMinSize((self.constrained_size[0], self.panelMinHeight))
  2185. # add pages to notebook
  2186. imageList = wx.ImageList(16, 16)
  2187. self.notebook.AssignImageList(imageList)
  2188. for section in sections:
  2189. self.notebook.AddPage(page=tab[section], text=section, name=section)
  2190. index = self.AddBitmapToImageList(section, imageList)
  2191. if index >= 0:
  2192. self.notebook.SetPageImage(section, index)
  2193. # are we running from command line?
  2194. # add 'command output' tab regardless standalone dialog
  2195. if self.parent.GetName() == "MainFrame" and self.parent.get_dcmd is None:
  2196. from core.gconsole import GConsole, EVT_CMD_RUN, EVT_CMD_DONE
  2197. from gui_core.goutput import GConsoleWindow
  2198. self._gconsole = GConsole(guiparent=self.notebook, giface=self._giface)
  2199. self.goutput = GConsoleWindow(
  2200. parent=self.notebook,
  2201. giface=self._giface,
  2202. gconsole=self._gconsole,
  2203. margin=False,
  2204. )
  2205. self._gconsole.Bind(
  2206. EVT_CMD_RUN,
  2207. lambda event: self._switchPageHandler(
  2208. event=event, notification=Notification.MAKE_VISIBLE
  2209. ),
  2210. )
  2211. self._gconsole.Bind(
  2212. EVT_CMD_DONE,
  2213. lambda event: self._switchPageHandler(
  2214. event=event, notification=Notification.RAISE_WINDOW
  2215. ),
  2216. )
  2217. self.outpage = self.notebook.AddPage(
  2218. page=self.goutput, text=_("Command output"), name="output"
  2219. )
  2220. else:
  2221. self.goutput = None
  2222. self._gconsole = None
  2223. self.manualTab = HelpPanel(parent=self.notebook, command=self.task.get_name())
  2224. if not self.manualTab.GetFile():
  2225. self.manualTab.Hide()
  2226. else:
  2227. self.notebook.AddPage(page=self.manualTab, text=_("Manual"), name="manual")
  2228. index = self.AddBitmapToImageList(section="manual", imageList=imageList)
  2229. if index >= 0:
  2230. self.notebook.SetPageImage("manual", index)
  2231. if self.manualTab.IsLoaded():
  2232. self.manualTab.SetMinSize((self.constrained_size[0], self.panelMinHeight))
  2233. self.notebook.SetSelection(0)
  2234. panelsizer.Add(self.notebook, proportion=1, flag=wx.EXPAND)
  2235. self.SetSizer(panelsizer)
  2236. panelsizer.Fit(self.notebook)
  2237. self.Bind(EVT_DIALOG_UPDATE, self.OnUpdateDialog)
  2238. def _getValue(self, p):
  2239. """Get value or default value of given parameter
  2240. :param p: parameter directory
  2241. """
  2242. if p.get("value", "") != "":
  2243. return p["value"]
  2244. return p.get("default", "")
  2245. def OnFileLoad(self, event):
  2246. """Load file to interactive input"""
  2247. me = event.GetId()
  2248. win = dict()
  2249. for p in self.task.params:
  2250. if "wxId" in p and me in p["wxId"]:
  2251. win["file"] = self.FindWindowById(p["wxId"][0])
  2252. win["text"] = self.FindWindowById(p["wxId"][1])
  2253. break
  2254. if not win:
  2255. return
  2256. path = win["file"].GetValue()
  2257. if not path:
  2258. gcmd.GMessage(parent=self, message=_("Nothing to load."))
  2259. return
  2260. data = ""
  2261. try:
  2262. f = open(path, "r")
  2263. except IOError as e:
  2264. gcmd.GError(
  2265. parent=self,
  2266. showTraceback=False,
  2267. message=_("Unable to load file.\n\nReason: %s") % e,
  2268. )
  2269. return
  2270. try:
  2271. data = f.read()
  2272. finally:
  2273. f.close()
  2274. win["text"].SetValue(data)
  2275. def OnFileSave(self, event):
  2276. """Save interactive input to the file"""
  2277. wId = event.GetId()
  2278. win = {}
  2279. for p in self.task.params:
  2280. if wId in p.get("wxId", []):
  2281. win["file"] = self.FindWindowById(p["wxId"][0])
  2282. win["text"] = self.FindWindowById(p["wxId"][1])
  2283. break
  2284. if not win:
  2285. return
  2286. text = win["text"].GetValue()
  2287. if not text:
  2288. gcmd.GMessage(parent=self, message=_("Nothing to save."))
  2289. return
  2290. dlg = wx.FileDialog(
  2291. parent=self,
  2292. message=_("Save input as..."),
  2293. defaultDir=os.getcwd(),
  2294. style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT,
  2295. )
  2296. if dlg.ShowModal() == wx.ID_OK:
  2297. path = dlg.GetPath()
  2298. enc = locale.getdefaultlocale()[1]
  2299. f = codecs.open(path, encoding=enc, mode="w", errors="replace")
  2300. try:
  2301. f.write(text + os.linesep)
  2302. finally:
  2303. f.close()
  2304. win["file"].SetValue(path)
  2305. dlg.Destroy()
  2306. def OnFileText(self, event):
  2307. """File input interactively entered"""
  2308. text = event.GetString()
  2309. p = self.task.get_param(value=event.GetId(), element="wxId", raiseError=False)
  2310. if not p:
  2311. return # should not happen
  2312. win = self.FindWindowById(p["wxId"][0])
  2313. if text:
  2314. filename = win.GetValue()
  2315. if not filename or filename == p["default"]: # m.proj has - as default
  2316. filename = grass.tempfile()
  2317. win.SetValue(filename)
  2318. enc = locale.getdefaultlocale()[1]
  2319. f = codecs.open(filename, encoding=enc, mode="w", errors="replace")
  2320. try:
  2321. f.write(text)
  2322. if text[-1] != os.linesep:
  2323. f.write(os.linesep)
  2324. finally:
  2325. f.close()
  2326. else:
  2327. win.SetValue("")
  2328. def OnVectorFormat(self, event):
  2329. """Change vector format (native / ogr).
  2330. Currently unused.
  2331. """
  2332. sel = event.GetSelection()
  2333. idEvent = event.GetId()
  2334. p = self.task.get_param(value=idEvent, element="wxId", raiseError=False)
  2335. if not p:
  2336. return # should not happen
  2337. # detect windows
  2338. winNative = None
  2339. winOgr = None
  2340. for id in p["wxId"]:
  2341. if id == idEvent:
  2342. continue
  2343. name = self.FindWindowById(id).GetName()
  2344. if name == "Select":
  2345. # fix the mystery (also in nviz_tools.py)
  2346. winNative = self.FindWindowById(id + 1)
  2347. elif name == "OgrSelect":
  2348. winOgr = self.FindWindowById(id)
  2349. # enable / disable widgets & update values
  2350. rbox = self.FindWindowByName("VectorFormat")
  2351. self.hsizer.Remove(rbox)
  2352. if sel == 0: # -> native
  2353. winOgr.Hide()
  2354. self.hsizer.Remove(winOgr)
  2355. self.hsizer.Add(
  2356. winNative,
  2357. flag=wx.ADJUST_MINSIZE
  2358. | wx.BOTTOM
  2359. | wx.LEFT
  2360. | wx.RIGHT
  2361. | wx.TOP
  2362. | wx.ALIGN_TOP,
  2363. border=5,
  2364. )
  2365. winNative.Show()
  2366. p["value"] = winNative.GetValue()
  2367. elif sel == 1: # -> OGR
  2368. sizer = wx.BoxSizer(wx.VERTICAL)
  2369. winNative.Hide()
  2370. self.hsizer.Remove(winNative)
  2371. sizer.Add(winOgr)
  2372. winOgr.Show()
  2373. p["value"] = winOgr.GetDsn()
  2374. self.hsizer.Add(
  2375. sizer,
  2376. flag=wx.ADJUST_MINSIZE
  2377. | wx.BOTTOM
  2378. | wx.LEFT
  2379. | wx.RIGHT
  2380. | wx.TOP
  2381. | wx.ALIGN_TOP,
  2382. border=5,
  2383. )
  2384. self.hsizer.Add(
  2385. rbox,
  2386. flag=wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT | wx.RIGHT | wx.ALIGN_TOP,
  2387. border=5,
  2388. )
  2389. self.hsizer.Layout()
  2390. self.Layout()
  2391. self.OnUpdateValues()
  2392. self.OnUpdateSelection(event)
  2393. def OnUpdateDialog(self, event):
  2394. for fn, kwargs in six.iteritems(event.data):
  2395. fn(**kwargs)
  2396. self.parent.updateValuesHook()
  2397. def OnVerbosity(self, event):
  2398. """Verbosity level changed"""
  2399. verbose = self.FindWindowById(self.task.get_flag("verbose")["wxId"][0])
  2400. quiet = self.FindWindowById(self.task.get_flag("quiet")["wxId"][0])
  2401. if event.IsChecked():
  2402. if event.GetId() == verbose.GetId():
  2403. if quiet.IsChecked():
  2404. quiet.SetValue(False)
  2405. self.task.get_flag("quiet")["value"] = False
  2406. else:
  2407. if verbose.IsChecked():
  2408. verbose.SetValue(False)
  2409. self.task.get_flag("verbose")["value"] = False
  2410. event.Skip()
  2411. def OnPageChange(self, event):
  2412. if not event:
  2413. sel = self.notebook.GetSelection()
  2414. else:
  2415. sel = event.GetSelection()
  2416. idx = self.notebook.GetPageIndexByName("manual")
  2417. if idx > -1 and sel == idx:
  2418. # calling LoadPage() is strangely time-consuming (only first call)
  2419. # FIXME: move to helpPage.__init__()
  2420. if not self.manualTab.IsLoaded():
  2421. wx.GetApp().Yield()
  2422. self.manualTab.LoadPage()
  2423. self.Layout()
  2424. if event:
  2425. # skip is needed for wx.Notebook on Windows
  2426. event.Skip()
  2427. # this is needed for dialogs launched from layer manager
  2428. # event is somehow propagated?
  2429. event.StopPropagation()
  2430. def _switchPageHandler(self, event, notification):
  2431. self._switchPage(notification=notification)
  2432. event.Skip()
  2433. def _switchPage(self, notification):
  2434. """Manages @c 'output' notebook page according to event notification."""
  2435. if notification == Notification.HIGHLIGHT:
  2436. self.notebook.HighlightPageByName("output")
  2437. if notification == Notification.MAKE_VISIBLE:
  2438. self.notebook.SetSelectionByName("output")
  2439. if notification == Notification.RAISE_WINDOW:
  2440. self.notebook.SetSelectionByName("output")
  2441. self.SetFocus()
  2442. self.Raise()
  2443. def OnColorChange(self, event):
  2444. myId = event.GetId()
  2445. for p in self.task.params:
  2446. if "wxId" in p and myId in p["wxId"]:
  2447. multiple = p["wxId"][1] is not None # multiple colors
  2448. hasTansp = p["wxId"][2] is not None
  2449. if multiple:
  2450. # selected color is added at the end of textCtrl
  2451. colorchooser = wx.FindWindowById(p["wxId"][0])
  2452. new_color = colorchooser.GetValue()[:]
  2453. new_label = utils.rgb2str.get(
  2454. new_color, ":".join(list(map(str, new_color)))
  2455. )
  2456. textCtrl = wx.FindWindowById(p["wxId"][1])
  2457. val = textCtrl.GetValue()
  2458. sep = ","
  2459. if val and val[-1] != sep:
  2460. val += sep
  2461. val += new_label
  2462. textCtrl.SetValue(val)
  2463. p["value"] = val
  2464. elif hasTansp and wx.FindWindowById(p["wxId"][2]).GetValue():
  2465. p["value"] = "none"
  2466. else:
  2467. colorchooser = wx.FindWindowById(p["wxId"][0])
  2468. new_color = colorchooser.GetValue()[:]
  2469. # This is weird: new_color is a 4-tuple and new_color[:] is a 3-tuple
  2470. # under wx2.8.1
  2471. new_label = utils.rgb2str.get(
  2472. new_color, ":".join(list(map(str, new_color)))
  2473. )
  2474. colorchooser.SetLabel(new_label)
  2475. colorchooser.SetColour(new_color)
  2476. colorchooser.Refresh()
  2477. p["value"] = colorchooser.GetLabel()
  2478. self.OnUpdateValues()
  2479. def OnUpdateValues(self, event=None):
  2480. """If we were part of a richer interface, report back the
  2481. current command being built.
  2482. This method should be set by the parent of this panel if
  2483. needed. It's a hook, actually. Beware of what is 'self' in
  2484. the method def, though. It will be called with no arguments.
  2485. """
  2486. pass
  2487. def OnCheckBoxMulti(self, event):
  2488. """Fill the values as a ','-separated string according to
  2489. current status of the checkboxes.
  2490. """
  2491. me = event.GetId()
  2492. theParam = None
  2493. for p in self.task.params:
  2494. if "wxId" in p and me in p["wxId"]:
  2495. theParam = p
  2496. myIndex = p["wxId"].index(me)
  2497. # Unpack current value list
  2498. currentValues = {}
  2499. for isThere in theParam.get("value", "").split(","):
  2500. currentValues[isThere] = 1
  2501. theValue = theParam["values"][myIndex]
  2502. if event.IsChecked():
  2503. currentValues[theValue] = 1
  2504. else:
  2505. del currentValues[theValue]
  2506. # Keep the original order, so that some defaults may be recovered
  2507. currentValueList = []
  2508. for v in theParam["values"]:
  2509. if v in currentValues:
  2510. currentValueList.append(v)
  2511. # Pack it back
  2512. theParam["value"] = ",".join(currentValueList)
  2513. self.OnUpdateValues()
  2514. event.Skip()
  2515. def OnSetValue(self, event):
  2516. """Retrieve the widget value and set the task value field
  2517. accordingly.
  2518. Use for widgets that have a proper GetValue() method, i.e. not
  2519. for selectors.
  2520. """
  2521. myId = event.GetId()
  2522. me = wx.FindWindowById(myId)
  2523. name = me.GetName()
  2524. found = False
  2525. for porf in self.task.params + self.task.flags:
  2526. if "wxId" not in porf:
  2527. continue
  2528. if myId in porf["wxId"]:
  2529. found = True
  2530. break
  2531. if not found:
  2532. return
  2533. if name == "GdalSelect":
  2534. porf["value"] = event.dsn
  2535. elif name == "ModelParam":
  2536. porf["parameterized"] = me.IsChecked()
  2537. elif name == "GdalSelectDataSource":
  2538. win = self.FindWindowById(porf["wxId"][0])
  2539. porf["value"] = win.GetDsn()
  2540. pLayer = self.task.get_param("layer", element="name", raiseError=False)
  2541. if pLayer:
  2542. pLayer["value"] = ""
  2543. else:
  2544. if isinstance(me, SpinCtrl):
  2545. porf["value"] = str(me.GetValue())
  2546. elif isinstance(me, wx.ComboBox):
  2547. porf["value"] = me.GetValue()
  2548. elif isinstance(me, wx.Choice):
  2549. porf["value"] = me.GetStringSelection()
  2550. else:
  2551. porf["value"] = me.GetValue()
  2552. self.OnUpdateValues(event)
  2553. event.Skip()
  2554. def OnSetSymbol(self, event):
  2555. """Shows dialog for symbol selection"""
  2556. myId = event.GetId()
  2557. for p in self.task.params:
  2558. if "wxId" in p and myId in p["wxId"]:
  2559. from gui_core.dialogs import SymbolDialog
  2560. dlg = SymbolDialog(
  2561. self, symbolPath=globalvar.SYMBDIR, currentSymbol=p["value"]
  2562. )
  2563. if dlg.ShowModal() == wx.ID_OK:
  2564. img = dlg.GetSelectedSymbolPath()
  2565. p["value"] = dlg.GetSelectedSymbolName()
  2566. bitmapButton = wx.FindWindowById(p["wxId"][0])
  2567. label = wx.FindWindowById(p["wxId"][1])
  2568. bitmapButton.SetBitmapLabel(wx.Bitmap(img + ".png"))
  2569. label.SetLabel(p["value"])
  2570. self.OnUpdateValues(event)
  2571. dlg.Destroy()
  2572. def OnTimelineTool(self, event):
  2573. """Show Timeline Tool with dataset(s) from gselect.
  2574. .. todo::
  2575. update from gselect automatically
  2576. """
  2577. myId = event.GetId()
  2578. for p in self.task.params:
  2579. if "wxId" in p and myId in p["wxId"]:
  2580. select = self.FindWindowById(p["wxId"][0])
  2581. if not select.GetValue():
  2582. gcmd.GMessage(parent=self, message=_("No dataset given."))
  2583. return
  2584. datasets = select.GetValue().split(",")
  2585. from timeline import frame
  2586. frame.run(parent=self, datasets=datasets)
  2587. def OnSelectFont(self, event):
  2588. """Select font using font dialog"""
  2589. myId = event.GetId()
  2590. for p in self.task.params:
  2591. if "wxId" in p and myId in p["wxId"]:
  2592. from gui_core.dialogs import DefaultFontDialog
  2593. dlg = DefaultFontDialog(
  2594. parent=self,
  2595. title=_("Select font"),
  2596. style=wx.DEFAULT_DIALOG_STYLE,
  2597. type="font",
  2598. )
  2599. if dlg.ShowModal() == wx.ID_OK:
  2600. if dlg.font:
  2601. p["value"] = dlg.font
  2602. self.FindWindowById(p["wxId"][1]).SetValue(dlg.font)
  2603. self.OnUpdateValues(event)
  2604. dlg.Destroy()
  2605. def OnUpdateSelection(self, event):
  2606. """Update dialog (layers, tables, columns, etc.)"""
  2607. if not hasattr(self.parent, "updateThread"):
  2608. if event:
  2609. event.Skip()
  2610. return
  2611. if event:
  2612. self.parent.updateThread.Update(
  2613. UpdateDialog, self, event, event.GetId(), self.task
  2614. )
  2615. else:
  2616. self.parent.updateThread.Update(UpdateDialog, self, None, None, self.task)
  2617. def createCmd(self, ignoreErrors=False, ignoreRequired=False):
  2618. """Produce a command line string (list) or feeding into GRASS.
  2619. :param ignoreErrors: True then it will return whatever has been
  2620. built so far, even though it would not be
  2621. a correct command for GRASS
  2622. """
  2623. try:
  2624. cmd = self.task.get_cmd(
  2625. ignoreErrors=ignoreErrors, ignoreRequired=ignoreRequired
  2626. )
  2627. except ValueError as err:
  2628. dlg = wx.MessageDialog(
  2629. parent=self,
  2630. message=gcmd.DecodeString(str(err)),
  2631. caption=_("Error in %s") % self.task.name,
  2632. style=wx.OK | wx.ICON_ERROR | wx.CENTRE,
  2633. )
  2634. dlg.ShowModal()
  2635. dlg.Destroy()
  2636. cmd = None
  2637. return cmd
  2638. def OnSize(self, event):
  2639. width = event.GetSize()[0]
  2640. fontsize = self.GetFont().GetPointSize()
  2641. text_width = max(width / (fontsize - 3), 70)
  2642. for id in self.label_id:
  2643. win = self.FindWindowById(id)
  2644. label = win.GetLabel()
  2645. label_new = "\n".join(textwrap.wrap(label, text_width))
  2646. win.SetLabel(label_new)
  2647. event.Skip()
  2648. def AddBitmapToImageList(self, section, imageList):
  2649. iconTheme = UserSettings.Get(group="appearance", key="iconTheme", subkey="type")
  2650. iconSectionDict = {
  2651. "manual": os.path.join(globalvar.ICONDIR, iconTheme, "help.png")
  2652. }
  2653. if section in iconSectionDict.keys():
  2654. image = wx.Image(iconSectionDict[section]).Scale(
  2655. 16, 16, wx.IMAGE_QUALITY_HIGH
  2656. )
  2657. idx = imageList.Add(BitmapFromImage(image))
  2658. return idx
  2659. return -1
  2660. class GUI:
  2661. def __init__(
  2662. self,
  2663. parent=None,
  2664. giface=None,
  2665. show=True,
  2666. modal=False,
  2667. centreOnParent=False,
  2668. checkError=False,
  2669. ):
  2670. """Parses GRASS commands when module is imported and used from
  2671. Layer Manager.
  2672. """
  2673. self.parent = parent
  2674. self.show = show
  2675. self.modal = modal
  2676. self._giface = giface
  2677. self.centreOnParent = centreOnParent
  2678. self.checkError = checkError
  2679. self.grass_task = None
  2680. self.cmd = list()
  2681. global _blackList
  2682. if self.parent:
  2683. _blackList["enabled"] = True
  2684. else:
  2685. _blackList["enabled"] = False
  2686. def GetCmd(self):
  2687. """Get validated command"""
  2688. return self.cmd
  2689. def ParseCommand(self, cmd, completed=None):
  2690. """Parse command
  2691. Note: cmd is given as list
  2692. If command is given with options, return validated cmd list:
  2693. - add key name for first parameter if not given
  2694. - change mapname to mapname@mapset
  2695. """
  2696. dcmd_params = {}
  2697. if completed is None:
  2698. get_dcmd = None
  2699. layer = None
  2700. dcmd_params = None
  2701. else:
  2702. get_dcmd = completed[0]
  2703. layer = completed[1]
  2704. if completed[2]:
  2705. dcmd_params.update(completed[2])
  2706. # parse the interface decription
  2707. try:
  2708. global _blackList
  2709. self.grass_task = gtask.parse_interface(cmd[0], blackList=_blackList)
  2710. except (grass.ScriptError, ValueError) as e:
  2711. raise gcmd.GException(e.value)
  2712. # if layer parameters previously set, re-insert them into dialog
  2713. if completed is not None:
  2714. if "params" in dcmd_params:
  2715. self.grass_task.params = dcmd_params["params"]
  2716. if "flags" in dcmd_params:
  2717. self.grass_task.flags = dcmd_params["flags"]
  2718. err = list()
  2719. # update parameters if needed && validate command
  2720. if len(cmd) > 1:
  2721. i = 0
  2722. cmd_validated = [cmd[0]]
  2723. for option in cmd[1:]:
  2724. if option[0] == "-": # flag
  2725. if len(option) == 1: # catch typo like 'g.proj - w'
  2726. raise gcmd.GException(
  2727. _("Unable to parse command '%s'") % " ".join(cmd)
  2728. )
  2729. if option[1] == "-":
  2730. self.grass_task.set_flag(option[2:], True)
  2731. else:
  2732. self.grass_task.set_flag(option[1], True)
  2733. cmd_validated.append(option)
  2734. else: # parameter
  2735. try:
  2736. key, value = option.split("=", 1)
  2737. except ValueError:
  2738. if self.grass_task.firstParam:
  2739. if i == 0: # add key name of first parameter if not given
  2740. key = self.grass_task.firstParam
  2741. value = option
  2742. else:
  2743. raise gcmd.GException(
  2744. _("Unable to parse command '%s'") % " ".join(cmd)
  2745. )
  2746. else:
  2747. continue
  2748. task = self.grass_task.get_param(key, raiseError=False)
  2749. if not task:
  2750. err.append(
  2751. _("%(cmd)s: parameter '%(key)s' not available")
  2752. % {"cmd": cmd[0], "key": key}
  2753. )
  2754. continue
  2755. self.grass_task.set_param(key, value)
  2756. cmd_validated.append(key + "=" + value)
  2757. i += 1
  2758. # update original command list
  2759. cmd = cmd_validated
  2760. if self.show is not None:
  2761. self.mf = TaskFrame(
  2762. parent=self.parent,
  2763. giface=self._giface,
  2764. task_description=self.grass_task,
  2765. get_dcmd=get_dcmd,
  2766. layer=layer,
  2767. )
  2768. else:
  2769. self.mf = None
  2770. if get_dcmd is not None:
  2771. # update only propwin reference
  2772. get_dcmd(dcmd=None, layer=layer, params=None, propwin=self.mf)
  2773. if self.show is not None:
  2774. self.mf.notebookpanel.OnUpdateSelection(None)
  2775. if self.show is True:
  2776. if self.parent and self.centreOnParent:
  2777. self.mf.CentreOnParent()
  2778. else:
  2779. self.mf.CenterOnScreen()
  2780. self.mf.Show(self.show)
  2781. self.mf.MakeModal(self.modal)
  2782. else:
  2783. self.mf.OnApply(None)
  2784. self.cmd = cmd
  2785. if self.checkError:
  2786. return self.grass_task, err
  2787. else:
  2788. return self.grass_task
  2789. def GetCommandInputMapParamKey(self, cmd):
  2790. """Get parameter key for input raster/vector map
  2791. :param cmd: module name
  2792. :return: parameter key
  2793. :return: None on failure
  2794. """
  2795. # parse the interface decription
  2796. if not self.grass_task:
  2797. tree = etree.fromstring(gtask.get_interface_description(cmd))
  2798. self.grass_task = gtask.processTask(tree).get_task()
  2799. for p in self.grass_task.params:
  2800. if p.get("name", "") in ("input", "map"):
  2801. age = p.get("age", "")
  2802. prompt = p.get("prompt", "")
  2803. element = p.get("element", "")
  2804. if (
  2805. age == "old"
  2806. and element in ("cell", "grid3", "vector")
  2807. and prompt in ("raster", "raster_3d", "vector")
  2808. ):
  2809. return p.get("name", None)
  2810. return None
  2811. class GrassGUIApp(wx.App):
  2812. """Stand-alone GRASS command GUI"""
  2813. def __init__(self, grass_task):
  2814. self.grass_task = grass_task
  2815. wx.App.__init__(self, False)
  2816. def OnInit(self):
  2817. msg = self.grass_task.get_error_msg()
  2818. if msg:
  2819. gcmd.GError(
  2820. msg
  2821. + "\n\n"
  2822. + _("Try to set up GRASS_ADDON_PATH or GRASS_ADDON_BASE variable.")
  2823. )
  2824. return True
  2825. self.mf = TaskFrame(
  2826. parent=None,
  2827. giface=StandaloneGrassInterface(),
  2828. task_description=self.grass_task,
  2829. )
  2830. self.mf.CentreOnScreen()
  2831. self.mf.Show(True)
  2832. self.SetTopWindow(self.mf)
  2833. return True
  2834. USAGE_MESSAGE = """Usage:
  2835. {name} <grass module>
  2836. {name} <full path to file>
  2837. python {name} <grass module>
  2838. Test:
  2839. python {name} test
  2840. python {name} g.region
  2841. python {name} "g.region -p"
  2842. python {name} temporal/t.list/t.list.py"""
  2843. if __name__ == "__main__":
  2844. if len(sys.argv) == 1:
  2845. sys.exit(_(USAGE_MESSAGE).format(name=sys.argv[0]))
  2846. if sys.argv[1] != "test":
  2847. q = wx.LogNull()
  2848. Debug.msg(1, "forms.py called using command: %s" % sys.argv[1])
  2849. cmd = utils.split(sys.argv[1])
  2850. task = gtask.grassTask(cmd[0])
  2851. task.set_options(cmd[1:])
  2852. Debug.msg(
  2853. 1,
  2854. "forms.py opening form for: %s"
  2855. % task.get_cmd(ignoreErrors=True, ignoreRequired=True),
  2856. )
  2857. app = GrassGUIApp(task)
  2858. app.MainLoop()
  2859. else: # Test
  2860. # Test grassTask from within a GRASS session
  2861. if os.getenv("GISBASE") is not None:
  2862. task = gtask.grassTask("d.vect")
  2863. task.get_param("map")["value"] = "map_name"
  2864. task.get_flag("i")["value"] = True
  2865. task.get_param("layer")["value"] = 1
  2866. task.get_param("label_bcolor")["value"] = "red"
  2867. # the default parameter display is added automatically
  2868. assert (
  2869. " ".join(task.get_cmd())
  2870. == "d.vect -i map=map_name layer=1 display=shape label_bcolor=red"
  2871. )
  2872. print("Creation of task successful")
  2873. # Test interface building with handmade grassTask,
  2874. # possibly outside of a GRASS session.
  2875. print("Now creating a module dialog (task frame)")
  2876. task = gtask.grassTask()
  2877. task.name = "TestTask"
  2878. task.description = (
  2879. "This is an artificial grassTask() object intended for testing purposes."
  2880. )
  2881. task.keywords = ["grass", "test", "task"]
  2882. task.params = [
  2883. {
  2884. "name": "text",
  2885. "description": "Descriptions go into tooltips if labels are present, like this one",
  2886. "label": "Enter some text",
  2887. "key_desc": ["value"],
  2888. "values_desc": [],
  2889. },
  2890. {
  2891. "name": "hidden_text",
  2892. "description": "This text should not appear in the form",
  2893. "hidden": True,
  2894. "key_desc": ["value"],
  2895. "values_desc": [],
  2896. },
  2897. {
  2898. "name": "text_default",
  2899. "description": "Enter text to override the default",
  2900. "default": "default text",
  2901. "key_desc": ["value"],
  2902. "values_desc": [],
  2903. },
  2904. {
  2905. "name": "text_prefilled",
  2906. "description": "You should see a friendly welcome message here",
  2907. "value": "hello, world",
  2908. "key_desc": ["value"],
  2909. "values_desc": [],
  2910. },
  2911. {
  2912. "name": "plain_color",
  2913. "description": "This is a plain color, and it is a compulsory parameter",
  2914. "required": False,
  2915. "gisprompt": True,
  2916. "prompt": "color",
  2917. "key_desc": ["value"],
  2918. "values_desc": [],
  2919. },
  2920. {
  2921. "name": "transparent_color",
  2922. "description": "This color becomes transparent when set to none",
  2923. "guisection": "tab",
  2924. "gisprompt": True,
  2925. "prompt": "color",
  2926. "key_desc": ["value"],
  2927. "values_desc": [],
  2928. },
  2929. {
  2930. "name": "multi",
  2931. "description": "A multiple selection",
  2932. "default": "red,green,blue",
  2933. "gisprompt": False,
  2934. "guisection": "tab",
  2935. "multiple": "yes",
  2936. "type": "string",
  2937. "value": "",
  2938. "values": ["red", "green", "yellow", "blue", "purple", "other"],
  2939. "key_desc": ["value"],
  2940. "values_desc": [],
  2941. },
  2942. {
  2943. "name": "single",
  2944. "description": "A single multiple-choice selection",
  2945. "values": ["red", "green", "yellow", "blue", "purple", "other"],
  2946. "guisection": "tab",
  2947. "key_desc": ["value"],
  2948. "values_desc": [],
  2949. },
  2950. {
  2951. "name": "large_multi",
  2952. "description": "A large multiple selection",
  2953. "gisprompt": False,
  2954. "multiple": "yes",
  2955. # values must be an array of strings
  2956. "values": utils.str2rgb.keys() + list(map(str, utils.str2rgb.values())),
  2957. "key_desc": ["value"],
  2958. "values_desc": [],
  2959. },
  2960. {
  2961. "name": "a_file",
  2962. "description": "A file selector",
  2963. "gisprompt": True,
  2964. "element": "file",
  2965. "key_desc": ["value"],
  2966. "values_desc": [],
  2967. },
  2968. ]
  2969. task.flags = [
  2970. {
  2971. "name": "a",
  2972. "description": "Some flag, will appear in Main since it is required",
  2973. "required": True,
  2974. "value": False,
  2975. "suppress_required": False,
  2976. },
  2977. {
  2978. "name": "b",
  2979. "description": "pre-filled flag, will appear in options since it is not required",
  2980. "value": True,
  2981. "suppress_required": False,
  2982. },
  2983. {
  2984. "name": "hidden_flag",
  2985. "description": "hidden flag, should not be changeable",
  2986. "hidden": "yes",
  2987. "value": True,
  2988. "suppress_required": False,
  2989. },
  2990. ]
  2991. q = wx.LogNull()
  2992. GrassGUIApp(task).MainLoop()