gselect.py 20 KB

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