gselect.py 65 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738
  1. """!
  2. @package gselect
  3. @brief Custom control that selects elements
  4. Classes:
  5. - Select
  6. - VectorSelect
  7. - TreeCrtlComboPopup
  8. - VectorDBInfo
  9. - LayerSelect
  10. - DriverSelect
  11. - DatabaseSelect
  12. - ColumnSelect
  13. - DbaseSelect
  14. - LocationSelect
  15. - MapsetSelect
  16. - SubGroupSelect
  17. - FormatSelect
  18. - GdalSelect
  19. - ProjSelect
  20. - ElementSelect
  21. - OgrTypeSelect
  22. (C) 2007-2011 by the GRASS Development Team This program is free
  23. software under the GNU General Public License (>=v2). Read the file
  24. COPYING that comes with GRASS for details.
  25. @author Michael Barton
  26. @author Martin Landa <landa.martin gmail.com>
  27. @author Vaclav Petras <wenzeslaus gmail.com> (menu customization)
  28. """
  29. import os
  30. import sys
  31. import glob
  32. import wx
  33. import wx.combo
  34. import wx.lib.filebrowsebutton as filebrowse
  35. from wx.lib.newevent import NewEvent
  36. import globalvar
  37. sys.path.append(os.path.join(globalvar.ETCDIR, "python"))
  38. import grass.script as grass
  39. from grass.script import task as gtask
  40. import gcmd
  41. import utils
  42. from preferences import globalSettings as UserSettings
  43. from debug import Debug
  44. wxGdalSelect, EVT_GDALSELECT = NewEvent()
  45. class Select(wx.combo.ComboCtrl):
  46. def __init__(self, parent, id = wx.ID_ANY, size = globalvar.DIALOG_GSELECT_SIZE,
  47. type = None, multiple = False, mapsets = None,
  48. updateOnPopup = True, onPopup = None):
  49. """!Custom control to create a ComboBox with a tree control to
  50. display and select GIS elements within acessible mapsets.
  51. Elements can be selected with mouse. Can allow multiple
  52. selections, when argument multiple=True. Multiple selections
  53. are separated by commas.
  54. @param type type of GIS elements ('raster, 'vector', ...)
  55. @param multiple multiple input allowed?
  56. @param mapsets force list of mapsets (otherwise search path)
  57. @param updateOnPopup True for updating list of elements on popup
  58. @param onPopup function to be called on Popup
  59. """
  60. wx.combo.ComboCtrl.__init__(self, parent=parent, id=id, size=size)
  61. self.GetChildren()[0].SetName("Select")
  62. self.GetChildren()[0].type = type
  63. self.tcp = TreeCtrlComboPopup()
  64. self.SetPopupControl(self.tcp)
  65. self.SetPopupExtents(0, 100)
  66. if type:
  67. self.tcp.SetData(type = type, mapsets = mapsets,
  68. multiple = multiple,
  69. updateOnPopup = updateOnPopup, onPopup = onPopup)
  70. self.GetChildren()[0].Bind(wx.EVT_KEY_UP, self.OnKeyUp)
  71. def OnKeyUp(self, event):
  72. """!Shows popupwindow if down arrow key is released"""
  73. if event.GetKeyCode() == wx.WXK_DOWN:
  74. self.ShowPopup()
  75. else:
  76. event.Skip()
  77. def SetElementList(self, type, mapsets = None):
  78. """!Set element list
  79. @param type GIS element type
  80. @param mapsets list of acceptable mapsets (None for all in search path)
  81. """
  82. self.tcp.SetData(type = type, mapsets = mapsets)
  83. def GetElementList(self):
  84. """!Load elements"""
  85. self.tcp.GetElementList()
  86. def SetType(self, etype, multiple = False, mapsets = None,
  87. updateOnPopup = True, onPopup = None):
  88. """!Param set element type for widget
  89. @param etype element type, see gselect.ElementSelect
  90. """
  91. self.tcp.SetData(type = etype, mapsets = mapsets,
  92. multiple = multiple,
  93. updateOnPopup = updateOnPopup, onPopup = onPopup)
  94. class VectorSelect(Select):
  95. def __init__(self, parent, ftype, **kwargs):
  96. """!Custom to create a ComboBox with a tree control to display and
  97. select vector maps. Control allows to filter vector maps. If you
  98. don't need this feature use Select class instead
  99. @ftype filter vector maps based on feature type
  100. """
  101. Select.__init__(self, parent = parent, id = wx.ID_ANY,
  102. type = 'vector', **kwargs)
  103. self.ftype = ftype
  104. # remove vector maps which do not contain given feature type
  105. self.tcp.SetFilter(self._isElement)
  106. def _isElement(self, vectorName):
  107. """!Check if element should be filtered out"""
  108. try:
  109. if int(grass.vector_info_topo(vectorName)[self.ftype]) < 1:
  110. return False
  111. except KeyError:
  112. return False
  113. return True
  114. class TreeCtrlComboPopup(wx.combo.ComboPopup):
  115. """!Create a tree ComboBox for selecting maps and other GIS elements
  116. in accessible mapsets within the current location
  117. """
  118. # overridden ComboPopup methods
  119. def Init(self):
  120. self.value = [] # for multiple is False -> len(self.value) in [0,1]
  121. self.curitem = None
  122. self.multiple = False
  123. self.type = None
  124. self.mapsets = None
  125. self.updateOnPopup = True
  126. self.onPopup = None
  127. self.SetFilter(None)
  128. def Create(self, parent):
  129. self.seltree = wx.TreeCtrl(parent, style=wx.TR_HIDE_ROOT
  130. |wx.TR_HAS_BUTTONS
  131. |wx.TR_SINGLE
  132. |wx.TR_LINES_AT_ROOT
  133. |wx.SIMPLE_BORDER
  134. |wx.TR_FULL_ROW_HIGHLIGHT)
  135. self.seltree.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
  136. self.seltree.Bind(wx.EVT_MOTION, self.OnMotion)
  137. self.seltree.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
  138. self.seltree.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.mapsetExpanded)
  139. self.seltree.Bind(wx.EVT_TREE_ITEM_COLLAPSED, self.mapsetCollapsed)
  140. self.seltree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.mapsetActivated)
  141. self.seltree.Bind(wx.EVT_TREE_SEL_CHANGED, self.mapsetSelected)
  142. self.seltree.Bind(wx.EVT_TREE_DELETE_ITEM, lambda x: None)
  143. # the following dummy handler are needed to keep tree events from propagating up to
  144. # the parent GIS Manager layer tree
  145. def mapsetExpanded(self, event):
  146. pass
  147. def mapsetCollapsed(self, event):
  148. pass
  149. def mapsetActivated(self, event):
  150. pass
  151. def mapsetSelected(self, event):
  152. pass
  153. # end of dummy events
  154. def GetControl(self):
  155. return self.seltree
  156. def GetStringValue(self):
  157. str = ""
  158. for value in self.value:
  159. str += value + ","
  160. str = str.rstrip(',')
  161. return str
  162. def SetFilter(self, filter):
  163. """!Set filter for GIS elements, see e.g. VectorSelect"""
  164. self.filterElements = filter
  165. def OnPopup(self, force = False):
  166. """!Limited only for first selected"""
  167. if not force and not self.updateOnPopup:
  168. return
  169. if self.onPopup:
  170. selected, exclude = self.onPopup(self.type)
  171. else:
  172. selected = None
  173. exclude = False
  174. self.GetElementList(selected, exclude)
  175. # selects map starting according to written text
  176. inputText = self.GetCombo().GetValue().strip()
  177. if inputText:
  178. root = self.seltree.GetRootItem()
  179. match = self.FindItem(root, inputText, startLetters = True)
  180. self.seltree.EnsureVisible(match)
  181. self.seltree.SelectItem(match)
  182. def GetElementList(self, elements = None, exclude = False):
  183. """!Get filtered list of GIS elements in accessible mapsets
  184. and display as tree with all relevant elements displayed
  185. beneath each mapset branch
  186. """
  187. # update list
  188. self.seltree.DeleteAllItems()
  189. self._getElementList(self.type, self.mapsets, elements, exclude)
  190. if len(self.value) > 0:
  191. root = self.seltree.GetRootItem()
  192. if not root:
  193. return
  194. item = self.FindItem(root, self.value[0])
  195. try:
  196. self.seltree.EnsureVisible(item)
  197. self.seltree.SelectItem(item)
  198. except:
  199. pass
  200. def SetStringValue(self, value):
  201. # this assumes that item strings are unique...
  202. root = self.seltree.GetRootItem()
  203. if not root:
  204. return
  205. found = self.FindItem(root, value)
  206. self.value = self.GetCombo().GetValue().strip(',').split(',')
  207. if found:
  208. self.value.append(found)
  209. self.seltree.SelectItem(found)
  210. def GetAdjustedSize(self, minWidth, prefHeight, maxHeight):
  211. """!Reads UserSettings to get height (which was 200 in old implementation).
  212. """
  213. height = UserSettings.Get(group = 'appearance', key = 'gSelectPopupHeight', subkey = 'value')
  214. return wx.Size(minWidth, min(height, maxHeight))
  215. def _getElementList(self, element, mapsets = None, elements = None, exclude = False):
  216. """!Get list of GIS elements in accessible mapsets and display as tree
  217. with all relevant elements displayed beneath each mapset branch
  218. @param element GIS element
  219. @param mapsets list of acceptable mapsets (None for all mapsets in search path)
  220. @param elements list of forced GIS elements
  221. @param exclude True to exclude, False for forcing the list (elements)
  222. """
  223. # get current mapset
  224. curr_mapset = grass.gisenv()['MAPSET']
  225. # map element types to g.mlist types
  226. elementdict = {'cell':'rast',
  227. 'raster':'rast',
  228. 'rast':'rast',
  229. 'raster files':'rast',
  230. 'grid3':'rast3d',
  231. 'rast3d':'rast3d',
  232. '3d-raster':'rast3d',
  233. 'raster3D':'rast3d',
  234. 'raster3D files':'rast3d',
  235. 'vector':'vect',
  236. 'vect':'vect',
  237. 'binary vector files':'vect',
  238. 'dig':'oldvect',
  239. 'oldvect':'oldvect',
  240. 'old vector':'oldvect',
  241. 'dig_ascii':'asciivect',
  242. 'asciivect':'asciivect',
  243. 'asciivector':'asciivect',
  244. 'ascii vector files':'asciivect',
  245. 'icons':'icon',
  246. 'icon':'icon',
  247. 'paint icon files':'icon',
  248. 'paint/labels':'labels',
  249. 'labels':'labels',
  250. 'label':'labels',
  251. 'paint label files':'labels',
  252. 'site_lists':'sites',
  253. 'sites':'sites',
  254. 'site list':'sites',
  255. 'site list files':'sites',
  256. 'windows':'region',
  257. 'region':'region',
  258. 'region definition':'region',
  259. 'region definition files':'region',
  260. 'windows3d':'region3d',
  261. 'region3d':'region3d',
  262. 'region3D definition':'region3d',
  263. 'region3D definition files':'region3d',
  264. 'group':'group',
  265. 'imagery group':'group',
  266. 'imagery group files':'group',
  267. '3d.view':'3dview',
  268. '3dview':'3dview',
  269. '3D viewing parameters':'3dview',
  270. '3D view parameters':'3dview'}
  271. if element not in elementdict:
  272. self.AddItem(_('Not selectable element'))
  273. return
  274. if globalvar.have_mlist:
  275. filesdict = grass.mlist_grouped(elementdict[element],
  276. check_search_path = False)
  277. else:
  278. filesdict = grass.list_grouped(elementdict[element],
  279. check_search_path = False)
  280. # list of mapsets in current location
  281. if mapsets is None:
  282. mapsets = grass.mapsets()
  283. # current mapset first
  284. if curr_mapset in mapsets and mapsets[0] != curr_mapset:
  285. mapsets.remove(curr_mapset)
  286. mapsets.insert(0, curr_mapset)
  287. first_mapset = None
  288. for mapset in mapsets:
  289. mapset_node = self.AddItem('Mapset: ' + mapset)
  290. if not first_mapset:
  291. first_mapset = mapset_node
  292. self.seltree.SetItemTextColour(mapset_node, wx.Colour(50, 50, 200))
  293. if mapset not in filesdict:
  294. continue
  295. try:
  296. elem_list = filesdict[mapset]
  297. elem_list.sort()
  298. for elem in elem_list:
  299. if elem != '':
  300. fullqElem = elem + '@' + mapset
  301. if elements is not None:
  302. if (exclude and fullqElem in elements) or \
  303. (not exclude and fullqElem not in elements):
  304. continue
  305. if self.filterElements:
  306. if self.filterElements(fullqElem):
  307. self.AddItem(elem, parent=mapset_node)
  308. else:
  309. self.AddItem(elem, parent=mapset_node)
  310. except StandardError, e:
  311. sys.stderr.write(_("GSelect: invalid item: %s") % e)
  312. continue
  313. if self.seltree.ItemHasChildren(mapset_node):
  314. sel = UserSettings.Get(group='appearance', key='elementListExpand',
  315. subkey='selection')
  316. collapse = True
  317. if sel == 0: # collapse all except PERMANENT and current
  318. if mapset in ('PERMANENT', curr_mapset):
  319. collapse = False
  320. elif sel == 1: # collapse all except PERMANENT
  321. if mapset == 'PERMANENT':
  322. collapse = False
  323. elif sel == 2: # collapse all except current
  324. if mapset == curr_mapset:
  325. collapse = False
  326. elif sel == 3: # collapse all
  327. pass
  328. elif sel == 4: # expand all
  329. collapse = False
  330. if collapse:
  331. self.seltree.Collapse(mapset_node)
  332. else:
  333. self.seltree.Expand(mapset_node)
  334. if first_mapset:
  335. # select first mapset (MSW hack)
  336. self.seltree.SelectItem(first_mapset)
  337. # helpers
  338. def FindItem(self, parentItem, text, startLetters = False):
  339. """!Finds item with given name or starting with given text"""
  340. startletters = startLetters
  341. item, cookie = self.seltree.GetFirstChild(parentItem)
  342. while wx.TreeItemId.IsOk(item):
  343. if self.seltree.GetItemText(item) == text:
  344. return item
  345. if self.seltree.ItemHasChildren(item):
  346. item = self.FindItem(item, text, startLetters = startletters)
  347. if wx.TreeItemId.IsOk(item):
  348. return item
  349. elif startletters and self.seltree.GetItemText(item).startswith(text.split('@')[0]):
  350. return item
  351. item, cookie = self.seltree.GetNextChild(parentItem, cookie)
  352. return wx.TreeItemId()
  353. def AddItem(self, value, parent=None):
  354. if not parent:
  355. root = self.seltree.GetRootItem()
  356. if not root:
  357. root = self.seltree.AddRoot("<hidden root>")
  358. parent = root
  359. item = self.seltree.AppendItem(parent, text=value)
  360. return item
  361. # able to recieve only wx.EVT_KEY_UP
  362. def OnKeyUp(self, event):
  363. """!Enables to select items using keyboard"""
  364. item = self.seltree.GetSelection()
  365. if event.GetKeyCode() == wx.WXK_DOWN:
  366. self.seltree.SelectItem(self.seltree.GetNextVisible(item))
  367. # problem with GetPrevVisible
  368. elif event.GetKeyCode() == wx.WXK_UP:
  369. if self.seltree.ItemHasChildren(item) and self.seltree.IsExpanded(self.seltree.GetPrevSibling(item)):
  370. itemPrev = self.seltree.GetLastChild(self.seltree.GetPrevSibling(item))
  371. else:
  372. itemPrev = self.seltree.GetPrevSibling(item)
  373. if not wx.TreeItemId.IsOk(itemPrev):
  374. itemPrev = self.seltree.GetItemParent(item)
  375. if item == self.seltree.GetFirstChild(self.seltree.GetRootItem())[0]:
  376. itemPrev = item
  377. self.seltree.SelectItem(itemPrev)
  378. # selects first item starting with the written text in next mapset
  379. elif event.GetKeyCode() == wx.WXK_TAB:
  380. selected = self.seltree.GetSelection()
  381. if self.seltree.ItemHasChildren(selected):
  382. parent = selected
  383. else:
  384. parent = self.seltree.GetItemParent(selected)
  385. nextSibling = self.seltree.GetNextSibling(parent)
  386. if wx.TreeItemId.IsOk(nextSibling):
  387. match = self.FindItem(nextSibling, self.GetCombo().GetValue().strip(), True)
  388. else:
  389. match = self.FindItem(self.seltree.GetFirstChild(self.seltree.GetItemParent(parent))[0],
  390. self.GetCombo().GetValue().strip(), True)
  391. self.seltree.SelectItem(match)
  392. elif event.GetKeyCode() == wx.WXK_RIGHT:
  393. if self.seltree.ItemHasChildren(item):
  394. self.seltree.Expand(item)
  395. elif event.GetKeyCode() == wx.WXK_LEFT:
  396. if self.seltree.ItemHasChildren(item):
  397. self.seltree.Collapse(item)
  398. elif event.GetKeyCode() == wx.WXK_ESCAPE:
  399. self.Dismiss()
  400. elif event.GetKeyCode() == wx.WXK_RETURN:
  401. if self.seltree.GetRootItem() == self.seltree.GetItemParent(item):
  402. self.value = []
  403. else:
  404. mapsetItem = self.seltree.GetItemParent(item)
  405. fullName = self.seltree.GetItemText(item) + '@' \
  406. + self.seltree.GetItemText(mapsetItem).split(' ', 1)[1]
  407. if self.multiple is True:
  408. # text item should be unique
  409. self.value.append(fullName)
  410. else:
  411. self.value = [fullName, ]
  412. self.Dismiss()
  413. def OnMotion(self, evt):
  414. """!Have the selection follow the mouse, like in a real combobox
  415. """
  416. item, flags = self.seltree.HitTest(evt.GetPosition())
  417. if item and flags & wx.TREE_HITTEST_ONITEMLABEL:
  418. self.seltree.SelectItem(item)
  419. self.curitem = item
  420. evt.Skip()
  421. def OnLeftDown(self, evt):
  422. """!Do the combobox selection
  423. """
  424. item, flags = self.seltree.HitTest(evt.GetPosition())
  425. if item and flags & wx.TREE_HITTEST_ONITEMLABEL:
  426. self.curitem = item
  427. if self.seltree.GetRootItem() == self.seltree.GetItemParent(item):
  428. self.value = [] # cannot select mapset item
  429. else:
  430. mapsetItem = self.seltree.GetItemParent(item)
  431. fullName = self.seltree.GetItemText(item) + '@' + \
  432. self.seltree.GetItemText(mapsetItem).split(' ', 1)[1]
  433. if self.multiple is True:
  434. # text item should be unique
  435. self.value.append(fullName)
  436. else:
  437. self.value = [fullName, ]
  438. self.Dismiss()
  439. evt.Skip()
  440. def SetData(self, **kargs):
  441. """!Set object properties"""
  442. if 'type' in kargs:
  443. self.type = kargs['type']
  444. if 'mapsets' in kargs:
  445. self.mapsets = kargs['mapsets']
  446. if 'multiple' in kargs:
  447. self.multiple = kargs['multiple']
  448. if 'updateOnPopup' in kargs:
  449. self.updateOnPopup = kargs['updateOnPopup']
  450. if 'onPopup' in kargs:
  451. self.onPopup = kargs['onPopup']
  452. def GetType(self):
  453. """!Get element type
  454. """
  455. return self.type
  456. class VectorDBInfo:
  457. """!Class providing information about attribute tables
  458. linked to a vector map"""
  459. def __init__(self, map):
  460. self.map = map
  461. # dictionary of layer number and associated (driver, database, table)
  462. self.layers = {}
  463. # dictionary of table and associated columns (type, length, values, ids)
  464. self.tables = {}
  465. if not self._CheckDBConnection(): # -> self.layers
  466. return
  467. self._DescribeTables() # -> self.tables
  468. def _CheckDBConnection(self):
  469. """!Check DB connection"""
  470. nuldev = file(os.devnull, 'w+')
  471. self.layers = grass.vector_db(map=self.map, stderr=nuldev)
  472. nuldev.close()
  473. if (len(self.layers.keys()) == 0):
  474. return False
  475. return True
  476. def _DescribeTables(self):
  477. """!Describe linked tables"""
  478. for layer in self.layers.keys():
  479. # determine column names and types
  480. table = self.layers[layer]["table"]
  481. columns = {} # {name: {type, length, [values], [ids]}}
  482. i = 0
  483. Debug.msg(1, "gselect.VectorDBInfo._DescribeTables(): table=%s driver=%s database=%s" % \
  484. (self.layers[layer]["table"], self.layers[layer]["driver"],
  485. self.layers[layer]["database"]))
  486. for item in grass.db_describe(table = self.layers[layer]["table"],
  487. driver = self.layers[layer]["driver"],
  488. database = self.layers[layer]["database"])['cols']:
  489. name, type, length = item
  490. # FIXME: support more datatypes
  491. if type.lower() == "integer":
  492. ctype = int
  493. elif type.lower() == "double precision":
  494. ctype = float
  495. else:
  496. ctype = str
  497. columns[name.strip()] = { 'index' : i,
  498. 'type' : type.lower(),
  499. 'ctype' : ctype,
  500. 'length' : int(length),
  501. 'values' : [],
  502. 'ids' : []}
  503. i += 1
  504. # check for key column
  505. # v.db.connect -g/p returns always key column name lowercase
  506. if self.layers[layer]["key"] not in columns.keys():
  507. for col in columns.keys():
  508. if col.lower() == self.layers[layer]["key"]:
  509. self.layers[layer]["key"] = col.upper()
  510. break
  511. self.tables[table] = columns
  512. return True
  513. def Reset(self):
  514. """!Reset"""
  515. for layer in self.layers:
  516. table = self.layers[layer]["table"] # get table desc
  517. columns = self.tables[table]
  518. for name in self.tables[table].keys():
  519. self.tables[table][name]['values'] = []
  520. self.tables[table][name]['ids'] = []
  521. def GetName(self):
  522. """!Get vector name"""
  523. return self.map
  524. def GetKeyColumn(self, layer):
  525. """!Get key column of given layer
  526. @param layer vector layer number
  527. """
  528. return str(self.layers[layer]['key'])
  529. def GetTable(self, layer):
  530. """!Get table name of given layer
  531. @param layer vector layer number
  532. """
  533. return self.layers[layer]['table']
  534. def GetDbSettings(self, layer):
  535. """!Get database settins
  536. @param layer layer number
  537. @return (driver, database)
  538. """
  539. return self.layers[layer]['driver'], self.layers[layer]['database']
  540. def GetTableDesc(self, table):
  541. """!Get table columns
  542. @param table table name
  543. """
  544. return self.tables[table]
  545. class LayerSelect(wx.ComboBox):
  546. def __init__(self, parent, id = wx.ID_ANY,
  547. size = globalvar.DIALOG_COMBOBOX_SIZE,
  548. vector = None, dsn = None, choices = [], all = False, default = None):
  549. """!Creates combo box for selecting vector map layer names
  550. @param vector vector map name (native or connected via v.external)
  551. @param dsn OGR data source name
  552. """
  553. super(LayerSelect, self).__init__(parent, id, size = size, choices = choices)
  554. self.all = all
  555. self.SetName("LayerSelect")
  556. # default value
  557. self.default = default
  558. self.InsertLayers(vector = vector, dsn = dsn)
  559. def InsertLayers(self, vector = None, dsn = None):
  560. """!Insert layers for a vector into the layer combobox
  561. @param vector vector map name (native or connected via v.external)
  562. @param dsn OGR data source name
  563. """
  564. layers = list()
  565. if self.all:
  566. layers.append('-1')
  567. if vector:
  568. # TODO: use Vect_get_field2() in C modules where possible
  569. # currently the following is identical to
  570. # layers = utils.GetVectorNumberOfLayers(self, vector)
  571. ret = gcmd.RunCommand('v.db.connect',
  572. read = True,
  573. quiet = True,
  574. fs = '|',
  575. flags = 'g',
  576. map = vector)
  577. if ret:
  578. for line in ret.splitlines():
  579. layerinfo = line.split('|')
  580. layername = layerinfo[0].split('/')
  581. # use this to get layer names
  582. # but only when all modules use Vect_get_field2()
  583. # which is not the case right now
  584. ### layers.append(layername[len(layername) - 1])
  585. layers.append(layername[0])
  586. elif dsn:
  587. ret = gcmd.RunCommand('v.in.ogr',
  588. read = True,
  589. quiet = True,
  590. flags = 'l',
  591. dsn = dsn)
  592. if ret:
  593. layers = ret.splitlines()
  594. if self.default:
  595. if len(layers) == 0:
  596. layers.insert(0, str(self.default))
  597. elif self.default not in layers:
  598. layers.append(self.default)
  599. if len(layers) > 0:
  600. self.SetItems(layers)
  601. class DriverSelect(wx.ComboBox):
  602. """!Creates combo box for selecting database driver.
  603. """
  604. def __init__(self, parent, choices, value,
  605. id=wx.ID_ANY, pos=wx.DefaultPosition,
  606. size=globalvar.DIALOG_LAYER_SIZE, **kargs):
  607. super(DriverSelect, self).__init__(parent, id, value, pos, size,
  608. choices, style=wx.CB_READONLY)
  609. self.SetName("DriverSelect")
  610. self.SetStringSelection(value)
  611. class DatabaseSelect(wx.TextCtrl):
  612. """!Creates combo box for selecting database driver.
  613. """
  614. def __init__(self, parent, value='',
  615. id=wx.ID_ANY, pos=wx.DefaultPosition,
  616. size=globalvar.DIALOG_TEXTCTRL_SIZE, **kargs):
  617. super(DatabaseSelect, self).__init__(parent, id, value, pos, size)
  618. self.SetName("DatabaseSelect")
  619. class TableSelect(wx.ComboBox):
  620. """!Creates combo box for selecting attribute tables from the database
  621. """
  622. def __init__(self, parent,
  623. id=wx.ID_ANY, value='', pos=wx.DefaultPosition,
  624. size=globalvar.DIALOG_COMBOBOX_SIZE,
  625. choices=[]):
  626. super(TableSelect, self).__init__(parent, id, value, pos, size, choices,
  627. style=wx.CB_READONLY)
  628. self.SetName("TableSelect")
  629. if not choices:
  630. self.InsertTables()
  631. def InsertTables(self, driver=None, database=None):
  632. """!Insert attribute tables into combobox"""
  633. items = []
  634. if not driver or not database:
  635. connect = grass.db_connection()
  636. driver = connect['driver']
  637. database = connect['database']
  638. ret = gcmd.RunCommand('db.tables',
  639. flags = 'p',
  640. read = True,
  641. driver = driver,
  642. database = database)
  643. if ret:
  644. for table in ret.splitlines():
  645. items.append(table)
  646. self.SetItems(items)
  647. self.SetValue('')
  648. class ColumnSelect(wx.ComboBox):
  649. """!Creates combo box for selecting columns in the attribute table
  650. for a vector map.
  651. @param parent window parent
  652. @param id window id
  653. @param value default value
  654. @param size window size
  655. @param vector vector map name
  656. @param layer layer number
  657. @param param parameters list (see menuform.py)
  658. @param **kwags wx.ComboBox parameters
  659. """
  660. def __init__(self, parent, id = wx.ID_ANY, value = '',
  661. size=globalvar.DIALOG_COMBOBOX_SIZE,
  662. vector = None, layer = 1, param = None, **kwargs):
  663. self.defaultValue = value
  664. self.param = param
  665. super(ColumnSelect, self).__init__(parent, id, value, size = size, **kwargs)
  666. self.SetName("ColumnSelect")
  667. if vector:
  668. self.InsertColumns(vector, layer)
  669. def InsertColumns(self, vector, layer, excludeKey = False, excludeCols = None, type = None, dbInfo = None):
  670. """!Insert columns for a vector attribute table into the columns combobox
  671. @param vector vector name
  672. @param layer vector layer number
  673. @param excludeKey exclude key column from the list?
  674. @param excludeCols list of columns to be removed from the list
  675. @param type only columns of given type (given as list)
  676. """
  677. if not dbInfo:
  678. dbInfo = VectorDBInfo(vector)
  679. try:
  680. table = dbInfo.GetTable(int(layer))
  681. columnchoices = dbInfo.GetTableDesc(table)
  682. keyColumn = dbInfo.GetKeyColumn(int(layer))
  683. columns = len(columnchoices.keys()) * ['']
  684. for key, val in columnchoices.iteritems():
  685. columns[val['index']] = key
  686. if excludeKey: # exclude key column
  687. columns.remove(keyColumn)
  688. if excludeCols: # exclude key column
  689. for key in columnchoices.iterkeys():
  690. if key in excludeCols:
  691. columns.remove(key)
  692. if type: # only selected column types
  693. for key, value in columnchoices.iteritems():
  694. if value['type'] not in type:
  695. try:
  696. columns.remove(key)
  697. except ValueError:
  698. pass
  699. except (KeyError, ValueError):
  700. columns = list()
  701. self.SetItems(columns)
  702. self.SetValue(self.defaultValue)
  703. if self.param:
  704. self.param['value'] = ''
  705. def InsertTableColumns(self, table, driver=None, database=None):
  706. """!Insert table columns
  707. @param table table name
  708. @param driver driver name
  709. @param database database name
  710. """
  711. columns = list()
  712. ret = gcmd.RunCommand('db.columns',
  713. read = True,
  714. driver = driver,
  715. database = database,
  716. table = table)
  717. if ret:
  718. columns = ret.splitlines()
  719. self.SetItems(columns)
  720. self.SetValue(self.defaultValue)
  721. if self.param:
  722. self.param['value'] = ''
  723. class DbaseSelect(wx.lib.filebrowsebutton.DirBrowseButton):
  724. """!Widget for selecting GRASS Database"""
  725. def __init__(self, parent, **kwargs):
  726. super(DbaseSelect, self).__init__(parent, id = wx.ID_ANY,
  727. size = globalvar.DIALOG_GSELECT_SIZE, labelText = '',
  728. dialogTitle = _('Choose GIS Data Directory'),
  729. buttonText = _('Browse'),
  730. startDirectory = grass.gisenv()['GISDBASE'],
  731. **kwargs)
  732. class LocationSelect(wx.ComboBox):
  733. """!Widget for selecting GRASS location"""
  734. def __init__(self, parent, id = wx.ID_ANY, size = globalvar.DIALOG_COMBOBOX_SIZE,
  735. gisdbase = None, **kwargs):
  736. super(LocationSelect, self).__init__(parent, id, size = size,
  737. style = wx.CB_READONLY, **kwargs)
  738. self.SetName("LocationSelect")
  739. if not gisdbase:
  740. self.gisdbase = grass.gisenv()['GISDBASE']
  741. else:
  742. self.gisdbase = gisdbase
  743. self.SetItems(utils.GetListOfLocations(self.gisdbase))
  744. def UpdateItems(self, dbase):
  745. """!Update list of locations
  746. @param dbase path to GIS database
  747. """
  748. self.gisdbase = dbase
  749. if dbase:
  750. self.SetItems(utils.GetListOfLocations(self.gisdbase))
  751. else:
  752. self.SetItems([])
  753. class MapsetSelect(wx.ComboBox):
  754. """!Widget for selecting GRASS mapset"""
  755. def __init__(self, parent, id = wx.ID_ANY, size = globalvar.DIALOG_COMBOBOX_SIZE,
  756. gisdbase = None, location = None, setItems = True, **kwargs):
  757. super(MapsetSelect, self).__init__(parent, id, size = size,
  758. style = wx.CB_READONLY, **kwargs)
  759. self.SetName("MapsetSelect")
  760. if not gisdbase:
  761. self.gisdbase = grass.gisenv()['GISDBASE']
  762. else:
  763. self.gisdbase = gisdbase
  764. if not location:
  765. self.location = grass.gisenv()['LOCATION_NAME']
  766. else:
  767. self.location = location
  768. if setItems:
  769. self.SetItems(utils.GetListOfMapsets(self.gisdbase, self.location, selectable = False)) # selectable
  770. def UpdateItems(self, location, dbase = None):
  771. """!Update list of mapsets for given location
  772. @param dbase path to GIS database (None to use currently selected)
  773. @param location name of location
  774. """
  775. if dbase:
  776. self.gisdbase = dbase
  777. self.location = location
  778. if location:
  779. self.SetItems(utils.GetListOfMapsets(self.gisdbase, self.location, selectable = False))
  780. else:
  781. self.SetItems([])
  782. class SubGroupSelect(wx.ComboBox):
  783. """!Widget for selecting subgroups"""
  784. def __init__(self, parent, id = wx.ID_ANY, size = globalvar.DIALOG_GSELECT_SIZE,
  785. **kwargs):
  786. super(SubGroupSelect, self).__init__(parent, id, size = size,
  787. style = wx.CB_READONLY, **kwargs)
  788. self.SetName("SubGroupSelect")
  789. def Insert(self, group):
  790. """!Insert subgroups for defined group"""
  791. if not group:
  792. return
  793. gisenv = grass.gisenv()
  794. try:
  795. name, mapset = group.split('@', 1)
  796. except ValueError:
  797. name = group
  798. mapset = gisenv['MAPSET']
  799. path = os.path.join(gisenv['GISDBASE'], gisenv['LOCATION_NAME'], mapset,
  800. 'group', name, 'subgroup')
  801. try:
  802. self.SetItems(os.listdir(path))
  803. except OSError:
  804. self.SetItems([])
  805. self.SetValue('')
  806. class FormatSelect(wx.Choice):
  807. def __init__(self, parent, ogr = False,
  808. sourceType = None, id = wx.ID_ANY, size = globalvar.DIALOG_COMBOBOX_SIZE,
  809. **kwargs):
  810. """!Widget for selecting external (GDAL/OGR) format
  811. @param parent parent window
  812. @param sourceType source type ('file', 'directory', 'database', 'protocol') or None
  813. @param ogr True for OGR otherwise GDAL
  814. """
  815. super(FormatSelect, self).__init__(parent, id, size = size,
  816. style = wx.CB_READONLY, **kwargs)
  817. self.SetName("FormatSelect")
  818. if ogr:
  819. ftype = 'ogr'
  820. else:
  821. ftype = 'gdal'
  822. formats = list()
  823. for f in utils.GetFormats()[ftype].values():
  824. formats += f
  825. self.SetItems(formats)
  826. def GetExtension(self, name):
  827. """!Get file extension by format name"""
  828. formatToExt = {
  829. # raster
  830. 'GeoTIFF' : 'tif',
  831. 'Erdas Imagine Images (.img)' : 'img',
  832. 'Ground-based SAR Applications Testbed File Format (.gff)' : 'gff',
  833. 'Arc/Info Binary Grid' : 'adf',
  834. 'Portable Network Graphics' : 'png',
  835. 'JPEG JFIF' : 'jpg',
  836. 'Japanese DEM (.mem)' : 'mem',
  837. 'Graphics Interchange Format (.gif)' : 'gif',
  838. 'X11 PixMap Format' : 'xpm',
  839. 'MS Windows Device Independent Bitmap' : 'bmp',
  840. 'SPOT DIMAP' : 'dim',
  841. 'RadarSat 2 XML Product' : 'xml',
  842. 'EarthWatch .TIL' : 'til',
  843. 'ERMapper .ers Labelled' : 'ers',
  844. 'ERMapper Compressed Wavelets' : 'ecw',
  845. 'GRIdded Binary (.grb)' : 'grb',
  846. 'EUMETSAT Archive native (.nat)' : 'nat',
  847. 'Idrisi Raster A.1' : 'rst',
  848. 'Golden Software ASCII Grid (.grd)' : 'grd',
  849. 'Golden Software Binary Grid (.grd)' : 'grd',
  850. 'Golden Software 7 Binary Grid (.grd)' : 'grd',
  851. 'R Object Data Store' : 'r',
  852. 'USGS DOQ (Old Style)' : 'doq',
  853. 'USGS DOQ (New Style)' : 'doq',
  854. 'ENVI .hdr Labelled' : 'hdr',
  855. 'ESRI .hdr Labelled' : 'hdr',
  856. 'Generic Binary (.hdr Labelled)' : 'hdr',
  857. 'PCI .aux Labelled' : 'aux',
  858. 'EOSAT FAST Format' : 'fst',
  859. 'VTP .bt (Binary Terrain) 1.3 Format' : 'bt',
  860. 'FARSITE v.4 Landscape File (.lcp)' : 'lcp',
  861. 'Swedish Grid RIK (.rik)' : 'rik',
  862. 'USGS Optional ASCII DEM (and CDED)' : 'dem',
  863. 'Northwood Numeric Grid Format .grd/.tab' : '',
  864. 'Northwood Classified Grid Format .grc/.tab' : '',
  865. 'ARC Digitized Raster Graphics' : 'arc',
  866. 'Magellan topo (.blx)' : 'blx',
  867. 'SAGA GIS Binary Grid (.sdat)' : 'sdat',
  868. # vector
  869. 'ESRI Shapefile' : 'shp',
  870. 'UK .NTF' : 'ntf',
  871. 'SDTS' : 'ddf',
  872. 'DGN' : 'dgn',
  873. 'VRT' : 'vrt',
  874. 'REC' : 'rec',
  875. 'BNA' : 'bna',
  876. 'CSV' : 'csv',
  877. 'GML' : 'gml',
  878. 'GPX' : 'gpx',
  879. 'KML' : 'kml',
  880. 'GMT' : 'gmt',
  881. 'PGeo' : 'mdb',
  882. 'XPlane' : 'dat',
  883. 'AVCBin' : 'adf',
  884. 'AVCE00' : 'e00',
  885. 'DXF' : 'dxf',
  886. 'Geoconcept' : 'gxt',
  887. 'GeoRSS' : 'xml',
  888. 'GPSTrackMaker' : 'gtm',
  889. 'VFK' : 'vfk'
  890. }
  891. try:
  892. return formatToExt[name]
  893. except KeyError:
  894. return ''
  895. class GdalSelect(wx.Panel):
  896. def __init__(self, parent, panel, ogr = False,
  897. default = 'file', exclude = [], envHandler = None):
  898. """!Widget for selecting GDAL/OGR datasource, format
  899. @param parent parent window
  900. @param ogr use OGR selector instead of GDAL
  901. """
  902. self.parent = parent
  903. self.ogr = ogr
  904. wx.Panel.__init__(self, parent = panel, id = wx.ID_ANY)
  905. self.settingsBox = wx.StaticBox(parent = self, id=wx.ID_ANY,
  906. label=" %s " % _("Settings"))
  907. self.inputBox = wx.StaticBox(parent = self, id=wx.ID_ANY,
  908. label=" %s " % _("Source"))
  909. # source type
  910. sources = list()
  911. self.sourceMap = { 'file' : -1,
  912. 'dir' : -1,
  913. 'db' : -1,
  914. 'pro' : -1 }
  915. idx = 0
  916. if 'file' not in exclude:
  917. sources.append(_("File"))
  918. self.sourceMap['file'] = idx
  919. idx += 1
  920. if 'directory' not in exclude:
  921. sources.append(_("Directory"))
  922. self.sourceMap['dir'] = idx
  923. idx += 1
  924. if 'database' not in exclude:
  925. sources.append(_("Database"))
  926. self.sourceMap['db'] = idx
  927. idx += 1
  928. if 'protocol' not in exclude:
  929. sources.append(_("Protocol"))
  930. self.sourceMap['pro'] = idx
  931. if self.ogr:
  932. self.settingsFile = os.path.join(utils.GetSettingsPath(), 'wxOGR')
  933. else:
  934. self.settingsFile = os.path.join(utils.GetSettingsPath(), 'wxGDAL')
  935. self._settings = self._loadSettings()
  936. self.settingsChoice = wx.Choice(parent = self, id = wx.ID_ANY)
  937. self.settingsChoice.Bind(wx.EVT_CHOICE, self.OnSettingsLoad)
  938. self.settingsChoice.SetItems(self._settings.keys())
  939. self.btnSettings = wx.Button(parent = self, id = wx.ID_SAVE)
  940. self.btnSettings.Bind(wx.EVT_BUTTON, self.OnSettingsSave)
  941. self.source = wx.RadioBox(parent = self, id = wx.ID_ANY,
  942. label = _('Source type'),
  943. style = wx.RA_SPECIFY_COLS,
  944. choices = sources)
  945. self.source.SetSelection(0)
  946. self.source.Bind(wx.EVT_RADIOBOX, self.OnSetType)
  947. # dsn widgets
  948. if not ogr:
  949. filemask = 'GeoTIFF (%s)|%s|%s (*.*)|*.*' % \
  950. (self._getExtPattern('tif'), self._getExtPattern('tif'), _('All files'))
  951. else:
  952. filemask = 'ESRI Shapefile (%s)|%s|%s (*.*)|*.*' % \
  953. (self._getExtPattern('shp'), self._getExtPattern('shp'), _('All files'))
  954. dsnFile = filebrowse.FileBrowseButton(parent=self, id=wx.ID_ANY,
  955. size=globalvar.DIALOG_GSELECT_SIZE, labelText = '',
  956. dialogTitle=_('Choose file to import'),
  957. buttonText=_('Browse'),
  958. startDirectory=os.getcwd(),
  959. changeCallback=self.OnSetDsn,
  960. fileMask=filemask)
  961. dsnFile.Hide()
  962. dsnDir = filebrowse.DirBrowseButton(parent=self, id=wx.ID_ANY,
  963. size=globalvar.DIALOG_GSELECT_SIZE, labelText='',
  964. dialogTitle=_('Choose input directory'),
  965. buttonText=_('Browse'),
  966. startDirectory=os.getcwd(),
  967. changeCallback=self.OnSetDsn)
  968. dsnDir.SetName('GdalSelect')
  969. dsnDir.Hide()
  970. dsnDbFile = filebrowse.FileBrowseButton(parent=self, id=wx.ID_ANY,
  971. size=globalvar.DIALOG_GSELECT_SIZE, labelText='',
  972. dialogTitle=_('Choose file'),
  973. buttonText=_('Browse'),
  974. startDirectory=os.getcwd(),
  975. changeCallback=self.OnSetDsn)
  976. dsnDbFile.Hide()
  977. dsnDbFile.SetName('GdalSelect')
  978. dsnDbText = wx.TextCtrl(parent = self, id = wx.ID_ANY)
  979. dsnDbText.Hide()
  980. dsnDbText.Bind(wx.EVT_TEXT, self.OnSetDsn)
  981. dsnDbText.SetName('GdalSelect')
  982. dsnDbChoice = wx.Choice(parent = self, id = wx.ID_ANY)
  983. dsnDbChoice.Hide()
  984. dsnDbChoice.Bind(wx.EVT_CHOICE, self.OnSetDsn)
  985. dsnDbChoice.SetName('GdalSelect')
  986. dsnPro = wx.TextCtrl(parent = self, id = wx.ID_ANY)
  987. dsnPro.Hide()
  988. dsnPro.Bind(wx.EVT_TEXT, self.OnSetDsn)
  989. dsnPro.SetName('GdalSelect')
  990. # format
  991. self.format = FormatSelect(parent = self,
  992. ogr = ogr)
  993. self.format.Bind(wx.EVT_CHOICE, self.OnSetFormat)
  994. self.extension = wx.TextCtrl(parent = self, id = wx.ID_ANY)
  995. self.extension.Bind(wx.EVT_TEXT, self.OnSetExtension)
  996. self.extension.Hide()
  997. if ogr:
  998. fType = 'ogr'
  999. else:
  1000. fType = 'gdal'
  1001. self.input = { 'file' : [_("File:"),
  1002. dsnFile,
  1003. utils.GetFormats()[fType]['file']],
  1004. 'dir' : [_("Directory:"),
  1005. dsnDir,
  1006. utils.GetFormats()[fType]['file']],
  1007. 'db' : [_("Database:"),
  1008. dsnDbFile,
  1009. utils.GetFormats()[fType]['database']],
  1010. 'pro' : [_("Protocol:"),
  1011. dsnPro,
  1012. utils.GetFormats()[fType]['protocol']],
  1013. 'db-win' : { 'file' : dsnDbFile,
  1014. 'text' : dsnDbText,
  1015. 'choice' : dsnDbChoice },
  1016. }
  1017. self.dsnType = default
  1018. self.input[self.dsnType][1].Show()
  1019. self.format.SetItems(self.input[self.dsnType][2])
  1020. self.dsnText = wx.StaticText(parent = self, id = wx.ID_ANY,
  1021. label = self.input[self.dsnType][0],
  1022. size = (75, -1))
  1023. self.extensionText = wx.StaticText(parent = self, id = wx.ID_ANY,
  1024. label = _("Extension:"))
  1025. self.extensionText.Hide()
  1026. self._layout()
  1027. if not ogr:
  1028. self.OnSetFormat(event = None, format = 'GeoTIFF')
  1029. else:
  1030. self.OnSetFormat(event = None, format = 'ESRI Shapefile')
  1031. def _layout(self):
  1032. """!Layout"""
  1033. mainSizer = wx.BoxSizer(wx.VERTICAL)
  1034. settingsSizer = wx.StaticBoxSizer(self.settingsBox, wx.HORIZONTAL)
  1035. settingsSizer.Add(item = wx.StaticText(parent = self,
  1036. id = wx.ID_ANY,
  1037. label = _("Load settings:")),
  1038. flag = wx.ALIGN_CENTER_VERTICAL | wx.RIGHT,
  1039. border = 5)
  1040. settingsSizer.Add(item = self.settingsChoice,
  1041. proportion = 1,
  1042. flag = wx.EXPAND)
  1043. settingsSizer.Add(item = self.btnSettings,
  1044. flag = wx.LEFT,
  1045. border = 5)
  1046. inputSizer = wx.StaticBoxSizer(self.inputBox, wx.HORIZONTAL)
  1047. self.dsnSizer = wx.GridBagSizer(vgap = 3, hgap = 3)
  1048. self.dsnSizer.AddGrowableRow(1)
  1049. self.dsnSizer.AddGrowableCol(3)
  1050. self.dsnSizer.Add(item=self.dsnText,
  1051. flag=wx.ALIGN_CENTER_VERTICAL,
  1052. pos = (0, 0))
  1053. self.dsnSizer.Add(item=self.input[self.dsnType][1],
  1054. flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
  1055. pos = (0, 1), span = (1, 3))
  1056. self.dsnSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
  1057. label = _("Format:")),
  1058. flag = wx.ALIGN_CENTER_VERTICAL,
  1059. pos = (1, 0))
  1060. self.dsnSizer.Add(item=self.format,
  1061. flag = wx.ALIGN_CENTER_VERTICAL,
  1062. pos = (1, 1))
  1063. self.dsnSizer.Add(item = self.extensionText,
  1064. flag=wx.ALIGN_CENTER_VERTICAL,
  1065. pos = (1, 2))
  1066. self.dsnSizer.Add(item=self.extension,
  1067. flag = wx.ALIGN_CENTER_VERTICAL,
  1068. pos = (1, 3))
  1069. inputSizer.Add(item=self.dsnSizer, proportion=1,
  1070. flag=wx.EXPAND | wx.ALL)
  1071. mainSizer.Add(item=settingsSizer, proportion=0,
  1072. flag=wx.ALL | wx.EXPAND, border=5)
  1073. mainSizer.Add(item=self.source, proportion=0,
  1074. flag=wx.LEFT | wx.RIGHT | wx.EXPAND, border=5)
  1075. mainSizer.Add(item=inputSizer, proportion=0,
  1076. flag=wx.ALL | wx.EXPAND, border=5)
  1077. self.SetSizer(mainSizer)
  1078. mainSizer.Fit(self)
  1079. def _getExtPatternGlob(self, ext):
  1080. """!Get pattern for case-insensitive globing"""
  1081. pattern = '*.'
  1082. for c in ext:
  1083. pattern += '[%s%s]' % (c.lower(), c.upper())
  1084. return pattern
  1085. def _getExtPattern(self, ext):
  1086. """!Get pattern for case-insensitive file mask"""
  1087. return '*.%s;*.%s' % (ext.lower(), ext.upper())
  1088. def OnSettingsLoad(self, event):
  1089. """!Load named settings"""
  1090. name = event.GetString()
  1091. if name not in self._settings:
  1092. gcmd.GError(parent = self,
  1093. message = _("Settings named '%s' not found") % name)
  1094. return
  1095. data = self._settings[name]
  1096. self.OnSetType(event = None, sel = self.sourceMap[data[0]])
  1097. self.OnSetFormat(event = None, format = data[2])
  1098. self.OnSetDsn(event = None, path = data[1])
  1099. def OnSettingsSave(self, event):
  1100. """!Save settings"""
  1101. dlg = wx.TextEntryDialog(parent = self,
  1102. message = _("Name:"),
  1103. caption = _("Save settings"))
  1104. if dlg.ShowModal() != wx.ID_OK:
  1105. return
  1106. if not dlg.GetValue():
  1107. gcmd.GMessage(parent = self,
  1108. message = _("Name not given, settings is not saved."))
  1109. return
  1110. name = dlg.GetValue()
  1111. try:
  1112. fd = open(self.settingsFile, 'a')
  1113. fd.write(name + ';' + self.dsnType + ';' +
  1114. self._getDsn() + ';' +
  1115. self.format.GetStringSelection())
  1116. fd.write('\n')
  1117. except IOError:
  1118. gcmd.GError(parent = self,
  1119. message = _("Unable to save settings"))
  1120. return
  1121. fd.close()
  1122. self._settings = self._loadSettings()
  1123. self.settingsChoice.Append(name)
  1124. self.settingsChoice.SetStringSelection(name)
  1125. dlg.Destroy()
  1126. def _loadSettings(self):
  1127. """!Load settings from the file
  1128. The file is defined by self.SettingsFile.
  1129. @return parsed dict
  1130. @return empty dict on error
  1131. """
  1132. data = dict()
  1133. if not os.path.exists(self.settingsFile):
  1134. return data
  1135. try:
  1136. fd = open(self.settingsFile, 'r')
  1137. for line in fd.readlines():
  1138. try:
  1139. name, ftype, dsn, format = line.rstrip('\n').split(';')
  1140. data[name] = (ftype, dsn, format)
  1141. except ValueError:
  1142. pass
  1143. except IOError:
  1144. return data
  1145. fd.close()
  1146. return data
  1147. def OnSetType(self, event, sel = None):
  1148. """!Datasource type changed"""
  1149. if event:
  1150. sel = event.GetSelection()
  1151. else:
  1152. self.source.SetSelection(sel)
  1153. win = self.input[self.dsnType][1]
  1154. self.dsnSizer.Remove(win)
  1155. win.Hide()
  1156. if sel == self.sourceMap['file']: # file
  1157. self.dsnType = 'file'
  1158. format = self.input[self.dsnType][2][0]
  1159. try:
  1160. ext = self.format.GetExtension(format)
  1161. if not ext:
  1162. raise KeyError
  1163. format += ' (%s)|%s|%s (*.*)|*.*' % \
  1164. (self._getExtPattern(ext), self._getExtPattern(ext), _('All files'))
  1165. except KeyError:
  1166. format += '%s (*.*)|*.*' % _('All files')
  1167. win = filebrowse.FileBrowseButton(parent=self, id=wx.ID_ANY,
  1168. size=globalvar.DIALOG_GSELECT_SIZE, labelText='',
  1169. dialogTitle=_('Choose file to import'),
  1170. buttonText=_('Browse'),
  1171. startDirectory=os.getcwd(),
  1172. changeCallback=self.OnSetDsn,
  1173. fileMask = format)
  1174. self.input[self.dsnType][1] = win
  1175. elif sel == self.sourceMap['dir']: # directory
  1176. self.dsnType = 'dir'
  1177. elif sel == self.sourceMap['db']: # database
  1178. self.dsnType = 'db'
  1179. elif sel == self.sourceMap['pro']: # protocol
  1180. self.dsnType = 'pro'
  1181. self.dsnText.SetLabel(self.input[self.dsnType][0])
  1182. if self.parent.GetName() == 'MultiImportDialog':
  1183. self.parent.list.DeleteAllItems()
  1184. self.format.SetItems(self.input[self.dsnType][2])
  1185. if sel in (self.sourceMap['file'],
  1186. self.sourceMap['dir']):
  1187. win = self.input[self.dsnType][1]
  1188. self.dsnSizer.Add(item=self.input[self.dsnType][1],
  1189. flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
  1190. pos = (0, 1))
  1191. win.SetValue('')
  1192. win.Show()
  1193. if not self.ogr:
  1194. self.OnSetFormat(event = None, format = 'GeoTIFF')
  1195. else:
  1196. self.OnSetFormat(event = None, format = 'ESRI Shapefile')
  1197. else:
  1198. if sel == self.sourceMap['pro']:
  1199. win = self.input[self.dsnType][1]
  1200. self.dsnSizer.Add(item=self.input[self.dsnType][1],
  1201. flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
  1202. pos = (0, 1))
  1203. win.SetValue('')
  1204. win.Show()
  1205. if sel == self.sourceMap['dir']:
  1206. if not self.extension.IsShown():
  1207. self.extensionText.Show()
  1208. self.extension.Show()
  1209. else:
  1210. if self.extension.IsShown():
  1211. self.extensionText.Hide()
  1212. self.extension.Hide()
  1213. self.dsnSizer.Layout()
  1214. def _getDsn(self):
  1215. """!Get datasource name"""
  1216. if self.format.GetStringSelection() == 'PostgreSQL':
  1217. return 'PG:dbname=%s' % self.input[self.dsnType][1].GetStringSelection()
  1218. return self.input[self.dsnType][1].GetValue()
  1219. def OnSetDsn(self, event, path = None):
  1220. """!Input DXF file/OGR dsn defined, update list of layer
  1221. widget"""
  1222. if event:
  1223. path = event.GetString()
  1224. else:
  1225. if self.format.GetStringSelection() == 'PostgreSQL':
  1226. for item in path.split(':', 1)[1].split(','):
  1227. key, value = item.split('=', 1)
  1228. if key == 'dbname':
  1229. self.input[self.dsnType][1].SetStringSelection(value)
  1230. break
  1231. else:
  1232. self.input[self.dsnType][1].SetValue(path)
  1233. if not path:
  1234. return
  1235. self._reloadLayers()
  1236. if event:
  1237. event.Skip()
  1238. def _reloadLayers(self):
  1239. """!Reload list of layers"""
  1240. dsn = self._getDsn()
  1241. if not dsn:
  1242. return
  1243. data = list()
  1244. layerId = 1
  1245. if self.ogr:
  1246. ret = gcmd.RunCommand('v.in.ogr',
  1247. quiet = True,
  1248. read = True,
  1249. flags = 't',
  1250. dsn = dsn)
  1251. if not ret:
  1252. self.parent.list.LoadData()
  1253. if hasattr(self, "btn_run"):
  1254. self.btn_run.Enable(False)
  1255. return
  1256. layerId = 1
  1257. for line in ret.splitlines():
  1258. layerName, featureType = map(lambda x: x.strip(), line.split(' ', 1))
  1259. grassName = utils.GetValidLayerName(layerName)
  1260. data.append((layerId, layerName, featureType, grassName))
  1261. layerId += 1
  1262. else:
  1263. if self.dsnType == 'file':
  1264. baseName = os.path.basename(dsn)
  1265. grassName = utils.GetValidLayerName(baseName.split('.', -1)[0])
  1266. data.append((layerId, baseName, grassName))
  1267. elif self.dsnType == 'dir':
  1268. ext = self.extension.GetValue()
  1269. for filename in glob.glob(os.path.join(dsn, "%s") % self._getExtPatternGlob(ext)):
  1270. baseName = os.path.basename(filename)
  1271. grassName = utils.GetValidLayerName(baseName.split('.', -1)[0])
  1272. data.append((layerId, baseName, grassName))
  1273. layerId += 1
  1274. if self.ogr:
  1275. dsn += '@OGR'
  1276. evt = wxGdalSelect(dsn = dsn)
  1277. evt.SetId(self.input[self.dsnType][1].GetId())
  1278. wx.PostEvent(self.parent, evt)
  1279. if self.parent.GetName() == 'MultiImportDialog':
  1280. self.parent.list.LoadData(data)
  1281. if len(data) > 0:
  1282. self.parent.btn_run.Enable(True)
  1283. else:
  1284. self.parent.btn_run.Enable(False)
  1285. def OnSetExtension(self, event):
  1286. """!Extension changed"""
  1287. # reload layers
  1288. self._reloadLayers()
  1289. def OnSetFormat(self, event, format = None):
  1290. """!Format changed"""
  1291. if self.dsnType not in ['file', 'dir', 'db']:
  1292. return
  1293. win = self.input[self.dsnType][1]
  1294. self.dsnSizer.Remove(win)
  1295. if self.dsnType == 'file':
  1296. win.Destroy()
  1297. else: # database
  1298. win.Hide()
  1299. if event:
  1300. format = event.GetString()
  1301. else:
  1302. self.format.SetStringSelection(format)
  1303. if self.dsnType == 'file':
  1304. try:
  1305. ext = self.format.GetExtension(format)
  1306. if not ext:
  1307. raise KeyError
  1308. format += ' (%s)|%s|%s (*.*)|*.*' % \
  1309. (self._getExtPattern(ext), self._getExtPattern(ext), _('All files'))
  1310. except KeyError:
  1311. format += '%s (*.*)|*.*' % _('All files')
  1312. win = filebrowse.FileBrowseButton(parent=self, id=wx.ID_ANY,
  1313. size=globalvar.DIALOG_GSELECT_SIZE, labelText='',
  1314. dialogTitle=_('Choose file'),
  1315. buttonText=_('Browse'),
  1316. startDirectory=os.getcwd(),
  1317. changeCallback=self.OnSetDsn,
  1318. fileMask = format)
  1319. elif self.dsnType == 'dir':
  1320. pass
  1321. else: # database
  1322. if format == 'SQLite' or format == 'Rasterlite':
  1323. win = self.input['db-win']['file']
  1324. elif format == 'PostgreSQL' or format == 'PostGIS WKT Raster driver':
  1325. if grass.find_program('psql', ['--help']):
  1326. win = self.input['db-win']['choice']
  1327. if not win.GetItems():
  1328. p = grass.Popen(['psql', '-ltA'], stdout = grass.PIPE)
  1329. ret = p.communicate()[0]
  1330. if ret:
  1331. db = list()
  1332. for line in ret.splitlines():
  1333. sline = line.split('|')
  1334. if len(sline) < 2:
  1335. continue
  1336. dbname = sline[0]
  1337. if dbname:
  1338. db.append(dbname)
  1339. win.SetItems(db)
  1340. else:
  1341. win = self.input['db-win']['text']
  1342. else:
  1343. win = self.input['db-win']['text']
  1344. self.input[self.dsnType][1] = win
  1345. if not win.IsShown():
  1346. win.Show()
  1347. self.dsnSizer.Add(item = self.input[self.dsnType][1],
  1348. flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
  1349. pos = (0, 1), span = (1, 3))
  1350. self.dsnSizer.Layout()
  1351. # update extension
  1352. self.extension.SetValue(self.GetFormatExt())
  1353. # reload layers
  1354. self._reloadLayers()
  1355. def GetType(self):
  1356. """!Get source type"""
  1357. return self.dsnType
  1358. def GetDsn(self):
  1359. """!Get DSN"""
  1360. if self.format.GetStringSelection() == 'PostgreSQL':
  1361. return 'PG:dbname=%s' % self.input[self.dsnType][1].GetStringSelection()
  1362. return self.input[self.dsnType][1].GetValue()
  1363. def GetDsnWin(self):
  1364. """!Get list of DSN windows"""
  1365. win = list()
  1366. for stype in ('file', 'dir', 'pro'):
  1367. win.append(self.input[stype][1])
  1368. for stype in ('file', 'text', 'choice'):
  1369. win.append(self.input['db-win'][stype])
  1370. return win
  1371. def GetFormatExt(self):
  1372. """!Get format extension"""
  1373. return self.format.GetExtension(self.format.GetStringSelection())
  1374. class ProjSelect(wx.ComboBox):
  1375. """!Widget for selecting input raster/vector map used by
  1376. r.proj/v.proj modules."""
  1377. def __init__(self, parent, isRaster, id = wx.ID_ANY, size = globalvar.DIALOG_COMBOBOX_SIZE,
  1378. **kwargs):
  1379. super(ProjSelect, self).__init__(parent, id, size = size,
  1380. style = wx.CB_READONLY, **kwargs)
  1381. self.SetName("ProjSelect")
  1382. self.isRaster = isRaster
  1383. def UpdateItems(self, dbase, location, mapset):
  1384. """!Update list of maps
  1385. """
  1386. if not dbase:
  1387. dbase = grass.gisenv()['GISDBASE']
  1388. if not mapset:
  1389. mapset = grass.gisenv()['MAPSET']
  1390. if self.isRaster:
  1391. ret = gcmd.RunCommand('r.proj',
  1392. quiet = True,
  1393. read = True,
  1394. flags = 'l',
  1395. dbase = dbase,
  1396. location = location,
  1397. mapset = mapset)
  1398. else:
  1399. ret = gcmd.RunCommand('v.proj',
  1400. quiet = True,
  1401. read = True,
  1402. flags = 'l',
  1403. dbase = dbase,
  1404. location = location,
  1405. mapset = mapset)
  1406. listMaps = list()
  1407. if ret:
  1408. for line in ret.splitlines():
  1409. listMaps.append(line.strip())
  1410. utils.ListSortLower(listMaps)
  1411. self.SetItems(listMaps)
  1412. self.SetValue('')
  1413. class ElementSelect(wx.Choice):
  1414. def __init__(self, parent, id = wx.ID_ANY, size = globalvar.DIALOG_COMBOBOX_SIZE,
  1415. **kwargs):
  1416. """!Widget for selecting GIS element
  1417. @param parent parent window
  1418. """
  1419. super(ElementSelect, self).__init__(parent, id, size = size,
  1420. style = wx.CB_READONLY, **kwargs)
  1421. self.SetName("ElementSelect")
  1422. task = gtask.parse_interface('g.list')
  1423. p = task.get_param(value = 'type')
  1424. self.values = p.get('values', [])
  1425. self.valuesDesc = p.get('values_desc', [])
  1426. self.SetItems(self.valuesDesc)
  1427. def GetValue(self, name):
  1428. """!Translate value
  1429. @param name element name
  1430. """
  1431. idx = self.valuesDesc.index(name)
  1432. if idx > -1:
  1433. return self.values[idx]
  1434. return ''
  1435. class OgrTypeSelect(wx.Panel):
  1436. def __init__(self, parent, panel, **kwargs):
  1437. """!Widget to choose OGR feature type
  1438. @param parent parent window
  1439. @param panel wx.Panel instance used as parent window
  1440. """
  1441. wx.Panel.__init__(self, parent = panel, id = wx.ID_ANY)
  1442. self.ftype = wx.Choice(parent = self, id = wx.ID_ANY,
  1443. size = (200, -1),
  1444. choices = (_("Point"), _("LineString"), _("Polygon")))
  1445. self._layout()
  1446. def _layout(self):
  1447. """!Do layout"""
  1448. sizer = wx.BoxSizer(wx.HORIZONTAL)
  1449. sizer.Add(item = wx.StaticText(parent = self,
  1450. id = wx.ID_ANY,
  1451. label = _("Feature type:")),
  1452. proportion = 1,
  1453. flag = wx.ALIGN_CENTER_VERTICAL,
  1454. border = 5)
  1455. sizer.Add(item = self.ftype,
  1456. proportion = 0,
  1457. flag = wx.EXPAND | wx.ALIGN_RIGHT)
  1458. self.SetSizer(sizer)
  1459. sizer.Fit(self)
  1460. def GetType(self):
  1461. """!Get selected type as string
  1462. @return feature type as string
  1463. """
  1464. sel = self.ftype.GetSelection()
  1465. if sel == 0:
  1466. return 'point'
  1467. elif sel == 1:
  1468. return 'line'
  1469. elif sel == 2:
  1470. return 'area'