123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570 |
- """
- @package gselect
- @brief Custom control that selects elements
- Classes:
- - Select
- - TreeCrtlComboPopup
- - VectorDBInfo
- - LayerSelect
- - DriverSelect
- - DatabaseSelect
- - ColumnSelect
- (C) 2007-2009 by the GRASS Development Team This program is free
- software under the GNU General Public License (>=v2). Read the file
- COPYING that comes with GRASS for details.
- @author Michael Barton
- @author Martin Landa <landa.martin gmail.com>
- """
- import os
- import sys
- import wx
- import wx.combo
- import grass.script as grass
- import globalvar
- import gcmd
- import utils
- from preferences import globalSettings as UserSettings
- class Select(wx.combo.ComboCtrl):
- def __init__(self, parent, id, size,
- type=None, multiple=False, mapsets=None, exceptOf=[]):
- """
- Custom control to create a ComboBox with a tree control
- to display and select GIS elements within acessible mapsets.
- Elements can be selected with mouse. Can allow multiple selections, when
- argument multiple=True. Multiple selections are separated by commas.
- """
- wx.combo.ComboCtrl.__init__(self, parent=parent, id=id, size=size)
- self.GetChildren()[0].SetName("Select")
- self.GetChildren()[0].type = type
-
- self.tcp = TreeCtrlComboPopup()
- self.SetPopupControl(self.tcp)
- self.SetPopupExtents(0,100)
- if type:
- self.tcp.GetElementList(type, mapsets, exceptOf)
- self.tcp.SetData(type = type, mapsets = mapsets,
- exceptOf = exceptOf, multiple = multiple)
- def SetElementList(self, type, mapsets = None, exceptOf = []):
- self.tcp.seltree.DeleteAllItems()
- self.tcp.GetElementList(type)
- self.tcp.SetData(type = type, mapsets = mapsets,
- exceptOf = exceptOf)
-
- class TreeCtrlComboPopup(wx.combo.ComboPopup):
- """
- Create a tree ComboBox for selecting maps and other GIS elements
- in accessible mapsets within the current location
- """
- # overridden ComboPopup methods
- def Init(self):
- self.value = [] # for multiple is False -> len(self.value) in [0,1]
- self.curitem = None
- self.multiple = False
- self.type = None
- self.mapsets = []
- self.exceptOf = []
- def Create(self, parent):
- self.seltree = wx.TreeCtrl(parent, style=wx.TR_HIDE_ROOT
- |wx.TR_HAS_BUTTONS
- |wx.TR_SINGLE
- |wx.TR_LINES_AT_ROOT
- |wx.SIMPLE_BORDER
- |wx.TR_FULL_ROW_HIGHLIGHT)
- self.seltree.Bind(wx.EVT_MOTION, self.OnMotion)
- self.seltree.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
- self.seltree.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.mapsetExpanded)
- self.seltree.Bind(wx.EVT_TREE_ITEM_COLLAPSED, self.mapsetCollapsed)
- self.seltree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.mapsetActivated)
- self.seltree.Bind(wx.EVT_TREE_SEL_CHANGED, self.mapsetSelected)
- self.seltree.Bind(wx.EVT_TREE_DELETE_ITEM, lambda x: None)
- # the following dummy handler are needed to keep tree events from propagating up to
- # the parent GIS Manager layer tree
- def mapsetExpanded(self, event):
- pass
- def mapsetCollapsed(self, event):
- pass
- def mapsetActivated(self, event):
- pass
- def mapsetSelected(self, event):
- pass
- # end of dummy events
- def GetControl(self):
- return self.seltree
- def GetStringValue(self):
- str = ""
- for value in self.value:
- str += value + ","
- str = str.rstrip(',')
-
- return str
- def OnPopup(self):
- """!Limited only for first selected"""
- # update list
- self.seltree.DeleteAllItems()
- self.GetElementList(self.type, self.mapsets, self.exceptOf)
- if len(self.value) > 0:
- root = self.seltree.GetRootItem()
- if not root:
- return
- item = self.FindItem(root, self.value[0])
- try:
- self.seltree.EnsureVisible(item)
- self.seltree.SelectItem(item)
- except:
- pass
-
- def SetStringValue(self, value):
- # this assumes that item strings are unique...
- root = self.seltree.GetRootItem()
- if not root:
- return
- found = self.FindItem(root, value)
- if found:
- self.value.append(found)
- self.seltree.SelectItem(found)
- def GetAdjustedSize(self, minWidth, prefHeight, maxHeight):
- return wx.Size(minWidth, min(200, maxHeight))
- def GetElementList(self, element, mapsets=None, exceptOf=[]):
- """
- Get list of GIS elements in accessible mapsets and display as tree
- with all relevant elements displayed beneath each mapset branch
- """
- # get current mapset
- curr_mapset = grass.gisenv()['MAPSET']
-
- # list of mapsets in current location
- if mapsets is None:
- mapsets = utils.ListOfMapsets()
- # map element types to g.mlist types
- elementdict = {'cell':'rast',
- 'raster':'rast',
- 'rast':'rast',
- 'raster files':'rast',
- 'grid3':'rast3d',
- 'rast3d':'rast3d',
- 'raster3D':'rast3d',
- 'raster3D files':'rast3d',
- 'vector':'vect',
- 'vect':'vect',
- 'binary vector files':'vect',
- 'dig':'oldvect',
- 'oldvect':'oldvect',
- 'old vector':'oldvect',
- 'dig_ascii':'asciivect',
- 'asciivect':'asciivect',
- 'asciivector':'asciivect',
- 'ascii vector files':'asciivect',
- 'icons':'icon',
- 'icon':'icon',
- 'paint icon files':'icon',
- 'paint/labels':'labels',
- 'labels':'labels',
- 'label':'labels',
- 'paint label files':'labels',
- 'site_lists':'sites',
- 'sites':'sites',
- 'site list':'sites',
- 'site list files':'sites',
- 'windows':'region',
- 'region':'region',
- 'region definition':'region',
- 'region definition files':'region',
- 'windows3d':'region3d',
- 'region3d':'region3d',
- 'region3D definition':'region3d',
- 'region3D definition files':'region3d',
- 'group':'group',
- 'imagery group':'group',
- 'imagery group files':'group',
- '3d.view':'3dview',
- '3dview':'3dview',
- '3D viewing parameters':'3dview',
- '3D view parameters':'3dview'}
- if element not in elementdict:
- self.AddItem(_('Not selectable element'))
- return
- # get directory tree nodes
- # reorder mapsets based on search path (TODO)
- for i in range(len(mapsets)):
- if i > 0 and mapsets[i] == curr_mapset:
- mapsets[i] = mapsets[0]
- mapsets[0] = curr_mapset
-
- if globalvar.have_mlist:
- filesdict = grass.mlist_grouped(elementdict[element])
- else:
- filesdict = grass.list_grouped(elementdict[element])
-
- first_dir = None
- for dir in mapsets:
- dir_node = self.AddItem('Mapset: ' + dir)
- if not first_dir:
- first_dir = dir_node
-
- self.seltree.SetItemTextColour(dir_node, wx.Colour(50, 50, 200))
- try:
- elem_list = filesdict[dir]
- elem_list.sort(key=str.lower)
- for elem in elem_list:
- if elem != '':
- fullqElem = elem + '@' + dir
- if len(exceptOf) > 0 and fullqElem in exceptOf:
- continue
- self.AddItem(fullqElem, parent=dir_node)
- except:
- continue
- if self.seltree.ItemHasChildren(dir_node):
- sel = UserSettings.Get(group='general', key='elementListExpand',
- subkey='selection')
- collapse = True
- if sel == 0: # collapse all except PERMANENT and current
- if dir in ('PERMANENT', curr_mapset):
- collapse = False
- elif sel == 1: # collapse all except PERMANENT
- if dir == 'PERMANENT':
- collapse = False
- elif sel == 2: # collapse all except current
- if dir == curr_mapset:
- collapse = False
- elif sel == 3: # collapse all
- pass
- elif sel == 4: # expand all
- collapse = False
-
- if collapse:
- self.seltree.Collapse(dir_node)
- else:
- self.seltree.Expand(dir_node)
-
- if first_dir:
- # select first mapset (MSW hack)
- self.seltree.SelectItem(first_dir)
-
- # helpers
- def FindItem(self, parentItem, text):
- item, cookie = self.seltree.GetFirstChild(parentItem)
- while item:
- if self.seltree.GetItemText(item) == text:
- return item
- if self.seltree.ItemHasChildren(item):
- item = self.FindItem(item, text)
- item, cookie = self.seltree.GetNextChild(parentItem, cookie)
- return wx.TreeItemId()
- def AddItem(self, value, parent=None):
- if not parent:
- root = self.seltree.GetRootItem()
- if not root:
- root = self.seltree.AddRoot("<hidden root>")
- parent = root
- item = self.seltree.AppendItem(parent, text=value)
- return item
- def OnMotion(self, evt):
- # have the selection follow the mouse, like in a real combobox
- item, flags = self.seltree.HitTest(evt.GetPosition())
- if item and flags & wx.TREE_HITTEST_ONITEMLABEL:
- self.seltree.SelectItem(item)
- self.curitem = item
- evt.Skip()
- def OnLeftDown(self, evt):
- # do the combobox selection
- item, flags = self.seltree.HitTest(evt.GetPosition())
- if item and flags & wx.TREE_HITTEST_ONITEMLABEL:
- self.curitem = item
- if self.seltree.GetRootItem() == self.seltree.GetItemParent(item):
- self.value = [] # cannot select mapset item
- else:
- if self.multiple is True:
- # text item should be unique
- self.value.append(self.seltree.GetItemText(item))
- else:
- self.value = [self.seltree.GetItemText(item), ]
- self.Dismiss()
- evt.Skip()
- def SetData(self, **kargs):
- """!Set object properties"""
- if kargs.has_key('type'):
- self.type = kargs['type']
- if kargs.has_key('mapsets'):
- self.mapsets = kargs['mapsets']
- if kargs.has_key('exceptOf'):
- self.exceptOf = kargs['exceptOf']
- if kargs.has_key('multiple'):
- self.multiple = kargs['multiple']
-
- class VectorDBInfo:
- """!Class providing information about attribute tables
- linked to a vector map"""
- def __init__(self, map):
- self.map = map
- # dictionary of layer number and associated (driver, database, table)
- self.layers = {}
- # dictionary of table and associated columns (type, length, values, ids)
- self.tables = {}
-
- if not self.__CheckDBConnection(): # -> self.layers
- return
- self.__DescribeTables() # -> self.tables
- def __CheckDBConnection(self):
- """!Check DB connection"""
- nuldev = file(os.devnull, 'w+')
- self.layers = grass.vector_db(map=self.map, stderr=nuldev)
- nuldev.close()
-
- if (len(self.layers.keys()) == 0):
- return False
- return True
- def __DescribeTables(self):
- """!Describe linked tables"""
- for layer in self.layers.keys():
- # determine column names and types
- table = self.layers[layer]["table"]
- columns = {} # {name: {type, length, [values], [ids]}}
- i = 0
- for item in grass.db_describe(table = self.layers[layer]["table"],
- driver = self.layers[layer]["driver"],
- database = self.layers[layer]["database"])['cols']:
- name, type, length = item
- # FIXME: support more datatypes
- if type.lower() == "integer":
- ctype = int
- elif type.lower() == "double precision":
- ctype = float
- else:
- ctype = str
- columns[name.strip()] = { 'index' : i,
- 'type' : type.lower(),
- 'ctype' : ctype,
- 'length' : int(length),
- 'values' : [],
- 'ids' : []}
- i += 1
-
- # check for key column
- # v.db.connect -g/p returns always key column name lowercase
- if self.layers[layer]["key"] not in columns.keys():
- for col in columns.keys():
- if col.lower() == self.layers[layer]["key"]:
- self.layers[layer]["key"] = col.upper()
- break
-
- self.tables[table] = columns
-
- return True
-
- def Reset(self):
- """!Reset"""
- for layer in self.layers:
- table = self.layers[layer]["table"] # get table desc
- columns = self.tables[table]
- for name in self.tables[table].keys():
- self.tables[table][name]['values'] = []
- self.tables[table][name]['ids'] = []
-
- class LayerSelect(wx.Choice):
- """
- Creates combo box for selecting data layers defined for vector.
- The 'layer' terminology is likely to change for GRASS 7
- """
- def __init__(self, parent,
- id=wx.ID_ANY, pos=wx.DefaultPosition,
- size=globalvar.DIALOG_LAYER_SIZE,
- vector=None, choices=[], all=False, default=None):
- super(LayerSelect, self).__init__(parent, id, pos=pos, size=size,
- choices=choices)
- self.all = all
-
- self.SetName("LayerSelect")
- # default value
- self.default = default
-
- if len(choices) > 1:
- return
- if vector:
- self.InsertLayers(vector)
- else:
- if all:
- self.SetItems(['-1', '1'])
- else:
- self.SetItems(['1'])
- self.SetStringSelection('1')
-
- def InsertLayers(self, vector):
- """!Insert layers for a vector into the layer combobox"""
- layerchoices = utils.GetVectorNumberOfLayers(vector)
-
- if self.all:
- layerchoices.insert(0, '-1')
- if len(layerchoices) > 1:
- self.SetItems(layerchoices)
- self.SetStringSelection('1')
- else:
- self.SetItems(['1'])
- self.SetStringSelection('1')
-
- if self.default:
- self.SetStringSelection(str(self.default))
-
- class DriverSelect(wx.ComboBox):
- """
- Creates combo box for selecting database driver.
- """
- def __init__(self, parent, choices, value,
- id=wx.ID_ANY, pos=wx.DefaultPosition,
- size=globalvar.DIALOG_LAYER_SIZE, **kargs):
- super(DriverSelect, self).__init__(parent, id, value, pos, size,
- choices, style=wx.CB_READONLY)
-
- self.SetName("DriverSelect")
-
- self.SetStringSelection(value)
- class DatabaseSelect(wx.TextCtrl):
- """
- Creates combo box for selecting database driver.
- """
- def __init__(self, parent, value='',
- id=wx.ID_ANY, pos=wx.DefaultPosition,
- size=globalvar.DIALOG_TEXTCTRL_SIZE, **kargs):
-
- super(DatabaseSelect, self).__init__(parent, id, value, pos, size)
-
- self.SetName("DatabaseSelect")
- class TableSelect(wx.ComboBox):
- """
- Creates combo box for selecting attribute tables from the database
- """
- def __init__(self, parent,
- id=wx.ID_ANY, value='', pos=wx.DefaultPosition,
- size=globalvar.DIALOG_COMBOBOX_SIZE,
- choices=[]):
- super(TableSelect, self).__init__(parent, id, value, pos, size, choices,
- style=wx.CB_READONLY)
- self.SetName("TableSelect")
- if not choices:
- self.InsertTables()
-
- def InsertTables(self, driver=None, database=None):
- """!Insert attribute tables into combobox"""
- items = []
- if not driver or not database:
- connect = grass.db_connection()
-
- driver = connect['driver']
- database = connect['database']
-
- ret = gcmd.RunCommand('db.tables',
- flags = 'p',
- read = True,
- driver = driver,
- database = database)
-
- if ret:
- for table in ret.splitlines():
- items.append(table)
-
- self.SetItems(items)
- self.SetValue('')
-
- class ColumnSelect(wx.ComboBox):
- """
- Creates combo box for selecting columns in the attribute table for a vector.
- The 'layer' terminology is likely to change for GRASS 7
- """
- def __init__(self, parent,
- id=wx.ID_ANY, value='', pos=wx.DefaultPosition,
- size=globalvar.DIALOG_COMBOBOX_SIZE, vector=None,
- layer=1, choices=[]):
-
- super(ColumnSelect, self).__init__(parent, id, value, pos, size, choices,
- style=wx.CB_READONLY)
- self.SetName("ColumnSelect")
- if vector:
- self.InsertColumns(vector, layer)
-
- def InsertColumns(self, vector, layer):
- """!Insert columns for a vector attribute table into the columns combobox"""
- dbInfo = VectorDBInfo(vector)
-
- try:
- table = dbInfo.layers[int(layer)]['table']
- columnchoices = dbInfo.tables[table]
- columns = len(columnchoices.keys()) * ['']
- for key, val in columnchoices.iteritems():
- columns[val['index']] = key
- except (KeyError, ValueError):
- columns = []
- self.SetItems(columns)
- self.SetValue('')
-
- def InsertTableColumns(self, table, driver=None, database=None):
- """!Insert table columns"""
- columns = []
-
- ret = gcmd.RunCommand('db.columns',
- read = True,
- driver = driver,
- database = database,
- table = table)
-
- if ret:
- columns = ret.splitlines()
- self.SetItems(columns)
-
|