gselect.py 96 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907
  1. """
  2. @package gui_core.gselect
  3. @brief Custom control that selects elements
  4. Classes:
  5. - :class:`Select`
  6. - :class:`VectorSelect`
  7. - :class:`ListCtrlComboPopup`
  8. - :class:`TreeCrtlComboPopup`
  9. - :class:`VectorDBInfo`
  10. - :class:`LayerSelect`
  11. - :class:`DriverSelect`
  12. - :class:`DatabaseSelect`
  13. - :class:`TableSelect`
  14. - :class:`ColumnSelect`
  15. - :class:`DbaseSelect`
  16. - :class:`LocationSelect`
  17. - :class:`MapsetSelect`
  18. - :class:`SubGroupSelect`
  19. - :class:`FormatSelect`
  20. - :class:`GdalSelect`
  21. - :class:`ProjSelect`
  22. - :class:`ElementSelect`
  23. - :class:`OgrTypeSelect`
  24. - :class:`CoordinatesSelect`
  25. - :class:`VectorCategorySelect`
  26. - :class:`SignatureSelect`
  27. - :class:`SeparatorSelect`
  28. - :class:`SqlWhereSelect`
  29. (C) 2007-2018 by the GRASS Development Team
  30. This program is free software under the GNU General Public License
  31. (>=v2). Read the file COPYING that comes with GRASS for details.
  32. @author Michael Barton
  33. @author Martin Landa <landa.martin gmail.com>
  34. @author Vaclav Petras <wenzeslaus gmail.com> (menu customization)
  35. @author Stepan Turek <stepan.turek seznam.cz> (CoordinatesSelect, ListCtrlComboPopup)
  36. @author Matej Krejci <matejkrejci gmail.com> (VectorCategorySelect)
  37. """
  38. from __future__ import print_function
  39. import os
  40. import sys
  41. import glob
  42. import six
  43. import ctypes
  44. import wx
  45. from core import globalvar
  46. import wx.lib.buttons as buttons
  47. import wx.lib.filebrowsebutton as filebrowse
  48. import grass.script as grass
  49. from grass.script import task as gtask
  50. from grass.exceptions import CalledModuleError
  51. from gui_core.widgets import ManageSettingsWidget, CoordinatesValidator
  52. from core.gcmd import RunCommand, GMessage, GWarning, GException
  53. from core.utils import (
  54. GetListOfLocations,
  55. GetListOfMapsets,
  56. GetFormats,
  57. rasterFormatExtension,
  58. vectorFormatExtension,
  59. )
  60. from core.utils import GetSettingsPath, GetValidLayerName, ListSortLower
  61. from core.utils import GetVectorNumberOfLayers
  62. from core.settings import UserSettings
  63. from core.debug import Debug
  64. from gui_core.vselect import VectorSelectBase
  65. from gui_core.wrap import (
  66. TreeCtrl,
  67. Button,
  68. StaticText,
  69. StaticBox,
  70. TextCtrl,
  71. Panel,
  72. ComboPopup,
  73. ComboCtrl,
  74. )
  75. from grass.pydispatch.signal import Signal
  76. class Select(ComboCtrl):
  77. def __init__(
  78. self,
  79. parent,
  80. id=wx.ID_ANY,
  81. size=globalvar.DIALOG_GSELECT_SIZE,
  82. type=None,
  83. multiple=False,
  84. nmaps=1,
  85. mapsets=None,
  86. updateOnPopup=True,
  87. onPopup=None,
  88. fullyQualified=True,
  89. extraItems={},
  90. layerTree=None,
  91. validator=wx.DefaultValidator,
  92. ):
  93. """Custom control to create a ComboBox with a tree control to
  94. display and select GIS elements within acessible mapsets.
  95. Elements can be selected with mouse. Can allow multiple
  96. selections, when argument <em>multiple</em> is True. Multiple
  97. selections are separated by commas.
  98. :param type: type of GIS elements ('raster, 'vector', ...)
  99. :param multiple: True for multiple input
  100. :param nmaps: number of maps to be entered
  101. :param mapsets: force list of mapsets (otherwise search path)
  102. :param updateOnPopup: True for updating list of elements on popup
  103. :param onPopup: function to be called on Popup
  104. :param fullyQualified: True to provide fully qualified names (map@mapset)
  105. :param extraItems: extra items to add (given as dictionary) - see gmodeler for usage
  106. :param layerTree: show only elements from given layer tree if not None
  107. :param validator: validator for TextCtrl
  108. """
  109. ComboCtrl.__init__(self, parent=parent, id=id, size=size, validator=validator)
  110. if globalvar.CheckWxVersion([3]):
  111. self.SetName("Select")
  112. else:
  113. self.GetChildren()[0].SetName("Select")
  114. self.GetChildren()[0].type = type
  115. self.tcp = TreeCtrlComboPopup()
  116. self.SetPopupControl(self.tcp)
  117. self.SetPopupExtents(0, 100)
  118. if type:
  119. self.tcp.SetData(
  120. type=type,
  121. mapsets=mapsets,
  122. multiple=multiple,
  123. nmaps=nmaps,
  124. updateOnPopup=updateOnPopup,
  125. onPopup=onPopup,
  126. fullyQualified=fullyQualified,
  127. extraItems=extraItems,
  128. layerTree=layerTree,
  129. )
  130. self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
  131. def OnKeyDown(self, event):
  132. """Open popup and send key events to the tree."""
  133. if self.IsPopupShown():
  134. # on some configurations we get key down, with some only key up
  135. # so we are trying to catch either key up or down
  136. # this mess shouldn't be necessary with wxPython 3
  137. self.tcp.OnKeyUp(event)
  138. else:
  139. if event.GetKeyCode() == wx.WXK_DOWN:
  140. self.ShowPopup()
  141. event.Skip()
  142. def SetElementList(self, type, mapsets=None):
  143. """Set element list
  144. :param type: GIS element type
  145. :param mapsets: list of acceptable mapsets (None for all in search path)
  146. """
  147. self.tcp.SetData(type=type, mapsets=mapsets)
  148. def GetElementList(self):
  149. """Load elements"""
  150. self.tcp.GetElementList()
  151. def SetType(
  152. self,
  153. etype,
  154. multiple=False,
  155. nmaps=1,
  156. mapsets=None,
  157. updateOnPopup=True,
  158. onPopup=None,
  159. ):
  160. """Param set element type for widget
  161. :param etype: element type, see gselect.ElementSelect
  162. """
  163. self.tcp.SetData(
  164. type=etype,
  165. mapsets=mapsets,
  166. multiple=multiple,
  167. nmaps=nmaps,
  168. updateOnPopup=updateOnPopup,
  169. onPopup=onPopup,
  170. )
  171. class VectorSelect(Select):
  172. def __init__(self, parent, ftype, **kwargs):
  173. """Custom to create a ComboBox with a tree control to display and
  174. select vector maps. You can filter the vector maps. If you
  175. don't need this feature use Select class instead
  176. :param ftype: filter vector maps based on feature type
  177. """
  178. Select.__init__(self, parent=parent, id=wx.ID_ANY, type="vector", **kwargs)
  179. self.ftype = ftype
  180. # remove vector maps which do not contain given feature type
  181. self.tcp.SetFilter(self._isElement)
  182. def _isElement(self, vectorName):
  183. """Check if element should be filtered out"""
  184. try:
  185. if int(grass.vector_info_topo(vectorName)[self.ftype]) < 1:
  186. return False
  187. except KeyError:
  188. return False
  189. return True
  190. class ListCtrlComboPopup(ComboPopup):
  191. """Create a list ComboBox using TreeCtrl with hidden root.
  192. .. todo::
  193. use event.EventObject instead of hardcoding (see forms.py)
  194. https://groups.google.com/forum/#!topic/wxpython-users/pRz6bi0k0XY
  195. """
  196. # overridden ComboPopup methods
  197. def Init(self):
  198. self.value = [] # for multiple is False ->
  199. # len(self.value) in [0,1]
  200. self.curitem = None
  201. self.multiple = False
  202. self.updateOnPopup = True
  203. self.filterItems = [] # limit items based on this list,
  204. # see layerTree parameter
  205. def Create(self, parent):
  206. self.seltree = TreeCtrl(
  207. parent,
  208. style=wx.TR_HIDE_ROOT
  209. | wx.TR_HAS_BUTTONS
  210. | wx.TR_SINGLE
  211. | wx.TR_LINES_AT_ROOT
  212. | wx.SIMPLE_BORDER
  213. | wx.TR_FULL_ROW_HIGHLIGHT,
  214. )
  215. self.seltree.Bind(wx.EVT_MOTION, self.OnMotion)
  216. self.seltree.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
  217. # the following dummy handler are needed to keep tree events
  218. # from propagating up to the parent GIS Manager layer tree
  219. self.seltree.Bind(wx.EVT_TREE_ITEM_EXPANDING, lambda x: None)
  220. self.seltree.Bind(wx.EVT_TREE_ITEM_COLLAPSED, lambda x: None)
  221. self.seltree.Bind(wx.EVT_TREE_SEL_CHANGED, lambda x: None)
  222. self.seltree.Bind(wx.EVT_TREE_DELETE_ITEM, lambda x: None)
  223. self.seltree.Bind(wx.EVT_TREE_BEGIN_DRAG, lambda x: None)
  224. self.seltree.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, lambda x: None)
  225. # navigation in list/tree is handled automatically since wxPython 3
  226. # for older versions, we have to workaround it and write our own
  227. # navigation
  228. if globalvar.CheckWxVersion(version=[3]):
  229. self.seltree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self._onItemConfirmed)
  230. self.seltree.Bind(wx.EVT_TREE_KEY_DOWN, self._onDismissPopup)
  231. else:
  232. self.seltree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, lambda x: None)
  233. self.seltree.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
  234. return True
  235. def GetControl(self):
  236. return self.seltree
  237. def GetComboCtrl(self):
  238. if globalvar.wxPythonPhoenix:
  239. return super(ListCtrlComboPopup, self).GetComboCtrl()
  240. else:
  241. return self.GetCombo()
  242. def GetStringValue(self):
  243. """Get value as a string separated by commas"""
  244. return ",".join(self.value)
  245. def SetStringValue(self, value):
  246. # this assumes that item strings are unique...
  247. root = self.seltree.GetRootItem()
  248. if not root:
  249. return
  250. winValue = self.GetComboCtrl().GetValue().strip(",")
  251. self.value = []
  252. if winValue:
  253. self.value = winValue.split(",")
  254. def OnPopup(self, force=False):
  255. """Limited only for first selected"""
  256. if not force and not self.updateOnPopup:
  257. return
  258. # selects map starting according to written text
  259. inputText = self.GetComboCtrl().GetValue().strip()
  260. if inputText:
  261. root = self.seltree.GetRootItem()
  262. match = self.FindItem(root, inputText, startLetters=True)
  263. if match.IsOk():
  264. self.seltree.EnsureVisible(match)
  265. self.seltree.SelectItem(match)
  266. def GetAdjustedSize(self, minWidth, prefHeight, maxHeight):
  267. """Reads UserSettings to get height (which was 200 in old implementation)."""
  268. height = UserSettings.Get(
  269. group="appearance", key="gSelectPopupHeight", subkey="value"
  270. )
  271. return wx.Size(minWidth, min(height, maxHeight))
  272. def FindItem(self, parentItem, text, startLetters=False):
  273. """Finds item with given name or starting with given text"""
  274. startletters = startLetters
  275. item, cookie = self.seltree.GetFirstChild(parentItem)
  276. while wx.TreeItemId.IsOk(item):
  277. if self.seltree.GetItemText(item) == text:
  278. return item
  279. if self.seltree.ItemHasChildren(item):
  280. item = self.FindItem(item, text, startLetters=startletters)
  281. if wx.TreeItemId.IsOk(item):
  282. return item
  283. elif startletters and self.seltree.GetItemText(item).startswith(
  284. text.split("@", 1)[0]
  285. ):
  286. return item
  287. item, cookie = self.seltree.GetNextChild(parentItem, cookie)
  288. return wx.TreeItemId()
  289. def AddItem(self, value):
  290. root = self.seltree.GetRootItem()
  291. if not root:
  292. root = self.seltree.AddRoot("<hidden root>")
  293. self.seltree.AppendItem(root, text=value)
  294. def SetItems(self, items):
  295. root = self.seltree.GetRootItem()
  296. if not root:
  297. root = self.seltree.AddRoot("<hidden root>")
  298. for item in items:
  299. self.seltree.AppendItem(root, text=item)
  300. def OnKeyUp(self, event):
  301. """Enable to select items using keyboard.
  302. Unused with wxPython 3, can be removed in the future.
  303. """
  304. item = self.seltree.GetSelection()
  305. if event.GetKeyCode() == wx.WXK_DOWN:
  306. self.seltree.SelectItem(self.seltree.GetNextVisible(item))
  307. elif event.GetKeyCode() == wx.WXK_UP:
  308. itemPrev = self.seltree.GetPrevSibling(item)
  309. self.seltree.SelectItem(itemPrev)
  310. elif event.GetKeyCode() == wx.WXK_ESCAPE:
  311. self.Dismiss()
  312. elif event.GetKeyCode() == wx.WXK_RETURN:
  313. self.seltree.SelectItem(item)
  314. self.curitem = item
  315. self._selectTreeItem(item)
  316. self.Dismiss()
  317. def _onDismissPopup(self, event):
  318. """Hide popup without selecting item on Esc"""
  319. if event.GetKeyCode() == wx.WXK_ESCAPE:
  320. self.Dismiss()
  321. else:
  322. event.Skip()
  323. def _selectTreeItem(self, item):
  324. item_str = self.seltree.GetItemText(item)
  325. if self.multiple:
  326. if item_str not in self.value:
  327. self.value.append(item_str)
  328. else:
  329. self.value = [item_str]
  330. def _onItemConfirmed(self, event):
  331. item = event.GetItem()
  332. self._selectTreeItem(item)
  333. self.Dismiss()
  334. def OnMotion(self, evt):
  335. """Have the selection follow the mouse, like in a real combobox"""
  336. item, flags = self.seltree.HitTest(evt.GetPosition())
  337. if item and flags & wx.TREE_HITTEST_ONITEMLABEL:
  338. self.seltree.SelectItem(item)
  339. self.curitem = item
  340. evt.Skip()
  341. def OnLeftDown(self, evt):
  342. """Do the combobox selection"""
  343. if self.curitem is None:
  344. return
  345. self._selectTreeItem(self.curitem)
  346. self.Dismiss()
  347. evt.Skip()
  348. def SetData(self, **kargs):
  349. """Set object properties"""
  350. if "multiple" in kargs:
  351. self.multiple = kargs["multiple"]
  352. if "onPopup" in kargs:
  353. self.onPopup = kargs["onPopup"]
  354. if kargs.get("layerTree", None):
  355. self.filterItems = [] # reset
  356. ltype = kargs["type"]
  357. for layer in kargs["layerTree"].GetVisibleLayers(skipDigitized=True):
  358. if layer.GetType() != ltype:
  359. continue
  360. self.filterItems.append(layer.GetName())
  361. def DeleteAllItems(self):
  362. """Delete all items in popup"""
  363. self.seltree.DeleteAllItems()
  364. class TreeCtrlComboPopup(ListCtrlComboPopup):
  365. """Create a tree ComboBox for selecting maps and other GIS elements
  366. in accessible mapsets within the current location
  367. """
  368. # overridden ComboPopup methods
  369. def Init(self):
  370. ListCtrlComboPopup.Init(self)
  371. self.nmaps = 1
  372. self.type = None
  373. self.mapsets = None
  374. self.onPopup = None
  375. self.fullyQualified = True
  376. self.extraItems = dict()
  377. self.SetFilter(None)
  378. self.tgis_error = False
  379. def SetFilter(self, filter):
  380. """Set filter for GIS elements, see e.g. VectorSelect"""
  381. self.filterElements = filter
  382. def OnPopup(self, force=False):
  383. """Limited only for first selected"""
  384. if not force and not self.updateOnPopup:
  385. return
  386. if self.onPopup:
  387. selected, exclude = self.onPopup(self.type)
  388. else:
  389. selected = None
  390. exclude = False
  391. self.GetElementList(selected, exclude)
  392. ListCtrlComboPopup.OnPopup(self, force)
  393. def GetElementList(self, elements=None, exclude=False):
  394. """Get filtered list of GIS elements in accessible mapsets
  395. and display as tree with all relevant elements displayed
  396. beneath each mapset branch
  397. """
  398. # update list
  399. self.seltree.DeleteAllItems()
  400. if self.type:
  401. self._getElementList(self.type, self.mapsets, elements, exclude)
  402. if len(self.value) > 0:
  403. root = self.seltree.GetRootItem()
  404. if not root:
  405. return
  406. item = self.FindItem(root, self.value[0])
  407. try:
  408. self.seltree.EnsureVisible(item)
  409. self.seltree.SelectItem(item)
  410. except:
  411. pass
  412. def _getElementList(self, element, mapsets=None, elements=None, exclude=False):
  413. """Get list of GIS elements in accessible mapsets and display as tree
  414. with all relevant elements displayed beneath each mapset branch
  415. :param element: GIS element
  416. :param mapsets: list of acceptable mapsets (None for all mapsets in search path)
  417. :param elements: list of forced GIS elements
  418. :param exclude: True to exclude, False for forcing the list (elements)
  419. """
  420. # get current mapset
  421. curr_mapset = grass.gisenv()["MAPSET"]
  422. # map element types to g.list types
  423. elementdict = {
  424. "cell": "raster",
  425. "raster": "raster",
  426. "grid3": "raster_3d",
  427. "raster_3d": "raster_3d",
  428. "vector": "vector",
  429. "paint/labels": "label",
  430. "label": "label",
  431. "windows": "region",
  432. "region": "region",
  433. "group": "group",
  434. "stds": "stds",
  435. "strds": "strds",
  436. "str3ds": "str3ds",
  437. "stvds": "stvds",
  438. }
  439. # to support multiple elements
  440. element_list = element.split(",")
  441. renamed_elements = []
  442. for elem in element_list:
  443. if elem not in elementdict:
  444. self.AddItem(_("Not selectable element"), node=False)
  445. return
  446. else:
  447. renamed_elements.append(elementdict[elem])
  448. if element in ("stds", "strds", "str3ds", "stvds"):
  449. if not self.tgis_error:
  450. import grass.temporal as tgis
  451. filesdict = tgis.tlist_grouped(elementdict[element], element == "stds")
  452. else:
  453. filesdict = None
  454. else:
  455. filesdict = grass.list_grouped(renamed_elements, check_search_path=False)
  456. # add extra items first
  457. if self.extraItems:
  458. for group, items in six.iteritems(self.extraItems):
  459. node = self.AddItem(group, node=True)
  460. self.seltree.SetItemTextColour(node, wx.Colour(50, 50, 200))
  461. for item in items:
  462. self.AddItem(item, node=False, parent=node)
  463. self.seltree.ExpandAllChildren(node)
  464. # list of mapsets in current location
  465. if mapsets is None:
  466. mapsets = grass.mapsets(search_path=True)
  467. # current mapset first
  468. if curr_mapset in mapsets and mapsets[0] != curr_mapset:
  469. mapsets.remove(curr_mapset)
  470. mapsets.insert(0, curr_mapset)
  471. first_mapset = None
  472. for mapset in mapsets:
  473. mapset_node = self.AddItem(
  474. _("Mapset") + ": " + mapset, node=True, mapset=mapset
  475. )
  476. node = mapset_node
  477. if not first_mapset:
  478. first_mapset = mapset_node
  479. self.seltree.SetItemTextColour(mapset_node, wx.Colour(50, 50, 200))
  480. if mapset not in filesdict:
  481. continue
  482. try:
  483. if isinstance(filesdict[mapset], dict):
  484. for elementType in filesdict[mapset].keys():
  485. node = self.AddItem(
  486. _("Type: ") + elementType,
  487. mapset=mapset,
  488. node=True,
  489. parent=mapset_node,
  490. )
  491. self.seltree.SetItemTextColour(node, wx.Colour(50, 50, 200))
  492. elem_list = filesdict[mapset][elementType]
  493. self._addItems(
  494. elist=elem_list,
  495. elements=elements,
  496. mapset=mapset,
  497. exclude=exclude,
  498. node=node,
  499. )
  500. else:
  501. elem_list = filesdict[mapset]
  502. self._addItems(
  503. elist=elem_list,
  504. elements=elements,
  505. mapset=mapset,
  506. exclude=exclude,
  507. node=node,
  508. )
  509. except Exception as e:
  510. sys.stderr.write(_("GSelect: invalid item: %s") % e)
  511. continue
  512. if self.seltree.ItemHasChildren(mapset_node):
  513. sel = UserSettings.Get(
  514. group="appearance", key="elementListExpand", subkey="selection"
  515. )
  516. collapse = True
  517. if sel == 0: # collapse all except PERMANENT and current
  518. if mapset in ("PERMANENT", curr_mapset):
  519. collapse = False
  520. elif sel == 1: # collapse all except PERMANENT
  521. if mapset == "PERMANENT":
  522. collapse = False
  523. elif sel == 2: # collapse all except current
  524. if mapset == curr_mapset:
  525. collapse = False
  526. elif sel == 3: # collapse all
  527. pass
  528. elif sel == 4: # expand all
  529. collapse = False
  530. if collapse:
  531. self.seltree.CollapseAllChildren(mapset_node)
  532. else:
  533. self.seltree.ExpandAllChildren(mapset_node)
  534. if first_mapset:
  535. # select first mapset (MSW hack)
  536. self.seltree.SelectItem(first_mapset)
  537. # helpers
  538. def _addItems(self, elist, elements, mapset, exclude, node):
  539. """Helper function for adding multiple items (maps, stds).
  540. :param list elist: list of map/stds names
  541. :param list elements: list of forced elements
  542. :param str mapset: mapset name
  543. :param exclude: True to exclude, False for forcing the list
  544. :param node: parent node
  545. """
  546. elist = grass.naturally_sorted(elist)
  547. for elem in elist:
  548. if elem != "":
  549. fullqElem = elem + "@" + mapset
  550. if self.filterItems and fullqElem not in self.filterItems:
  551. continue # skip items missed in self.filterItems
  552. if elements is not None:
  553. if (exclude and fullqElem in elements) or (
  554. not exclude and fullqElem not in elements
  555. ):
  556. continue
  557. if self.filterElements:
  558. if self.filterElements(fullqElem):
  559. self.AddItem(elem, mapset=mapset, node=False, parent=node)
  560. else:
  561. self.AddItem(elem, mapset=mapset, node=False, parent=node)
  562. def AddItem(self, value, mapset=None, node=True, parent=None):
  563. if not parent:
  564. root = self.seltree.GetRootItem()
  565. if not root:
  566. root = self.seltree.AddRoot("<hidden root>")
  567. parent = root
  568. data = {"node": node, "mapset": mapset}
  569. item = self.seltree.AppendItem(parent, text=value, data=data)
  570. return item
  571. def OnKeyUp(self, event):
  572. """Enables to select items using keyboard
  573. Unused with wxPython 3, can be removed in the future.
  574. """
  575. item = self.seltree.GetSelection()
  576. if event.GetKeyCode() == wx.WXK_DOWN:
  577. self.seltree.SelectItem(self.seltree.GetNextVisible(item))
  578. # problem with GetPrevVisible
  579. elif event.GetKeyCode() == wx.WXK_UP:
  580. if self.seltree.ItemHasChildren(item) and self.seltree.IsExpanded(
  581. self.seltree.GetPrevSibling(item)
  582. ):
  583. itemPrev = self.seltree.GetLastChild(self.seltree.GetPrevSibling(item))
  584. else:
  585. itemPrev = self.seltree.GetPrevSibling(item)
  586. if not wx.TreeItemId.IsOk(itemPrev):
  587. itemPrev = self.seltree.GetItemParent(item)
  588. if (
  589. item
  590. == self.seltree.GetFirstChild(self.seltree.GetRootItem())[0]
  591. ):
  592. itemPrev = item
  593. self.seltree.SelectItem(itemPrev)
  594. # selects first item starting with the written text in next mapset
  595. elif event.GetKeyCode() == wx.WXK_TAB:
  596. selected = self.seltree.GetSelection()
  597. if self.seltree.ItemHasChildren(selected):
  598. parent = selected
  599. else:
  600. parent = self.seltree.GetItemParent(selected)
  601. nextSibling = self.seltree.GetNextSibling(parent)
  602. if wx.TreeItemId.IsOk(nextSibling):
  603. match = self.FindItem(
  604. nextSibling, self.GetCombo().GetValue().strip(), True
  605. )
  606. else:
  607. match = self.FindItem(
  608. self.seltree.GetFirstChild(self.seltree.GetItemParent(parent))[0],
  609. self.GetCombo().GetValue().strip(),
  610. True,
  611. )
  612. self.seltree.SelectItem(match)
  613. elif event.GetKeyCode() == wx.WXK_RIGHT:
  614. if self.seltree.ItemHasChildren(item):
  615. self.seltree.Expand(item)
  616. elif event.GetKeyCode() == wx.WXK_LEFT:
  617. if self.seltree.ItemHasChildren(item):
  618. self.seltree.Collapse(item)
  619. elif event.GetKeyCode() == wx.WXK_ESCAPE:
  620. self.Dismiss()
  621. elif event.GetKeyCode() == wx.WXK_RETURN:
  622. if self.seltree.GetItemData(item)["node"]:
  623. self.value = []
  624. else:
  625. self._selectTreeItem(item)
  626. self.Dismiss()
  627. def OnLeftDown(self, evt):
  628. """Do the combobox selection"""
  629. item, flags = self.seltree.HitTest(evt.GetPosition())
  630. if item and flags & wx.TREE_HITTEST_ONITEMLABEL:
  631. self.curitem = item
  632. if self.seltree.GetItemData(item)["node"]:
  633. evt.Skip()
  634. return
  635. self._selectTreeItem(item)
  636. self.Dismiss()
  637. evt.Skip()
  638. def _selectTreeItem(self, item):
  639. fullName = self.seltree.GetItemText(item)
  640. if self.fullyQualified and self.seltree.GetItemData(item)["mapset"]:
  641. fullName += "@" + self.seltree.GetItemData(item)["mapset"]
  642. if self.multiple:
  643. self.value.append(fullName)
  644. else:
  645. if self.nmaps > 1: # see key_desc
  646. if len(self.value) >= self.nmaps:
  647. self.value = [fullName]
  648. else:
  649. self.value.append(fullName)
  650. else:
  651. self.value = [fullName]
  652. def _onItemConfirmed(self, event):
  653. item = event.GetItem()
  654. if self.seltree.GetItemData(item)["node"]:
  655. self.value = []
  656. else:
  657. self._selectTreeItem(item)
  658. self.Dismiss()
  659. def SetData(self, **kargs):
  660. """Set object properties"""
  661. ListCtrlComboPopup.SetData(self, **kargs)
  662. if "type" in kargs:
  663. self.type = kargs["type"]
  664. if self.type in ("stds", "strds", "str3ds", "stvds"):
  665. # Initiate the temporal framework. Catch database error
  666. # and set the error flag for the stds listing.
  667. try:
  668. import grass.temporal as tgis
  669. from grass.pygrass import messages
  670. try:
  671. tgis.init(True)
  672. except messages.FatalError as e:
  673. sys.stderr.write(_("Temporal GIS error:\n%s") % e)
  674. self.tgis_error = True
  675. except ImportError as e:
  676. # PyGRASS (ctypes) is the likely cause
  677. sys.stderr.write(
  678. _(
  679. "Unable to import pyGRASS: %s\n"
  680. "Some functionality will be not accessible"
  681. )
  682. % e
  683. )
  684. self.tgis_error = True
  685. if "mapsets" in kargs:
  686. self.mapsets = kargs["mapsets"]
  687. if "nmaps" in kargs:
  688. self.nmaps = kargs["nmaps"]
  689. if "updateOnPopup" in kargs:
  690. self.updateOnPopup = kargs["updateOnPopup"]
  691. if "fullyQualified" in kargs:
  692. self.fullyQualified = kargs["fullyQualified"]
  693. if "extraItems" in kargs:
  694. self.extraItems = kargs["extraItems"]
  695. def GetType(self):
  696. """Get element type"""
  697. return self.type
  698. class VectorDBInfo:
  699. """Class providing information about attribute tables
  700. linked to a vector map"""
  701. def __init__(self, map):
  702. self.map = map
  703. # dictionary of layer number and associated (driver, database, table)
  704. self.layers = {}
  705. # dictionary of table and associated columns (type, length, values,
  706. # ids)
  707. self.tables = {}
  708. if not self._CheckDBConnection(): # -> self.layers
  709. return
  710. self._DescribeTables() # -> self.tables
  711. def _CheckDBConnection(self):
  712. """Check DB connection"""
  713. nuldev = open(os.devnull, "w+")
  714. # if map is not defined (happens with vnet initialization) or it
  715. # doesn't exist
  716. try:
  717. self.layers = grass.vector_db(map=self.map, stderr=nuldev)
  718. except CalledModuleError:
  719. return False
  720. finally: # always close nuldev
  721. nuldev.close()
  722. return bool(len(self.layers.keys()) > 0)
  723. def _DescribeTables(self):
  724. """Describe linked tables"""
  725. for layer in self.layers.keys():
  726. # determine column names and types
  727. table = self.layers[layer]["table"]
  728. columns = {} # {name: {type, length, [values], [ids]}}
  729. i = 0
  730. Debug.msg(
  731. 1,
  732. "gselect.VectorDBInfo._DescribeTables(): table=%s driver=%s database=%s"
  733. % (
  734. self.layers[layer]["table"],
  735. self.layers[layer]["driver"],
  736. self.layers[layer]["database"],
  737. ),
  738. )
  739. for item in grass.db_describe(
  740. table=self.layers[layer]["table"],
  741. driver=self.layers[layer]["driver"],
  742. database=self.layers[layer]["database"],
  743. )["cols"]:
  744. name, type, length = item
  745. # FIXME: support more datatypes
  746. if type.lower() == "integer":
  747. ctype = int
  748. elif type.lower() == "double precision":
  749. ctype = float
  750. else:
  751. ctype = str
  752. columns[name.strip()] = {
  753. "index": i,
  754. "type": type.lower(),
  755. "ctype": ctype,
  756. "length": int(length),
  757. "values": [],
  758. "ids": [],
  759. }
  760. i += 1
  761. # check for key column
  762. # v.db.connect -g/p returns always key column name lowercase
  763. if self.layers[layer]["key"] not in columns.keys():
  764. for col in columns.keys():
  765. if col.lower() == self.layers[layer]["key"]:
  766. self.layers[layer]["key"] = col.upper()
  767. break
  768. self.tables[table] = columns
  769. return True
  770. def Reset(self):
  771. """Reset"""
  772. for layer in self.layers:
  773. table = self.layers[layer]["table"] # get table desc
  774. for name in self.tables[table].keys():
  775. self.tables[table][name]["values"] = []
  776. self.tables[table][name]["ids"] = []
  777. def GetName(self):
  778. """Get vector name"""
  779. return self.map
  780. def GetKeyColumn(self, layer):
  781. """Get key column of given layer
  782. :param layer: vector layer number
  783. """
  784. return str(self.layers[layer]["key"])
  785. def GetTable(self, layer):
  786. """Get table name of given layer
  787. :param layer: vector layer number
  788. """
  789. if layer not in self.layers:
  790. raise GException(_("No table linked to layer <{}>.".format(layer)))
  791. return self.layers[layer]["table"]
  792. def GetDbSettings(self, layer):
  793. """Get database settings
  794. :param layer: layer number
  795. :return: (driver, database)
  796. """
  797. return self.layers[layer]["driver"], self.layers[layer]["database"]
  798. def GetTableDesc(self, table):
  799. """Get table columns
  800. :param table: table name
  801. """
  802. return self.tables[table]
  803. class LayerSelect(wx.ComboBox):
  804. def __init__(
  805. self,
  806. parent,
  807. id=wx.ID_ANY,
  808. size=globalvar.DIALOG_COMBOBOX_SIZE,
  809. vector=None,
  810. dsn=None,
  811. choices=[],
  812. all=False,
  813. default=None,
  814. ):
  815. """Creates combo box for selecting vector map layer names
  816. :param str vector: vector map name (native or connected via v.external)
  817. :param str dsn: OGR data source name
  818. """
  819. super(LayerSelect, self).__init__(parent, id, size=size, choices=choices)
  820. self.all = all
  821. self.SetName("LayerSelect")
  822. # default value
  823. self.default = default
  824. self.InsertLayers(vector=vector, dsn=dsn)
  825. def InsertLayers(self, vector=None, dsn=None):
  826. """Insert layers for a vector into the layer combobox
  827. :param str vector: vector map name (native or connected via v.external)
  828. :param str dsn: OGR data source name
  829. """
  830. layers = list()
  831. if vector:
  832. layers = GetVectorNumberOfLayers(vector)
  833. elif dsn:
  834. ret = RunCommand("v.in.ogr", read=True, quiet=True, flags="l", input=dsn)
  835. if ret:
  836. layers = ret.splitlines()
  837. if self.default:
  838. if len(layers) == 0:
  839. layers.insert(0, str(self.default))
  840. elif self.default not in layers:
  841. layers.append(self.default)
  842. if self.all:
  843. layers.insert(0, "-1")
  844. if len(layers) > 0:
  845. self.SetItems(layers)
  846. else:
  847. self.Clear()
  848. self.SetValue("")
  849. if self.default and self.default in layers:
  850. self.SetValue(self.default)
  851. class DriverSelect(wx.ComboBox):
  852. """Creates combo box for selecting database driver."""
  853. def __init__(
  854. self,
  855. parent,
  856. choices,
  857. value,
  858. id=wx.ID_ANY,
  859. pos=wx.DefaultPosition,
  860. size=globalvar.DIALOG_LAYER_SIZE,
  861. **kargs,
  862. ):
  863. super(DriverSelect, self).__init__(
  864. parent, id, value, pos, size, choices, style=wx.CB_READONLY
  865. )
  866. self.SetName("DriverSelect")
  867. self.SetStringSelection(value)
  868. class DatabaseSelect(TextCtrl):
  869. """Creates combo box for selecting database driver."""
  870. def __init__(
  871. self,
  872. parent,
  873. value="",
  874. id=wx.ID_ANY,
  875. size=globalvar.DIALOG_TEXTCTRL_SIZE,
  876. **kargs,
  877. ):
  878. super(DatabaseSelect, self).__init__(parent, id, value, size=size, **kargs)
  879. self.SetName("DatabaseSelect")
  880. class TableSelect(wx.ComboBox):
  881. """Creates combo box for selecting attribute tables from the database"""
  882. def __init__(
  883. self,
  884. parent,
  885. id=wx.ID_ANY,
  886. value="",
  887. size=globalvar.DIALOG_COMBOBOX_SIZE,
  888. choices=[],
  889. **kargs,
  890. ):
  891. super(TableSelect, self).__init__(
  892. parent, id, value, size=size, choices=choices, style=wx.CB_READONLY, **kargs
  893. )
  894. self.SetName("TableSelect")
  895. if not choices:
  896. self.InsertTables()
  897. def InsertTables(self, driver=None, database=None):
  898. """Insert attribute tables into combobox"""
  899. items = []
  900. if not driver or not database:
  901. connect = grass.db_connection()
  902. driver = connect["driver"]
  903. database = connect["database"]
  904. ret = RunCommand(
  905. "db.tables", flags="p", read=True, driver=driver, database=database
  906. )
  907. if ret:
  908. for table in ret.splitlines():
  909. items.append(table)
  910. self.SetItems(items)
  911. self.SetValue("")
  912. class ColumnSelect(ComboCtrl):
  913. """Creates combo box for selecting columns in the attribute table
  914. for a vector map.
  915. :param parent: window parent
  916. :param id: window id
  917. :param value: default value
  918. :param size: window size
  919. :param str vector: vector map name
  920. :param layer: layer number
  921. :param multiple: True if it is possible to add multiple columns
  922. :param param: parameters list (see menuform.py)
  923. :param kwags: wx.ComboBox parameters
  924. """
  925. def __init__(
  926. self,
  927. parent,
  928. id=wx.ID_ANY,
  929. value="",
  930. size=globalvar.DIALOG_COMBOBOX_SIZE,
  931. vector=None,
  932. layer=1,
  933. multiple=False,
  934. param=None,
  935. **kwargs,
  936. ):
  937. self.defaultValue = value
  938. self.param = param
  939. self.columns = []
  940. ComboCtrl.__init__(self, parent, id, size=size, **kwargs)
  941. self.GetChildren()[0].SetName("ColumnSelect")
  942. self.GetChildren()[0].type = type
  943. self.tcp = ListCtrlComboPopup()
  944. self.SetPopupControl(self.tcp)
  945. self.tcp.SetData(multiple=multiple)
  946. if vector:
  947. self.InsertColumns(vector, layer)
  948. self.GetChildren()[0].Bind(wx.EVT_KEY_UP, self.OnKeyUp)
  949. def GetColumns(self):
  950. return self.columns
  951. def OnKeyUp(self, event):
  952. """Shows popupwindow if down arrow key is released"""
  953. if event.GetKeyCode() == wx.WXK_DOWN and not self.IsPopupShown():
  954. self.ShowPopup()
  955. else:
  956. event.Skip()
  957. def Clear(self):
  958. self.tcp.DeleteAllItems()
  959. self.SetValue("")
  960. def InsertColumns(
  961. self, vector, layer, excludeKey=False, excludeCols=None, type=None, dbInfo=None
  962. ):
  963. """Insert columns for a vector attribute table into the columns combobox
  964. :param str vector: vector name
  965. :param int layer: vector layer number
  966. :param excludeKey: exclude key column from the list?
  967. :param excludeCols: list of columns to be removed from the list
  968. :param type: only columns of given type (given as list)
  969. """
  970. if not dbInfo:
  971. dbInfo = VectorDBInfo(vector)
  972. try:
  973. try:
  974. layer = int(layer)
  975. except TypeError:
  976. # assuming layer 1
  977. layer = 1
  978. table = dbInfo.GetTable(layer)
  979. columnchoices = dbInfo.GetTableDesc(table)
  980. keyColumn = dbInfo.GetKeyColumn(layer)
  981. self.columns = len(columnchoices.keys()) * [""]
  982. for key, val in six.iteritems(columnchoices):
  983. self.columns[val["index"]] = key
  984. if excludeKey: # exclude key column
  985. self.columns.remove(keyColumn)
  986. if excludeCols: # exclude key column
  987. for key in six.iterkeys(columnchoices):
  988. if key in excludeCols:
  989. self.columns.remove(key)
  990. if type: # only selected column types
  991. for key, value in six.iteritems(columnchoices):
  992. if value["type"] not in type:
  993. try:
  994. self.columns.remove(key)
  995. except ValueError:
  996. pass
  997. except (KeyError, ValueError, GException):
  998. self.columns[:] = []
  999. # update list
  1000. self.tcp.DeleteAllItems()
  1001. for col in self.columns:
  1002. self.tcp.AddItem(col)
  1003. self.SetValue(self.defaultValue)
  1004. if self.param:
  1005. value = self.param.get("value", "")
  1006. if value != "" and value in self.columns:
  1007. self.SetValue(value)
  1008. def InsertTableColumns(self, table, driver=None, database=None):
  1009. """Insert table columns
  1010. :param str table: table name
  1011. :param str driver: driver name
  1012. :param str database: database name
  1013. """
  1014. self.columns[:] = []
  1015. ret = RunCommand(
  1016. "db.columns", read=True, driver=driver, database=database, table=table
  1017. )
  1018. if ret:
  1019. self.columns = ret.splitlines()
  1020. # update list
  1021. self.tcp.DeleteAllItems()
  1022. self.SetValue(self.defaultValue)
  1023. for col in self.columns:
  1024. self.tcp.AddItem(col)
  1025. if self.param:
  1026. value = self.param.get("value", "")
  1027. if value != "" and value in self.columns:
  1028. self.SetValue(value)
  1029. class DbaseSelect(wx.lib.filebrowsebutton.DirBrowseButton):
  1030. """Widget for selecting GRASS Database"""
  1031. def __init__(self, parent, **kwargs):
  1032. super(DbaseSelect, self).__init__(
  1033. parent,
  1034. id=wx.ID_ANY,
  1035. size=globalvar.DIALOG_GSELECT_SIZE,
  1036. labelText="",
  1037. dialogTitle=_("Choose GIS Data Directory"),
  1038. buttonText=_("Browse"),
  1039. startDirectory=grass.gisenv()["GISDBASE"],
  1040. **kwargs,
  1041. )
  1042. class LocationSelect(wx.ComboBox):
  1043. """Widget for selecting GRASS location"""
  1044. def __init__(
  1045. self,
  1046. parent,
  1047. id=wx.ID_ANY,
  1048. size=globalvar.DIALOG_COMBOBOX_SIZE,
  1049. gisdbase=None,
  1050. **kwargs,
  1051. ):
  1052. super(LocationSelect, self).__init__(parent, id, size=size, **kwargs)
  1053. self.SetName("LocationSelect")
  1054. if not gisdbase:
  1055. self.gisdbase = grass.gisenv()["GISDBASE"]
  1056. else:
  1057. self.gisdbase = gisdbase
  1058. self.SetItems(GetListOfLocations(self.gisdbase))
  1059. def UpdateItems(self, dbase):
  1060. """Update list of locations
  1061. :param str dbase: path to GIS database
  1062. """
  1063. self.gisdbase = dbase
  1064. if dbase:
  1065. self.SetItems(GetListOfLocations(self.gisdbase))
  1066. else:
  1067. self.SetItems([])
  1068. class MapsetSelect(wx.ComboBox):
  1069. """Widget for selecting GRASS mapset"""
  1070. def __init__(
  1071. self,
  1072. parent,
  1073. id=wx.ID_ANY,
  1074. size=globalvar.DIALOG_COMBOBOX_SIZE,
  1075. gisdbase=None,
  1076. location=None,
  1077. setItems=True,
  1078. searchPath=False,
  1079. new=False,
  1080. skipCurrent=False,
  1081. multiple=False,
  1082. **kwargs,
  1083. ):
  1084. style = 0
  1085. # disabled, read-only widget has no TextCtrl children (TODO: rewrite)
  1086. # if not new and not multiple:
  1087. ### style = wx.CB_READONLY
  1088. wx.ComboBox.__init__(self, parent, id, size=size, style=style, **kwargs)
  1089. self.searchPath = searchPath
  1090. self.skipCurrent = skipCurrent
  1091. self.SetName("MapsetSelect")
  1092. self.value = ""
  1093. self.multiple = multiple
  1094. if not gisdbase:
  1095. self.gisdbase = grass.gisenv()["GISDBASE"]
  1096. else:
  1097. self.gisdbase = gisdbase
  1098. if not location:
  1099. self.location = grass.gisenv()["LOCATION_NAME"]
  1100. else:
  1101. self.location = location
  1102. if setItems:
  1103. self.SetItems(self._getMapsets())
  1104. if self.multiple:
  1105. self.Bind(wx.EVT_COMBOBOX, self._onSelection)
  1106. self.Bind(wx.EVT_TEXT, self._onSelection)
  1107. def _onSelection(self, event):
  1108. value = self.GetValue()
  1109. if value:
  1110. if self.value:
  1111. self.value += ","
  1112. self.value += value
  1113. self.SetValue(self.value)
  1114. else:
  1115. self.value = value
  1116. event.Skip()
  1117. def UpdateItems(self, location, dbase=None):
  1118. """Update list of mapsets for given location
  1119. :param str dbase: path to GIS database (None to use currently
  1120. selected)
  1121. :param str location: name of location
  1122. """
  1123. if dbase:
  1124. self.gisdbase = dbase
  1125. self.location = location
  1126. if location:
  1127. self.SetItems(self._getMapsets())
  1128. else:
  1129. self.SetItems([])
  1130. def _getMapsets(self):
  1131. if self.searchPath:
  1132. mlist = RunCommand(
  1133. "g.mapsets", read=True, flags="p", sep="newline"
  1134. ).splitlines()
  1135. else:
  1136. mlist = GetListOfMapsets(self.gisdbase, self.location, selectable=False)
  1137. gisenv = grass.gisenv()
  1138. if (
  1139. self.skipCurrent
  1140. and gisenv["LOCATION_NAME"] == self.location
  1141. and gisenv["MAPSET"] in mlist
  1142. ):
  1143. mlist.remove(gisenv["MAPSET"])
  1144. return mlist
  1145. class SubGroupSelect(wx.ComboBox):
  1146. """Widget for selecting subgroups"""
  1147. def __init__(
  1148. self, parent, id=wx.ID_ANY, size=globalvar.DIALOG_GSELECT_SIZE, **kwargs
  1149. ):
  1150. super(SubGroupSelect, self).__init__(parent, id, size=size, **kwargs)
  1151. self.SetName("SubGroupSelect")
  1152. def Insert(self, group):
  1153. """Insert subgroups for defined group"""
  1154. if not group:
  1155. return
  1156. gisenv = grass.gisenv()
  1157. try:
  1158. name, mapset = group.split("@", 1)
  1159. except ValueError:
  1160. name = group
  1161. mapset = gisenv["MAPSET"]
  1162. mlist = RunCommand("i.group", group=group, read=True, flags="sg").splitlines()
  1163. try:
  1164. self.SetItems(mlist)
  1165. except OSError:
  1166. self.SetItems([])
  1167. class FormatSelect(wx.Choice):
  1168. def __init__(
  1169. self, parent, srcType, ogr=False, size=globalvar.DIALOG_SPIN_SIZE, **kwargs
  1170. ):
  1171. """Widget for selecting external (GDAL/OGR) format
  1172. :param parent: parent window
  1173. :param srcType: source type ('file', 'database', 'protocol')
  1174. :param ogr: True for OGR otherwise GDAL
  1175. """
  1176. super(FormatSelect, self).__init__(parent, id=wx.ID_ANY, size=size, **kwargs)
  1177. self.SetName("FormatSelect")
  1178. if ogr:
  1179. ftype = "ogr"
  1180. else:
  1181. ftype = "gdal"
  1182. formats = list()
  1183. for f in GetFormats()[ftype][srcType].items():
  1184. formats += f
  1185. self.SetItems(formats)
  1186. def GetExtension(self, name):
  1187. """Get file extension by format name"""
  1188. formatToExt = dict()
  1189. formatToExt.update(rasterFormatExtension)
  1190. formatToExt.update(vectorFormatExtension)
  1191. return formatToExt.get(name, "")
  1192. class GdalSelect(wx.Panel):
  1193. def __init__(
  1194. self,
  1195. parent,
  1196. panel,
  1197. ogr=False,
  1198. link=False,
  1199. dest=False,
  1200. exclude=None,
  1201. settings=True,
  1202. ):
  1203. """Widget for selecting GDAL/OGR datasource, format
  1204. .. todo::
  1205. Split into GdalSelect and OgrSelect and optionally to
  1206. GdalSelectOutput, OgrSelectOutput
  1207. :param parent: parent window
  1208. :param bool ogr: use OGR selector instead of GDAL
  1209. :param bool dest: True for output (destination)
  1210. :param default: default type (ignored when dest == True)
  1211. :param exclude: list of types to be excluded
  1212. """
  1213. self.parent = parent
  1214. self.ogr = ogr
  1215. self.link = link
  1216. self.dest = dest
  1217. self._sourceType = None
  1218. wx.Panel.__init__(self, parent=panel, name="GdalSelect")
  1219. self.reloadDataRequired = Signal("GdalSelect.reloadDataRequired")
  1220. self.inputBox = StaticBox(parent=self)
  1221. if dest:
  1222. self.inputBox.SetLabel(" %s " % _("Output settings"))
  1223. else:
  1224. self.inputBox.SetLabel(" %s " % _("Source input"))
  1225. # source type
  1226. sources = list()
  1227. self.sourceMap = {"file": -1, "dir": -1, "db": -1, "pro": -1, "native": -1}
  1228. idx = 0
  1229. if dest:
  1230. sources.append(_("Native"))
  1231. self.sourceMap["native"] = idx
  1232. idx += 1
  1233. if exclude is None:
  1234. exclude = []
  1235. if "file" not in exclude:
  1236. sources.append(_("File"))
  1237. self.sourceMap["file"] = idx
  1238. idx += 1
  1239. if "directory" not in exclude:
  1240. sources.append(_("Directory"))
  1241. self.sourceMap["dir"] = idx
  1242. idx += 1
  1243. if "database" not in exclude:
  1244. sources.append(_("Database"))
  1245. self.sourceMap["db"] = idx
  1246. idx += 1
  1247. if "protocol" not in exclude:
  1248. sources.append(_("Protocol"))
  1249. self.sourceMap["pro"] = idx
  1250. idx += 1
  1251. self.sourceMapByIdx = {}
  1252. for name, idx in self.sourceMap.items():
  1253. self.sourceMapByIdx[idx] = name
  1254. self.source = wx.RadioBox(
  1255. parent=self, id=wx.ID_ANY, style=wx.RA_SPECIFY_COLS, choices=sources
  1256. )
  1257. if dest:
  1258. self.source.SetLabel(" %s " % _("Output type"))
  1259. else:
  1260. self.source.SetLabel(" %s " % _("Source type"))
  1261. self.source.SetSelection(0)
  1262. self.source.Bind(
  1263. wx.EVT_RADIOBOX,
  1264. lambda evt: self.SetSourceType(self.sourceMapByIdx[evt.GetInt()]),
  1265. )
  1266. self.nativeWidgets = {}
  1267. self.fileWidgets = {}
  1268. self.dirWidgets = {}
  1269. self.dbWidgets = {}
  1270. self.protocolWidgets = {}
  1271. self.pgWidgets = {}
  1272. if ogr:
  1273. fType = "ogr"
  1274. else:
  1275. fType = "gdal"
  1276. # file
  1277. fileMask = "%(all)s (*)|*|" % {"all": _("All files")}
  1278. if not ogr:
  1279. extList = rasterFormatExtension
  1280. fileMask += (
  1281. "%(name)s (*.%(low1)s;*.%(low2)s;*.%(up1)s;*.%(up2)s)|"
  1282. "*.%(low1)s;*.%(low2)s;*.%(up1)s;*.%(up2)s|"
  1283. % {
  1284. "name": "GeoTIFF",
  1285. "low1": "tif",
  1286. "low2": "tiff",
  1287. "up1": "TIF",
  1288. "up2": "TIFF",
  1289. }
  1290. )
  1291. else:
  1292. extList = vectorFormatExtension
  1293. fileMask += "%(name)s (*.%(low)s;*.%(up)s)|*.%(low)s;*.%(up)s|" % {
  1294. "name": "ESRI Shapefile",
  1295. "low": "shp",
  1296. "up": "SHP",
  1297. }
  1298. for name, ext in sorted(extList.items()):
  1299. if name in ("ESRI Shapefile", "GeoTIFF"):
  1300. continue
  1301. fileMask += "%(name)s (*.%(low)s;*.%(up)s)|*.%(low)s;*.%(up)s|" % {
  1302. "name": name,
  1303. "low": ext.lower(),
  1304. "up": ext.upper(),
  1305. }
  1306. fileMask += "%s (*.zip;*.ZIP)|*.zip;*.ZIP|" % _("ZIP files")
  1307. fileMask += "%s (*.gz;*.GZ)|*.gz;*.GZ|" % _("GZIP files")
  1308. fileMask += "%s (*.tar;*.TAR)|*.tar;*.TAR|" % _("TAR files")
  1309. # don't include last '|' - windows and mac throw error
  1310. fileMask += (
  1311. "%s (*.tar.gz;*.TAR.GZ;*.tgz;*.TGZ)|*.tar.gz;*.TAR.GZ;*.tgz;*.TGZ"
  1312. % _("TARGZ files")
  1313. )
  1314. # only contains formats with extensions hardcoded
  1315. self.filePanel = wx.Panel(parent=self)
  1316. browse = filebrowse.FileBrowseButton(
  1317. parent=self.filePanel,
  1318. id=wx.ID_ANY,
  1319. size=globalvar.DIALOG_GSELECT_SIZE,
  1320. labelText=_("File:"),
  1321. dialogTitle=_("Choose file to import"),
  1322. buttonText=_("Browse"),
  1323. startDirectory=os.getcwd(),
  1324. changeCallback=self.OnUpdate,
  1325. fileMask=fileMask,
  1326. )
  1327. browse.GetChildren()[1].SetName("GdalSelectDataSource")
  1328. self.fileWidgets["browse"] = browse
  1329. self.fileWidgets["options"] = TextCtrl(parent=self.filePanel)
  1330. # directory
  1331. self.dirPanel = wx.Panel(parent=self)
  1332. browse = filebrowse.DirBrowseButton(
  1333. parent=self.dirPanel,
  1334. id=wx.ID_ANY,
  1335. size=globalvar.DIALOG_GSELECT_SIZE,
  1336. labelText=_("Directory:"),
  1337. dialogTitle=_("Choose input directory"),
  1338. buttonText=_("Browse"),
  1339. startDirectory=os.getcwd(),
  1340. changeCallback=self.OnUpdate,
  1341. )
  1342. browse.GetChildren()[1].SetName("GdalSelectDataSource")
  1343. self.dirWidgets["browse"] = browse
  1344. formatSelect = wx.Choice(parent=self.dirPanel)
  1345. self.dirWidgets["format"] = formatSelect
  1346. self.fileFormats = GetFormats(writableOnly=dest)[fType]["file"]
  1347. formatSelect.SetItems(sorted(list(self.fileFormats.values())))
  1348. formatSelect.Bind(
  1349. wx.EVT_CHOICE,
  1350. lambda evt: self.SetExtension(
  1351. self.dirWidgets["format"].GetStringSelection()
  1352. ),
  1353. )
  1354. formatSelect.Bind(wx.EVT_CHOICE, self.OnUpdate)
  1355. self.dirWidgets["extensionLabel"] = StaticText(
  1356. parent=self.dirPanel, label=_("Extension:")
  1357. )
  1358. self.dirWidgets["extension"] = TextCtrl(parent=self.dirPanel)
  1359. self.dirWidgets["extension"].Bind(wx.EVT_TEXT, self.ExtensionChanged)
  1360. self.dirWidgets["options"] = TextCtrl(parent=self.dirPanel)
  1361. if self.ogr:
  1362. shapefile = "ESRI Shapefile"
  1363. if shapefile in self.fileFormats:
  1364. formatSelect.SetStringSelection(shapefile)
  1365. self.SetExtension(shapefile)
  1366. else:
  1367. tiff = "GeoTIFF"
  1368. if tiff in self.fileFormats:
  1369. formatSelect.SetStringSelection(tiff)
  1370. self.SetExtension(tiff)
  1371. # database
  1372. self.dbPanel = wx.Panel(parent=self)
  1373. self.dbFormats = GetFormats(writableOnly=dest)[fType]["database"]
  1374. dbChoice = wx.Choice(parent=self.dbPanel, choices=list(self.dbFormats.values()))
  1375. dbChoice.Bind(
  1376. wx.EVT_CHOICE,
  1377. lambda evt: self.SetDatabase(db=dbChoice.GetStringSelection()),
  1378. )
  1379. self.dbWidgets["format"] = dbChoice
  1380. browse = filebrowse.FileBrowseButton(
  1381. parent=self.dbPanel,
  1382. id=wx.ID_ANY,
  1383. size=globalvar.DIALOG_GSELECT_SIZE,
  1384. labelText=_("Name:"),
  1385. dialogTitle=_("Choose file"),
  1386. buttonText=_("Browse"),
  1387. startDirectory=os.getcwd(),
  1388. changeCallback=self.OnUpdate,
  1389. )
  1390. browse.GetChildren()[1].SetName("GdalSelectDataSource")
  1391. self.dbWidgets["browse"] = browse
  1392. self.dbWidgets["choice"] = wx.Choice(
  1393. parent=self.dbPanel, name="GdalSelectDataSource"
  1394. )
  1395. self.dbWidgets["choice"].Bind(wx.EVT_CHOICE, self.OnUpdate)
  1396. self.dbWidgets["text"] = TextCtrl(
  1397. parent=self.dbPanel, name="GdalSelectDataSource"
  1398. )
  1399. self.dbWidgets["text"].Bind(wx.EVT_TEXT, self.OnUpdate)
  1400. self.dbWidgets["textLabel1"] = StaticText(parent=self.dbPanel, label=_("Name:"))
  1401. self.dbWidgets["textLabel2"] = StaticText(parent=self.dbPanel, label=_("Name:"))
  1402. self.dbWidgets["featType"] = wx.RadioBox(
  1403. parent=self.dbPanel,
  1404. id=wx.ID_ANY,
  1405. label=" %s " % _("Feature type:"),
  1406. choices=[_("simple features"), _("topological")],
  1407. majorDimension=2,
  1408. style=wx.RA_SPECIFY_COLS,
  1409. )
  1410. if dest:
  1411. self.dbWidgets["featType"].Disable()
  1412. else:
  1413. self.dbWidgets["featType"].Hide()
  1414. browse = filebrowse.DirBrowseButton(
  1415. parent=self.dbPanel,
  1416. id=wx.ID_ANY,
  1417. size=globalvar.DIALOG_GSELECT_SIZE,
  1418. labelText=_("Directory:"),
  1419. dialogTitle=_("Choose input directory"),
  1420. buttonText=_("Browse"),
  1421. startDirectory=os.getcwd(),
  1422. changeCallback=self.OnUpdate,
  1423. )
  1424. self.dbWidgets["dirbrowse"] = browse
  1425. self.dbWidgets["options"] = TextCtrl(parent=self.dbPanel)
  1426. # protocol
  1427. self.protocolPanel = wx.Panel(parent=self)
  1428. self.protocolFormats = GetFormats(writableOnly=self.dest)[fType]["protocol"]
  1429. protocolChoice = wx.Choice(
  1430. parent=self.protocolPanel, choices=list(self.protocolFormats.values())
  1431. )
  1432. self.protocolWidgets["format"] = protocolChoice
  1433. self.protocolWidgets["text"] = TextCtrl(parent=self.protocolPanel)
  1434. self.protocolWidgets["text"].Bind(wx.EVT_TEXT, self.OnUpdate)
  1435. self.protocolWidgets["options"] = TextCtrl(parent=self.protocolPanel)
  1436. # native
  1437. self.nativePanel = wx.Panel(parent=self)
  1438. self._layout()
  1439. sourceType = "file"
  1440. self.SetSourceType(sourceType) # needed always to fit dialog size
  1441. if self.dest:
  1442. current = RunCommand(
  1443. "v.external.out",
  1444. parent=self,
  1445. read=True,
  1446. parse=grass.parse_key_val,
  1447. flags="g",
  1448. )
  1449. if current["format"] == "native":
  1450. sourceType = "native"
  1451. elif current["format"] in GetFormats()["ogr"]["database"].values():
  1452. sourceType = "db"
  1453. else:
  1454. sourceType = "dir"
  1455. if self.dest:
  1456. wx.CallAfter(self._postInit, sourceType, current)
  1457. def _postInit(self, sourceType, data):
  1458. """Fill in default values."""
  1459. format = data.get("format", "")
  1460. pg = "conninfo" in data.keys()
  1461. if pg:
  1462. dsn = ""
  1463. for item in data.get("conninfo").split(" "):
  1464. k, v = item.split("=")
  1465. if k == "dbname":
  1466. dsn = v
  1467. break
  1468. optList = list()
  1469. for k, v in six.iteritems(data):
  1470. if k in ("format", "conninfo", "topology"):
  1471. continue
  1472. optList.append("%s=%s" % (k, v))
  1473. options = ",".join(optList)
  1474. else:
  1475. dsn = data.get("dsn")
  1476. options = data.get("options", "")
  1477. self.SetSourceType(sourceType)
  1478. self.source.SetSelection(self.sourceMap[sourceType])
  1479. # v.external.out does not return dsn for the native format
  1480. if dsn:
  1481. dsn = os.path.expandvars(dsn) # v.external.out uses $HOME
  1482. # fill in default values
  1483. if sourceType == "dir":
  1484. self.dirWidgets["format"].SetStringSelection(format)
  1485. self.dirWidgets["browse"].SetValue(dsn)
  1486. self.dirWidgets["options"].SetValue(options)
  1487. elif sourceType == "db":
  1488. self.dbWidgets["format"].SetStringSelection(format)
  1489. self.dbWidgets["options"].SetValue(options)
  1490. name = self._getCurrentDbWidgetName()
  1491. if name == "choice":
  1492. if dsn in self.dbWidgets[name].GetItems():
  1493. self.dbWidgets[name].SetStringSelection(dsn)
  1494. if "topology" in data.keys():
  1495. self.dbWidgets["featType"].SetSelection(1)
  1496. else:
  1497. self.dbWidgets[name].SetValue(dsn)
  1498. def _layout(self):
  1499. """Layout"""
  1500. self.mainSizer = wx.BoxSizer(wx.VERTICAL)
  1501. self.changingSizer = wx.StaticBoxSizer(self.inputBox, wx.VERTICAL)
  1502. # file
  1503. paddingSizer = wx.BoxSizer(wx.VERTICAL)
  1504. sizer = wx.GridBagSizer(vgap=5, hgap=10)
  1505. paddingSizer.Add(
  1506. self.fileWidgets["browse"], flag=wx.BOTTOM | wx.EXPAND, border=35
  1507. )
  1508. sizer.Add(paddingSizer, flag=wx.EXPAND, pos=(0, 0), span=(1, 2))
  1509. sizer.AddGrowableCol(0)
  1510. if self.dest:
  1511. sizer.Add(
  1512. StaticText(parent=self.filePanel, label=_("Creation options:")),
  1513. flag=wx.ALIGN_CENTER_VERTICAL,
  1514. pos=(1, 0),
  1515. )
  1516. sizer.Add(
  1517. self.fileWidgets["options"],
  1518. flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
  1519. pos=(1, 1),
  1520. )
  1521. else:
  1522. self.fileWidgets["options"].Hide()
  1523. self.filePanel.SetSizer(sizer)
  1524. # directory
  1525. sizer = wx.GridBagSizer(vgap=3, hgap=10)
  1526. sizer.Add(
  1527. StaticText(parent=self.dirPanel, label=_("Format:")),
  1528. flag=wx.ALIGN_CENTER_VERTICAL,
  1529. pos=(0, 0),
  1530. )
  1531. sizer.Add(
  1532. self.dirWidgets["format"],
  1533. flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
  1534. pos=(0, 1),
  1535. )
  1536. sizer.Add(
  1537. self.dirWidgets["extensionLabel"], flag=wx.ALIGN_CENTER_VERTICAL, pos=(0, 2)
  1538. )
  1539. sizer.Add(
  1540. self.dirWidgets["extension"], flag=wx.ALIGN_CENTER_VERTICAL, pos=(0, 3)
  1541. )
  1542. sizer.Add(
  1543. self.dirWidgets["browse"],
  1544. flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
  1545. pos=(1, 0),
  1546. span=(1, 4),
  1547. )
  1548. if self.dest:
  1549. sizer.Add(
  1550. StaticText(parent=self.dirPanel, label=_("Creation options:")),
  1551. flag=wx.ALIGN_CENTER_VERTICAL,
  1552. pos=(2, 0),
  1553. )
  1554. sizer.Add(
  1555. self.dirWidgets["options"],
  1556. flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
  1557. pos=(2, 1),
  1558. )
  1559. helpBtn = Button(parent=self.dirPanel, id=wx.ID_HELP)
  1560. helpBtn.Bind(wx.EVT_BUTTON, self.OnHelp)
  1561. sizer.Add(helpBtn, flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, pos=(2, 2))
  1562. self.dirWidgets["extensionLabel"].Hide()
  1563. self.dirWidgets["extension"].Hide()
  1564. else:
  1565. self.dirWidgets["options"].Hide()
  1566. sizer.AddGrowableCol(1)
  1567. self.dirPanel.SetSizer(sizer)
  1568. # database
  1569. sizer = wx.GridBagSizer(vgap=1, hgap=5)
  1570. sizer.Add(
  1571. StaticText(parent=self.dbPanel, label=_("Format:")),
  1572. flag=wx.ALIGN_CENTER_VERTICAL,
  1573. pos=(0, 0),
  1574. )
  1575. sizer.Add(self.dbWidgets["format"], flag=wx.ALIGN_CENTER_VERTICAL, pos=(0, 1))
  1576. sizer.Add(
  1577. self.dbWidgets["textLabel1"], flag=wx.ALIGN_CENTER_VERTICAL, pos=(1, 0)
  1578. )
  1579. sizer.Add(
  1580. self.dbWidgets["text"],
  1581. flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
  1582. pos=(1, 1),
  1583. span=(1, 2),
  1584. )
  1585. sizer.Add(
  1586. self.dbWidgets["browse"],
  1587. flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
  1588. pos=(2, 0),
  1589. span=(1, 3),
  1590. )
  1591. sizer.Add(
  1592. self.dbWidgets["dirbrowse"],
  1593. flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
  1594. pos=(3, 0),
  1595. span=(1, 2),
  1596. )
  1597. sizer.Add(
  1598. self.dbWidgets["textLabel2"], flag=wx.ALIGN_CENTER_VERTICAL, pos=(4, 0)
  1599. )
  1600. sizer.Add(
  1601. self.dbWidgets["choice"],
  1602. flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
  1603. pos=(4, 1),
  1604. span=(1, 2),
  1605. )
  1606. if self.dest:
  1607. sizer.Add(self.dbWidgets["featType"], pos=(0, 2), flag=wx.EXPAND)
  1608. sizer.Add(
  1609. StaticText(parent=self.dbPanel, label=_("Creation options:")),
  1610. flag=wx.ALIGN_CENTER_VERTICAL,
  1611. pos=(5, 0),
  1612. )
  1613. sizer.Add(
  1614. self.dbWidgets["options"],
  1615. flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
  1616. pos=(5, 1),
  1617. span=(1, 2),
  1618. )
  1619. # help button
  1620. helpBtn = Button(parent=self.dbPanel, id=wx.ID_HELP)
  1621. helpBtn.Bind(wx.EVT_BUTTON, self.OnHelp)
  1622. sizer.Add(helpBtn, pos=(5, 3))
  1623. else:
  1624. self.dbWidgets["options"].Hide()
  1625. self.dbPanel.SetSizer(sizer)
  1626. sizer.SetEmptyCellSize((0, 0))
  1627. sizer.AddGrowableCol(1)
  1628. # protocol
  1629. sizer = wx.GridBagSizer(vgap=3, hgap=3)
  1630. sizer.Add(
  1631. StaticText(parent=self.protocolPanel, label=_("Format:")),
  1632. flag=wx.ALIGN_CENTER_VERTICAL,
  1633. pos=(0, 0),
  1634. )
  1635. sizer.Add(
  1636. self.protocolWidgets["format"], flag=wx.ALIGN_CENTER_VERTICAL, pos=(0, 1)
  1637. )
  1638. sizer.Add(
  1639. StaticText(parent=self.protocolPanel, label=_("Protocol:")),
  1640. flag=wx.ALIGN_CENTER_VERTICAL,
  1641. pos=(1, 0),
  1642. )
  1643. sizer.Add(
  1644. self.protocolWidgets["text"],
  1645. flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
  1646. pos=(1, 1),
  1647. )
  1648. if self.dest:
  1649. sizer.Add(
  1650. StaticText(parent=self.protocolPanel, label=_("Creation options:")),
  1651. flag=wx.ALIGN_CENTER_VERTICAL,
  1652. pos=(2, 0),
  1653. )
  1654. sizer.Add(
  1655. self.protocolWidgets["options"],
  1656. flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
  1657. pos=(2, 1),
  1658. )
  1659. else:
  1660. self.protocolWidgets["options"].Hide()
  1661. sizer.AddGrowableCol(1)
  1662. self.protocolPanel.SetSizer(sizer)
  1663. # native
  1664. sizer = wx.BoxSizer(wx.VERTICAL)
  1665. sizer.Add(
  1666. StaticText(parent=self.nativePanel, label=_("No settings available")),
  1667. flag=wx.ALL | wx.EXPAND,
  1668. border=5,
  1669. )
  1670. self.nativePanel.SetSizer(sizer)
  1671. for panel in (
  1672. self.nativePanel,
  1673. self.filePanel,
  1674. self.dirPanel,
  1675. self.dbPanel,
  1676. self.protocolPanel,
  1677. ):
  1678. self.changingSizer.Add(panel, proportion=1, flag=wx.EXPAND)
  1679. self.mainSizer.Add(
  1680. self.source, proportion=0, flag=wx.LEFT | wx.RIGHT | wx.EXPAND, border=5
  1681. )
  1682. self.mainSizer.Add(
  1683. self.changingSizer, proportion=1, flag=wx.ALL | wx.EXPAND, border=5
  1684. )
  1685. self.SetSizer(self.mainSizer)
  1686. self.mainSizer.Fit(self)
  1687. def _getExtension(self, name):
  1688. """Get file extension by format name"""
  1689. formatToExt = dict()
  1690. formatToExt.update(rasterFormatExtension)
  1691. formatToExt.update(vectorFormatExtension)
  1692. return formatToExt.get(name, "")
  1693. def _getFormatAbbreviation(self, formats, formatName):
  1694. """Get format abbreviation
  1695. :param dict formats: {formatAbbreviation: formatLongName}
  1696. :param str formatName: long format name
  1697. return str: return format abbreviation
  1698. """
  1699. for key, value in formats.items():
  1700. if formatName == value:
  1701. return key
  1702. def SetSourceType(self, sourceType):
  1703. """Set source type (db, file, dir, ...).
  1704. Does not switch radioboxes."""
  1705. self._sourceType = sourceType
  1706. self.changingSizer.Show(self.filePanel, show=(sourceType == "file"))
  1707. self.changingSizer.Show(self.nativePanel, show=(sourceType == "native"))
  1708. self.changingSizer.Show(self.dirPanel, show=(sourceType == "dir"))
  1709. self.changingSizer.Show(self.protocolPanel, show=(sourceType == "pro"))
  1710. self.changingSizer.Show(self.dbPanel, show=(sourceType == "db"))
  1711. self.changingSizer.Layout()
  1712. if sourceType == "db":
  1713. self.dbWidgets["format"].SetItems(list(self.dbFormats.values()))
  1714. if self.dbFormats:
  1715. if "PostgreSQL" in self.dbFormats.values():
  1716. self.dbWidgets["format"].SetStringSelection("PostgreSQL")
  1717. else:
  1718. self.dbWidgets["format"].SetSelection(0)
  1719. self.dbWidgets["format"].Enable()
  1720. if sourceType == "db":
  1721. db = self.dbWidgets["format"].GetStringSelection()
  1722. self.SetDatabase(db)
  1723. if not self.dest:
  1724. self.reloadDataRequired.emit(listData=None, data=None)
  1725. self._reloadLayers()
  1726. def OnSettingsChanged(self, data):
  1727. """User changed setting"""
  1728. # data list: [type, dsn, format, options]
  1729. if len(data) == 3:
  1730. data.append("")
  1731. elif len(data) < 3:
  1732. return
  1733. self.source.SetSelection(self.sourceMap[data[0]])
  1734. self.SetSourceType(data[0])
  1735. if data[0] == "file":
  1736. self.fileWidgets["browse"].SetValue(data[1])
  1737. self.fileWidgets["options"].SetValue(data[3])
  1738. elif data[0] == "dir":
  1739. self.dirWidgets["browse"].SetValue(data[1])
  1740. self.dirWidgets["format"].SetStringSelection(data[2])
  1741. self.dirWidgets["options"].SetValue(data[3])
  1742. self.SetExtension(data[2])
  1743. elif data[0] == "pro":
  1744. self.protocolWidgets["text"].SetValue(data[2])
  1745. self.protocolWidgets["options"].SetValue(data[3])
  1746. elif data[0] == "db":
  1747. name = self._getCurrentDbWidgetName()
  1748. if name == "choice":
  1749. if len(data[1].split(":", 1)) > 1:
  1750. for item in data[1].split(":", 1)[1].split(","):
  1751. key, value = item.split("=", 1)
  1752. if key == "dbname":
  1753. self.dbWidgets[name].SetStringSelection(value)
  1754. break
  1755. else:
  1756. self.dbWidgets[name].SetStringSelection(data[1])
  1757. else:
  1758. self.dbWidgets[name].SetValue(data[1])
  1759. self.dbWidgets["options"].SetValue(data[3])
  1760. if not self.dest:
  1761. self.reloadDataRequired.emit(listData=None, data=None)
  1762. self._reloadLayers()
  1763. def AttachSettings(self):
  1764. if self.ogr:
  1765. settingsFile = os.path.join(GetSettingsPath(), "wxOGR")
  1766. else:
  1767. settingsFile = os.path.join(GetSettingsPath(), "wxGDAL")
  1768. self.settsManager = ManageSettingsWidget(parent=self, settingsFile=settingsFile)
  1769. self.settsManager.settingsChanged.connect(self.OnSettingsChanged)
  1770. self.settsManager.settingsSaving.connect(self.OnSettingsSaving)
  1771. # do layout
  1772. self.mainSizer.Insert(0, self.settsManager, flag=wx.ALL | wx.EXPAND, border=5)
  1773. def OnSettingsSaving(self, name):
  1774. """Saving data"""
  1775. if not self.GetDsn():
  1776. GMessage(
  1777. parent=self,
  1778. message=_("No data source defined, settings are not saved."),
  1779. )
  1780. return
  1781. self.settsManager.SetDataToSave(
  1782. (self._sourceType, self.GetDsn(), self.GetFormat(), self.GetOptions())
  1783. )
  1784. self.settsManager.SaveSettings(name)
  1785. def _getExtPatternGlob(self, ext):
  1786. """Get pattern for case-insensitive globing"""
  1787. pattern = "*."
  1788. for c in ext:
  1789. pattern += "[%s%s]" % (c.lower(), c.upper())
  1790. return pattern
  1791. def _getCurrentDbWidgetName(self):
  1792. """Returns active dns database widget name."""
  1793. for widget in ("browse", "dirbrowse", "text", "choice"):
  1794. if self.dbWidgets[widget].IsShown():
  1795. return widget
  1796. def GetDsn(self):
  1797. """Get datasource name"""
  1798. if self._sourceType == "db":
  1799. if self.dbWidgets["format"].GetStringSelection() in (
  1800. "PostgreSQL",
  1801. "PostGIS Raster driver",
  1802. ):
  1803. dsn = "PG:dbname=%s" % self.dbWidgets["choice"].GetStringSelection()
  1804. else:
  1805. name = self._getCurrentDbWidgetName()
  1806. if name == "choice":
  1807. dsn = self.dbWidgets[name].GetStringSelection()
  1808. else:
  1809. dsn = self.dbWidgets[name].GetValue()
  1810. else:
  1811. if self._sourceType == "file":
  1812. dsn = self.fileWidgets["browse"].GetValue()
  1813. elif self._sourceType == "dir":
  1814. dsn = self.dirWidgets["browse"].GetValue()
  1815. elif self._sourceType == "pro":
  1816. dsn = self.protocolWidgets["text"].GetValue()
  1817. else:
  1818. dsn = ""
  1819. # check compressed files
  1820. try:
  1821. ext = os.path.splitext(dsn)[1].lower()
  1822. except KeyError:
  1823. ext = None
  1824. if ext == ".zip":
  1825. dsn = "/vsizip/" + dsn
  1826. elif ext == ".gzip":
  1827. dsn = "/vsigzip/" + dsn
  1828. elif ext in (".tar", ".tar.gz", ".tgz"):
  1829. dsn = "/vsitar/" + dsn
  1830. return dsn
  1831. def SetDatabase(self, db):
  1832. """Update database panel."""
  1833. sizer = self.dbPanel.GetSizer()
  1834. showBrowse = db in ("SQLite", "Rasterlite")
  1835. showDirbrowse = db in ("FileGDB")
  1836. showChoice = db in (
  1837. "PostgreSQL",
  1838. "PostGIS WKT Raster driver",
  1839. "PostGIS Raster driver",
  1840. )
  1841. enableFeatType = self.dest and self.ogr and db in ("PostgreSQL")
  1842. showText = not (showBrowse or showChoice or showDirbrowse)
  1843. sizer.Show(self.dbWidgets["browse"], show=showBrowse)
  1844. sizer.Show(self.dbWidgets["dirbrowse"], show=showDirbrowse)
  1845. sizer.Show(self.dbWidgets["choice"], show=showChoice)
  1846. sizer.Show(self.dbWidgets["textLabel2"], show=showChoice)
  1847. sizer.Show(self.dbWidgets["text"], show=showText)
  1848. sizer.Show(self.dbWidgets["textLabel1"], show=showText)
  1849. self.dbWidgets["featType"].Enable(enableFeatType)
  1850. if showChoice:
  1851. # try to get list of PG databases
  1852. dbNames = RunCommand(
  1853. "db.databases", parent=self, quiet=True, read=True, driver="pg"
  1854. ).splitlines()
  1855. if dbNames is not None:
  1856. self.dbWidgets["choice"].SetItems(sorted(dbNames))
  1857. self.dbWidgets["choice"].SetSelection(0)
  1858. elif grass.find_program("psql", "--help"):
  1859. if not self.dbWidgets["choice"].GetItems():
  1860. p = grass.Popen(["psql", "-ltA"], stdout=grass.PIPE)
  1861. ret = p.communicate()[0]
  1862. if ret:
  1863. dbNames = list()
  1864. for line in ret.splitlines():
  1865. sline = line.split("|")
  1866. if len(sline) < 2:
  1867. continue
  1868. dbname = sline[0]
  1869. if dbname:
  1870. dbNames.append(dbname)
  1871. self.dbWidgets["choice"].SetItems(db)
  1872. self.dbWidgets["choice"].SetSelection(0)
  1873. else:
  1874. sizer.Show(self.dbWidgets["text"])
  1875. sizer.Show(self.dbWidgets["choice"], False)
  1876. sizer.Layout()
  1877. def OnUpdate(self, event):
  1878. """Update required - load layers."""
  1879. if not self.dest:
  1880. self._reloadLayers()
  1881. event.Skip()
  1882. def _reloadLayers(self):
  1883. """Reload list of layers"""
  1884. def hasRastSameProjAsLocation(dsn):
  1885. ret = RunCommand("r.external", quiet=True, read=True, flags="t", input=dsn)
  1886. # v.external returns info for individual bands, however projection is shared by all bands ->
  1887. # (it is possible to take first line)
  1888. lines = ret.splitlines()
  1889. projectionMatch = "0"
  1890. if lines:
  1891. bandNumber, bandType, projectionMatch = map(
  1892. lambda x: x.strip(), lines[0].split(",")
  1893. )
  1894. return projectionMatch
  1895. def getProjMatchCaption(projectionMatch):
  1896. if projectionMatch == "0":
  1897. projectionMatchCaption = _("No")
  1898. else:
  1899. projectionMatchCaption = _("Yes")
  1900. return projectionMatchCaption
  1901. dsn = self.GetDsn()
  1902. if not dsn:
  1903. return
  1904. data = list()
  1905. listData = list()
  1906. layerId = 1
  1907. if self.ogr:
  1908. ret = RunCommand("v.external", quiet=True, read=True, flags="t", input=dsn)
  1909. if not ret:
  1910. self.reloadDataRequired.emit(listData=None, data=None)
  1911. return
  1912. layerId = 1
  1913. for line in ret.splitlines():
  1914. layerName, featureType, projectionMatch, geometryColumn = map(
  1915. lambda x: x.strip(), line.split(",")
  1916. )
  1917. projectionMatchCaption = getProjMatchCaption(projectionMatch)
  1918. grassName = GetValidLayerName(layerName)
  1919. if geometryColumn:
  1920. featureType = geometryColumn + "/" + featureType
  1921. listData.append(
  1922. (layerId, layerName, featureType, projectionMatchCaption, grassName)
  1923. )
  1924. data.append(
  1925. (layerId, layerName, featureType, int(projectionMatch), grassName)
  1926. )
  1927. layerId += 1
  1928. else:
  1929. if self._sourceType == "file":
  1930. baseName = os.path.basename(dsn)
  1931. grassName = GetValidLayerName(baseName.split(".", -1)[0])
  1932. projectionMatch = hasRastSameProjAsLocation(dsn)
  1933. projectionMatchCaption = getProjMatchCaption(projectionMatch)
  1934. listData.append((layerId, baseName, projectionMatchCaption, grassName))
  1935. data.append((layerId, baseName, int(projectionMatch), grassName))
  1936. elif self._sourceType == "dir":
  1937. ext = self.dirWidgets["extension"].GetValue()
  1938. for filename in glob.glob(
  1939. os.path.join(dsn, "%s") % self._getExtPatternGlob(ext)
  1940. ):
  1941. baseName = os.path.basename(filename)
  1942. grassName = GetValidLayerName(baseName.split(".", -1)[0])
  1943. projectionMatch = hasRastSameProjAsLocation(filename)
  1944. projectionMatchCaption = getProjMatchCaption(projectionMatch)
  1945. listData.append(
  1946. (layerId, baseName, projectionMatchCaption, grassName)
  1947. )
  1948. data.append((layerId, baseName, int(projectionMatch), grassName))
  1949. layerId += 1
  1950. # emit signal
  1951. self.reloadDataRequired.emit(listData=listData, data=data)
  1952. def ExtensionChanged(self, event):
  1953. if not self.dest:
  1954. # reload layers
  1955. self._reloadLayers()
  1956. def SetExtension(self, name):
  1957. """Extension changed"""
  1958. ext = self._getExtension(name)
  1959. self.dirWidgets["extension"].SetValue(ext)
  1960. def GetType(self):
  1961. """Get source type"""
  1962. return self._sourceType
  1963. def GetFormat(self, getFormatAbbreviation=False):
  1964. """Get format as string"""
  1965. def _getFormat(getFormatAbbreviation, format_group):
  1966. """Get format long name or format abbreviation
  1967. :param bool getFormatAbbreviation: get format abbreviation
  1968. :param dict format_group: formats dict {formatAbbreviation: formatLongName}
  1969. for 'file', 'protocol', 'database' group
  1970. return str: long format name or format abbreviation
  1971. """
  1972. format = self.dirWidgets["format"].GetStringSelection()
  1973. if getFormatAbbreviation:
  1974. return self._getFormatAbbreviation(
  1975. formats=self.fileFormats,
  1976. formatName=format,
  1977. )
  1978. return format
  1979. if self._sourceType == "dir":
  1980. format = _getFormat(getFormatAbbreviation, format_group=self.fileFormats)
  1981. elif self._sourceType == "pro":
  1982. format = _getFormat(
  1983. getFormatAbbreviation, format_group=self.protocolFormats
  1984. )
  1985. elif self._sourceType == "db":
  1986. format = _getFormat(getFormatAbbreviation, format_group=self.dbFormats)
  1987. else:
  1988. format = ""
  1989. return format.replace(" ", "_")
  1990. def GetFormatExt(self):
  1991. """Get format extension"""
  1992. return self._getExtension(self.GetFormat())
  1993. def GetOptions(self):
  1994. """Get creation options"""
  1995. if self._sourceType == "file":
  1996. options = self.fileWidgets["options"].GetValue()
  1997. elif self._sourceType == "dir":
  1998. options = self.dirWidgets["options"].GetValue()
  1999. elif self._sourceType == "pro":
  2000. options = self.protocolWidgets["options"].GetValue()
  2001. elif self._sourceType == "db":
  2002. if self.dbWidgets["featType"].GetSelection() == 1:
  2003. options = "topology=yes "
  2004. else:
  2005. options = ""
  2006. options += self.dbWidgets["options"].GetValue()
  2007. return options.strip()
  2008. def OnHelp(self, event):
  2009. """Show related manual page"""
  2010. cmd = ""
  2011. if self.dest:
  2012. if self.ogr:
  2013. cmd = "v.external.out"
  2014. else:
  2015. cmd = "r.external.out"
  2016. else:
  2017. if self.link:
  2018. if self.ogr:
  2019. cmd = "v.external"
  2020. else:
  2021. cmd = "r.external"
  2022. else:
  2023. if self.ogr:
  2024. cmd = "v.in.ogr"
  2025. else:
  2026. cmd = "r.in.gdal"
  2027. RunCommand("g.manual", entry=cmd)
  2028. class ProjSelect(wx.ComboBox):
  2029. """Widget for selecting input raster/vector map used by
  2030. r.proj/v.proj modules."""
  2031. def __init__(
  2032. self,
  2033. parent,
  2034. isRaster,
  2035. id=wx.ID_ANY,
  2036. size=globalvar.DIALOG_COMBOBOX_SIZE,
  2037. **kwargs,
  2038. ):
  2039. super(ProjSelect, self).__init__(parent, id, size=size, **kwargs)
  2040. self.SetName("ProjSelect")
  2041. self.isRaster = isRaster
  2042. def UpdateItems(self, dbase, location, mapset):
  2043. """Update list of maps"""
  2044. if not dbase:
  2045. dbase = grass.gisenv()["GISDBASE"]
  2046. if not mapset:
  2047. mapset = grass.gisenv()["MAPSET"]
  2048. if self.isRaster:
  2049. ret = RunCommand(
  2050. "r.proj",
  2051. quiet=True,
  2052. read=True,
  2053. flags="l",
  2054. dbase=dbase,
  2055. location=location,
  2056. mapset=mapset,
  2057. )
  2058. else:
  2059. ret = RunCommand(
  2060. "v.proj",
  2061. quiet=True,
  2062. read=True,
  2063. flags="l",
  2064. dbase=dbase,
  2065. location=location,
  2066. mapset=mapset,
  2067. )
  2068. listMaps = list()
  2069. if ret:
  2070. for line in ret.splitlines():
  2071. listMaps.append(line.strip())
  2072. ListSortLower(listMaps)
  2073. self.SetItems(listMaps)
  2074. self.SetValue("")
  2075. class ElementSelect(wx.Choice):
  2076. def __init__(
  2077. self,
  2078. parent,
  2079. id=wx.ID_ANY,
  2080. elements=None,
  2081. size=globalvar.DIALOG_COMBOBOX_SIZE,
  2082. **kwargs,
  2083. ):
  2084. """Widget for selecting GIS element
  2085. :param parent: parent window
  2086. :param elements: filter elements
  2087. """
  2088. super(ElementSelect, self).__init__(parent, id, size=size, **kwargs)
  2089. self.SetName("ElementSelect")
  2090. task = gtask.parse_interface("g.list")
  2091. p = task.get_param(value="type")
  2092. self.values = p.get("values", [])
  2093. self.valuesDesc = p.get("values_desc", [])
  2094. if elements:
  2095. values = []
  2096. valuesDesc = []
  2097. for idx in range(0, len(self.values)):
  2098. value = self.values[idx]
  2099. if value in elements:
  2100. values.append(value)
  2101. valuesDesc.append(self.valuesDesc[idx])
  2102. self.values = values
  2103. self.valuesDesc = valuesDesc
  2104. self.SetItems(self.valuesDesc)
  2105. def GetValue(self, name):
  2106. """Translate value
  2107. :param name: element name
  2108. """
  2109. idx = self.valuesDesc.index(name)
  2110. if idx > -1:
  2111. return self.values[idx]
  2112. return ""
  2113. class OgrTypeSelect(wx.Panel):
  2114. def __init__(self, parent, panel, **kwargs):
  2115. """Widget to choose OGR feature type
  2116. :param parent: parent window
  2117. :param panel: wx.Panel instance used as parent window
  2118. """
  2119. wx.Panel.__init__(self, parent=panel, id=wx.ID_ANY)
  2120. self.ftype = wx.Choice(
  2121. parent=self,
  2122. id=wx.ID_ANY,
  2123. size=(200, -1),
  2124. choices=(_("Point"), _("LineString"), _("Polygon")),
  2125. )
  2126. self._layout()
  2127. def _layout(self):
  2128. """Do layout"""
  2129. sizer = wx.BoxSizer(wx.HORIZONTAL)
  2130. sizer.Add(
  2131. StaticText(parent=self, id=wx.ID_ANY, label=_("Feature type:")),
  2132. proportion=1,
  2133. flag=wx.ALIGN_CENTER_VERTICAL,
  2134. border=5,
  2135. )
  2136. sizer.Add(self.ftype, proportion=0, flag=wx.EXPAND | wx.ALIGN_RIGHT)
  2137. self.SetSizer(sizer)
  2138. sizer.Fit(self)
  2139. def GetType(self):
  2140. """Get selected type as string
  2141. :return: feature type as string
  2142. """
  2143. sel = self.ftype.GetSelection()
  2144. if sel == 0:
  2145. return "point"
  2146. elif sel == 1:
  2147. return "line"
  2148. elif sel == 2:
  2149. return "boundary"
  2150. class CoordinatesSelect(Panel):
  2151. def __init__(self, parent, giface, multiple=False, **kwargs):
  2152. """Widget to get coordinates from map window by mouse click
  2153. :param parent: parent window
  2154. :param giface: GRASS interface
  2155. :param multiple: - True if it is possible to insert more coordinates
  2156. """
  2157. self._giface = giface
  2158. self.multiple = multiple
  2159. self.mapWin = None
  2160. self.drawMapWin = None
  2161. super(CoordinatesSelect, self).__init__(parent=parent, id=wx.ID_ANY)
  2162. self.coordsField = TextCtrl(
  2163. parent=self,
  2164. id=wx.ID_ANY,
  2165. size=globalvar.DIALOG_TEXTCTRL_SIZE,
  2166. validator=CoordinatesValidator(),
  2167. )
  2168. icon = wx.Bitmap(os.path.join(globalvar.ICONDIR, "grass", "pointer.png"))
  2169. self.buttonInsCoords = buttons.ThemedGenBitmapToggleButton(
  2170. parent=self, id=wx.ID_ANY, bitmap=icon, size=globalvar.DIALOG_COLOR_SIZE
  2171. )
  2172. self.registered = False
  2173. self.buttonInsCoords.Bind(wx.EVT_BUTTON, self._onClick)
  2174. mapdisp = self._giface.GetMapDisplay()
  2175. if mapdisp:
  2176. switcher = mapdisp.GetToolSwitcher()
  2177. switcher.AddCustomToolToGroup(
  2178. group="mouseUse",
  2179. btnId=self.buttonInsCoords.GetId(),
  2180. toggleHandler=self.buttonInsCoords.SetValue,
  2181. )
  2182. self._doLayout()
  2183. self.coordsField.Bind(wx.EVT_TEXT, lambda event: self._draw(delay=1))
  2184. def _doLayout(self):
  2185. self.dialogSizer = wx.BoxSizer(wx.HORIZONTAL)
  2186. self.dialogSizer.Add(self.coordsField, proportion=1, flag=wx.EXPAND)
  2187. self.dialogSizer.Add(self.buttonInsCoords)
  2188. self.SetSizer(self.dialogSizer)
  2189. def _onClick(self, event):
  2190. """Button for interacitve inserting of coordinates clicked"""
  2191. self.mapWin = self._giface.GetMapWindow()
  2192. if self.buttonInsCoords.GetToggle() and self.mapWin:
  2193. switcher = self._giface.GetMapDisplay().GetToolSwitcher()
  2194. switcher.ToolChanged(self.buttonInsCoords.GetId())
  2195. if not self.mapWin.RegisterMouseEventHandler(
  2196. wx.EVT_LEFT_DOWN, self._onMapClickHandler, "cross"
  2197. ):
  2198. return
  2199. self.registered = True
  2200. self._giface.GetMapDisplay().Raise()
  2201. else:
  2202. if self.mapWin and self.mapWin.UnregisterMouseEventHandler(
  2203. wx.EVT_LEFT_DOWN, self._onMapClickHandler
  2204. ):
  2205. self.registered = False
  2206. return
  2207. def drawCleanUp(self):
  2208. if self.drawMapWin:
  2209. self.drawMapWin.UnregisterGraphicsToDraw(self.pointsToDraw)
  2210. def _draw(self, delay):
  2211. """Draws points representing inserted coordinates in mapwindow."""
  2212. if self.drawMapWin != self.mapWin:
  2213. self.drawCleanUp()
  2214. if self.mapWin:
  2215. self.drawMapWin = self.mapWin
  2216. self.pointsToDraw = self.drawMapWin.RegisterGraphicsToDraw(
  2217. graphicsType="point"
  2218. )
  2219. if self.drawMapWin:
  2220. items = self.pointsToDraw.GetAllItems()
  2221. for i in items:
  2222. self.pointsToDraw.DeleteItem(i)
  2223. coords = self._getCoords()
  2224. if coords is not None:
  2225. for i in range(len(coords) // 2):
  2226. i = i * 2
  2227. self.pointsToDraw.AddItem(coords=(coords[i], coords[i + 1]))
  2228. self._giface.updateMap.emit(render=False, renderVector=False, delay=delay)
  2229. def _getCoords(self):
  2230. """Get list of coordinates.
  2231. :return: None if values are not valid
  2232. """
  2233. if self.coordsField.GetValidator().Validate(self):
  2234. return self.coordsField.GetValue().split(",")
  2235. return None
  2236. def _onMapClickHandler(self, event):
  2237. """Gets coordinates from mapwindow"""
  2238. if event == "unregistered":
  2239. return
  2240. e, n = self.mapWin.GetLastEN()
  2241. prevCoords = ""
  2242. if self.multiple:
  2243. prevCoords = self.coordsField.GetValue().strip()
  2244. if prevCoords != "":
  2245. prevCoords += ","
  2246. value = prevCoords + str(e) + "," + str(n)
  2247. self.coordsField.SetValue(value)
  2248. self._draw(delay=0)
  2249. def OnClose(self):
  2250. """Unregistrates _onMapClickHandler from mapWin"""
  2251. self.drawCleanUp()
  2252. self._giface.updateMap.emit(render=False, renderVector=False)
  2253. mapdisp = self._giface.GetMapDisplay()
  2254. if mapdisp:
  2255. switcher = mapdisp.GetToolSwitcher()
  2256. switcher.RemoveCustomToolFromGroup(self.buttonInsCoords.GetId())
  2257. if self.mapWin and self.registered:
  2258. self.mapWin.UnregisterMouseEventHandler(
  2259. wx.EVT_LEFT_DOWN, self._onMapClickHandler
  2260. )
  2261. def GetTextWin(self):
  2262. """Get TextCtrl widget"""
  2263. return self.coordsField
  2264. class VectorCategorySelect(wx.Panel):
  2265. """Widget that allows interactive selection of vector features"""
  2266. def __init__(self, parent, giface, task=None):
  2267. super(VectorCategorySelect, self).__init__(parent=parent, id=wx.ID_ANY)
  2268. self.task = task
  2269. self.parent = parent
  2270. self.giface = giface
  2271. self.selectedFeatures = None
  2272. self.registered = False
  2273. self._vectorSelect = None
  2274. self.mapdisp = self.giface.GetMapDisplay()
  2275. self.catsField = TextCtrl(
  2276. parent=self, id=wx.ID_ANY, size=globalvar.DIALOG_TEXTCTRL_SIZE
  2277. )
  2278. icon = wx.Bitmap(os.path.join(globalvar.ICONDIR, "grass", "select.png"))
  2279. self.buttonVecSelect = buttons.ThemedGenBitmapToggleButton(
  2280. parent=self, id=wx.ID_ANY, bitmap=icon, size=globalvar.DIALOG_COLOR_SIZE
  2281. )
  2282. self.buttonVecSelect.Bind(wx.EVT_BUTTON, self._onClick)
  2283. if self.mapdisp:
  2284. switcher = self.mapdisp.GetToolSwitcher()
  2285. switcher.AddCustomToolToGroup(
  2286. group="mouseUse",
  2287. btnId=self.buttonVecSelect.GetId(),
  2288. toggleHandler=self.buttonVecSelect.SetValue,
  2289. )
  2290. self._layout()
  2291. def _isMapSelected(self):
  2292. """Check if layer list contains at least one selected map"""
  2293. layerList = self.giface.GetLayerList()
  2294. layerSelected = layerList.GetSelectedLayer()
  2295. if layerSelected is None:
  2296. GWarning(_("No vector map selected in layer manager. Operation canceled."))
  2297. return False
  2298. return True
  2299. def _chckMap(self):
  2300. """Check if selected map in 'input' widget is the same as selected map in lmgr"""
  2301. if self._isMapSelected():
  2302. layerList = self.giface.GetLayerList()
  2303. layerSelected = layerList.GetSelectedLayer()
  2304. # d.vect module
  2305. inputName = self.task.get_param(value="map", raiseError=False)
  2306. if not inputName:
  2307. inputName = self.task.get_param("input")
  2308. if inputName["value"] != str(layerSelected):
  2309. if inputName["value"] == "" or inputName["value"] is None:
  2310. GWarning(_("Input vector map is not selected"))
  2311. return False
  2312. GWarning(
  2313. _(
  2314. "Input vector map <%s> and selected map <%s> in layer manager are different. "
  2315. "Operation canceled."
  2316. )
  2317. % (inputName["value"], str(layerSelected))
  2318. )
  2319. return False
  2320. return True
  2321. return False
  2322. def _onClick(self, evt=None):
  2323. if self.task is not None:
  2324. if not self._chckMap():
  2325. self.buttonVecSelect.SetValue(False)
  2326. return
  2327. else:
  2328. if not self._isMapSelected():
  2329. self.buttonVecSelect.SetValue(False)
  2330. return
  2331. if self._vectorSelect is None:
  2332. if self.mapdisp:
  2333. if self.buttonVecSelect.IsEnabled():
  2334. switcher = self.mapdisp.GetToolSwitcher()
  2335. switcher.ToolChanged(self.buttonVecSelect.GetId())
  2336. self._vectorSelect = VectorSelectBase(self.mapdisp, self.giface)
  2337. if not self.mapdisp.GetWindow().RegisterMouseEventHandler(
  2338. wx.EVT_LEFT_DOWN, self._onMapClickHandler, "cross"
  2339. ):
  2340. return
  2341. self.registered = True
  2342. self.mapdisp.Raise()
  2343. else:
  2344. self.OnClose()
  2345. def OnClose(self, event=None):
  2346. if not self.mapdisp:
  2347. return
  2348. switcher = self.mapdisp.GetToolSwitcher()
  2349. switcher.RemoveCustomToolFromGroup(self.buttonVecSelect.GetId())
  2350. if self._vectorSelect is not None:
  2351. tmp = self._vectorSelect.GetLineStringSelectedCats()
  2352. self._vectorSelect.OnClose()
  2353. self.catsField.SetValue(tmp)
  2354. self._vectorSelect = None
  2355. def _onMapClickHandler(self, event):
  2356. """Update category text input widget"""
  2357. if event == "unregistered":
  2358. return
  2359. if self.task is None:
  2360. if not self._isMapSelected():
  2361. self.OnClose()
  2362. else:
  2363. self.catsField.SetValue(self._vectorSelect.GetLineStringSelectedCats())
  2364. else:
  2365. if not self._chckMap():
  2366. self.OnClose()
  2367. else:
  2368. self.catsField.SetValue(self._vectorSelect.GetLineStringSelectedCats())
  2369. def GetTextWin(self):
  2370. return self.catsField
  2371. def GetValue(self):
  2372. return self.catsField.GetValue()
  2373. def SetValue(self, value):
  2374. self.catsField.SetValue(value)
  2375. def _layout(self):
  2376. self.dialogSizer = wx.BoxSizer(wx.HORIZONTAL)
  2377. self.dialogSizer.Add(self.catsField, proportion=1, flag=wx.EXPAND)
  2378. self.dialogSizer.Add(self.buttonVecSelect)
  2379. self.SetSizer(self.dialogSizer)
  2380. class SignatureSelect(wx.ComboBox):
  2381. """Widget for selecting signatures"""
  2382. def __init__(
  2383. self,
  2384. parent,
  2385. element,
  2386. mapsets,
  2387. id=wx.ID_ANY,
  2388. size=globalvar.DIALOG_GSELECT_SIZE,
  2389. **kwargs,
  2390. ):
  2391. super(SignatureSelect, self).__init__(parent, id, size=size, **kwargs)
  2392. items = []
  2393. if mapsets:
  2394. for mapset in mapsets:
  2395. self._append_mapset_signatures(mapset, element, items)
  2396. else:
  2397. self._append_mapset_signatures(None, element, items)
  2398. self.SetItems(items)
  2399. self.SetValue("")
  2400. def _append_mapset_signatures(self, mapset, element, items):
  2401. try:
  2402. from grass.lib.imagery import (
  2403. I_SIGFILE_TYPE_SIG,
  2404. I_SIGFILE_TYPE_SIGSET,
  2405. I_signatures_list_by_type,
  2406. I_free_signatures_list,
  2407. )
  2408. except ImportError as e:
  2409. sys.stderr.write(
  2410. _("Unable to import C imagery library functions: %s\n") % e
  2411. )
  2412. return
  2413. # Extend here if a new signature type is introduced
  2414. if element == "signatures/sig":
  2415. sig_type = I_SIGFILE_TYPE_SIG
  2416. elif element == "signatures/sigset":
  2417. sig_type = I_SIGFILE_TYPE_SIGSET
  2418. else:
  2419. return
  2420. list_ptr = ctypes.POINTER(ctypes.c_char_p)
  2421. sig_list = list_ptr()
  2422. count = I_signatures_list_by_type(sig_type, mapset, ctypes.byref(sig_list))
  2423. for n in range(count):
  2424. items.append(grass.decode(sig_list[n]))
  2425. I_free_signatures_list(count, ctypes.byref(sig_list))
  2426. class SeparatorSelect(wx.ComboBox):
  2427. """Widget for selecting seperator"""
  2428. def __init__(
  2429. self, parent, id=wx.ID_ANY, size=globalvar.DIALOG_GSELECT_SIZE, **kwargs
  2430. ):
  2431. super(SeparatorSelect, self).__init__(parent, id, size=size, **kwargs)
  2432. self.SetName("SeparatorSelect")
  2433. self.SetItems(["pipe", "comma", "space", "tab", "newline"])
  2434. class SqlWhereSelect(wx.Panel):
  2435. def __init__(self, parent, **kwargs):
  2436. """Widget to define SQL WHERE condition.
  2437. :param parent: parent window
  2438. """
  2439. super(SqlWhereSelect, self).__init__(parent=parent, id=wx.ID_ANY)
  2440. self.parent = parent
  2441. self.vector_map = None
  2442. self.sqlField = TextCtrl(
  2443. parent=self, id=wx.ID_ANY, size=globalvar.DIALOG_TEXTCTRL_SIZE
  2444. )
  2445. self.GetChildren()[0].SetName("SqlWhereSelect")
  2446. icon = wx.Bitmap(os.path.join(globalvar.ICONDIR, "grass", "table.png"))
  2447. self.buttonInsSql = buttons.ThemedGenBitmapButton(
  2448. parent=self, id=wx.ID_ANY, bitmap=icon, size=globalvar.DIALOG_COLOR_SIZE
  2449. )
  2450. self.buttonInsSql.Bind(wx.EVT_BUTTON, self._onClick)
  2451. self._doLayout()
  2452. def _doLayout(self):
  2453. self.dialogSizer = wx.BoxSizer(wx.HORIZONTAL)
  2454. self.dialogSizer.Add(self.sqlField, proportion=1, flag=wx.EXPAND)
  2455. self.dialogSizer.Add(self.buttonInsSql)
  2456. self.SetSizer(self.dialogSizer)
  2457. def GetTextWin(self):
  2458. return self.sqlField
  2459. def _onClick(self, event):
  2460. from dbmgr.sqlbuilder import SQLBuilderWhere
  2461. try:
  2462. if not self.vector_map:
  2463. raise GException(_("No vector map selected"))
  2464. win = SQLBuilderWhere(
  2465. parent=self, vectmap=self.vector_map, layer=self.vector_layer
  2466. )
  2467. win.Show()
  2468. except GException as e:
  2469. GMessage(parent=self.parent, message="{}".format(e))
  2470. def SetData(self, vector, layer):
  2471. self.vector_map = vector
  2472. self.vector_layer = int(layer) # TODO: support layer names
  2473. def SetValue(self, value):
  2474. self.sqlField.SetValue(value)