gselect.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. """
  2. MODULE: gselect
  3. CLASSES:
  4. * Select
  5. * TreeCrtlComboPopup
  6. PURPOSE: Custon control that selects GRASS GIS elements
  7. AUTHORS: The GRASS Development Team. Michael Barton & Martin Landa
  8. COPYRIGHT: (C) 2007 by the GRASS Development Team
  9. This program is free software under the GNU General Public
  10. License (>=v2). Read the file COPYING that comes with GRASS
  11. for details.
  12. """
  13. import os
  14. import sys
  15. import wx
  16. import wx.combo
  17. import grass
  18. import globalvar
  19. import gcmd
  20. import utils
  21. from preferences import globalSettings as UserSettings
  22. class Select(wx.combo.ComboCtrl):
  23. def __init__(self, parent, id, size,
  24. type, multiple=False, mapsets=None, exceptOf=[]):
  25. """
  26. Custom control to create a ComboBox with a tree control
  27. to display and select GIS elements within acessible mapsets.
  28. Elements can be selected with mouse. Can allow multiple selections, when
  29. argument multiple=True. Multiple selections are separated by commas.
  30. """
  31. wx.combo.ComboCtrl.__init__(self, parent=parent, id=id, size=size)
  32. self.tcp = TreeCtrlComboPopup()
  33. self.SetPopupControl(self.tcp)
  34. self.SetPopupExtents(0,100)
  35. self.tcp.GetElementList(type, mapsets, exceptOf)
  36. self.tcp.SetData(type, mapsets, exceptOf, multiple)
  37. def SetElementList(self, type):
  38. self.tcp.seltree.DeleteAllItems()
  39. self.tcp.GetElementList(type)
  40. class TreeCtrlComboPopup(wx.combo.ComboPopup):
  41. """
  42. Create a tree ComboBox for selecting maps and other GIS elements
  43. in accessible mapsets within the current location
  44. """
  45. # overridden ComboPopup methods
  46. def Init(self):
  47. self.value = [] # for multiple is False -> len(self.value) in [0,1]
  48. self.curitem = None
  49. self.multiple = False
  50. self.type = None
  51. self.mapsets = []
  52. self.exceptOf = []
  53. def Create(self, parent):
  54. self.seltree = wx.TreeCtrl(parent, style=wx.TR_HIDE_ROOT
  55. |wx.TR_HAS_BUTTONS
  56. |wx.TR_SINGLE
  57. |wx.TR_LINES_AT_ROOT
  58. |wx.SIMPLE_BORDER
  59. |wx.TR_FULL_ROW_HIGHLIGHT)
  60. self.seltree.Bind(wx.EVT_MOTION, self.OnMotion)
  61. self.seltree.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
  62. self.seltree.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.mapsetExpanded)
  63. self.seltree.Bind(wx.EVT_TREE_ITEM_COLLAPSED, self.mapsetCollapsed)
  64. self.seltree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.mapsetActivated)
  65. self.seltree.Bind(wx.EVT_TREE_SEL_CHANGED, self.mapsetSelected)
  66. self.seltree.Bind(wx.EVT_TREE_DELETE_ITEM, lambda x: None)
  67. # the following dummy handler are needed to keep tree events from propagating up to
  68. # the parent GIS Manager layer tree
  69. def mapsetExpanded(self, event):
  70. pass
  71. def mapsetCollapsed(self, event):
  72. pass
  73. def mapsetActivated(self, event):
  74. pass
  75. def mapsetSelected(self, event):
  76. pass
  77. # end of dummy events
  78. def GetControl(self):
  79. return self.seltree
  80. def GetStringValue(self):
  81. str = ""
  82. for value in self.value:
  83. str += value + ","
  84. str = str.rstrip(',')
  85. return str
  86. def OnPopup(self):
  87. """Limited only for first selected"""
  88. # update list
  89. self.seltree.DeleteAllItems()
  90. self.GetElementList(self.type, self.mapsets, self.exceptOf)
  91. if len(self.value) > 0:
  92. root = self.seltree.GetRootItem()
  93. if not root:
  94. return
  95. item = self.FindItem(root, self.value[0])
  96. self.seltree.EnsureVisible(item)
  97. self.seltree.SelectItem(item)
  98. def SetStringValue(self, value):
  99. # this assumes that item strings are unique...
  100. root = self.seltree.GetRootItem()
  101. if not root:
  102. return
  103. found = self.FindItem(root, value)
  104. if found:
  105. self.value.append(found)
  106. self.seltree.SelectItem(found)
  107. def GetAdjustedSize(self, minWidth, prefHeight, maxHeight):
  108. return wx.Size(minWidth, min(200, maxHeight))
  109. def GetElementList(self, element, mapsets=None, exceptOf=[]):
  110. """
  111. Get list of GIS elements in accessible mapsets and display as tree
  112. with all relevant elements displayed beneath each mapset branch
  113. """
  114. # get current mapset
  115. curr_mapset = grass.gisenv()['MAPSET']
  116. # list of mapsets in current location
  117. if mapsets is None:
  118. mapsets = utils.ListOfMapsets()
  119. # map element types to g.mlist types
  120. elementdict = {'cell':'rast',
  121. 'raster':'rast',
  122. 'rast':'rast',
  123. 'raster files':'rast',
  124. 'grid3':'rast3d',
  125. 'rast3d':'rast3d',
  126. 'raster3D':'rast3d',
  127. 'raster3D files':'rast3d',
  128. 'vector':'vect',
  129. 'vect':'vect',
  130. 'binary vector files':'vect',
  131. 'dig':'oldvect',
  132. 'oldvect':'oldvect',
  133. 'old vector':'oldvect',
  134. 'dig_ascii':'asciivect',
  135. 'asciivect':'asciivect',
  136. 'asciivector':'asciivect',
  137. 'ascii vector files':'asciivect',
  138. 'icons':'icon',
  139. 'icon':'icon',
  140. 'paint icon files':'icon',
  141. 'paint/labels':'labels',
  142. 'labels':'labels',
  143. 'label':'labels',
  144. 'paint label files':'labels',
  145. 'site_lists':'sites',
  146. 'sites':'sites',
  147. 'site list':'sites',
  148. 'site list files':'sites',
  149. 'windows':'region',
  150. 'region':'region',
  151. 'region definition':'region',
  152. 'region definition files':'region',
  153. 'windows3d':'region3d',
  154. 'region3d':'region3d',
  155. 'region3D definition':'region3d',
  156. 'region3D definition files':'region3d',
  157. 'group':'group',
  158. 'imagery group':'group',
  159. 'imagery group files':'group',
  160. '3d.view':'3dview',
  161. '3dview':'3dview',
  162. '3D viewing parameters':'3dview',
  163. '3D view parameters':'3dview'}
  164. if element not in elementdict:
  165. self.AddItem(_('Not selectable element'))
  166. return
  167. # get directory tree nodes
  168. # reorder mapsets based on search path (TODO)
  169. for i in range(len(mapsets)):
  170. if i > 0 and mapsets[i] == curr_mapset:
  171. mapsets[i] = mapsets[0]
  172. mapsets[0] = curr_mapset
  173. filesdict = grass.list_grouped(elementdict[element])
  174. for dir in mapsets:
  175. dir_node = self.AddItem('Mapset: '+dir)
  176. self.seltree.SetItemTextColour(dir_node,wx.Colour(50,50,200))
  177. try:
  178. elem_list = filesdict[dir]
  179. elem_list.sort()
  180. for elem in elem_list:
  181. if elem != '':
  182. fullqElem = elem + '@' + dir
  183. if len(exceptOf) > 0 and fullqElem in exceptOf:
  184. continue
  185. self.AddItem(fullqElem, parent=dir_node)
  186. except:
  187. continue
  188. if self.seltree.ItemHasChildren(dir_node):
  189. sel = UserSettings.Get(group='general', key='elementListExpand',
  190. subkey='selection')
  191. collapse = True
  192. if sel == 0: # collapse all expect PERMANENT and current
  193. if dir in ('PERMANENT', curr_mapset):
  194. collapse = False
  195. elif sel == 1: # collapse all expect PERMANENT
  196. if dir == 'PERMANENT':
  197. collapse = False
  198. elif sel == 2: # collapse all
  199. pass
  200. elif sel == 3: # expand all
  201. collapse = False
  202. if collapse:
  203. self.seltree.Collapse(dir_node)
  204. else:
  205. self.seltree.Expand(dir_node)
  206. # helpers
  207. def FindItem(self, parentItem, text):
  208. item, cookie = self.seltree.GetFirstChild(parentItem)
  209. while item:
  210. if self.seltree.GetItemText(item) == text:
  211. return item
  212. if self.seltree.ItemHasChildren(item):
  213. item = self.FindItem(item, text)
  214. item, cookie = self.seltree.GetNextChild(parentItem, cookie)
  215. return wx.TreeItemId()
  216. def AddItem(self, value, parent=None):
  217. if not parent:
  218. root = self.seltree.GetRootItem()
  219. if not root:
  220. root = self.seltree.AddRoot("<hidden root>")
  221. parent = root
  222. item = self.seltree.AppendItem(parent, text=value)
  223. return item
  224. def OnMotion(self, evt):
  225. # have the selection follow the mouse, like in a real combobox
  226. item, flags = self.seltree.HitTest(evt.GetPosition())
  227. if item and flags & wx.TREE_HITTEST_ONITEMLABEL:
  228. self.seltree.SelectItem(item)
  229. self.curitem = item
  230. evt.Skip()
  231. def OnLeftDown(self, evt):
  232. # do the combobox selection
  233. item, flags = self.seltree.HitTest(evt.GetPosition())
  234. if item and flags & wx.TREE_HITTEST_ONITEMLABEL:
  235. self.curitem = item
  236. if self.seltree.GetRootItem() == self.seltree.GetItemParent(item):
  237. self.value = [] # cannot select mapset item
  238. else:
  239. if self.multiple is True:
  240. # text item should be unique
  241. self.value.append(self.seltree.GetItemText(item))
  242. else:
  243. self.value = [self.seltree.GetItemText(item), ]
  244. self.Dismiss()
  245. evt.Skip()
  246. def SetData(self, type, mapsets, exceptOf, multiple):
  247. """Select multiple items?"""
  248. self.type = type
  249. self.mapsets = mapsets
  250. self.exceptOf = exceptOf
  251. self.multiple = multiple
  252. class VectorDBInfo:
  253. """Class providing information about attribute tables
  254. linked to a vector map"""
  255. def __init__(self, map):
  256. self.map = map
  257. self.layers = {} # dictionary of layer number and associated table
  258. self.tables = {} # dictionary of table and associated columns
  259. ## {table : {column name : type, length, values, ids}}
  260. #self.tables = {}
  261. if not self.__GetLayers(): # -> self.layers
  262. return
  263. self.__GetColumns() # -> self.tables
  264. def __GetLayers(self):
  265. """Create layers dictionary"""
  266. layerCommand = gcmd.Command(cmd=["v.db.connect",
  267. "-g", "--q",
  268. "map=%s" % self.map],
  269. rerr=None, stderr=None)
  270. if layerCommand.returncode != 0:
  271. return False
  272. # create dictionary of layers (as strings) and associated table names for vector
  273. for line in layerCommand.ReadStdOutput():
  274. lineList = line.split(' ')
  275. layer = lineList[0]
  276. if '/' in layer:
  277. layer = layer.split('/')[0]
  278. table = lineList[1]
  279. self.layers[layer] = table
  280. if (len(self.layers.keys()) == 0):
  281. return False
  282. return True
  283. def __GetColumns(self):
  284. """Create dictionary of tables and associated columns"""
  285. for layer in self.layers.keys():
  286. columns = []
  287. # determine column names and types
  288. table = self.layers[layer]
  289. cmd = ['v.info', '-c', 'map=%s' % table, 'layer=%s' % layer]
  290. try:
  291. info = gcmd.Command(cmd).ReadStdOutput()
  292. for line in info:
  293. col = line.split('|')[1]
  294. columns.append(col)
  295. except gcmd.CmdError:
  296. columns = []
  297. self.tables[table] = columns
  298. return True
  299. class LayerSelect(wx.ComboBox):
  300. """
  301. Creates combo box for selecting data layers defined for vector.
  302. The 'layer' terminology is likely to change for GRASS 7
  303. """
  304. def __init__(self, parent,
  305. id=wx.ID_ANY, value='1', pos=wx.DefaultPosition,
  306. size=wx.DefaultSize, choices=['1'], **kargs):
  307. super(LayerSelect, self).__init__(parent, id, value, pos, size, choices)
  308. lsvector = kargs['vector'] # vector map to check for attribute tables
  309. if lsvector == '':
  310. return
  311. else:
  312. self.InsertLayers(lsvector)
  313. def InsertLayers(self, vector):
  314. """insert layers for a vector into the layer combobox"""
  315. layerchoices = VectorDBInfo(vector).layers.keys()
  316. self.SetItems(layerchoices)
  317. self.SetSelection(0)
  318. self.SetValue('1') # all vectors have a layer 1 by default
  319. class ColumnSelect(wx.ComboBox):
  320. """
  321. Creates combo box for selecting columns in the attribute table for a vector.
  322. The 'layer' terminology is likely to change for GRASS 7
  323. """
  324. def __init__(self, parent,
  325. id=wx.ID_ANY, value='', pos=wx.DefaultPosition,
  326. size=wx.DefaultSize, choices=[''], **kargs):
  327. super(ColumnSelect, self).__init__(parent, id, value, pos, size, choices)
  328. csvector = kargs['vector'] # vector map to check for attribute tables
  329. cslayer = kargs['layer'] # data layer connected to attribute table
  330. if csvector == '' or cslayer == '':
  331. return
  332. else:
  333. self.InsertColumns(csvector, cslayer)
  334. def InsertColumns(self, vector, layer):
  335. """insert columns for a vector attribute table into the columns combobox"""
  336. if vector == '' or layer == '': return
  337. table = VectorDBInfo(vector).layers[str(layer)]
  338. columnchoices = VectorDBInfo(vector).tables[table]
  339. #columnchoices.sort()
  340. self.SetItems(columnchoices)
  341. class DbColumnSelect(wx.ComboBox):
  342. """
  343. Creates combo box for selecting columns from any table.
  344. """
  345. def __init__(self, parent,
  346. id=wx.ID_ANY, value='', pos=wx.DefaultPosition,
  347. size=wx.DefaultSize, choices=[''], **kargs):
  348. super(ColumnSelect, self).__init__(parent, id, value, pos, size, choices)
  349. dbtable = kargs['table'] # table to check for columns
  350. dbdriver = kargs['driver'] # driver for table
  351. dbdatabase = kargs['database'] # database for table
  352. if dbtable == '':
  353. return
  354. else:
  355. self.InsertColumns(dbtable)
  356. def InsertColumns(self, table):
  357. """insert columns for a table into the columns combobox"""
  358. if table == '' : return
  359. cmd = ['db.columns','table=%s' % table]
  360. if dbdriver != '': cmd.append('driver=%s' % dbdriver)
  361. if dbdatabase != '': cmd.append('database=%s' % dbdatabase)
  362. try:
  363. columnchoices = gcmd.Command(cmd).ReadStdOutput()
  364. except gcmd.CmdError:
  365. columnchoices = []
  366. #columnchoices.sort()
  367. self.SetItems(columnchoices)