gselect.py 95 KB

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