gselect.py 17 KB

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