gselect.py 19 KB

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