gselect.py 24 KB

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