GRASS Programmer's Manual  6.4.3(2013)-r
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Macros Pages
gselect.py
Go to the documentation of this file.
1 """!
2 @package gui_core.gselect
3 
4 @brief Custom control that selects elements
5 
6 Classes:
7  - gselect::Select
8  - gselect::VectorSelect
9  - gselect::TreeCrtlComboPopup
10  - gselect::VectorDBInfo
11  - gselect::LayerSelect
12  - gselect::DriverSelect
13  - gselect::DatabaseSelect
14  - gselect::TableSelect
15  - gselect::ColumnSelect
16  - gselect::DbaseSelect
17  - gselect::LocationSelect
18  - gselect::MapsetSelect
19  - gselect::SubGroupSelect
20  - gselect::FormatSelect
21  - gselect::GdalSelect
22  - gselect::ProjSelect
23  - gselect::ElementSelect
24 
25 (C) 2007-2011 by the GRASS Development Team
26 
27 This program is free software under the GNU General Public License
28 (>=v2). Read the file COPYING that comes with GRASS for details.
29 
30 @author Michael Barton
31 @author Martin Landa <landa.martin gmail.com>
32 @author Vaclav Petras <wenzeslaus gmail.com> (menu customization)
33 """
34 
35 import os
36 import sys
37 import glob
38 
39 import wx
40 import wx.combo
41 import wx.lib.filebrowsebutton as filebrowse
42 from wx.lib.newevent import NewEvent
43 
44 from core import globalvar
45 
46 import grass.script as grass
47 from grass.script import task as gtask
48 
49 from core.gcmd import RunCommand, GError, GMessage
50 from core.utils import GetListOfLocations, GetListOfMapsets, GetFormats
51 from core.utils import GetSettingsPath, GetValidLayerName, ListSortLower, GetAllVectorLayers
52 from core.settings import UserSettings
53 from core.debug import Debug
54 
55 wxGdalSelect, EVT_GDALSELECT = NewEvent()
56 
57 class Select(wx.combo.ComboCtrl):
58  def __init__(self, parent, id = wx.ID_ANY, size = globalvar.DIALOG_GSELECT_SIZE,
59  type = None, multiple = False, mapsets = None,
60  updateOnPopup = True, onPopup = None,
61  fullyQualified = True):
62  """!Custom control to create a ComboBox with a tree control to
63  display and select GIS elements within acessible mapsets.
64  Elements can be selected with mouse. Can allow multiple
65  selections, when argument multiple=True. Multiple selections
66  are separated by commas.
67 
68  @param type type of GIS elements ('raster, 'vector', ...)
69  @param multiple multiple input allowed?
70  @param mapsets force list of mapsets (otherwise search path)
71  @param updateOnPopup True for updating list of elements on popup
72  @param onPopup function to be called on Popup
73  @param fullyQualified True to provide fully qualified names (map@mapset)
74  """
75  wx.combo.ComboCtrl.__init__(self, parent=parent, id=id, size=size)
76  self.GetChildren()[0].SetName("Select")
77  self.GetChildren()[0].type = type
78 
80  self.SetPopupControl(self.tcp)
81  self.SetPopupExtents(0, 100)
82  if type:
83  self.tcp.SetData(type = type, mapsets = mapsets,
84  multiple = multiple,
85  updateOnPopup = updateOnPopup, onPopup = onPopup,
86  fullyQualified = fullyQualified)
87  self.GetChildren()[0].Bind(wx.EVT_KEY_UP, self.OnKeyUp)
88 
89  def OnKeyUp(self, event):
90  """!Shows popupwindow if down arrow key is released"""
91  if event.GetKeyCode() == wx.WXK_DOWN and not self.IsPopupShown():
92  self.ShowPopup()
93  else:
94  event.Skip()
95 
96  def SetElementList(self, type, mapsets = None):
97  """!Set element list
98 
99  @param type GIS element type
100  @param mapsets list of acceptable mapsets (None for all in search path)
101  """
102  self.tcp.SetData(type = type, mapsets = mapsets)
103 
104  def GetElementList(self):
105  """!Load elements"""
106  self.tcp.GetElementList()
107 
108  def SetType(self, etype, multiple = False, mapsets = None,
109  updateOnPopup = True, onPopup = None):
110  """!Param set element type for widget
111 
112  @param etype element type, see gselect.ElementSelect
113  """
114  self.tcp.SetData(type = etype, mapsets = mapsets,
115  multiple = multiple,
116  updateOnPopup = updateOnPopup, onPopup = onPopup)
117 
119  def __init__(self, parent, ftype, **kwargs):
120  """!Custom to create a ComboBox with a tree control to display and
121  select vector maps. You can filter the vector maps. If you
122  don't need this feature use Select class instead
123 
124  @ftype filter vector maps based on feature type
125  """
126  Select.__init__(self, parent = parent, id = wx.ID_ANY,
127  type = 'vector', **kwargs)
128 
129  self.ftype = ftype
130 
131  # remove vector maps which do not contain given feature type
132  self.tcp.SetFilter(self._isElement)
133 
134  def _isElement(self, vectorName):
135  """!Check if element should be filtered out"""
136  try:
137  if int(grass.vector_info_topo(vectorName)[self.ftype]) < 1:
138  return False
139  except KeyError:
140  return False
141 
142  return True
143 
144 class TreeCtrlComboPopup(wx.combo.ComboPopup):
145  """!Create a tree ComboBox for selecting maps and other GIS elements
146  in accessible mapsets within the current location
147  """
148  # overridden ComboPopup methods
149  def Init(self):
150  self.value = [] # for multiple is False -> len(self.value) in [0,1]
151  self.curitem = None
152  self.multiple = False
153  self.type = None
154  self.mapsets = None
155  self.updateOnPopup = True
156  self.onPopup = None
157  self.fullyQualified = True
158 
159  self.SetFilter(None)
160 
161  def Create(self, parent):
162  self.seltree = wx.TreeCtrl(parent, style=wx.TR_HIDE_ROOT
163  |wx.TR_HAS_BUTTONS
164  |wx.TR_SINGLE
165  |wx.TR_LINES_AT_ROOT
166  |wx.SIMPLE_BORDER
167  |wx.TR_FULL_ROW_HIGHLIGHT)
168  self.seltree.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
169  self.seltree.Bind(wx.EVT_MOTION, self.OnMotion)
170  self.seltree.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
171  # the following dummy handler are needed to keep tree events
172  # from propagating up to the parent GIS Manager layer tree
173  self.seltree.Bind(wx.EVT_TREE_ITEM_EXPANDING, lambda x: None)
174  self.seltree.Bind(wx.EVT_TREE_ITEM_COLLAPSED, lambda x: None)
175  self.seltree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, lambda x: None)
176  self.seltree.Bind(wx.EVT_TREE_SEL_CHANGED, lambda x: None)
177  self.seltree.Bind(wx.EVT_TREE_DELETE_ITEM, lambda x: None)
178  self.seltree.Bind(wx.EVT_TREE_BEGIN_DRAG, lambda x: None)
179  self.seltree.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, lambda x: None)
180 
181  def GetControl(self):
182  return self.seltree
183 
184  def GetStringValue(self):
185  """!Get value as a string separated by commas"""
186  return ','.join(self.value)
187 
188  def SetFilter(self, filter):
189  """!Set filter for GIS elements, see e.g. VectorSelect"""
190  self.filterElements = filter
191 
192  def OnPopup(self, force = False):
193  """!Limited only for first selected"""
194  if not force and not self.updateOnPopup:
195  return
196  if self.onPopup:
197  selected, exclude = self.onPopup(self.type)
198  else:
199  selected = None
200  exclude = False
201 
202  self.GetElementList(selected, exclude)
203 
204  # selects map starting according to written text
205  inputText = self.GetCombo().GetValue().strip()
206  if inputText:
207  root = self.seltree.GetRootItem()
208  match = self.FindItem(root, inputText, startLetters = True)
209  self.seltree.EnsureVisible(match)
210  self.seltree.SelectItem(match)
211 
212 
213  def GetElementList(self, elements = None, exclude = False):
214  """!Get filtered list of GIS elements in accessible mapsets
215  and display as tree with all relevant elements displayed
216  beneath each mapset branch
217  """
218  # update list
219  self.seltree.DeleteAllItems()
220  self._getElementList(self.type, self.mapsets, elements, exclude)
221 
222  if len(self.value) > 0:
223  root = self.seltree.GetRootItem()
224  if not root:
225  return
226  item = self.FindItem(root, self.value[0])
227  try:
228  self.seltree.EnsureVisible(item)
229  self.seltree.SelectItem(item)
230  except:
231  pass
232 
233  def SetStringValue(self, value):
234  # this assumes that item strings are unique...
235  root = self.seltree.GetRootItem()
236  if not root:
237  return
238  found = self.FindItem(root, value)
239  winValue = self.GetCombo().GetValue().strip(',')
240  self.value = []
241  if winValue:
242  self.value = winValue.split(',')
243 
244  if found:
245  self.value.append(found)
246  self.seltree.SelectItem(found)
247 
248  def GetAdjustedSize(self, minWidth, prefHeight, maxHeight):
249  """!Reads UserSettings to get height (which was 200 in old implementation).
250  """
251  height = UserSettings.Get(group = 'appearance', key = 'gSelectPopupHeight', subkey = 'value')
252  return wx.Size(minWidth, min(height, maxHeight))
253 
254  def _getElementList(self, element, mapsets = None, elements = None, exclude = False):
255  """!Get list of GIS elements in accessible mapsets and display as tree
256  with all relevant elements displayed beneath each mapset branch
257 
258  @param element GIS element
259  @param mapsets list of acceptable mapsets (None for all mapsets in search path)
260  @param elements list of forced GIS elements
261  @param exclude True to exclude, False for forcing the list (elements)
262  """
263  # get current mapset
264  curr_mapset = grass.gisenv()['MAPSET']
265 
266  # map element types to g.mlist types
267  elementdict = {'cell':'rast',
268  'raster':'rast',
269  'rast':'rast',
270  'raster files':'rast',
271  'grid3':'rast3d',
272  'rast3d':'rast3d',
273  '3d-raster':'rast3d',
274  'raster3d':'rast3d',
275  'raster3D files':'rast3d',
276  'vector':'vect',
277  'vect':'vect',
278  'binary vector files':'vect',
279  'dig':'oldvect',
280  'oldvect':'oldvect',
281  'old vector':'oldvect',
282  'dig_ascii':'asciivect',
283  'asciivect':'asciivect',
284  'asciivector':'asciivect',
285  'ascii vector files':'asciivect',
286  'icons':'icon',
287  'icon':'icon',
288  'paint icon files':'icon',
289  'paint/labels':'labels',
290  'labels':'labels',
291  'label':'labels',
292  'paint label files':'labels',
293  'site_lists':'sites',
294  'sites':'sites',
295  'site list':'sites',
296  'site list files':'sites',
297  'windows':'region',
298  'region':'region',
299  'region definition':'region',
300  'region definition files':'region',
301  'windows3d':'region3d',
302  'region3d':'region3d',
303  'region3D definition':'region3d',
304  'region3D definition files':'region3d',
305  'group':'group',
306  'imagery group':'group',
307  'imagery group files':'group',
308  '3d.view':'3dview',
309  '3dview':'3dview',
310  '3D viewing parameters':'3dview',
311  '3D view parameters':'3dview'}
312 
313  if element not in elementdict:
314  self.AddItem(_('Not selectable element'))
315  return
316 
317  if globalvar.have_mlist:
318  filesdict = grass.mlist_grouped(elementdict[element],
319  check_search_path = False)
320  else:
321  filesdict = grass.list_grouped(elementdict[element],
322  check_search_path = False)
323 
324  # list of mapsets in current location
325  if mapsets is None:
326  mapsets = grass.mapsets(search_path = True)
327 
328  # current mapset first
329  if curr_mapset in mapsets and mapsets[0] != curr_mapset:
330  mapsets.remove(curr_mapset)
331  mapsets.insert(0, curr_mapset)
332 
333  first_mapset = None
334  for mapset in mapsets:
335  mapset_node = self.AddItem(_('Mapset') + ': ' + mapset)
336  if not first_mapset:
337  first_mapset = mapset_node
338 
339  self.seltree.SetItemTextColour(mapset_node, wx.Colour(50, 50, 200))
340  if mapset not in filesdict:
341  continue
342  try:
343  elem_list = filesdict[mapset]
344  elem_list.sort()
345  for elem in elem_list:
346  if elem != '':
347  fullqElem = elem + '@' + mapset
348  if elements is not None:
349  if (exclude and fullqElem in elements) or \
350  (not exclude and fullqElem not in elements):
351  continue
352 
353  if self.filterElements:
354  if self.filterElements(fullqElem):
355  self.AddItem(elem, parent = mapset_node)
356  else:
357  self.AddItem(elem, parent = mapset_node)
358  except StandardError, e:
359  sys.stderr.write(_("GSelect: invalid item: %s") % e)
360  continue
361 
362  if self.seltree.ItemHasChildren(mapset_node):
363  sel = UserSettings.Get(group='appearance', key='elementListExpand',
364  subkey='selection')
365  collapse = True
366 
367  if sel == 0: # collapse all except PERMANENT and current
368  if mapset in ('PERMANENT', curr_mapset):
369  collapse = False
370  elif sel == 1: # collapse all except PERMANENT
371  if mapset == 'PERMANENT':
372  collapse = False
373  elif sel == 2: # collapse all except current
374  if mapset == curr_mapset:
375  collapse = False
376  elif sel == 3: # collapse all
377  pass
378  elif sel == 4: # expand all
379  collapse = False
380 
381  if collapse:
382  self.seltree.Collapse(mapset_node)
383  else:
384  self.seltree.Expand(mapset_node)
385 
386  if first_mapset:
387  # select first mapset (MSW hack)
388  self.seltree.SelectItem(first_mapset)
389 
390  # helpers
391  def FindItem(self, parentItem, text, startLetters = False):
392  """!Finds item with given name or starting with given text"""
393  startletters = startLetters
394  item, cookie = self.seltree.GetFirstChild(parentItem)
395  while wx.TreeItemId.IsOk(item):
396  if self.seltree.GetItemText(item) == text:
397  return item
398  if self.seltree.ItemHasChildren(item):
399  item = self.FindItem(item, text, startLetters = startletters)
400  if wx.TreeItemId.IsOk(item):
401  return item
402  elif startletters and self.seltree.GetItemText(item).startswith(text.split('@', 1)[0]):
403  return item
404  item, cookie = self.seltree.GetNextChild(parentItem, cookie)
405  return wx.TreeItemId()
406 
407  def AddItem(self, value, parent=None):
408  if not parent:
409  root = self.seltree.GetRootItem()
410  if not root:
411  root = self.seltree.AddRoot("<hidden root>")
412  parent = root
413 
414  item = self.seltree.AppendItem(parent, text=value)
415  return item
416 
417  # able to recieve only wx.EVT_KEY_UP
418  def OnKeyUp(self, event):
419  """!Enables to select items using keyboard"""
420 
421  item = self.seltree.GetSelection()
422  if event.GetKeyCode() == wx.WXK_DOWN:
423  self.seltree.SelectItem(self.seltree.GetNextVisible(item))
424 
425  # problem with GetPrevVisible
426  elif event.GetKeyCode() == wx.WXK_UP:
427  if self.seltree.ItemHasChildren(item) and self.seltree.IsExpanded(self.seltree.GetPrevSibling(item)):
428  itemPrev = self.seltree.GetLastChild(self.seltree.GetPrevSibling(item))
429  else:
430  itemPrev = self.seltree.GetPrevSibling(item)
431  if not wx.TreeItemId.IsOk(itemPrev):
432  itemPrev = self.seltree.GetItemParent(item)
433  if item == self.seltree.GetFirstChild(self.seltree.GetRootItem())[0]:
434  itemPrev = item
435  self.seltree.SelectItem(itemPrev)
436 
437  # selects first item starting with the written text in next mapset
438  elif event.GetKeyCode() == wx.WXK_TAB:
439  selected = self.seltree.GetSelection()
440  if self.seltree.ItemHasChildren(selected):
441  parent = selected
442  else:
443  parent = self.seltree.GetItemParent(selected)
444  nextSibling = self.seltree.GetNextSibling(parent)
445  if wx.TreeItemId.IsOk(nextSibling):
446  match = self.FindItem(nextSibling, self.GetCombo().GetValue().strip(), True)
447  else:
448  match = self.FindItem(self.seltree.GetFirstChild(self.seltree.GetItemParent(parent))[0],
449  self.GetCombo().GetValue().strip(), True)
450  self.seltree.SelectItem(match)
451 
452  elif event.GetKeyCode() == wx.WXK_RIGHT:
453  if self.seltree.ItemHasChildren(item):
454  self.seltree.Expand(item)
455 
456  elif event.GetKeyCode() == wx.WXK_LEFT:
457  if self.seltree.ItemHasChildren(item):
458  self.seltree.Collapse(item)
459 
460  elif event.GetKeyCode() == wx.WXK_ESCAPE:
461  self.Dismiss()
462 
463  elif event.GetKeyCode() == wx.WXK_RETURN:
464  if self.seltree.GetRootItem() == self.seltree.GetItemParent(item):
465  self.value = []
466  else:
467  mapsetItem = self.seltree.GetItemParent(item)
468  fullName = self.seltree.GetItemText(item)
469  if self.fullyQualified:
470  fullName += '@' + self.seltree.GetItemText(mapsetItem).split(':', -1)[1].strip()
471 
472  if self.multiple is True:
473  # text item should be unique
474  self.value.append(fullName)
475  else:
476  self.value = [fullName]
477 
478  self.Dismiss()
479 
480  def OnMotion(self, evt):
481  """!Have the selection follow the mouse, like in a real combobox
482  """
483  item, flags = self.seltree.HitTest(evt.GetPosition())
484  if item and flags & wx.TREE_HITTEST_ONITEMLABEL:
485  self.seltree.SelectItem(item)
486  self.curitem = item
487  evt.Skip()
488 
489  def OnLeftDown(self, evt):
490  """!Do the combobox selection
491  """
492  item, flags = self.seltree.HitTest(evt.GetPosition())
493  if item and flags & wx.TREE_HITTEST_ONITEMLABEL:
494  self.curitem = item
495 
496  if self.seltree.GetRootItem() == self.seltree.GetItemParent(item):
497  self.value = [] # cannot select mapset item
498  else:
499  mapsetItem = self.seltree.GetItemParent(item)
500  fullName = self.seltree.GetItemText(item)
501  if self.fullyQualified:
502  fullName += '@' + self.seltree.GetItemText(mapsetItem).split(':', -1)[1].strip()
503 
504  if self.multiple is True:
505  # text item should be unique
506  self.value.append(fullName)
507  else:
508  self.value = [fullName]
509 
510  self.Dismiss()
511 
512  evt.Skip()
513 
514  def SetData(self, **kargs):
515  """!Set object properties"""
516  if 'type' in kargs:
517  self.type = kargs['type']
518  if 'mapsets' in kargs:
519  self.mapsets = kargs['mapsets']
520  if 'multiple' in kargs:
521  self.multiple = kargs['multiple']
522  if 'updateOnPopup' in kargs:
523  self.updateOnPopup = kargs['updateOnPopup']
524  if 'onPopup' in kargs:
525  self.onPopup = kargs['onPopup']
526  if 'fullyQualified' in kargs:
527  self.fullyQualified = kargs['fullyQualified']
528 
529  def GetType(self):
530  """!Get element type
531  """
532  return self.type
533 
535  """!Class providing information about attribute tables
536  linked to a vector map"""
537  def __init__(self, map):
538  self.map = map
539 
540  # dictionary of layer number and associated (driver, database, table)
541  self.layers = {}
542  # dictionary of table and associated columns (type, length, values, ids)
543  self.tables = {}
544 
545  if not self._CheckDBConnection(): # -> self.layers
546  return
547 
548  self._DescribeTables() # -> self.tables
549 
550  def _CheckDBConnection(self):
551  """!Check DB connection"""
552  nuldev = file(os.devnull, 'w+')
553  self.layers = grass.vector_db(map=self.map, stderr=nuldev)
554  nuldev.close()
555 
556  if (len(self.layers.keys()) == 0):
557  return False
558 
559  return True
560 
561  def _DescribeTables(self):
562  """!Describe linked tables"""
563  for layer in self.layers.keys():
564  # determine column names and types
565  table = self.layers[layer]["table"]
566  columns = {} # {name: {type, length, [values], [ids]}}
567  i = 0
568  Debug.msg(1, "gselect.VectorDBInfo._DescribeTables(): table=%s driver=%s database=%s" % \
569  (self.layers[layer]["table"], self.layers[layer]["driver"],
570  self.layers[layer]["database"]))
571  for item in grass.db_describe(table = self.layers[layer]["table"],
572  driver = self.layers[layer]["driver"],
573  database = self.layers[layer]["database"])['cols']:
574  name, type, length = item
575  # FIXME: support more datatypes
576  if type.lower() == "integer":
577  ctype = int
578  elif type.lower() == "double precision":
579  ctype = float
580  else:
581  ctype = str
582 
583  columns[name.strip()] = { 'index' : i,
584  'type' : type.lower(),
585  'ctype' : ctype,
586  'length' : int(length),
587  'values' : [],
588  'ids' : []}
589  i += 1
590 
591  # check for key column
592  # v.db.connect -g/p returns always key column name lowercase
593  if self.layers[layer]["key"] not in columns.keys():
594  for col in columns.keys():
595  if col.lower() == self.layers[layer]["key"]:
596  self.layers[layer]["key"] = col.upper()
597  break
598 
599  self.tables[table] = columns
600 
601  return True
602 
603  def Reset(self):
604  """!Reset"""
605  for layer in self.layers:
606  table = self.layers[layer]["table"] # get table desc
607  columns = self.tables[table]
608  for name in self.tables[table].keys():
609  self.tables[table][name]['values'] = []
610  self.tables[table][name]['ids'] = []
611 
612  def GetName(self):
613  """!Get vector name"""
614  return self.map
615 
616  def GetKeyColumn(self, layer):
617  """!Get key column of given layer
618 
619  @param layer vector layer number
620  """
621  return str(self.layers[layer]['key'])
622 
623  def GetTable(self, layer):
624  """!Get table name of given layer
625 
626  @param layer vector layer number
627  """
628  return self.layers[layer]['table']
629 
630  def GetDbSettings(self, layer):
631  """!Get database settins
632 
633  @param layer layer number
634 
635  @return (driver, database)
636  """
637  return self.layers[layer]['driver'], self.layers[layer]['database']
638 
639  def GetTableDesc(self, table):
640  """!Get table columns
641 
642  @param table table name
643  """
644  return self.tables[table]
645 
646 class LayerSelect(wx.ComboBox):
647  """!Creates combo box for selecting data layers defined for vector.
648  """
649  def __init__(self, parent,
650  id = wx.ID_ANY, pos = wx.DefaultPosition,
651  size = globalvar.DIALOG_LAYER_SIZE,
652  vector = None, choices = [], initial = [], default = None):
653 
654  super(LayerSelect, self).__init__(parent, id, pos=pos, size=size,
655  choices=choices)
656 
657  self.parent = parent
658  self.initial = initial
659 
660  self.SetName("LayerSelect")
661 
662  # default value
663  self.default = default
664  self.current = None
665  self.Bind(wx.EVT_COMBOBOX, self._selectionChanged)
666 
667  self.InsertLayers(vector = vector)
668 
669  def InsertLayers(self, vector):
670  """!Insert layers for a vector into the layer combobox
671 
672  @param vector name of vector map
673  """
674  if vector:
675  layers = GetAllVectorLayers(vector)
676  else:
677  layers = list()
678 
679  for layer in self.initial:
680  if layer in layers:
681  continue
682  layers.append(layer)
683 
684  if self.default:
685  if len(layers) == 0:
686  layers.insert(0, str(self.default))
687  elif self.default not in layers:
688  layers.append(self.default)
689 
690  if len(layers) >= 1:
691  self.SetItems(layers)
692 
693  self.Select()
694 
695  def _selectionChanged(self, event):
696  """!Selection changed, store value."""
697  self.current = self.GetValue()
698  event.Skip()
699 
700  def Select(self):
701  """!Select value (currently selected or default)"""
702  items = self.GetItems()
703  if items:
704  if self.current is not None and self.current in items:
705  self.SetStringSelection(self.current)
706  elif self.default:
707  self.SetStringSelection(str(self.default))
708  else:
709  self.SetSelection(0)
710  else:
711  self.SetValue('')
712 
713 class DriverSelect(wx.ComboBox):
714  """!Creates combo box for selecting database driver.
715  """
716  def __init__(self, parent, choices, value,
717  id=wx.ID_ANY, pos=wx.DefaultPosition,
718  size=globalvar.DIALOG_LAYER_SIZE, **kargs):
719 
720  super(DriverSelect, self).__init__(parent, id, value, pos, size,
721  choices, style=wx.CB_READONLY)
722 
723  self.SetName("DriverSelect")
724 
725  self.SetStringSelection(value)
726 
727 class DatabaseSelect(wx.TextCtrl):
728  """!Creates combo box for selecting database driver.
729  """
730  def __init__(self, parent, value='',
731  id=wx.ID_ANY, pos=wx.DefaultPosition,
732  size=globalvar.DIALOG_TEXTCTRL_SIZE, **kargs):
733 
734  super(DatabaseSelect, self).__init__(parent, id, value, pos, size)
735 
736  self.SetName("DatabaseSelect")
737 
738 class TableSelect(wx.ComboBox):
739  """!Creates combo box for selecting attribute tables from the database
740  """
741  def __init__(self, parent,
742  id=wx.ID_ANY, value='', pos=wx.DefaultPosition,
743  size=globalvar.DIALOG_COMBOBOX_SIZE,
744  choices=[]):
745 
746  super(TableSelect, self).__init__(parent, id, value, pos, size, choices,
747  style=wx.CB_READONLY)
748 
749  self.SetName("TableSelect")
750 
751  if not choices:
752  self.InsertTables()
753 
754  def InsertTables(self, driver=None, database=None):
755  """!Insert attribute tables into combobox"""
756  items = []
757 
758  if not driver or not database:
759  connect = grass.db_connection()
760 
761  driver = connect['driver']
762  database = connect['database']
763 
764  ret = RunCommand('db.tables',
765  flags = 'p',
766  read = True,
767  driver = driver,
768  database = database)
769 
770  if ret:
771  for table in ret.splitlines():
772  items.append(table)
773 
774  self.SetItems(items)
775  self.SetValue('')
776 
777 class ColumnSelect(wx.ComboBox):
778  """!Creates combo box for selecting columns in the attribute table
779  for a vector map.
780 
781  @param parent window parent
782  @param id window id
783  @param value default value
784  @param size window size
785  @param vector vector map name
786  @param layer layer number
787  @param param parameters list (see menuform.py)
788  @param **kwags wx.ComboBox parameters
789  """
790  def __init__(self, parent, id = wx.ID_ANY, value = '',
791  size=globalvar.DIALOG_COMBOBOX_SIZE,
792  vector = None, layer = 1, param = None, **kwargs):
793  self.defaultValue = value
794  self.param = param
795 
796  super(ColumnSelect, self).__init__(parent, id, value, size = size, **kwargs)
797  self.SetName("ColumnSelect")
798 
799  if vector:
800  self.InsertColumns(vector, layer)
801 
802  def InsertColumns(self, vector, layer, excludeKey = False, excludeCols = None, type = None, dbInfo = None):
803  """!Insert columns for a vector attribute table into the columns combobox
804 
805  @param vector vector name
806  @param layer vector layer number
807  @param excludeKey exclude key column from the list?
808  @param excludeCols list of columns to be removed from the list
809  @param type only columns of given type (given as list)
810  """
811  if not dbInfo:
812  dbInfo = VectorDBInfo(vector)
813 
814  try:
815  table = dbInfo.GetTable(int(layer))
816  columnchoices = dbInfo.GetTableDesc(table)
817  keyColumn = dbInfo.GetKeyColumn(int(layer))
818  columns = len(columnchoices.keys()) * ['']
819  for key, val in columnchoices.iteritems():
820  columns[val['index']] = key
821  if excludeKey: # exclude key column
822  columns.remove(keyColumn)
823  if excludeCols: # exclude key column
824  for key in columnchoices.iterkeys():
825  if key in excludeCols:
826  columns.remove(key)
827  if type: # only selected column types
828  for key, value in columnchoices.iteritems():
829  if value['type'] not in type:
830  try:
831  columns.remove(key)
832  except ValueError:
833  pass
834  except (KeyError, ValueError):
835  columns = list()
836 
837  self.SetItems(columns)
838  self.SetValue(self.defaultValue)
839 
840  if self.param:
841  value = self.param.get('value', '')
842  if value != '' and value in columns:
843  self.SetValue(value)
844 
845  def InsertTableColumns(self, table, driver=None, database=None):
846  """!Insert table columns
847 
848  @param table table name
849  @param driver driver name
850  @param database database name
851  """
852  columns = list()
853 
854  ret = RunCommand('db.columns',
855  read = True,
856  driver = driver,
857  database = database,
858  table = table)
859 
860  if ret:
861  columns = ret.splitlines()
862 
863  self.SetItems(columns)
864  self.SetValue(self.defaultValue)
865 
866  if self.param:
867  value = self.param.get('value', '')
868  if value != '' and value in columns:
869  self.SetValue(value)
870 
871 class DbaseSelect(wx.lib.filebrowsebutton.DirBrowseButton):
872  """!Widget for selecting GRASS Database"""
873  def __init__(self, parent, **kwargs):
874  super(DbaseSelect, self).__init__(parent, id = wx.ID_ANY,
875  size = globalvar.DIALOG_GSELECT_SIZE, labelText = '',
876  dialogTitle = _('Choose GIS Data Directory'),
877  buttonText = _('Browse'),
878  startDirectory = grass.gisenv()['GISDBASE'],
879  **kwargs)
880 
881 class LocationSelect(wx.ComboBox):
882  """!Widget for selecting GRASS location"""
883  def __init__(self, parent, id = wx.ID_ANY, size = globalvar.DIALOG_COMBOBOX_SIZE,
884  gisdbase = None, **kwargs):
885  super(LocationSelect, self).__init__(parent, id, size = size,
886  style = wx.CB_READONLY, **kwargs)
887  self.SetName("LocationSelect")
888 
889  if not gisdbase:
890  self.gisdbase = grass.gisenv()['GISDBASE']
891  else:
892  self.gisdbase = gisdbase
893 
894  self.SetItems(GetListOfLocations(self.gisdbase))
895 
896  def UpdateItems(self, dbase):
897  """!Update list of locations
898 
899  @param dbase path to GIS database
900  """
901  self.gisdbase = dbase
902  if dbase:
903  self.SetItems(GetListOfLocations(self.gisdbase))
904  else:
905  self.SetItems([])
906 
907 class MapsetSelect(wx.ComboBox):
908  """!Widget for selecting GRASS mapset"""
909  def __init__(self, parent, id = wx.ID_ANY, size = globalvar.DIALOG_COMBOBOX_SIZE,
910  gisdbase = None, location = None, setItems = True,
911  searchPath = False, skipCurrent = False, **kwargs):
912  super(MapsetSelect, self).__init__(parent, id, size = size,
913  style = wx.CB_READONLY, **kwargs)
914  self.searchPath = searchPath
915  self.skipCurrent = skipCurrent
916 
917  self.SetName("MapsetSelect")
918  if not gisdbase:
919  self.gisdbase = grass.gisenv()['GISDBASE']
920  else:
921  self.gisdbase = gisdbase
922 
923  if not location:
924  self.location = grass.gisenv()['LOCATION_NAME']
925  else:
926  self.location = location
927 
928  if setItems:
929  self.SetItems(self._getMapsets())
930 
931  def UpdateItems(self, location, dbase = None):
932  """!Update list of mapsets for given location
933 
934  @param dbase path to GIS database (None to use currently selected)
935  @param location name of location
936  """
937  if dbase:
938  self.gisdbase = dbase
939  self.location = location
940  if location:
941  self.SetItems(self._getMapsets())
942  else:
943  self.SetItems([])
944 
945  def _getMapsets(self):
946  if self.searchPath:
947  mlist = RunCommand('g.mapsets',
948  read = True, flags = 'p',
949  fs = 'newline').splitlines()
950  else:
951  mlist = GetListOfMapsets(self.gisdbase, self.location,
952  selectable = False)
953 
954  gisenv = grass.gisenv()
955  if self.skipCurrent and \
956  gisenv['LOCATION_NAME'] == self.location and \
957  gisenv['MAPSET'] in mlist:
958  mlist.remove(gisenv['MAPSET'])
959 
960  return mlist
961 
962 class SubGroupSelect(wx.ComboBox):
963  """!Widget for selecting subgroups"""
964  def __init__(self, parent, id = wx.ID_ANY, size = globalvar.DIALOG_GSELECT_SIZE,
965  **kwargs):
966  super(SubGroupSelect, self).__init__(parent, id, size = size,
967  **kwargs)
968  self.SetName("SubGroupSelect")
969 
970  def Insert(self, group):
971  """!Insert subgroups for defined group"""
972  if not group:
973  return
974  gisenv = grass.gisenv()
975  try:
976  name, mapset = group.split('@', 1)
977  except ValueError:
978  name = group
979  mapset = gisenv['MAPSET']
980 
981  path = os.path.join(gisenv['GISDBASE'], gisenv['LOCATION_NAME'], mapset,
982  'group', name, 'subgroup')
983  try:
984  self.SetItems(os.listdir(path))
985  except OSError:
986  self.SetItems([])
987  self.SetValue('')
988 
989 class FormatSelect(wx.Choice):
990  def __init__(self, parent, ogr = False,
991  sourceType = None, id = wx.ID_ANY, size = globalvar.DIALOG_COMBOBOX_SIZE,
992  **kwargs):
993  """!Widget for selecting external (GDAL/OGR) format
994 
995  @param parent parent window
996  @param sourceType source type ('file', 'directory', 'database', 'protocol') or None
997  @param ogr True for OGR otherwise GDAL
998  """
999  super(FormatSelect, self).__init__(parent, id, size = size,
1000  **kwargs)
1001  self.SetName("FormatSelect")
1002 
1003  if ogr:
1004  ftype = 'ogr'
1005  else:
1006  ftype = 'gdal'
1007 
1008  formats = list()
1009  for f in GetFormats()[ftype].values():
1010  formats += f
1011  self.SetItems(formats)
1012 
1013  def GetExtension(self, name):
1014  """!Get file extension by format name"""
1015  formatToExt = {
1016  # raster
1017  'GeoTIFF' : 'tif',
1018  'Erdas Imagine Images (.img)' : 'img',
1019  'Ground-based SAR Applications Testbed File Format (.gff)' : 'gff',
1020  'Arc/Info Binary Grid' : 'adf',
1021  'Portable Network Graphics' : 'png',
1022  'JPEG JFIF' : 'jpg',
1023  'Japanese DEM (.mem)' : 'mem',
1024  'Graphics Interchange Format (.gif)' : 'gif',
1025  'X11 PixMap Format' : 'xpm',
1026  'MS Windows Device Independent Bitmap' : 'bmp',
1027  'SPOT DIMAP' : 'dim',
1028  'RadarSat 2 XML Product' : 'xml',
1029  'EarthWatch .TIL' : 'til',
1030  'ERMapper .ers Labelled' : 'ers',
1031  'ERMapper Compressed Wavelets' : 'ecw',
1032  'GRIdded Binary (.grb)' : 'grb',
1033  'EUMETSAT Archive native (.nat)' : 'nat',
1034  'Idrisi Raster A.1' : 'rst',
1035  'Golden Software ASCII Grid (.grd)' : 'grd',
1036  'Golden Software Binary Grid (.grd)' : 'grd',
1037  'Golden Software 7 Binary Grid (.grd)' : 'grd',
1038  'R Object Data Store' : 'r',
1039  'USGS DOQ (Old Style)' : 'doq',
1040  'USGS DOQ (New Style)' : 'doq',
1041  'ENVI .hdr Labelled' : 'hdr',
1042  'ESRI .hdr Labelled' : 'hdr',
1043  'Generic Binary (.hdr Labelled)' : 'hdr',
1044  'PCI .aux Labelled' : 'aux',
1045  'EOSAT FAST Format' : 'fst',
1046  'VTP .bt (Binary Terrain) 1.3 Format' : 'bt',
1047  'FARSITE v.4 Landscape File (.lcp)' : 'lcp',
1048  'Swedish Grid RIK (.rik)' : 'rik',
1049  'USGS Optional ASCII DEM (and CDED)' : 'dem',
1050  'Northwood Numeric Grid Format .grd/.tab' : '',
1051  'Northwood Classified Grid Format .grc/.tab' : '',
1052  'ARC Digitized Raster Graphics' : 'arc',
1053  'Magellan topo (.blx)' : 'blx',
1054  'SAGA GIS Binary Grid (.sdat)' : 'sdat',
1055  # vector
1056  'ESRI Shapefile' : 'shp',
1057  'UK .NTF' : 'ntf',
1058  'SDTS' : 'ddf',
1059  'DGN' : 'dgn',
1060  'VRT' : 'vrt',
1061  'REC' : 'rec',
1062  'BNA' : 'bna',
1063  'CSV' : 'csv',
1064  'GML' : 'gml',
1065  'GPX' : 'gpx',
1066  'KML' : 'kml',
1067  'GMT' : 'gmt',
1068  'PGeo' : 'mdb',
1069  'XPlane' : 'dat',
1070  'AVCBin' : 'adf',
1071  'AVCE00' : 'e00',
1072  'DXF' : 'dxf',
1073  'Geoconcept' : 'gxt',
1074  'GeoRSS' : 'xml',
1075  'GPSTrackMaker' : 'gtm',
1076  'VFK' : 'vfk'
1077  }
1078 
1079  try:
1080  return formatToExt[name]
1081  except KeyError:
1082  return ''
1083 
1084 class GdalSelect(wx.Panel):
1085  def __init__(self, parent, panel, ogr = False, link = False, dest = False,
1086  default = 'file', exclude = [], envHandler = None):
1087  """!Widget for selecting GDAL/OGR datasource, format
1088 
1089  @param parent parent window
1090  @param ogr use OGR selector instead of GDAL
1091  @param dest True for output (destination)
1092  @param default deafult type (ignored when dest == True)
1093  @param exclude list of types to be excluded
1094  """
1095  self.parent = parent
1096  self.ogr = ogr
1097  self.dest = dest
1098  wx.Panel.__init__(self, parent = panel, id = wx.ID_ANY)
1099 
1100  self.settingsBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
1101  label = " %s " % _("Settings"))
1102 
1103  self.inputBox = wx.StaticBox(parent = self, id = wx.ID_ANY)
1104  if dest:
1105  self.inputBox.SetLabel(" %s " % _("Output settings"))
1106  else:
1107  self.inputBox.SetLabel(" %s " % _("Source settings"))
1108 
1109  # source type
1110  sources = list()
1111  self.sourceMap = { 'file' : -1,
1112  'dir' : -1,
1113  'db' : -1,
1114  'pro' : -1,
1115  'native' : -1 }
1116  idx = 0
1117  if dest:
1118  sources.append(_("Native"))
1119  self.sourceMap['native'] = idx
1120  idx += 1
1121  if 'file' not in exclude:
1122  sources.append(_("File"))
1123  self.sourceMap['file'] = idx
1124  idx += 1
1125  if 'directory' not in exclude:
1126  sources.append(_("Directory"))
1127  self.sourceMap['dir'] = idx
1128  idx += 1
1129  if 'database' not in exclude:
1130  sources.append(_("Database"))
1131  self.sourceMap['db'] = idx
1132  idx += 1
1133  if 'protocol' not in exclude:
1134  sources.append(_("Protocol"))
1135  self.sourceMap['pro'] = idx
1136  idx += 1
1137 
1138  if self.ogr:
1139  self.settingsFile = os.path.join(GetSettingsPath(), 'wxOGR')
1140  else:
1141  self.settingsFile = os.path.join(GetSettingsPath(), 'wxGDAL')
1142 
1143  self.settingsChoice = wx.Choice(parent = self, id = wx.ID_ANY)
1144  self.settingsChoice.Bind(wx.EVT_CHOICE, self.OnSettingsLoad)
1145  self._settings = self._loadSettings() # -> self.settingsChoice.SetItems()
1146  self.btnSettingsSave = wx.Button(parent = self, id = wx.ID_SAVE)
1147  self.btnSettingsSave.Bind(wx.EVT_BUTTON, self.OnSettingsSave)
1148  self.btnSettingsSave.SetToolTipString(_("Save current settings"))
1149  self.btnSettingsDel = wx.Button(parent = self, id = wx.ID_REMOVE)
1150  self.btnSettingsDel.Bind(wx.EVT_BUTTON, self.OnSettingsDelete)
1151  self.btnSettingsSave.SetToolTipString(_("Delete currently selected settings"))
1152 
1153  self.source = wx.RadioBox(parent = self, id = wx.ID_ANY,
1154  style = wx.RA_SPECIFY_COLS,
1155  choices = sources)
1156  if dest:
1157  self.source.SetLabel(" %s " % _('Output type'))
1158  else:
1159  self.source.SetLabel(" %s " % _('Source type'))
1160 
1161  self.source.SetSelection(0)
1162  self.source.Bind(wx.EVT_RADIOBOX, self.OnSetType)
1163 
1164  # dsn widgets
1165  if not ogr:
1166  filemask = 'GeoTIFF (%s)|%s|%s (*.*)|*.*' % \
1167  (self._getExtPattern('tif'), self._getExtPattern('tif'), _('All files'))
1168  else:
1169  filemask = 'ESRI Shapefile (%s)|%s|%s (*.*)|*.*' % \
1170  (self._getExtPattern('shp'), self._getExtPattern('shp'), _('All files'))
1171 
1172  dsnFile = filebrowse.FileBrowseButton(parent=self, id=wx.ID_ANY,
1173  size=globalvar.DIALOG_GSELECT_SIZE, labelText = '',
1174  dialogTitle=_('Choose file to import'),
1175  buttonText=_('Browse'),
1176  startDirectory=os.getcwd(),
1177  changeCallback=self.OnSetDsn,
1178  fileMask=filemask)
1179  dsnFile.Hide()
1180 
1181  dsnDir = filebrowse.DirBrowseButton(parent=self, id=wx.ID_ANY,
1182  size=globalvar.DIALOG_GSELECT_SIZE, labelText='',
1183  dialogTitle=_('Choose input directory'),
1184  buttonText=_('Browse'),
1185  startDirectory=os.getcwd(),
1186  changeCallback=self.OnSetDsn)
1187  dsnDir.SetName('GdalSelect')
1188  dsnDir.Hide()
1189 
1190  dsnDbFile = filebrowse.FileBrowseButton(parent=self, id=wx.ID_ANY,
1191  size=globalvar.DIALOG_GSELECT_SIZE, labelText='',
1192  dialogTitle=_('Choose file'),
1193  buttonText=_('Browse'),
1194  startDirectory=os.getcwd(),
1195  changeCallback=self.OnSetDsn)
1196  dsnDbFile.Hide()
1197  dsnDbFile.SetName('GdalSelect')
1198 
1199  dsnDbText = wx.TextCtrl(parent = self, id = wx.ID_ANY)
1200  dsnDbText.Hide()
1201  dsnDbText.Bind(wx.EVT_TEXT, self.OnSetDsn)
1202  dsnDbText.SetName('GdalSelect')
1203 
1204  dsnDbChoice = wx.Choice(parent = self, id = wx.ID_ANY)
1205  dsnDbChoice.Hide()
1206  dsnDbChoice.Bind(wx.EVT_CHOICE, self.OnSetDsn)
1207  dsnDbChoice.SetName('GdalSelect')
1208 
1209  dsnPro = wx.TextCtrl(parent = self, id = wx.ID_ANY)
1210  dsnPro.Hide()
1211  dsnPro.Bind(wx.EVT_TEXT, self.OnSetDsn)
1212  dsnPro.SetName('GdalSelect')
1213 
1214  # format
1215  self.format = FormatSelect(parent = self,
1216  ogr = ogr)
1217  self.format.Bind(wx.EVT_CHOICE, self.OnSetFormat)
1218  self.extension = wx.TextCtrl(parent = self, id = wx.ID_ANY)
1219  self.extension.Bind(wx.EVT_TEXT, self.OnSetExtension)
1220  self.extension.Hide()
1221 
1222  if ogr:
1223  fType = 'ogr'
1224  else:
1225  fType = 'gdal'
1226  self.input = { 'file' : [_("File:"),
1227  dsnFile,
1228  GetFormats()[fType]['file']],
1229  'dir' : [_("Name:"),
1230  dsnDir,
1231  GetFormats()[fType]['file']],
1232  'db' : [_("Name:"),
1233  dsnDbText,
1234  GetFormats()[fType]['database']],
1235  'pro' : [_("Protocol:"),
1236  dsnPro,
1237  GetFormats()[fType]['protocol']],
1238  'db-win' : { 'file' : dsnDbFile,
1239  'text' : dsnDbText,
1240  'choice' : dsnDbChoice },
1241  'native' : [_("Name:"), dsnDir, ''],
1242  }
1243 
1244  if self.dest:
1245  current = RunCommand('v.external.out',
1246  parent = self,
1247  read = True, parse = grass.parse_key_val,
1248  flags = 'g')
1249  if current['format'] == 'native':
1250  self.dsnType = 'native'
1251  elif current['format'] in GetFormats()['ogr']['database']:
1252  self.dsnType = 'db'
1253  else:
1254  self.dsnType = 'dir'
1255  else:
1256  self.dsnType = default
1257 
1258  self.dsnText = wx.StaticText(parent = self, id = wx.ID_ANY,
1259  label = self.input[self.dsnType][0],
1260  size = (75, -1))
1261  self.extensionText = wx.StaticText(parent = self, id = wx.ID_ANY,
1262  label = _("Extension:"))
1263  self.extensionText.Hide()
1264 
1265  self.creationOpt = wx.TextCtrl(parent = self, id = wx.ID_ANY)
1266  if not self.dest:
1267  self.creationOpt.Hide()
1268 
1269  self._layout()
1270 
1271  self.OnSetType(event = None, sel = self.sourceMap[self.dsnType])
1272  if self.dest:
1273  if current['format'] != 'native':
1274  self.OnSetFormat(event = None, format = current['format'])
1275  self.OnSetDsn(event = None, path = current['dsn'])
1276  self.creationOpt.SetValue(current['options'])
1277  else:
1278  if not ogr:
1279  self.OnSetFormat(event = None, format = 'GeoTIFF')
1280  else:
1281  self.OnSetFormat(event = None, format = 'ESRI Shapefile')
1282 
1283  def _layout(self):
1284  """!Layout"""
1285  mainSizer = wx.BoxSizer(wx.VERTICAL)
1286 
1287  settingsSizer = wx.StaticBoxSizer(self.settingsBox, wx.HORIZONTAL)
1288  settingsSizer.Add(item = wx.StaticText(parent = self,
1289  id = wx.ID_ANY,
1290  label = _("Load settings:")),
1291  flag = wx.ALIGN_CENTER_VERTICAL | wx.RIGHT,
1292  border = 5)
1293  settingsSizer.Add(item = self.settingsChoice,
1294  proportion = 1,
1295  flag = wx.EXPAND)
1296  settingsSizer.Add(item = self.btnSettingsSave,
1297  flag = wx.LEFT | wx.RIGHT,
1298  border = 5)
1299  settingsSizer.Add(item = self.btnSettingsDel,
1300  flag = wx.RIGHT,
1301  border = 5)
1302 
1303  inputSizer = wx.StaticBoxSizer(self.inputBox, wx.HORIZONTAL)
1304 
1305  self.dsnSizer = wx.GridBagSizer(vgap = 3, hgap = 3)
1306  #self.dsnSizer.AddGrowableRow(0)
1307  self.dsnSizer.AddGrowableCol(3)
1308 
1309  row = 0
1310  self.dsnSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
1311  label = _("Format:")),
1312  flag = wx.ALIGN_CENTER_VERTICAL,
1313  pos = (row, 0))
1314  self.dsnSizer.Add(item=self.format,
1315  flag = wx.ALIGN_CENTER_VERTICAL,
1316  pos = (row, 1))
1317  self.dsnSizer.Add(item = self.extensionText,
1318  flag=wx.ALIGN_CENTER_VERTICAL,
1319  pos = (row, 2))
1320  self.dsnSizer.Add(item=self.extension,
1321  flag = wx.ALIGN_CENTER_VERTICAL,
1322  pos = (row, 3))
1323  row += 1
1324  self.dsnSizer.Add(item = self.dsnText,
1325  flag = wx.ALIGN_CENTER_VERTICAL,
1326  pos = (row, 0))
1327  self.dsnSizer.Add(item = self.input[self.dsnType][1],
1328  flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
1329  pos = (row, 1), span = (1, 3))
1330  row += 1
1331  if self.creationOpt.IsShown():
1332  self.dsnSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
1333  label = _("Creation options:")),
1334  flag = wx.ALIGN_CENTER_VERTICAL,
1335  pos = (row, 0))
1336  self.dsnSizer.Add(item = self.creationOpt,
1337  flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
1338  pos = (row, 1), span = (1, 3))
1339  row += 1
1340 
1341  inputSizer.Add(item=self.dsnSizer, proportion = 1,
1342  flag=wx.EXPAND | wx.BOTTOM, border = 10)
1343 
1344  mainSizer.Add(item=settingsSizer, proportion=0,
1345  flag=wx.ALL | wx.EXPAND, border=5)
1346  mainSizer.Add(item=self.source, proportion=0,
1347  flag=wx.LEFT | wx.RIGHT | wx.EXPAND, border=5)
1348  mainSizer.Add(item=inputSizer, proportion=0,
1349  flag=wx.ALL | wx.EXPAND, border=5)
1350 
1351  self.SetSizer(mainSizer)
1352  mainSizer.Fit(self)
1353 
1354  def _getExtPatternGlob(self, ext):
1355  """!Get pattern for case-insensitive globing"""
1356  pattern = '*.'
1357  for c in ext:
1358  pattern += '[%s%s]' % (c.lower(), c.upper())
1359  return pattern
1360 
1361  def _getExtPattern(self, ext):
1362  """!Get pattern for case-insensitive file mask"""
1363  return '*.%s;*.%s' % (ext.lower(), ext.upper())
1364 
1365  def OnSettingsLoad(self, event):
1366  """!Load named settings"""
1367  name = event.GetString()
1368  if name not in self._settings:
1369  GError(parent = self,
1370  message = _("Settings <%s> not found") % name)
1371  return
1372  data = self._settings[name]
1373  self.OnSetType(event = None, sel = self.sourceMap[data[0]])
1374  self.OnSetFormat(event = None, format = data[2])
1375  self.OnSetDsn(event = None, path = data[1])
1376  self.creationOpt.SetValue(data[3])
1377 
1378  def OnSettingsSave(self, event):
1379  """!Save settings"""
1380  dlg = wx.TextEntryDialog(parent = self,
1381  message = _("Name:"),
1382  caption = _("Save settings"))
1383  if dlg.ShowModal() != wx.ID_OK:
1384  return
1385 
1386  # check required params
1387  if not dlg.GetValue():
1388  GMessage(parent = self,
1389  message = _("Name not given, settings is not saved."))
1390  return
1391 
1392  if not self.GetDsn():
1393  GMessage(parent = self,
1394  message = _("No data source defined, settings is not saved."))
1395  return
1396 
1397  name = dlg.GetValue()
1398 
1399  # check if settings item already exists
1400  if name in self._settings:
1401  dlgOwt = wx.MessageDialog(self, message = _("Settings <%s> already exists. "
1402  "Do you want to overwrite the settings?") % name,
1403  caption = _("Save settings"), style = wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
1404  if dlgOwt.ShowModal() != wx.ID_YES:
1405  dlgOwt.Destroy()
1406  return
1407 
1408  self._settings[name] = (self.dsnType, self.GetDsn(),
1409  self.format.GetStringSelection(),
1410  self.creationOpt.GetValue())
1411 
1412  if self._saveSettings() == 0:
1413  self._settings = self._loadSettings()
1414  self.settingsChoice.SetStringSelection(name)
1415 
1416  dlg.Destroy()
1417 
1418  def OnSettingsDelete(self, event):
1419  """!Save settings"""
1420  name = self.settingsChoice.GetStringSelection()
1421  if not name:
1422  GMessage(parent = self,
1423  message = _("No settings is defined. Operation canceled."))
1424  return
1425 
1426  self._settings.pop(name)
1427  if self._saveSettings() == 0:
1428  self._settings = self._loadSettings()
1429 
1430  def _saveSettings(self):
1431  """!Save settings into the file
1432 
1433  @return 0 on success
1434  @return -1 on failure
1435  """
1436  try:
1437  fd = open(self.settingsFile, 'w')
1438  for key, value in self._settings.iteritems():
1439  fd.write('%s;%s;%s;%s\n' % (key, value[0], value[1], value[2]))
1440  except IOError:
1441  GError(parent = self,
1442  message = _("Unable to save settings"))
1443  return -1
1444  else:
1445  fd.close()
1446 
1447  return 0
1448 
1449  def _loadSettings(self):
1450  """!Load settings from the file
1451 
1452  The file is defined by self.SettingsFile.
1453 
1454  @return parsed dict
1455  @return empty dict on error
1456  """
1457  data = dict()
1458  if not os.path.exists(self.settingsFile):
1459  return data
1460 
1461  try:
1462  fd = open(self.settingsFile, 'r')
1463  for line in fd.readlines():
1464  try:
1465  lineData = line.rstrip('\n').split(';')
1466  if len(lineData) > 4:
1467  # type, dsn, format, options
1468  data[lineData[0]] = (lineData[1], lineData[2], lineData[3], lineData[4])
1469  else:
1470  data[lineData[0]] = (lineData[1], lineData[2], lineData[3], '')
1471  except ValueError:
1472  pass
1473  except IOError:
1474  return data
1475  else:
1476  fd.close()
1477 
1478  self.settingsChoice.SetItems(sorted(data.keys()))
1479 
1480  return data
1481 
1482  def OnSetType(self, event, sel = None):
1483  """!Datasource type changed"""
1484  if event:
1485  sel = event.GetSelection()
1486  else:
1487  self.source.SetSelection(sel)
1488  win = self.input[self.dsnType][1]
1489  if win:
1490  self.dsnSizer.Remove(win)
1491  win.Hide()
1492 
1493  if sel == self.sourceMap['file']: # file
1494  self.dsnType = 'file'
1495  format = self.input[self.dsnType][2][0]
1496  try:
1497  ext = self.format.GetExtension(format)
1498  if not ext:
1499  raise KeyError
1500  format += ' (%s)|%s|%s (*.*)|*.*' % \
1501  (self._getExtPattern(ext), self._getExtPattern(ext), _('All files'))
1502  except KeyError:
1503  format += '%s (*.*)|*.*' % _('All files')
1504 
1505  win = filebrowse.FileBrowseButton(parent=self, id=wx.ID_ANY,
1506  size=globalvar.DIALOG_GSELECT_SIZE, labelText='',
1507  dialogTitle=_('Choose file to import'),
1508  buttonText=_('Browse'),
1509  startDirectory=os.getcwd(),
1510  changeCallback=self.OnSetDsn,
1511  fileMask = format)
1512  self.input[self.dsnType][1] = win
1513 
1514  elif sel == self.sourceMap['dir']: # directory
1515  self.dsnType = 'dir'
1516  elif sel == self.sourceMap['db']: # database
1517  self.dsnType = 'db'
1518  elif sel == self.sourceMap['pro']: # protocol
1519  self.dsnType = 'pro'
1520  elif sel == self.sourceMap['native']:
1521  self.dsnType = 'native'
1522 
1523  if self.dsnType == 'db':
1524  self.input[self.dsnType][1] = self.input['db-win']['text']
1525  win = self.input[self.dsnType][1]
1526 
1527  self.dsnSizer.Add(item = self.input[self.dsnType][1],
1528  flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
1529  pos = (1, 1), span = (1, 3))
1530  win.SetValue('')
1531  win.Show()
1532 
1533  if sel == self.sourceMap['native']: # native
1534  win.Enable(False)
1535  self.format.Enable(False)
1536  self.creationOpt.Enable(False)
1537  self.parent.btnOk.Enable(True)
1538  else:
1539  if not win.IsEnabled():
1540  win.Enable(True)
1541  if not self.format.IsEnabled():
1542  self.format.Enable(True)
1543  self.creationOpt.Enable(True)
1544  self.dsnText.SetLabel(self.input[self.dsnType][0])
1545  self.format.SetItems(self.input[self.dsnType][2])
1546  if self.parent.GetName() == 'MultiImportDialog':
1547  self.parent.list.DeleteAllItems()
1548 
1549  if sel in (self.sourceMap['file'], self.sourceMap['dir']):
1550  if not self.ogr:
1551  self.OnSetFormat(event = None, format = 'GeoTIFF')
1552  else:
1553  self.OnSetFormat(event = None, format = 'ESRI Shapefile')
1554 
1555  if sel == self.sourceMap['dir'] and not self.dest:
1556  if not self.extension.IsShown():
1557  self.extensionText.Show()
1558  self.extension.Show()
1559  else:
1560  if self.extension.IsShown():
1561  self.extensionText.Hide()
1562  self.extension.Hide()
1563 
1564  self.dsnSizer.Layout()
1565 
1566  def GetDsn(self):
1567  """!Get datasource name
1568  """
1569  if self.format.GetStringSelection() == 'PostgreSQL':
1570  dsn = 'PG:dbname=%s' % self.input[self.dsnType][1].GetStringSelection()
1571  else:
1572  dsn = self.input[self.dsnType][1].GetValue()
1573 
1574  return dsn
1575 
1576  def OnSetDsn(self, event, path = None):
1577  """!Input DXF file/OGR dsn defined, update list of layer
1578  widget"""
1579  if event:
1580  path = event.GetString()
1581  else:
1582  if self.format.GetStringSelection() == 'PostgreSQL':
1583  for item in path.split(':', 1)[1].split(','):
1584  key, value = item.split('=', 1)
1585  if key == 'dbname':
1586  if not self.input[self.dsnType][1].SetStringSelection(value):
1587  GMessage(_("Database <%s> not accessible.") % value,
1588  parent = self)
1589  break
1590  else:
1591  self.input[self.dsnType][1].SetValue(path)
1592 
1593  if not path:
1594  if self.dest:
1595  self.parent.btnOk.Enable(False)
1596  return
1597 
1598  if self.dest:
1599  self.parent.btnOk.Enable(True)
1600  else:
1601  self._reloadLayers()
1602 
1603  if event:
1604  event.Skip()
1605 
1606  def _reloadLayers(self):
1607  """!Reload list of layers"""
1608  dsn = self.GetDsn()
1609  if not dsn:
1610  return
1611 
1612  data = list()
1613  layerId = 1
1614 
1615  if self.ogr:
1616  ret = RunCommand('v.in.ogr',
1617  quiet = True,
1618  read = True,
1619  flags = 'l',
1620  dsn = dsn)
1621  if not ret:
1622  self.parent.list.LoadData()
1623  if hasattr(self, "btn_run"):
1624  self.btn_run.Enable(False)
1625  return
1626 
1627  layerId = 1
1628  for line in ret.split(','):
1629  layerName = line.strip()
1630  grassName = GetValidLayerName(layerName)
1631  data.append((layerId, layerName, grassName))
1632  layerId += 1
1633  else:
1634  if self.dsnType == 'file':
1635  baseName = os.path.basename(dsn)
1636  grassName = GetValidLayerName(baseName.split('.', -1)[0])
1637  data.append((layerId, baseName, grassName))
1638  elif self.dsnType == 'dir':
1639  ext = self.extension.GetValue()
1640  for filename in glob.glob(os.path.join(dsn, "%s") % self._getExtPatternGlob(ext)):
1641  baseName = os.path.basename(filename)
1642  grassName = GetValidLayerName(baseName.split('.', -1)[0])
1643  data.append((layerId, baseName, grassName))
1644  layerId += 1
1645  if self.ogr:
1646  dsn += '@OGR'
1647 
1648  evt = wxGdalSelect(dsn = dsn)
1649  evt.SetId(self.input[self.dsnType][1].GetId())
1650  wx.PostEvent(self.parent, evt)
1651 
1652  if self.parent.GetName() == 'MultiImportDialog':
1653  self.parent.list.LoadData(data)
1654  if len(data) > 0:
1655  self.parent.btn_run.Enable(True)
1656  else:
1657  self.parent.btn_run.Enable(False)
1658 
1659  def OnSetExtension(self, event):
1660  """!Extension changed"""
1661  if not self.dest:
1662  # reload layers
1663  self._reloadLayers()
1664 
1665  def OnSetFormat(self, event, format = None):
1666  """!Format changed"""
1667  if self.dsnType not in ['file', 'dir', 'db']:
1668  return
1669 
1670  win = self.input[self.dsnType][1]
1671  self.dsnSizer.Remove(win)
1672 
1673  if self.dsnType == 'file':
1674  win.Destroy()
1675  else: # database
1676  win.Hide()
1677 
1678  if event:
1679  format = event.GetString()
1680  else:
1681  self.format.SetStringSelection(format)
1682 
1683  if self.dsnType == 'file':
1684  try:
1685  ext = self.format.GetExtension(format)
1686  if not ext:
1687  raise KeyError
1688  format += ' (%s)|%s|%s (*.*)|*.*' % \
1689  (self._getExtPattern(ext), self._getExtPattern(ext), _('All files'))
1690  except KeyError:
1691  format += '%s (*.*)|*.*' % _('All files')
1692 
1693  win = filebrowse.FileBrowseButton(parent=self, id=wx.ID_ANY,
1694  size=globalvar.DIALOG_GSELECT_SIZE, labelText='',
1695  dialogTitle=_('Choose file'),
1696  buttonText=_('Browse'),
1697  startDirectory=os.getcwd(),
1698  changeCallback=self.OnSetDsn,
1699  fileMask = format)
1700 
1701  elif self.dsnType == 'dir':
1702  pass
1703 
1704  else: # database
1705  if format == 'SQLite' or format == 'Rasterlite':
1706  win = self.input['db-win']['file']
1707  elif format == 'PostgreSQL' or format == 'PostGIS WKT Raster driver':
1708  if grass.find_program('psql', ['--help']):
1709  win = self.input['db-win']['choice']
1710  if not win.GetItems():
1711  p = grass.Popen(['psql', '-ltA'], stdout = grass.PIPE)
1712  ret = p.communicate()[0]
1713  if ret:
1714  db = list()
1715  for line in ret.splitlines():
1716  sline = line.split('|')
1717  if len(sline) < 2:
1718  continue
1719  dbname = sline[0]
1720  if dbname:
1721  db.append(dbname)
1722  win.SetItems(db)
1723  if self.dest and win.GetStringSelection():
1724  self.parent.btnOk.Enable(True)
1725  else:
1726  win = self.input['db-win']['text']
1727  else:
1728  win = self.input['db-win']['text']
1729 
1730  self.input[self.dsnType][1] = win
1731  if not win.IsShown():
1732  win.Show()
1733  self.dsnSizer.Add(item = self.input[self.dsnType][1],
1734  flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
1735  pos = (1, 1), span = (1, 3))
1736  self.dsnSizer.Layout()
1737 
1738  # update extension
1739  self.extension.SetValue(self.GetFormatExt())
1740 
1741  if not self.dest:
1742  # reload layers
1743  self._reloadLayers()
1744 
1745  def GetType(self):
1746  """!Get source type"""
1747  return self.dsnType
1748 
1749  def GetDsnWin(self):
1750  """!Get list of DSN windows"""
1751  win = list()
1752  for stype in ('file', 'dir', 'pro'):
1753  win.append(self.input[stype][1])
1754  for stype in ('file', 'text', 'choice'):
1755  win.append(self.input['db-win'][stype])
1756 
1757  return win
1758 
1759  def GetFormat(self):
1760  """!Get format as string"""
1761  return self.format.GetStringSelection().replace(' ', '_')
1762 
1763  def GetFormatExt(self):
1764  """!Get format extension"""
1765  return self.format.GetExtension(self.format.GetStringSelection())
1766 
1767  def GetOptions(self):
1768  """!Get creation options"""
1769  if not self.creationOpt.IsShown():
1770  return ''
1771 
1772  return self.creationOpt.GetValue()
1773 
1774 class ProjSelect(wx.ComboBox):
1775  """!Widget for selecting input raster/vector map used by
1776  r.proj/v.proj modules."""
1777  def __init__(self, parent, isRaster, id = wx.ID_ANY, size = globalvar.DIALOG_COMBOBOX_SIZE,
1778  **kwargs):
1779  super(ProjSelect, self).__init__(parent, id, size = size,
1780  style = wx.CB_READONLY, **kwargs)
1781  self.SetName("ProjSelect")
1782  self.isRaster = isRaster
1783 
1784  def UpdateItems(self, dbase, location, mapset):
1785  """!Update list of maps
1786 
1787  """
1788  if not dbase:
1789  dbase = grass.gisenv()['GISDBASE']
1790  if not mapset:
1791  mapset = grass.gisenv()['MAPSET']
1792  if self.isRaster:
1793  ret = RunCommand('r.proj',
1794  quiet = True,
1795  read = True,
1796  flags = 'l',
1797  dbase = dbase,
1798  location = location,
1799  mapset = mapset)
1800  else:
1801  ret = RunCommand('v.proj',
1802  quiet = True,
1803  read = True,
1804  flags = 'l',
1805  dbase = dbase,
1806  location = location,
1807  mapset = mapset)
1808  listMaps = list()
1809  if ret:
1810  for line in ret.splitlines():
1811  listMaps.append(line.strip())
1812  ListSortLower(listMaps)
1813 
1814  self.SetItems(listMaps)
1815  self.SetValue('')
1816 
1817 class ElementSelect(wx.Choice):
1818  def __init__(self, parent, id = wx.ID_ANY, size = globalvar.DIALOG_COMBOBOX_SIZE,
1819  **kwargs):
1820  """!Widget for selecting GIS element
1821 
1822  @param parent parent window
1823  """
1824  super(ElementSelect, self).__init__(parent, id, size = size,
1825  **kwargs)
1826  self.SetName("ElementSelect")
1827 
1828  task = gtask.parse_interface('g.list')
1829  p = task.get_param(value = 'type')
1830  self.values = p.get('values', [])
1831 
1832  self.SetItems(self.values)
1833 
1834  def GetValue(self, name):
1835  """!Translate value
1836 
1837  @param name element name
1838  """
1839  return name