gselect.py 69 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836
  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. """!Get value as a string separated by commas"""
  158. return ','.join(self.value)
  159. def SetFilter(self, filter):
  160. """!Set filter for GIS elements, see e.g. VectorSelect"""
  161. self.filterElements = filter
  162. def OnPopup(self, force = False):
  163. """!Limited only for first selected"""
  164. if not force and not self.updateOnPopup:
  165. return
  166. if self.onPopup:
  167. selected, exclude = self.onPopup(self.type)
  168. else:
  169. selected = None
  170. exclude = False
  171. self.GetElementList(selected, exclude)
  172. # selects map starting according to written text
  173. inputText = self.GetCombo().GetValue().strip()
  174. if inputText:
  175. root = self.seltree.GetRootItem()
  176. match = self.FindItem(root, inputText, startLetters = True)
  177. self.seltree.EnsureVisible(match)
  178. self.seltree.SelectItem(match)
  179. def GetElementList(self, elements = None, exclude = False):
  180. """!Get filtered list of GIS elements in accessible mapsets
  181. and display as tree with all relevant elements displayed
  182. beneath each mapset branch
  183. """
  184. # update list
  185. self.seltree.DeleteAllItems()
  186. self._getElementList(self.type, self.mapsets, elements, exclude)
  187. if len(self.value) > 0:
  188. root = self.seltree.GetRootItem()
  189. if not root:
  190. return
  191. item = self.FindItem(root, self.value[0])
  192. try:
  193. self.seltree.EnsureVisible(item)
  194. self.seltree.SelectItem(item)
  195. except:
  196. pass
  197. def SetStringValue(self, value):
  198. # this assumes that item strings are unique...
  199. root = self.seltree.GetRootItem()
  200. if not root:
  201. return
  202. found = self.FindItem(root, value)
  203. winValue = self.GetCombo().GetValue().strip(',')
  204. self.value = []
  205. if winValue:
  206. self.value = winValue.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(search_path = True)
  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('@', 1)[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].strip()
  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].strip()
  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,
  757. searchPath = False, new = False, **kwargs):
  758. style = 0
  759. if not new:
  760. style = wx.CB_READONLY
  761. super(MapsetSelect, self).__init__(parent, id, size = size,
  762. style = style, **kwargs)
  763. self.searchPath = searchPath
  764. self.SetName("MapsetSelect")
  765. if not gisdbase:
  766. self.gisdbase = grass.gisenv()['GISDBASE']
  767. else:
  768. self.gisdbase = gisdbase
  769. if not location:
  770. self.location = grass.gisenv()['LOCATION_NAME']
  771. else:
  772. self.location = location
  773. if setItems:
  774. self.SetItems(self._getMapsets())
  775. def UpdateItems(self, location, dbase = None):
  776. """!Update list of mapsets for given location
  777. @param dbase path to GIS database (None to use currently selected)
  778. @param location name of location
  779. """
  780. if dbase:
  781. self.gisdbase = dbase
  782. self.location = location
  783. if location:
  784. self.SetItems(self._getMapsets())
  785. else:
  786. self.SetItems([])
  787. def _getMapsets(self):
  788. if self.searchPath:
  789. return gcmd.RunCommand('g.mapsets',
  790. read = True,
  791. flags = 'p',
  792. fs = 'newline').splitlines()
  793. return utils.GetListOfMapsets(self.gisdbase, self.location, selectable = False)
  794. class SubGroupSelect(wx.ComboBox):
  795. """!Widget for selecting subgroups"""
  796. def __init__(self, parent, id = wx.ID_ANY, size = globalvar.DIALOG_GSELECT_SIZE,
  797. **kwargs):
  798. super(SubGroupSelect, self).__init__(parent, id, size = size,
  799. style = wx.CB_READONLY, **kwargs)
  800. self.SetName("SubGroupSelect")
  801. def Insert(self, group):
  802. """!Insert subgroups for defined group"""
  803. if not group:
  804. return
  805. gisenv = grass.gisenv()
  806. try:
  807. name, mapset = group.split('@', 1)
  808. except ValueError:
  809. name = group
  810. mapset = gisenv['MAPSET']
  811. path = os.path.join(gisenv['GISDBASE'], gisenv['LOCATION_NAME'], mapset,
  812. 'group', name, 'subgroup')
  813. try:
  814. self.SetItems(os.listdir(path))
  815. except OSError:
  816. self.SetItems([])
  817. self.SetValue('')
  818. class FormatSelect(wx.Choice):
  819. def __init__(self, parent, ogr = False,
  820. sourceType = None, id = wx.ID_ANY, size = globalvar.DIALOG_SPIN_SIZE,
  821. **kwargs):
  822. """!Widget for selecting external (GDAL/OGR) format
  823. @param parent parent window
  824. @param sourceType source type ('file', 'directory', 'database', 'protocol') or None
  825. @param ogr True for OGR otherwise GDAL
  826. """
  827. super(FormatSelect, self).__init__(parent, id, size = size,
  828. style = wx.CB_READONLY, **kwargs)
  829. self.SetName("FormatSelect")
  830. if ogr:
  831. ftype = 'ogr'
  832. else:
  833. ftype = 'gdal'
  834. formats = list()
  835. for f in utils.GetFormats()[ftype].values():
  836. formats += f
  837. self.SetItems(formats)
  838. def GetExtension(self, name):
  839. """!Get file extension by format name"""
  840. formatToExt = {
  841. # raster
  842. 'GeoTIFF' : 'tif',
  843. 'Erdas Imagine Images (.img)' : 'img',
  844. 'Ground-based SAR Applications Testbed File Format (.gff)' : 'gff',
  845. 'Arc/Info Binary Grid' : 'adf',
  846. 'Portable Network Graphics' : 'png',
  847. 'JPEG JFIF' : 'jpg',
  848. 'Japanese DEM (.mem)' : 'mem',
  849. 'Graphics Interchange Format (.gif)' : 'gif',
  850. 'X11 PixMap Format' : 'xpm',
  851. 'MS Windows Device Independent Bitmap' : 'bmp',
  852. 'SPOT DIMAP' : 'dim',
  853. 'RadarSat 2 XML Product' : 'xml',
  854. 'EarthWatch .TIL' : 'til',
  855. 'ERMapper .ers Labelled' : 'ers',
  856. 'ERMapper Compressed Wavelets' : 'ecw',
  857. 'GRIdded Binary (.grb)' : 'grb',
  858. 'EUMETSAT Archive native (.nat)' : 'nat',
  859. 'Idrisi Raster A.1' : 'rst',
  860. 'Golden Software ASCII Grid (.grd)' : 'grd',
  861. 'Golden Software Binary Grid (.grd)' : 'grd',
  862. 'Golden Software 7 Binary Grid (.grd)' : 'grd',
  863. 'R Object Data Store' : 'r',
  864. 'USGS DOQ (Old Style)' : 'doq',
  865. 'USGS DOQ (New Style)' : 'doq',
  866. 'ENVI .hdr Labelled' : 'hdr',
  867. 'ESRI .hdr Labelled' : 'hdr',
  868. 'Generic Binary (.hdr Labelled)' : 'hdr',
  869. 'PCI .aux Labelled' : 'aux',
  870. 'EOSAT FAST Format' : 'fst',
  871. 'VTP .bt (Binary Terrain) 1.3 Format' : 'bt',
  872. 'FARSITE v.4 Landscape File (.lcp)' : 'lcp',
  873. 'Swedish Grid RIK (.rik)' : 'rik',
  874. 'USGS Optional ASCII DEM (and CDED)' : 'dem',
  875. 'Northwood Numeric Grid Format .grd/.tab' : '',
  876. 'Northwood Classified Grid Format .grc/.tab' : '',
  877. 'ARC Digitized Raster Graphics' : 'arc',
  878. 'Magellan topo (.blx)' : 'blx',
  879. 'SAGA GIS Binary Grid (.sdat)' : 'sdat',
  880. # vector
  881. 'ESRI Shapefile' : 'shp',
  882. 'UK .NTF' : 'ntf',
  883. 'SDTS' : 'ddf',
  884. 'DGN' : 'dgn',
  885. 'VRT' : 'vrt',
  886. 'REC' : 'rec',
  887. 'BNA' : 'bna',
  888. 'CSV' : 'csv',
  889. 'GML' : 'gml',
  890. 'GPX' : 'gpx',
  891. 'KML' : 'kml',
  892. 'GMT' : 'gmt',
  893. 'PGeo' : 'mdb',
  894. 'XPlane' : 'dat',
  895. 'AVCBin' : 'adf',
  896. 'AVCE00' : 'e00',
  897. 'DXF' : 'dxf',
  898. 'Geoconcept' : 'gxt',
  899. 'GeoRSS' : 'xml',
  900. 'GPSTrackMaker' : 'gtm',
  901. 'VFK' : 'vfk'
  902. }
  903. try:
  904. return formatToExt[name]
  905. except KeyError:
  906. return ''
  907. class GdalSelect(wx.Panel):
  908. def __init__(self, parent, panel, ogr = False, dest = False,
  909. default = 'file', exclude = [], envHandler = None):
  910. """!Widget for selecting GDAL/OGR datasource, format
  911. @param parent parent window
  912. @param ogr use OGR selector instead of GDAL
  913. @param dest True for output (destination)
  914. @param default deafult type (ignored when dest == True)
  915. @param exclude list of types to be excluded
  916. """
  917. self.parent = parent
  918. self.ogr = ogr
  919. self.dest = dest
  920. wx.Panel.__init__(self, parent = panel, id = wx.ID_ANY)
  921. self.settingsBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
  922. label = " %s " % _("Settings"))
  923. self.inputBox = wx.StaticBox(parent = self, id = wx.ID_ANY)
  924. if dest:
  925. self.inputBox.SetLabel(" %s " % _("Output settings"))
  926. else:
  927. self.inputBox.SetLabel(" %s " % _("Source settings"))
  928. # source type
  929. sources = list()
  930. self.sourceMap = { 'file' : -1,
  931. 'dir' : -1,
  932. 'db' : -1,
  933. 'pro' : -1,
  934. 'native' : -1 }
  935. idx = 0
  936. if dest:
  937. sources.append(_("Native"))
  938. self.sourceMap['native'] = idx
  939. idx += 1
  940. if 'file' not in exclude:
  941. sources.append(_("File"))
  942. self.sourceMap['file'] = idx
  943. idx += 1
  944. if 'directory' not in exclude:
  945. sources.append(_("Directory"))
  946. self.sourceMap['dir'] = idx
  947. idx += 1
  948. if 'database' not in exclude:
  949. sources.append(_("Database"))
  950. self.sourceMap['db'] = idx
  951. idx += 1
  952. if 'protocol' not in exclude:
  953. sources.append(_("Protocol"))
  954. self.sourceMap['pro'] = idx
  955. if self.ogr:
  956. self.settingsFile = os.path.join(utils.GetSettingsPath(), 'wxOGR')
  957. else:
  958. self.settingsFile = os.path.join(utils.GetSettingsPath(), 'wxGDAL')
  959. self._settings = self._loadSettings()
  960. self.settingsChoice = wx.Choice(parent = self, id = wx.ID_ANY)
  961. self.settingsChoice.Bind(wx.EVT_CHOICE, self.OnSettingsLoad)
  962. self.settingsChoice.SetItems(self._settings.keys())
  963. self.btnSettings = wx.Button(parent = self, id = wx.ID_SAVE)
  964. self.btnSettings.Bind(wx.EVT_BUTTON, self.OnSettingsSave)
  965. self.source = wx.RadioBox(parent = self, id = wx.ID_ANY,
  966. style = wx.RA_SPECIFY_COLS,
  967. choices = sources)
  968. if dest:
  969. self.source.SetLabel(" %s " % _('Output type'))
  970. else:
  971. self.source.SetLabel(" %s " % _('Source type'))
  972. self.source.SetSelection(0)
  973. self.source.Bind(wx.EVT_RADIOBOX, self.OnSetType)
  974. # dsn widgets
  975. if not ogr:
  976. filemask = 'GeoTIFF (%s)|%s|%s (*.*)|*.*' % \
  977. (self._getExtPattern('tif'), self._getExtPattern('tif'), _('All files'))
  978. else:
  979. filemask = 'ESRI Shapefile (%s)|%s|%s (*.*)|*.*' % \
  980. (self._getExtPattern('shp'), self._getExtPattern('shp'), _('All files'))
  981. dsnFile = filebrowse.FileBrowseButton(parent=self, id=wx.ID_ANY,
  982. size=globalvar.DIALOG_GSELECT_SIZE, labelText = '',
  983. dialogTitle=_('Choose file to import'),
  984. buttonText=_('Browse'),
  985. startDirectory=os.getcwd(),
  986. changeCallback=self.OnSetDsn,
  987. fileMask=filemask)
  988. dsnFile.Hide()
  989. dsnDir = filebrowse.DirBrowseButton(parent=self, id=wx.ID_ANY,
  990. size=globalvar.DIALOG_GSELECT_SIZE, labelText='',
  991. dialogTitle=_('Choose input directory'),
  992. buttonText=_('Browse'),
  993. startDirectory=os.getcwd(),
  994. changeCallback=self.OnSetDsn)
  995. dsnDir.SetName('GdalSelect')
  996. dsnDir.Hide()
  997. dsnDbFile = filebrowse.FileBrowseButton(parent=self, id=wx.ID_ANY,
  998. size=globalvar.DIALOG_GSELECT_SIZE, labelText='',
  999. dialogTitle=_('Choose file'),
  1000. buttonText=_('Browse'),
  1001. startDirectory=os.getcwd(),
  1002. changeCallback=self.OnSetDsn)
  1003. dsnDbFile.Hide()
  1004. dsnDbFile.SetName('GdalSelect')
  1005. dsnDbText = wx.TextCtrl(parent = self, id = wx.ID_ANY)
  1006. dsnDbText.Hide()
  1007. dsnDbText.Bind(wx.EVT_TEXT, self.OnSetDsn)
  1008. dsnDbText.SetName('GdalSelect')
  1009. dsnDbChoice = wx.Choice(parent = self, id = wx.ID_ANY)
  1010. dsnDbChoice.Hide()
  1011. dsnDbChoice.Bind(wx.EVT_CHOICE, self.OnSetDsn)
  1012. dsnDbChoice.SetName('GdalSelect')
  1013. dsnPro = wx.TextCtrl(parent = self, id = wx.ID_ANY)
  1014. dsnPro.Hide()
  1015. dsnPro.Bind(wx.EVT_TEXT, self.OnSetDsn)
  1016. dsnPro.SetName('GdalSelect')
  1017. # format
  1018. self.format = FormatSelect(parent = self,
  1019. ogr = ogr)
  1020. self.format.Bind(wx.EVT_CHOICE, self.OnSetFormat)
  1021. self.extension = wx.TextCtrl(parent = self, id = wx.ID_ANY)
  1022. self.extension.Bind(wx.EVT_TEXT, self.OnSetExtension)
  1023. self.extension.Hide()
  1024. if ogr:
  1025. fType = 'ogr'
  1026. else:
  1027. fType = 'gdal'
  1028. self.input = { 'file' : [_("File:"),
  1029. dsnFile,
  1030. utils.GetFormats(writableOnly = dest)[fType]['file']],
  1031. 'dir' : [_("Name:"),
  1032. dsnDir,
  1033. utils.GetFormats(writableOnly = dest)[fType]['file']],
  1034. 'db' : [_("Name:"),
  1035. dsnDbText,
  1036. utils.GetFormats(writableOnly = dest)[fType]['database']],
  1037. 'pro' : [_("Protocol:"),
  1038. dsnPro,
  1039. utils.GetFormats(writableOnly = dest)[fType]['protocol']],
  1040. 'db-win' : { 'file' : dsnDbFile,
  1041. 'text' : dsnDbText,
  1042. 'choice' : dsnDbChoice },
  1043. 'native' : [_("Name:"), dsnDir, ''],
  1044. }
  1045. if self.dest:
  1046. current = gcmd.RunCommand('v.external.out',
  1047. parent = self,
  1048. read = True, parse = grass.parse_key_val,
  1049. flags = 'g')
  1050. if current['format'] == 'native':
  1051. self.dsnType = 'native'
  1052. elif current['format'] in utils.GetFormats()['ogr']['database']:
  1053. self.dsnType = 'db'
  1054. else:
  1055. self.dsnType = 'dir'
  1056. else:
  1057. self.dsnType = default
  1058. self.dsnText = wx.StaticText(parent = self, id = wx.ID_ANY,
  1059. label = self.input[self.dsnType][0],
  1060. size = (75, -1))
  1061. self.extensionText = wx.StaticText(parent = self, id = wx.ID_ANY,
  1062. label = _("Extension:"))
  1063. self.extensionText.Hide()
  1064. self.creationOpt = wx.TextCtrl(parent = self, id = wx.ID_ANY)
  1065. if not self.dest:
  1066. self.creationOpt.Hide()
  1067. self._layout()
  1068. self.OnSetType(event = None, sel = self.sourceMap[self.dsnType])
  1069. if self.dest:
  1070. if current['format'] != 'native':
  1071. self.OnSetFormat(event = None, format = current['format'])
  1072. self.OnSetDsn(event = None, path = current['dsn'])
  1073. self.creationOpt.SetValue(current['options'])
  1074. else:
  1075. if not ogr:
  1076. self.OnSetFormat(event = None, format = 'GeoTIFF')
  1077. else:
  1078. self.OnSetFormat(event = None, format = 'ESRI Shapefile')
  1079. def _layout(self):
  1080. """!Layout"""
  1081. mainSizer = wx.BoxSizer(wx.VERTICAL)
  1082. settingsSizer = wx.StaticBoxSizer(self.settingsBox, wx.HORIZONTAL)
  1083. settingsSizer.Add(item = wx.StaticText(parent = self,
  1084. id = wx.ID_ANY,
  1085. label = _("Load settings:")),
  1086. flag = wx.ALIGN_CENTER_VERTICAL | wx.RIGHT,
  1087. border = 5)
  1088. settingsSizer.Add(item = self.settingsChoice,
  1089. proportion = 1,
  1090. flag = wx.EXPAND)
  1091. settingsSizer.Add(item = self.btnSettings,
  1092. flag = wx.LEFT,
  1093. border = 5)
  1094. inputSizer = wx.StaticBoxSizer(self.inputBox, wx.HORIZONTAL)
  1095. self.dsnSizer = wx.GridBagSizer(vgap = 3, hgap = 3)
  1096. #self.dsnSizer.AddGrowableRow(0)
  1097. self.dsnSizer.AddGrowableCol(3)
  1098. row = 0
  1099. self.dsnSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
  1100. label = _("Format:")),
  1101. flag = wx.ALIGN_CENTER_VERTICAL,
  1102. pos = (row, 0))
  1103. self.dsnSizer.Add(item=self.format,
  1104. flag = wx.ALIGN_CENTER_VERTICAL,
  1105. pos = (row, 1))
  1106. self.dsnSizer.Add(item = self.extensionText,
  1107. flag=wx.ALIGN_CENTER_VERTICAL,
  1108. pos = (row, 2))
  1109. self.dsnSizer.Add(item=self.extension,
  1110. flag = wx.ALIGN_CENTER_VERTICAL,
  1111. pos = (row, 3))
  1112. row += 1
  1113. self.dsnSizer.Add(item = self.dsnText,
  1114. flag = wx.ALIGN_CENTER_VERTICAL,
  1115. pos = (row, 0))
  1116. self.dsnSizer.Add(item = self.input[self.dsnType][1],
  1117. flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
  1118. pos = (row, 1), span = (1, 3))
  1119. row += 1
  1120. if self.creationOpt.IsShown():
  1121. self.dsnSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
  1122. label = _("Creation options:")),
  1123. flag = wx.ALIGN_CENTER_VERTICAL,
  1124. pos = (row, 0))
  1125. self.dsnSizer.Add(item = self.creationOpt,
  1126. flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
  1127. pos = (row, 1), span = (1, 3))
  1128. row += 1
  1129. inputSizer.Add(item=self.dsnSizer, proportion = 1,
  1130. flag=wx.EXPAND | wx.BOTTOM, border = 10)
  1131. mainSizer.Add(item=settingsSizer, proportion=0,
  1132. flag=wx.ALL | wx.EXPAND, border=5)
  1133. mainSizer.Add(item=self.source, proportion=0,
  1134. flag=wx.LEFT | wx.RIGHT | wx.EXPAND, border=5)
  1135. mainSizer.Add(item=inputSizer, proportion=0,
  1136. flag=wx.ALL | wx.EXPAND, border=5)
  1137. self.SetSizer(mainSizer)
  1138. mainSizer.Fit(self)
  1139. def _getExtPatternGlob(self, ext):
  1140. """!Get pattern for case-insensitive globing"""
  1141. pattern = '*.'
  1142. for c in ext:
  1143. pattern += '[%s%s]' % (c.lower(), c.upper())
  1144. return pattern
  1145. def _getExtPattern(self, ext):
  1146. """!Get pattern for case-insensitive file mask"""
  1147. return '*.%s;*.%s' % (ext.lower(), ext.upper())
  1148. def OnSettingsLoad(self, event):
  1149. """!Load named settings"""
  1150. name = event.GetString()
  1151. if name not in self._settings:
  1152. gcmd.GError(parent = self,
  1153. message = _("Settings <%s> not found") % name)
  1154. return
  1155. data = self._settings[name]
  1156. self.OnSetType(event = None, sel = self.sourceMap[data[0]])
  1157. self.OnSetFormat(event = None, format = data[2])
  1158. self.OnSetDsn(event = None, path = data[1])
  1159. self.creationOpt.SetValue(data[3])
  1160. def OnSettingsSave(self, event):
  1161. """!Save settings"""
  1162. dlg = wx.TextEntryDialog(parent = self,
  1163. message = _("Name:"),
  1164. caption = _("Save settings"))
  1165. if dlg.ShowModal() != wx.ID_OK:
  1166. return
  1167. if not dlg.GetValue():
  1168. gcmd.GMessage(parent = self,
  1169. message = _("Name not given, settings is not saved."))
  1170. return
  1171. name = dlg.GetValue()
  1172. try:
  1173. fd = open(self.settingsFile, 'a')
  1174. fd.write(name + ';' + self.dsnType + ';' +
  1175. self.GetDsn() + ';' +
  1176. self.format.GetStringSelection())
  1177. fd.write('\n')
  1178. except IOError:
  1179. gcmd.GError(parent = self,
  1180. message = _("Unable to save settings"))
  1181. return
  1182. fd.close()
  1183. self._settings = self._loadSettings()
  1184. self.settingsChoice.Append(name)
  1185. self.settingsChoice.SetStringSelection(name)
  1186. dlg.Destroy()
  1187. def _loadSettings(self):
  1188. """!Load settings from the file
  1189. The file is defined by self.SettingsFile.
  1190. @return parsed dict
  1191. @return empty dict on error
  1192. """
  1193. data = dict()
  1194. if not os.path.exists(self.settingsFile):
  1195. return data
  1196. try:
  1197. fd = open(self.settingsFile, 'r')
  1198. for line in fd.readlines():
  1199. try:
  1200. lineData = line.rstrip('\n').split(';')
  1201. if len(lineData) > 4:
  1202. # type, dsn, format, options
  1203. data[lineData[0]] = (lineData[1], lineData[2], lineData[3], lineData[4])
  1204. else:
  1205. data[lineData[0]] = (lineData[1], lineData[2], lineData[3], '')
  1206. except ValueError:
  1207. pass
  1208. except IOError:
  1209. return data
  1210. fd.close()
  1211. return data
  1212. def OnSetType(self, event, sel = None):
  1213. """!Datasource type changed"""
  1214. if event:
  1215. sel = event.GetSelection()
  1216. else:
  1217. self.source.SetSelection(sel)
  1218. win = self.input[self.dsnType][1]
  1219. if win:
  1220. self.dsnSizer.Remove(win)
  1221. win.Hide()
  1222. if sel == self.sourceMap['file']: # file
  1223. self.dsnType = 'file'
  1224. format = self.input[self.dsnType][2][0]
  1225. try:
  1226. ext = self.format.GetExtension(format)
  1227. if not ext:
  1228. raise KeyError
  1229. format += ' (%s)|%s|%s (*.*)|*.*' % \
  1230. (self._getExtPattern(ext), self._getExtPattern(ext), _('All files'))
  1231. except KeyError:
  1232. format += '%s (*.*)|*.*' % _('All files')
  1233. win = filebrowse.FileBrowseButton(parent=self, id=wx.ID_ANY,
  1234. size=globalvar.DIALOG_GSELECT_SIZE, labelText='',
  1235. dialogTitle=_('Choose file to import'),
  1236. buttonText=_('Browse'),
  1237. startDirectory=os.getcwd(),
  1238. changeCallback=self.OnSetDsn,
  1239. fileMask = format)
  1240. self.input[self.dsnType][1] = win
  1241. elif sel == self.sourceMap['dir']: # directory
  1242. self.dsnType = 'dir'
  1243. elif sel == self.sourceMap['db']: # database
  1244. self.dsnType = 'db'
  1245. elif sel == self.sourceMap['pro']: # protocol
  1246. self.dsnType = 'pro'
  1247. elif sel == self.sourceMap['native']:
  1248. self.dsnType = 'native'
  1249. if self.dsnType == 'db':
  1250. self.input[self.dsnType][1] = self.input['db-win']['text']
  1251. win = self.input[self.dsnType][1]
  1252. self.dsnSizer.Add(item = self.input[self.dsnType][1],
  1253. flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
  1254. pos = (1, 1), span = (1, 3))
  1255. win.SetValue('')
  1256. win.Show()
  1257. if sel in (self.sourceMap['file'],
  1258. self.sourceMap['dir']):
  1259. if not self.ogr:
  1260. self.OnSetFormat(event = None, format = 'GeoTIFF')
  1261. else:
  1262. self.OnSetFormat(event = None, format = 'ESRI Shapefile')
  1263. if sel == self.sourceMap['native']: # native
  1264. win.Enable(False)
  1265. self.format.Enable(False)
  1266. self.creationOpt.Enable(False)
  1267. self.parent.btnOk.Enable(True)
  1268. else:
  1269. if not self.format.IsEnabled():
  1270. win.Enable(True)
  1271. self.format.Enable(True)
  1272. self.creationOpt.Enable(True)
  1273. self.dsnText.SetLabel(self.input[self.dsnType][0])
  1274. self.format.SetItems(self.input[self.dsnType][2])
  1275. if self.parent.GetName() == 'MultiImportDialog':
  1276. self.parent.list.DeleteAllItems()
  1277. if sel == self.sourceMap['dir'] and not self.dest:
  1278. if not self.extension.IsShown():
  1279. self.extensionText.Show()
  1280. self.extension.Show()
  1281. else:
  1282. if self.extension.IsShown():
  1283. self.extensionText.Hide()
  1284. self.extension.Hide()
  1285. self.dsnSizer.Layout()
  1286. def GetDsn(self):
  1287. """!Get datasource name"""
  1288. if self.format.GetStringSelection() == 'PostgreSQL':
  1289. return 'PG:dbname=%s' % self.input[self.dsnType][1].GetStringSelection()
  1290. return self.input[self.dsnType][1].GetValue()
  1291. def OnSetDsn(self, event, path = None):
  1292. """!Input DXF file/OGR dsn defined, update list of layer
  1293. widget"""
  1294. if event:
  1295. path = event.GetString()
  1296. else:
  1297. if self.format.GetStringSelection() == 'PostgreSQL':
  1298. for item in path.split(':', 1)[1].split(','):
  1299. key, value = item.split('=', 1)
  1300. if key == 'dbname':
  1301. self.input[self.dsnType][1].SetStringSelection(value)
  1302. break
  1303. else:
  1304. self.input[self.dsnType][1].SetValue(path)
  1305. if not path:
  1306. if self.dest:
  1307. self.parent.btnOk.Enable(False)
  1308. return
  1309. if self.dest:
  1310. self.parent.btnOk.Enable(True)
  1311. else:
  1312. self._reloadLayers()
  1313. if event:
  1314. event.Skip()
  1315. def _reloadLayers(self):
  1316. """!Reload list of layers"""
  1317. dsn = self.GetDsn()
  1318. if not dsn:
  1319. return
  1320. data = list()
  1321. layerId = 1
  1322. if self.ogr:
  1323. ret = gcmd.RunCommand('v.in.ogr',
  1324. quiet = True,
  1325. read = True,
  1326. flags = 'a',
  1327. dsn = dsn)
  1328. if not ret:
  1329. self.parent.list.LoadData()
  1330. if hasattr(self, "btn_run"):
  1331. self.btn_run.Enable(False)
  1332. return
  1333. layerId = 1
  1334. for line in ret.splitlines():
  1335. layerName, featureType = map(lambda x: x.strip(), line.split(' ', 1))
  1336. grassName = utils.GetValidLayerName(layerName)
  1337. data.append((layerId, layerName, featureType, grassName))
  1338. layerId += 1
  1339. else:
  1340. if self.dsnType == 'file':
  1341. baseName = os.path.basename(dsn)
  1342. grassName = utils.GetValidLayerName(baseName.split('.', -1)[0])
  1343. data.append((layerId, baseName, grassName))
  1344. elif self.dsnType == 'dir':
  1345. ext = self.extension.GetValue()
  1346. for filename in glob.glob(os.path.join(dsn, "%s") % self._getExtPatternGlob(ext)):
  1347. baseName = os.path.basename(filename)
  1348. grassName = utils.GetValidLayerName(baseName.split('.', -1)[0])
  1349. data.append((layerId, baseName, grassName))
  1350. layerId += 1
  1351. if self.ogr:
  1352. dsn += '@OGR'
  1353. evt = wxGdalSelect(dsn = dsn)
  1354. evt.SetId(self.input[self.dsnType][1].GetId())
  1355. wx.PostEvent(self.parent, evt)
  1356. if self.parent.GetName() == 'MultiImportDialog':
  1357. self.parent.list.LoadData(data)
  1358. if len(data) > 0:
  1359. self.parent.btn_run.Enable(True)
  1360. else:
  1361. self.parent.btn_run.Enable(False)
  1362. def OnSetExtension(self, event):
  1363. """!Extension changed"""
  1364. if not self.dest:
  1365. # reload layers
  1366. self._reloadLayers()
  1367. def OnSetFormat(self, event, format = None):
  1368. """!Format changed"""
  1369. if self.dsnType not in ['file', 'dir', 'db']:
  1370. return
  1371. win = self.input[self.dsnType][1]
  1372. self.dsnSizer.Remove(win)
  1373. if self.dsnType == 'file':
  1374. win.Destroy()
  1375. else: # database
  1376. win.Hide()
  1377. if event:
  1378. format = event.GetString()
  1379. else:
  1380. self.format.SetStringSelection(format)
  1381. if self.dsnType == 'file':
  1382. try:
  1383. ext = self.format.GetExtension(format)
  1384. if not ext:
  1385. raise KeyError
  1386. format += ' (%s)|%s|%s (*.*)|*.*' % \
  1387. (self._getExtPattern(ext), self._getExtPattern(ext), _('All files'))
  1388. except KeyError:
  1389. format += '%s (*.*)|*.*' % _('All files')
  1390. win = filebrowse.FileBrowseButton(parent=self, id=wx.ID_ANY,
  1391. size=globalvar.DIALOG_GSELECT_SIZE, labelText='',
  1392. dialogTitle=_('Choose file'),
  1393. buttonText=_('Browse'),
  1394. startDirectory=os.getcwd(),
  1395. changeCallback=self.OnSetDsn,
  1396. fileMask = format)
  1397. elif self.dsnType == 'dir':
  1398. pass
  1399. else: # database
  1400. if format == 'SQLite' or format == 'Rasterlite':
  1401. win = self.input['db-win']['file']
  1402. elif format == 'PostgreSQL' or format == 'PostGIS WKT Raster driver':
  1403. if grass.find_program('psql', ['--help']):
  1404. win = self.input['db-win']['choice']
  1405. if not win.GetItems():
  1406. p = grass.Popen(['psql', '-ltA'], stdout = grass.PIPE)
  1407. ret = p.communicate()[0]
  1408. if ret:
  1409. db = list()
  1410. for line in ret.splitlines():
  1411. sline = line.split('|')
  1412. if len(sline) < 2:
  1413. continue
  1414. dbname = sline[0]
  1415. if dbname:
  1416. db.append(dbname)
  1417. win.SetItems(db)
  1418. if self.dest and win.GetStringSelection():
  1419. self.parent.btnOk.Enable(True)
  1420. else:
  1421. win = self.input['db-win']['text']
  1422. else:
  1423. win = self.input['db-win']['text']
  1424. self.input[self.dsnType][1] = win
  1425. if not win.IsShown():
  1426. win.Show()
  1427. self.dsnSizer.Add(item = self.input[self.dsnType][1],
  1428. flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
  1429. pos = (1, 1), span = (1, 3))
  1430. self.dsnSizer.Layout()
  1431. # update extension
  1432. self.extension.SetValue(self.GetFormatExt())
  1433. if not self.dest:
  1434. # reload layers
  1435. self._reloadLayers()
  1436. def GetType(self):
  1437. """!Get source type"""
  1438. return self.dsnType
  1439. def GetDsn(self):
  1440. """!Get DSN"""
  1441. if self.format.GetStringSelection() == 'PostgreSQL':
  1442. return 'PG:dbname=%s' % self.input[self.dsnType][1].GetStringSelection()
  1443. return self.input[self.dsnType][1].GetValue()
  1444. def GetDsnWin(self):
  1445. """!Get list of DSN windows"""
  1446. win = list()
  1447. for stype in ('file', 'dir', 'pro'):
  1448. win.append(self.input[stype][1])
  1449. for stype in ('file', 'text', 'choice'):
  1450. win.append(self.input['db-win'][stype])
  1451. return win
  1452. def GetFormat(self):
  1453. """!Get format as string"""
  1454. return self.format.GetStringSelection().replace(' ', '_')
  1455. def GetFormatExt(self):
  1456. """!Get format extension"""
  1457. return self.format.GetExtension(self.format.GetStringSelection())
  1458. def GetOptions(self):
  1459. """!Get creation options"""
  1460. if not self.creationOpt.IsShown():
  1461. return ''
  1462. return self.creationOpt.GetValue()
  1463. class ProjSelect(wx.ComboBox):
  1464. """!Widget for selecting input raster/vector map used by
  1465. r.proj/v.proj modules."""
  1466. def __init__(self, parent, isRaster, id = wx.ID_ANY, size = globalvar.DIALOG_COMBOBOX_SIZE,
  1467. **kwargs):
  1468. super(ProjSelect, self).__init__(parent, id, size = size,
  1469. style = wx.CB_READONLY, **kwargs)
  1470. self.SetName("ProjSelect")
  1471. self.isRaster = isRaster
  1472. def UpdateItems(self, dbase, location, mapset):
  1473. """!Update list of maps
  1474. """
  1475. if not dbase:
  1476. dbase = grass.gisenv()['GISDBASE']
  1477. if not mapset:
  1478. mapset = grass.gisenv()['MAPSET']
  1479. if self.isRaster:
  1480. ret = gcmd.RunCommand('r.proj',
  1481. quiet = True,
  1482. read = True,
  1483. flags = 'l',
  1484. dbase = dbase,
  1485. location = location,
  1486. mapset = mapset)
  1487. else:
  1488. ret = gcmd.RunCommand('v.proj',
  1489. quiet = True,
  1490. read = True,
  1491. flags = 'l',
  1492. dbase = dbase,
  1493. location = location,
  1494. mapset = mapset)
  1495. listMaps = list()
  1496. if ret:
  1497. for line in ret.splitlines():
  1498. listMaps.append(line.strip())
  1499. utils.ListSortLower(listMaps)
  1500. self.SetItems(listMaps)
  1501. self.SetValue('')
  1502. class ElementSelect(wx.Choice):
  1503. def __init__(self, parent, id = wx.ID_ANY, size = globalvar.DIALOG_COMBOBOX_SIZE,
  1504. **kwargs):
  1505. """!Widget for selecting GIS element
  1506. @param parent parent window
  1507. """
  1508. super(ElementSelect, self).__init__(parent, id, size = size,
  1509. style = wx.CB_READONLY, **kwargs)
  1510. self.SetName("ElementSelect")
  1511. task = gtask.parse_interface('g.list')
  1512. p = task.get_param(value = 'type')
  1513. self.values = p.get('values', [])
  1514. self.valuesDesc = p.get('values_desc', [])
  1515. self.SetItems(self.valuesDesc)
  1516. def GetValue(self, name):
  1517. """!Translate value
  1518. @param name element name
  1519. """
  1520. idx = self.valuesDesc.index(name)
  1521. if idx > -1:
  1522. return self.values[idx]
  1523. return ''
  1524. class OgrTypeSelect(wx.Panel):
  1525. def __init__(self, parent, panel, **kwargs):
  1526. """!Widget to choose OGR feature type
  1527. @param parent parent window
  1528. @param panel wx.Panel instance used as parent window
  1529. """
  1530. wx.Panel.__init__(self, parent = panel, id = wx.ID_ANY)
  1531. self.ftype = wx.Choice(parent = self, id = wx.ID_ANY,
  1532. size = (200, -1),
  1533. choices = (_("Point"), _("LineString"), _("Polygon")))
  1534. self._layout()
  1535. def _layout(self):
  1536. """!Do layout"""
  1537. sizer = wx.BoxSizer(wx.HORIZONTAL)
  1538. sizer.Add(item = wx.StaticText(parent = self,
  1539. id = wx.ID_ANY,
  1540. label = _("Feature type:")),
  1541. proportion = 1,
  1542. flag = wx.ALIGN_CENTER_VERTICAL,
  1543. border = 5)
  1544. sizer.Add(item = self.ftype,
  1545. proportion = 0,
  1546. flag = wx.EXPAND | wx.ALIGN_RIGHT)
  1547. self.SetSizer(sizer)
  1548. sizer.Fit(self)
  1549. def GetType(self):
  1550. """!Get selected type as string
  1551. @return feature type as string
  1552. """
  1553. sel = self.ftype.GetSelection()
  1554. if sel == 0:
  1555. return 'point'
  1556. elif sel == 1:
  1557. return 'line'
  1558. elif sel == 2:
  1559. return 'boundary'