gselect.py 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290
  1. """!
  2. @package gselect
  3. @brief Custom control that selects elements
  4. Classes:
  5. - Select
  6. - VectorSelect
  7. - TreeCrtlComboPopup
  8. - VectorDBInfo
  9. - LayerSelect
  10. - LayerNameSelect
  11. - DriverSelect
  12. - DatabaseSelect
  13. - ColumnSelect
  14. - LocationSelect
  15. - MapsetSelect
  16. - SubGroupSelect
  17. - FormatSelect
  18. - GdalSelect
  19. (C) 2007-2010 by the GRASS Development Team This program is free
  20. software under the GNU General Public License (>=v2). Read the file
  21. COPYING that comes with GRASS for details.
  22. @author Michael Barton
  23. @author Martin Landa <landa.martin gmail.com>
  24. """
  25. import os
  26. import sys
  27. import glob
  28. import wx
  29. import wx.combo
  30. import wx.lib.filebrowsebutton as filebrowse
  31. from wx.lib.newevent import NewEvent
  32. import globalvar
  33. sys.path.append(os.path.join(globalvar.ETCDIR, "python"))
  34. import grass.script as grass
  35. import gcmd
  36. import utils
  37. from preferences import globalSettings as UserSettings
  38. from debug import Debug
  39. wxGdalSelect, EVT_GDALSELECT = NewEvent()
  40. class Select(wx.combo.ComboCtrl):
  41. def __init__(self, parent, id, size = globalvar.DIALOG_GSELECT_SIZE,
  42. type = None, multiple = False, mapsets = None, exclude = [],
  43. updateOnPopup = True):
  44. """!Custom control to create a ComboBox with a tree control
  45. to display and select GIS elements within acessible mapsets.
  46. Elements can be selected with mouse. Can allow multiple selections, when
  47. argument multiple=True. Multiple selections are separated by commas.
  48. """
  49. wx.combo.ComboCtrl.__init__(self, parent=parent, id=id, size=size)
  50. self.GetChildren()[0].SetName("Select")
  51. self.GetChildren()[0].type = type
  52. self.tcp = TreeCtrlComboPopup()
  53. self.SetPopupControl(self.tcp)
  54. self.SetPopupExtents(0,100)
  55. if type:
  56. self.tcp.SetData(type = type, mapsets = mapsets,
  57. exclude = exclude, multiple = multiple,
  58. updateOnPopup = updateOnPopup)
  59. def SetElementList(self, type, mapsets = None, exclude = []):
  60. """!Set element list
  61. @param type GIS element type
  62. @param mapsets list of acceptable mapsets (None for all in search path)
  63. @param exclude list of GIS elements to be excluded
  64. """
  65. self.tcp.SetData(type = type, mapsets = mapsets,
  66. exclude = exclude)
  67. def GetElementList(self):
  68. """!Load elements"""
  69. self.tcp.GetElementList()
  70. class VectorSelect(Select):
  71. def __init__(self, parent, ftype, **kwargs):
  72. """!Custom to create a ComboBox with a tree control to display and
  73. select vector maps. Control allows to filter vector maps. If you
  74. don't need this feature use Select class instead
  75. @ftype filter vector maps based on feature type
  76. """
  77. Select.__init__(self, parent = parent, id = wx.ID_ANY,
  78. type = 'vector', **kwargs)
  79. self.ftype = ftype
  80. # remove vector maps which do not contain given feature type
  81. self.tcp.SetFilter(self._isElement)
  82. def _isElement(self, vectorName):
  83. """!Check if element should be filtered out"""
  84. try:
  85. if int(grass.vector_info_topo(vectorName)[self.ftype]) < 1:
  86. return False
  87. except KeyError:
  88. return False
  89. return True
  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 len(self.value) > 0:
  153. root = self.seltree.GetRootItem()
  154. if not root:
  155. return
  156. item = self.FindItem(root, self.value[0])
  157. try:
  158. self.seltree.EnsureVisible(item)
  159. self.seltree.SelectItem(item)
  160. except:
  161. pass
  162. def SetStringValue(self, value):
  163. # this assumes that item strings are unique...
  164. root = self.seltree.GetRootItem()
  165. if not root:
  166. return
  167. found = self.FindItem(root, value)
  168. if found:
  169. self.value.append(found)
  170. self.seltree.SelectItem(found)
  171. def GetAdjustedSize(self, minWidth, prefHeight, maxHeight):
  172. return wx.Size(minWidth, min(200, maxHeight))
  173. def _getElementList(self, element, mapsets=None, exclude=[]):
  174. """!Get list of GIS elements in accessible mapsets and display as tree
  175. with all relevant elements displayed beneath each mapset branch
  176. @param element GIS element
  177. @param mapsets list of acceptable mapsets (None for all mapsets in search path)
  178. @param exclude list of GIS elements to be excluded
  179. """
  180. # get current mapset
  181. curr_mapset = grass.gisenv()['MAPSET']
  182. # list of mapsets in current location
  183. if mapsets is None:
  184. mapsets = utils.ListOfMapsets()
  185. # map element types to g.mlist types
  186. elementdict = {'cell':'rast',
  187. 'raster':'rast',
  188. 'rast':'rast',
  189. 'raster files':'rast',
  190. 'grid3':'rast3d',
  191. 'rast3d':'rast3d',
  192. 'raster3D':'rast3d',
  193. 'raster3D files':'rast3d',
  194. 'vector':'vect',
  195. 'vect':'vect',
  196. 'binary vector files':'vect',
  197. 'dig':'oldvect',
  198. 'oldvect':'oldvect',
  199. 'old vector':'oldvect',
  200. 'dig_ascii':'asciivect',
  201. 'asciivect':'asciivect',
  202. 'asciivector':'asciivect',
  203. 'ascii vector files':'asciivect',
  204. 'icons':'icon',
  205. 'icon':'icon',
  206. 'paint icon files':'icon',
  207. 'paint/labels':'labels',
  208. 'labels':'labels',
  209. 'label':'labels',
  210. 'paint label files':'labels',
  211. 'site_lists':'sites',
  212. 'sites':'sites',
  213. 'site list':'sites',
  214. 'site list files':'sites',
  215. 'windows':'region',
  216. 'region':'region',
  217. 'region definition':'region',
  218. 'region definition files':'region',
  219. 'windows3d':'region3d',
  220. 'region3d':'region3d',
  221. 'region3D definition':'region3d',
  222. 'region3D definition files':'region3d',
  223. 'group':'group',
  224. 'imagery group':'group',
  225. 'imagery group files':'group',
  226. '3d.view':'3dview',
  227. '3dview':'3dview',
  228. '3D viewing parameters':'3dview',
  229. '3D view parameters':'3dview'}
  230. if element not in elementdict:
  231. self.AddItem(_('Not selectable element'))
  232. return
  233. # get directory tree nodes
  234. # reorder mapsets based on search path (TODO)
  235. for i in range(len(mapsets)):
  236. if i > 0 and mapsets[i] == curr_mapset:
  237. mapsets[i] = mapsets[0]
  238. mapsets[0] = curr_mapset
  239. if globalvar.have_mlist:
  240. filesdict = grass.mlist_grouped(elementdict[element])
  241. else:
  242. filesdict = grass.list_grouped(elementdict[element])
  243. first_dir = None
  244. for dir in mapsets:
  245. dir_node = self.AddItem('Mapset: ' + dir)
  246. if not first_dir:
  247. first_dir = dir_node
  248. self.seltree.SetItemTextColour(dir_node, wx.Colour(50, 50, 200))
  249. try:
  250. elem_list = filesdict[dir]
  251. elem_list.sort(key=str.lower)
  252. for elem in elem_list:
  253. if elem != '':
  254. fullqElem = elem + '@' + dir
  255. if len(exclude) > 0 and fullqElem in exclude:
  256. continue
  257. if self.filterElements:
  258. if self.filterElements(fullqElem):
  259. self.AddItem(fullqElem, parent=dir_node)
  260. else:
  261. self.AddItem(fullqElem, parent=dir_node)
  262. except:
  263. continue
  264. if self.seltree.ItemHasChildren(dir_node):
  265. sel = UserSettings.Get(group='general', key='elementListExpand',
  266. subkey='selection')
  267. collapse = True
  268. if sel == 0: # collapse all except PERMANENT and current
  269. if dir in ('PERMANENT', curr_mapset):
  270. collapse = False
  271. elif sel == 1: # collapse all except PERMANENT
  272. if dir == 'PERMANENT':
  273. collapse = False
  274. elif sel == 2: # collapse all except current
  275. if dir == curr_mapset:
  276. collapse = False
  277. elif sel == 3: # collapse all
  278. pass
  279. elif sel == 4: # expand all
  280. collapse = False
  281. if collapse:
  282. self.seltree.Collapse(dir_node)
  283. else:
  284. self.seltree.Expand(dir_node)
  285. if first_dir:
  286. # select first mapset (MSW hack)
  287. self.seltree.SelectItem(first_dir)
  288. # helpers
  289. def FindItem(self, parentItem, text):
  290. item, cookie = self.seltree.GetFirstChild(parentItem)
  291. while item:
  292. if self.seltree.GetItemText(item) == text:
  293. return item
  294. if self.seltree.ItemHasChildren(item):
  295. item = self.FindItem(item, text)
  296. item, cookie = self.seltree.GetNextChild(parentItem, cookie)
  297. return wx.TreeItemId()
  298. def AddItem(self, value, parent=None):
  299. if not parent:
  300. root = self.seltree.GetRootItem()
  301. if not root:
  302. root = self.seltree.AddRoot("<hidden root>")
  303. parent = root
  304. item = self.seltree.AppendItem(parent, text=value)
  305. return item
  306. def OnMotion(self, evt):
  307. # have the selection follow the mouse, like in a real combobox
  308. item, flags = self.seltree.HitTest(evt.GetPosition())
  309. if item and flags & wx.TREE_HITTEST_ONITEMLABEL:
  310. self.seltree.SelectItem(item)
  311. self.curitem = item
  312. evt.Skip()
  313. def OnLeftDown(self, evt):
  314. # do the combobox selection
  315. item, flags = self.seltree.HitTest(evt.GetPosition())
  316. if item and flags & wx.TREE_HITTEST_ONITEMLABEL:
  317. self.curitem = item
  318. if self.seltree.GetRootItem() == self.seltree.GetItemParent(item):
  319. self.value = [] # cannot select mapset item
  320. else:
  321. if self.multiple is True:
  322. # text item should be unique
  323. self.value.append(self.seltree.GetItemText(item))
  324. else:
  325. self.value = [self.seltree.GetItemText(item), ]
  326. self.Dismiss()
  327. evt.Skip()
  328. def SetData(self, **kargs):
  329. """!Set object properties"""
  330. if kargs.has_key('type'):
  331. self.type = kargs['type']
  332. if kargs.has_key('mapsets'):
  333. self.mapsets = kargs['mapsets']
  334. if kargs.has_key('exclude'):
  335. self.exclude = kargs['exclude']
  336. if kargs.has_key('multiple'):
  337. self.multiple = kargs['multiple']
  338. if kargs.has_key('updateOnPopup'):
  339. self.updateOnPopup = kargs['updateOnPopup']
  340. class VectorDBInfo:
  341. """!Class providing information about attribute tables
  342. linked to a vector map"""
  343. def __init__(self, map):
  344. self.map = map
  345. # dictionary of layer number and associated (driver, database, table)
  346. self.layers = {}
  347. # dictionary of table and associated columns (type, length, values, ids)
  348. self.tables = {}
  349. if not self._CheckDBConnection(): # -> self.layers
  350. return
  351. self._DescribeTables() # -> self.tables
  352. def _CheckDBConnection(self):
  353. """!Check DB connection"""
  354. nuldev = file(os.devnull, 'w+')
  355. self.layers = grass.vector_db(map=self.map, stderr=nuldev)
  356. nuldev.close()
  357. if (len(self.layers.keys()) == 0):
  358. return False
  359. return True
  360. def _DescribeTables(self):
  361. """!Describe linked tables"""
  362. for layer in self.layers.keys():
  363. # determine column names and types
  364. table = self.layers[layer]["table"]
  365. columns = {} # {name: {type, length, [values], [ids]}}
  366. i = 0
  367. Debug.msg(1, "gselect.VectorDBInfo._DescribeTables(): table=%s driver=%s database=%s" % \
  368. (self.layers[layer]["table"], self.layers[layer]["driver"],
  369. self.layers[layer]["database"]))
  370. for item in grass.db_describe(table = self.layers[layer]["table"],
  371. driver = self.layers[layer]["driver"],
  372. database = self.layers[layer]["database"])['cols']:
  373. name, type, length = item
  374. # FIXME: support more datatypes
  375. if type.lower() == "integer":
  376. ctype = int
  377. elif type.lower() == "double precision":
  378. ctype = float
  379. else:
  380. ctype = str
  381. columns[name.strip()] = { 'index' : i,
  382. 'type' : type.lower(),
  383. 'ctype' : ctype,
  384. 'length' : int(length),
  385. 'values' : [],
  386. 'ids' : []}
  387. i += 1
  388. # check for key column
  389. # v.db.connect -g/p returns always key column name lowercase
  390. if self.layers[layer]["key"] not in columns.keys():
  391. for col in columns.keys():
  392. if col.lower() == self.layers[layer]["key"]:
  393. self.layers[layer]["key"] = col.upper()
  394. break
  395. self.tables[table] = columns
  396. return True
  397. def Reset(self):
  398. """!Reset"""
  399. for layer in self.layers:
  400. table = self.layers[layer]["table"] # get table desc
  401. columns = self.tables[table]
  402. for name in self.tables[table].keys():
  403. self.tables[table][name]['values'] = []
  404. self.tables[table][name]['ids'] = []
  405. def GetName(self):
  406. """!Get vector name"""
  407. return self.map
  408. def GetKeyColumn(self, layer):
  409. """!Get key column of given layer
  410. @param layer vector layer number
  411. """
  412. return self.layers[layer]['key']
  413. def GetTable(self, layer):
  414. """!Get table name of given layer
  415. @param layer vector layer number
  416. """
  417. return self.layers[layer]['table']
  418. def GetDbSettings(self, layer):
  419. """!Get database settins
  420. @param layer layer number
  421. @return (driver, database)
  422. """
  423. return self.layers[layer]['driver'], self.layers[layer]['database']
  424. def GetTableDesc(self, table):
  425. """!Get table columns
  426. @param table table name
  427. """
  428. return self.tables[table]
  429. class LayerSelect(wx.Choice):
  430. def __init__(self, parent, id = wx.ID_ANY,
  431. size=globalvar.DIALOG_LAYER_SIZE,
  432. vector = None, choices = [], all = False, default = None):
  433. """!Creates widget for selecting vector map layer numbers
  434. @param vector vector map name or None
  435. @param choices list of predefined choices
  436. @param all adds layer '-1' (e.g., for d.vect)
  437. @param default default layer number
  438. """
  439. super(LayerSelect, self).__init__(parent, id, size = size,
  440. choices = choices)
  441. self.all = all
  442. self.SetName("LayerSelect")
  443. # default value
  444. self.default = default
  445. if len(choices) > 1:
  446. return
  447. if vector:
  448. self.InsertLayers(vector)
  449. else:
  450. if all:
  451. self.SetItems(['-1', '1'])
  452. else:
  453. self.SetItems(['1'])
  454. self.SetStringSelection('1')
  455. def InsertLayers(self, vector):
  456. """!Insert layers for a vector into the layer combobox"""
  457. layerchoices = utils.GetVectorNumberOfLayers(vector)
  458. if self.all:
  459. layerchoices.insert(0, '-1')
  460. if len(layerchoices) > 1:
  461. self.SetItems(layerchoices)
  462. self.SetStringSelection('1')
  463. else:
  464. self.SetItems(['1'])
  465. self.SetStringSelection('1')
  466. if self.default:
  467. self.SetStringSelection(str(self.default))
  468. class LayerNameSelect(wx.ComboBox):
  469. def __init__(self, parent, id = wx.ID_ANY,
  470. size = globalvar.DIALOG_COMBOBOX_SIZE,
  471. vector = None, dsn = None):
  472. """!Creates combo box for selecting vector map layer names
  473. @param vector vector map name (native or connected via v.external)
  474. @param dsn OGR data source name
  475. """
  476. super(LayerNameSelect, self).__init__(parent, id, size = size)
  477. self.SetName("LayerNameSelect")
  478. if vector:
  479. # -> native
  480. self.InsertLayers(vector = vector)
  481. elif dsn:
  482. self.InsertLayers(dsn = dsn)
  483. def InsertLayers(self, vector = None, dsn = None):
  484. """!Insert layers for a vector into the layer combobox
  485. @todo Implement native format
  486. @param vector vector map name (native or connected via v.external)
  487. @param dsn OGR data source name
  488. """
  489. layers = list()
  490. if vector:
  491. # TODO
  492. pass
  493. elif dsn:
  494. ret = gcmd.RunCommand('v.in.ogr',
  495. read = True,
  496. quiet = True,
  497. flags = 'l',
  498. dsn = dsn)
  499. if ret:
  500. layers = ret.splitlines()
  501. self.SetItems(layers)
  502. self.SetSelection(0)
  503. class DriverSelect(wx.ComboBox):
  504. """!Creates combo box for selecting database driver.
  505. """
  506. def __init__(self, parent, choices, value,
  507. id=wx.ID_ANY, pos=wx.DefaultPosition,
  508. size=globalvar.DIALOG_LAYER_SIZE, **kargs):
  509. super(DriverSelect, self).__init__(parent, id, value, pos, size,
  510. choices, style=wx.CB_READONLY)
  511. self.SetName("DriverSelect")
  512. self.SetStringSelection(value)
  513. class DatabaseSelect(wx.TextCtrl):
  514. """!Creates combo box for selecting database driver.
  515. """
  516. def __init__(self, parent, value='',
  517. id=wx.ID_ANY, pos=wx.DefaultPosition,
  518. size=globalvar.DIALOG_TEXTCTRL_SIZE, **kargs):
  519. super(DatabaseSelect, self).__init__(parent, id, value, pos, size)
  520. self.SetName("DatabaseSelect")
  521. class TableSelect(wx.ComboBox):
  522. """!Creates combo box for selecting attribute tables from the database
  523. """
  524. def __init__(self, parent,
  525. id=wx.ID_ANY, value='', pos=wx.DefaultPosition,
  526. size=globalvar.DIALOG_COMBOBOX_SIZE,
  527. choices=[]):
  528. super(TableSelect, self).__init__(parent, id, value, pos, size, choices,
  529. style=wx.CB_READONLY)
  530. self.SetName("TableSelect")
  531. if not choices:
  532. self.InsertTables()
  533. def InsertTables(self, driver=None, database=None):
  534. """!Insert attribute tables into combobox"""
  535. items = []
  536. if not driver or not database:
  537. connect = grass.db_connection()
  538. driver = connect['driver']
  539. database = connect['database']
  540. ret = gcmd.RunCommand('db.tables',
  541. flags = 'p',
  542. read = True,
  543. driver = driver,
  544. database = database)
  545. if ret:
  546. for table in ret.splitlines():
  547. items.append(table)
  548. self.SetItems(items)
  549. self.SetValue('')
  550. class ColumnSelect(wx.ComboBox):
  551. """!Creates combo box for selecting columns in the attribute table
  552. for a vector map.
  553. @param parent window parent
  554. @param id window id
  555. @param value default value
  556. @param size window size
  557. @param vector vector map name
  558. @param layer layer number
  559. @param param parameters list (see menuform.py)
  560. @param **kwags wx.ComboBox parameters
  561. """
  562. def __init__(self, parent, id = wx.ID_ANY, value = '',
  563. size=globalvar.DIALOG_COMBOBOX_SIZE,
  564. vector = None, layer = 1, param = None, **kwargs):
  565. self.defaultValue = value
  566. self.param = param
  567. super(ColumnSelect, self).__init__(parent, id, value, size = size, **kwargs)
  568. self.SetName("ColumnSelect")
  569. if vector:
  570. self.InsertColumns(vector, layer)
  571. def InsertColumns(self, vector, layer, excludeKey = False, type = None):
  572. """!Insert columns for a vector attribute table into the columns combobox
  573. @param vector vector name
  574. @param layer vector layer number
  575. @param excludeKey exclude key column from the list?
  576. @param type only columns of given type (given as list)
  577. """
  578. dbInfo = VectorDBInfo(vector)
  579. try:
  580. table = dbInfo.GetTable(int(layer))
  581. columnchoices = dbInfo.GetTableDesc(table)
  582. keyColumn = dbInfo.GetKeyColumn(int(layer))
  583. columns = len(columnchoices.keys()) * ['']
  584. for key, val in columnchoices.iteritems():
  585. columns[val['index']] = key
  586. if excludeKey: # exclude key column
  587. columns.remove(keyColumn)
  588. if type: # only selected column types
  589. for key, value in columnchoices.iteritems():
  590. if value['type'] not in type:
  591. columns.remove(key)
  592. except (KeyError, ValueError):
  593. columns = list()
  594. self.SetItems(columns)
  595. self.SetValue(self.defaultValue)
  596. if self.param:
  597. self.param['value'] = ''
  598. def InsertTableColumns(self, table, driver=None, database=None):
  599. """!Insert table columns
  600. @param table table name
  601. @param driver driver name
  602. @param database database name
  603. """
  604. columns = list()
  605. ret = gcmd.RunCommand('db.columns',
  606. read = True,
  607. driver = driver,
  608. database = database,
  609. table = table)
  610. if ret:
  611. columns = ret.splitlines()
  612. self.SetItems(columns)
  613. self.SetValue(self.defaultValue)
  614. if self.param:
  615. self.param['value'] = ''
  616. class LocationSelect(wx.ComboBox):
  617. """!Widget for selecting GRASS location"""
  618. def __init__(self, parent, id = wx.ID_ANY, size = globalvar.DIALOG_COMBOBOX_SIZE,
  619. gisdbase = None, **kwargs):
  620. super(LocationSelect, self).__init__(parent, id, size = size,
  621. style = wx.CB_READONLY, **kwargs)
  622. self.SetName("LocationSelect")
  623. if not gisdbase:
  624. self.gisdbase = grass.gisenv()['GISDBASE']
  625. else:
  626. self.gisdbase = gisdbase
  627. self.SetItems(utils.GetListOfLocations(self.gisdbase))
  628. class MapsetSelect(wx.ComboBox):
  629. """!Widget for selecting GRASS mapset"""
  630. def __init__(self, parent, id = wx.ID_ANY, size = globalvar.DIALOG_COMBOBOX_SIZE,
  631. gisdbase = None, location = None, setItems = True, **kwargs):
  632. super(MapsetSelect, self).__init__(parent, id, size = size,
  633. style = wx.CB_READONLY, **kwargs)
  634. self.SetName("MapsetSelect")
  635. if not gisdbase:
  636. self.gisdbase = grass.gisenv()['GISDBASE']
  637. else:
  638. self.gisdbase = gisdbase
  639. if not location:
  640. self.location = grass.gisenv()['LOCATION_NAME']
  641. else:
  642. self.location = location
  643. if setItems:
  644. self.SetItems(utils.GetListOfMapsets(self.gisdbase, self.location, selectable = True)) # selectable
  645. class SubGroupSelect(wx.ComboBox):
  646. """!Widget for selecting subgroups"""
  647. def __init__(self, parent, id = wx.ID_ANY, size = globalvar.DIALOG_GSELECT_SIZE,
  648. **kwargs):
  649. super(SubGroupSelect, self).__init__(parent, id, size = size,
  650. style = wx.CB_READONLY, **kwargs)
  651. self.SetName("SubGroupSelect")
  652. def Insert(self, group):
  653. """!Insert subgroups for defined group"""
  654. if not group:
  655. return
  656. gisenv = grass.gisenv()
  657. try:
  658. name, mapset = group.split('@', 1)
  659. except ValueError:
  660. name = group
  661. mapset = gisenv['MAPSET']
  662. path = os.path.join(gisenv['GISDBASE'], gisenv['LOCATION_NAME'], mapset,
  663. 'group', name, 'subgroup')
  664. try:
  665. self.SetItems(os.listdir(path))
  666. except OSError:
  667. self.SetItems([])
  668. self.SetValue('')
  669. class FormatSelect(wx.Choice):
  670. def __init__(self, parent, ogr = False,
  671. sourceType = None, id = wx.ID_ANY, size = globalvar.DIALOG_COMBOBOX_SIZE,
  672. **kwargs):
  673. """!Widget for selecting external (GDAL/OGR) format
  674. @param parent parent window
  675. @param sourceType source type ('file', 'directory', 'database', 'protocol') or None
  676. @param ogr True for OGR otherwise GDAL
  677. """
  678. super(FormatSelect, self).__init__(parent, id, size = size,
  679. style = wx.CB_READONLY, **kwargs)
  680. self.SetName("FormatSelect")
  681. if ogr:
  682. ftype = 'ogr'
  683. else:
  684. ftype = 'gdal'
  685. formats = list()
  686. for f in utils.GetFormats()[ftype].values():
  687. formats += f
  688. self.SetItems(formats)
  689. def GetExtension(self, name):
  690. """!Get file extension by format name"""
  691. formatToExt = {
  692. # raster
  693. 'GeoTIFF' : 'tif',
  694. 'Erdas Imagine Images (.img)' : 'img',
  695. 'Ground-based SAR Applications Testbed File Format (.gff)' : 'gff',
  696. 'Arc/Info Binary Grid' : 'adf',
  697. 'Portable Network Graphics' : 'png',
  698. 'JPEG JFIF' : 'jpg',
  699. 'Japanese DEM (.mem)' : 'mem',
  700. 'Graphics Interchange Format (.gif)' : 'gif',
  701. 'X11 PixMap Format' : 'xpm',
  702. 'MS Windows Device Independent Bitmap' : 'bmp',
  703. 'SPOT DIMAP' : 'dim',
  704. 'RadarSat 2 XML Product' : 'xml',
  705. 'EarthWatch .TIL' : 'til',
  706. 'ERMapper .ers Labelled' : 'ers',
  707. 'ERMapper Compressed Wavelets' : 'ecw',
  708. 'GRIdded Binary (.grb)' : 'grb',
  709. 'EUMETSAT Archive native (.nat)' : 'nat',
  710. 'Idrisi Raster A.1' : 'rst',
  711. 'Golden Software ASCII Grid (.grd)' : 'grd',
  712. 'Golden Software Binary Grid (.grd)' : 'grd',
  713. 'Golden Software 7 Binary Grid (.grd)' : 'grd',
  714. 'R Object Data Store' : 'r',
  715. 'USGS DOQ (Old Style)' : 'doq',
  716. 'USGS DOQ (New Style)' : 'doq',
  717. 'ENVI .hdr Labelled' : 'hdr',
  718. 'ESRI .hdr Labelled' : 'hdr',
  719. 'Generic Binary (.hdr Labelled)' : 'hdr',
  720. 'PCI .aux Labelled' : 'aux',
  721. 'EOSAT FAST Format' : 'fst',
  722. 'VTP .bt (Binary Terrain) 1.3 Format' : 'bt',
  723. 'FARSITE v.4 Landscape File (.lcp)' : 'lcp',
  724. 'Swedish Grid RIK (.rik)' : 'rik',
  725. 'USGS Optional ASCII DEM (and CDED)' : 'dem',
  726. 'Northwood Numeric Grid Format .grd/.tab' : '',
  727. 'Northwood Classified Grid Format .grc/.tab' : '',
  728. 'ARC Digitized Raster Graphics' : 'arc',
  729. 'Magellan topo (.blx)' : 'blx',
  730. 'SAGA GIS Binary Grid (.sdat)' : 'sdat',
  731. # vector
  732. 'ESRI Shapefile' : 'shp',
  733. 'UK .NTF' : 'ntf',
  734. 'SDTS' : 'ddf',
  735. 'DGN' : 'dgn',
  736. 'VRT' : 'vrt',
  737. 'REC' : 'rec',
  738. 'BNA' : 'bna',
  739. 'CSV' : 'csv',
  740. 'GML' : 'gml',
  741. 'GPX' : 'gpx',
  742. 'KML' : 'kml',
  743. 'GMT' : 'gmt',
  744. 'PGeo' : 'mdb',
  745. 'XPlane' : 'dat',
  746. 'AVCBin' : 'adf',
  747. 'AVCE00' : 'e00',
  748. 'DXF' : 'dxf',
  749. 'Geoconcept' : 'gxt',
  750. 'GeoRSS' : 'xml',
  751. 'GPSTrackMaker' : 'gtm',
  752. 'VFK' : 'vfk'
  753. }
  754. try:
  755. return formatToExt[name]
  756. except KeyError:
  757. return ''
  758. class GdalSelect(wx.Panel):
  759. def __init__(self, parent, panel, ogr = False,
  760. default = 'file',
  761. exclude = [],
  762. envHandler = None):
  763. """!Widget for selecting GDAL/OGR datasource, format
  764. @param parent parent window
  765. @param ogr use OGR selector instead of GDAL
  766. """
  767. self.parent = parent
  768. self.ogr = ogr
  769. wx.Panel.__init__(self, parent = panel, id = wx.ID_ANY)
  770. self.inputBox = wx.StaticBox(parent = self, id=wx.ID_ANY,
  771. label=" %s " % _("Source name"))
  772. # source type
  773. sources = list()
  774. self.sourceMap = { 'file' : -1,
  775. 'dir' : -1,
  776. 'db' : -1,
  777. 'pro' : -1 }
  778. idx = 0
  779. if 'file' not in exclude:
  780. sources.append(_("File"))
  781. self.sourceMap['file'] = idx
  782. idx += 1
  783. if 'directory' not in exclude:
  784. sources.append(_("Directory"))
  785. self.sourceMap['dir'] = idx
  786. idx += 1
  787. if 'database' not in exclude:
  788. sources.append(_("Database"))
  789. self.sourceMap['db'] = idx
  790. idx += 1
  791. if 'protocol' not in exclude:
  792. sources.append(_("Protocol"))
  793. self.sourceMap['pro'] = idx
  794. self.source = wx.RadioBox(parent = self, id = wx.ID_ANY,
  795. label = _('Source type'),
  796. style = wx.RA_SPECIFY_COLS,
  797. choices = sources)
  798. self.source.SetSelection(0)
  799. self.source.Bind(wx.EVT_RADIOBOX, self.OnSetType)
  800. # dsn widgets
  801. if not ogr:
  802. filemask = 'GeoTIFF (*.tif)|*.tif'
  803. else:
  804. filemask = 'ESRI Shapefile (*.shp)|*.shp'
  805. dsnFile = filebrowse.FileBrowseButton(parent=self, id=wx.ID_ANY,
  806. size=globalvar.DIALOG_GSELECT_SIZE, labelText='',
  807. dialogTitle=_('Choose input file'),
  808. buttonText=_('Browse'),
  809. startDirectory=os.getcwd(),
  810. changeCallback=self.OnSetDsn,
  811. fileMask=filemask)
  812. dsnFile.Hide()
  813. dsnDir = filebrowse.DirBrowseButton(parent=self, id=wx.ID_ANY,
  814. size=globalvar.DIALOG_GSELECT_SIZE, labelText='',
  815. dialogTitle=_('Choose input directory'),
  816. buttonText=_('Browse'),
  817. startDirectory=os.getcwd(),
  818. changeCallback=self.OnSetDsn)
  819. dsnDir.SetName('GdalSelect')
  820. dsnDir.Hide()
  821. dsnDbFile = filebrowse.FileBrowseButton(parent=self, id=wx.ID_ANY,
  822. size=globalvar.DIALOG_GSELECT_SIZE, labelText='',
  823. dialogTitle=_('Choose file'),
  824. buttonText=_('Browse'),
  825. startDirectory=os.getcwd(),
  826. changeCallback=self.OnSetDsn)
  827. dsnDbFile.Hide()
  828. dsnDbFile.SetName('GdalSelect')
  829. dsnDbText = wx.TextCtrl(parent = self, id = wx.ID_ANY)
  830. dsnDbText.Hide()
  831. dsnDbText.Bind(wx.EVT_TEXT, self.OnSetDsn)
  832. dsnDbText.SetName('GdalSelect')
  833. dsnDbChoice = wx.Choice(parent = self, id = wx.ID_ANY)
  834. dsnDbChoice.Hide()
  835. dsnDbChoice.Bind(wx.EVT_CHOICE, self.OnSetDsn)
  836. dsnDbChoice.SetName('GdalSelect')
  837. dsnPro = wx.TextCtrl(parent = self, id = wx.ID_ANY)
  838. dsnPro.Hide()
  839. dsnPro.Bind(wx.EVT_TEXT, self.OnSetDsn)
  840. dsnPro.SetName('GdalSelect')
  841. # format
  842. self.format = FormatSelect(parent = self,
  843. ogr = ogr)
  844. self.format.Bind(wx.EVT_CHOICE, self.OnSetFormat)
  845. if ogr:
  846. fType = 'ogr'
  847. else:
  848. fType = 'gdal'
  849. self.input = { 'file' : [_("File:"),
  850. dsnFile,
  851. utils.GetFormats()[fType]['file']],
  852. 'dir' : [_("Directory:"),
  853. dsnDir,
  854. utils.GetFormats()[fType]['file']],
  855. 'db' : [_("Database:"),
  856. dsnDbFile,
  857. utils.GetFormats()[fType]['database']],
  858. 'pro' : [_("Protocol:"),
  859. dsnPro,
  860. utils.GetFormats()[fType]['protocol']],
  861. 'db-win' : { 'file' : dsnDbFile,
  862. 'text' : dsnDbText,
  863. 'choice' : dsnDbChoice },
  864. }
  865. self.dsnType = default
  866. self.input[self.dsnType][1].Show()
  867. self.format.SetItems(self.input[self.dsnType][2])
  868. if not ogr:
  869. self.format.SetStringSelection('GeoTIFF')
  870. else:
  871. self.format.SetStringSelection('ESRI Shapefile')
  872. self.dsnText = wx.StaticText(parent = self, id = wx.ID_ANY,
  873. label = self.input[self.dsnType][0],
  874. size = (75, -1))
  875. self.formatText = wx.StaticText(parent = self, id = wx.ID_ANY,
  876. label = _("Format:"))
  877. self._layout()
  878. def _layout(self):
  879. """!Layout"""
  880. mainSizer = wx.BoxSizer(wx.VERTICAL)
  881. inputSizer = wx.StaticBoxSizer(self.inputBox, wx.HORIZONTAL)
  882. self.dsnSizer = wx.GridBagSizer(vgap=3, hgap=3)
  883. self.dsnSizer.AddGrowableRow(1)
  884. self.dsnSizer.AddGrowableCol(1)
  885. self.dsnSizer.Add(item=self.dsnText,
  886. flag=wx.ALIGN_CENTER_VERTICAL,
  887. pos = (0, 0))
  888. self.dsnSizer.Add(item=self.input[self.dsnType][1],
  889. flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
  890. pos = (0, 1))
  891. self.dsnSizer.Add(item=self.formatText,
  892. flag=wx.ALIGN_CENTER_VERTICAL,
  893. pos = (1, 0))
  894. self.dsnSizer.Add(item=self.format,
  895. flag = wx.ALIGN_CENTER_VERTICAL,
  896. pos = (1, 1))
  897. inputSizer.Add(item=self.dsnSizer, proportion=1,
  898. flag=wx.EXPAND | wx.ALL)
  899. mainSizer.Add(item=self.source, proportion=0,
  900. flag=wx.ALL | wx.EXPAND, border=5)
  901. mainSizer.Add(item=inputSizer, proportion=0,
  902. flag=wx.ALL | wx.EXPAND, border=5)
  903. self.SetSizer(mainSizer)
  904. mainSizer.Fit(self)
  905. def OnSetType(self, event):
  906. """!Datasource type changed"""
  907. sel = event.GetSelection()
  908. win = self.input[self.dsnType][1]
  909. self.dsnSizer.Remove(win)
  910. win.Hide()
  911. if sel == self.sourceMap['file']: # file
  912. self.dsnType = 'file'
  913. format = self.input[self.dsnType][2][0]
  914. try:
  915. ext = self.format.GetExtension(format)
  916. if not ext:
  917. raise KeyError
  918. format += ' (*.%s)|*.%s' % (ext, ext)
  919. except KeyError:
  920. format += ' (*.*)|*.*'
  921. win = filebrowse.FileBrowseButton(parent=self, id=wx.ID_ANY,
  922. size=globalvar.DIALOG_GSELECT_SIZE, labelText='',
  923. dialogTitle=_('Choose input file'),
  924. buttonText=_('Browse'),
  925. startDirectory=os.getcwd(),
  926. changeCallback=self.OnSetDsn,
  927. fileMask = format)
  928. self.input[self.dsnType][1] = win
  929. elif sel == self.sourceMap['dir']: # directory
  930. self.dsnType = 'dir'
  931. elif sel == self.sourceMap['db']: # database
  932. self.dsnType = 'db'
  933. elif sel == self.sourceMap['pro']: # protocol
  934. self.dsnType = 'pro'
  935. self.dsnText.SetLabel(self.input[self.dsnType][0])
  936. if self.parent.GetName() == 'MultiImportDialog':
  937. self.parent.list.DeleteAllItems()
  938. self.format.SetItems(self.input[self.dsnType][2])
  939. if sel in (self.sourceMap['file'],
  940. self.sourceMap['dir']):
  941. win = self.input[self.dsnType][1]
  942. self.dsnSizer.Add(item=self.input[self.dsnType][1],
  943. flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
  944. pos = (0, 1))
  945. win.SetValue('')
  946. win.Show()
  947. if not self.ogr:
  948. self.format.SetStringSelection('GeoTIFF')
  949. else:
  950. self.format.SetStringSelection('ESRI Shapefile')
  951. elif sel == self.sourceMap['pro']:
  952. win = self.input[self.dsnType][1]
  953. self.dsnSizer.Add(item=self.input[self.dsnType][1],
  954. flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
  955. pos = (0, 1))
  956. win.SetValue('')
  957. win.Show()
  958. self.dsnSizer.Layout()
  959. def OnSetDsn(self, event):
  960. """!Input DXF file/OGR dsn defined, update list of layer widget"""
  961. path = event.GetString()
  962. if not path:
  963. return
  964. data = list()
  965. layerId = 1
  966. if self.format.GetStringSelection() == 'PostgreSQL':
  967. dsn = 'PG:dbname=%s' % self.input[self.dsnType][1].GetStringSelection()
  968. else:
  969. dsn = self.input[self.dsnType][1].GetValue()
  970. if self.dsnType == 'file':
  971. baseName = os.path.basename(dsn)
  972. grassName = utils.GetValidLayerName(baseName.split('.', -1)[0])
  973. data.append((layerId, baseName, grassName))
  974. elif self.dsnType == 'dir':
  975. try:
  976. ext = self.format.GetExtension(self.format.GetStringSelection())
  977. except KeyError:
  978. ext = ''
  979. for file in glob.glob(os.path.join(dsn, "*.%s") % ext):
  980. baseName = os.path.basename(file)
  981. grassName = utils.GetValidLayerName(baseName.split('.', -1)[0])
  982. data.append((layerId, baseName, grassName))
  983. layerId += 1
  984. elif self.dsnType == 'db':
  985. ret = gcmd.RunCommand('v.in.ogr',
  986. quiet = True,
  987. parent = self,
  988. read = True,
  989. flags = 'l',
  990. dsn = dsn)
  991. if not ret:
  992. self.list.LoadData()
  993. self.btn_run.Enable(False)
  994. return
  995. layerId = 1
  996. for line in ret.splitlines():
  997. layerName = line.strip()
  998. grassName = utils.GetValidLayerName(layerName)
  999. data.append((layerId, layerName.strip(), grassName.strip()))
  1000. layerId += 1
  1001. evt = wxGdalSelect(dsn = dsn + '@OGR')
  1002. evt.SetId(self.input[self.dsnType][1].GetId())
  1003. wx.PostEvent(self.parent, evt)
  1004. if self.parent.GetName() == 'MultiImportDialog':
  1005. self.parent.list.LoadData(data)
  1006. if len(data) > 0:
  1007. self.parent.btn_run.Enable(True)
  1008. else:
  1009. self.parent.btn_run.Enable(False)
  1010. event.Skip()
  1011. def OnSetFormat(self, event):
  1012. """!Format changed"""
  1013. if self.dsnType not in ['file', 'db']:
  1014. return
  1015. win = self.input[self.dsnType][1]
  1016. self.dsnSizer.Remove(win)
  1017. if self.dsnType == 'file':
  1018. win.Destroy()
  1019. else: # database
  1020. win.Hide()
  1021. format = event.GetString()
  1022. if self.dsnType == 'file':
  1023. try:
  1024. ext = self.format.GetExtension(format)
  1025. if not ext:
  1026. raise KeyError
  1027. format += ' (*.%s)|*.%s' % (ext, ext)
  1028. except KeyError:
  1029. format += ' (*.*)|*.*'
  1030. win = filebrowse.FileBrowseButton(parent=self, id=wx.ID_ANY,
  1031. size=globalvar.DIALOG_GSELECT_SIZE, labelText='',
  1032. dialogTitle=_('Choose file'),
  1033. buttonText=_('Browse'),
  1034. startDirectory=os.getcwd(),
  1035. changeCallback=self.OnSetDsn,
  1036. fileMask = format)
  1037. else: # database
  1038. if format == 'SQLite' or format == 'Rasterlite':
  1039. win = self.input['db-win']['file']
  1040. elif format == 'PostgreSQL' or format == 'PostGIS WKT Raster driver':
  1041. if grass.find_program('psql'):
  1042. win = self.input['db-win']['choice']
  1043. if not win.GetItems():
  1044. p = grass.Popen(['psql', '-ltA'], stdout = grass.PIPE)
  1045. ret = p.communicate()[0]
  1046. if ret:
  1047. db = list()
  1048. for line in ret.splitlines():
  1049. sline = line.split('|')
  1050. if len(sline) < 2:
  1051. continue
  1052. dbname = sline[0]
  1053. if dbname:
  1054. db.append(dbname)
  1055. win.SetItems(db)
  1056. else:
  1057. win = self.input['db-win']['text']
  1058. else:
  1059. win = self.input['db-win']['text']
  1060. self.input[self.dsnType][1] = win
  1061. if not win.IsShown():
  1062. win.Show()
  1063. self.dsnSizer.Add(item=self.input[self.dsnType][1],
  1064. flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
  1065. pos = (0, 1))
  1066. self.dsnSizer.Layout()
  1067. def GetType(self):
  1068. """!Get source type"""
  1069. return self.dsnType
  1070. def GetDsn(self):
  1071. """!Get DSN"""
  1072. if self.format.GetStringSelection() == 'PostgreSQL':
  1073. return 'PG:dbname=%s' % self.input[self.dsnType][1].GetStringSelection()
  1074. return self.input[self.dsnType][1].GetValue()
  1075. def GetDsnWin(self):
  1076. """!Get list of DSN windows"""
  1077. win = list()
  1078. for stype in ('file', 'dir', 'pro'):
  1079. win.append(self.input[stype][1])
  1080. for stype in ('file', 'text', 'choice'):
  1081. win.append(self.input['db-win'][stype])
  1082. return win
  1083. def GetFormatExt(self):
  1084. """!Get format extension"""
  1085. return self.format.GetExtension(self.format.GetStringSelection())