gselect.py 19 KB

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