GRASS Programmer's Manual  6.4.2(2012)
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
dbm.py
Go to the documentation of this file.
1 """!
2 @package dbm.py
3 
4 @brief GRASS Attribute Table Manager
5 
6 This program is based on FileHunter, published in 'The wxPython Linux
7 Tutorial' on wxPython WIKI pages.
8 
9 It also uses some functions at
10 http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/426407
11 
12 @code
13 python dbm.py vector@mapset
14 @endcode
15 
16 List of classes:
17  - Log
18  - VirtualAttributeList
19  - AttributeManager
20 
21 (C) 2007-2009, 2011 by the GRASS Development Team
22 
23 This program is free software under the GNU General Public
24 License (>=v2). Read the file COPYING that comes with GRASS
25 for details.
26 
27 @author Jachym Cepicky <jachym.cepicky gmail.com>
28 @author Martin Landa <landa.martin gmail.com>
29 """
30 
31 import sys
32 import os
33 import locale
34 import tempfile
35 import copy
36 import types
37 
38 ### i18N
39 import gettext
40 gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode=True)
41 
42 import globalvar
43 import wx
44 import wx.lib.mixins.listctrl as listmix
45 import wx.lib.flatnotebook as FN
46 
47 import grass.script as grass
48 
49 import sqlbuilder
50 import gcmd
51 import utils
52 import gdialogs
53 import dbm_base
54 from debug import Debug
55 from dbm_dialogs import ModifyTableRecord
56 from preferences import globalSettings as UserSettings
57 
58 class Log:
59  """
60  The log output is redirected to the status bar of the containing frame.
61  """
62  def __init__(self, parent):
63  self.parent = parent
64 
65  def write(self, text_string):
66  """!Update status bar"""
67  self.parent.SetStatusText(text_string.strip())
68 
69 
70 class VirtualAttributeList(wx.ListCtrl,
71  listmix.ListCtrlAutoWidthMixin,
72  listmix.ColumnSorterMixin):
73  """
74  Support virtual list class
75  """
76  def __init__(self, parent, log, mapDBInfo, layer):
77  #
78  # initialize variables
79  #
80  self.parent = parent
81  self.log = log
82  self.mapDBInfo = mapDBInfo
83  self.layer = layer
84 
85  self.columns = {} # <- LoadData()
86 
87  wx.ListCtrl.__init__(self, parent=parent, id=wx.ID_ANY,
88  style=wx.LC_REPORT | wx.LC_HRULES |
89  wx.LC_VRULES | wx.LC_VIRTUAL | wx.LC_SORT_ASCENDING)
90 
91  try:
92  keyColumn = self.LoadData(layer)
93  except gcmd.GException, e:
94  GError(parent = self,
95  message = e.value)
96  return
97 
98  #
99  # add some attributes (colourful background for each item rows)
100  #
101  self.attr1 = wx.ListItemAttr()
102  self.attr1.SetBackgroundColour(wx.Colour(238,238,238))
103  self.attr2 = wx.ListItemAttr()
104  self.attr2.SetBackgroundColour("white")
105  self.il = wx.ImageList(16, 16)
106  self.sm_up = self.il.Add(wx.ArtProvider_GetBitmap(wx.ART_GO_UP, wx.ART_TOOLBAR,
107  (16,16)))
108  self.sm_dn = self.il.Add(wx.ArtProvider_GetBitmap(wx.ART_GO_DOWN, wx.ART_TOOLBAR,
109  (16,16)))
110  self.SetImageList(self.il, wx.IMAGE_LIST_SMALL)
111 
112  # setup mixins
113  listmix.ListCtrlAutoWidthMixin.__init__(self)
114  listmix.ColumnSorterMixin.__init__(self, len(self.columns))
115 
116  # sort item by category (id)
117  if keyColumn > -1:
118  self.SortListItems(col=keyColumn, ascending=True)
119  else:
120  self.SortListItems(col=0, ascending=True)
121 
122  # events
123  self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected)
124  self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnItemDeselected)
125  self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColumnSort)
126  self.Bind(wx.EVT_LIST_COL_RIGHT_CLICK, self.OnColumnMenu)
127 
128  def Update(self, mapDBInfo):
129  """!Update list according new mapDBInfo description"""
130  self.mapDBInfo = mapDBInfo
131  self.LoadData(self.layer)
132 
133  def LoadData(self, layer, columns=None, where=None, sql=None):
134  """!Load data into list
135 
136  @param layer layer number
137  @param columns list of columns for output (-> v.db.select)
138  @param where where statement (-> v.db.select)
139  @param sql full sql statement (-> db.select)
140 
141  @return id of key column
142  @return -1 if key column is not displayed
143  """
144  self.log.write(_("Loading data..."))
145 
146  tableName = self.mapDBInfo.layers[layer]['table']
147  keyColumn = self.mapDBInfo.layers[layer]['key']
148  try:
149  self.columns = self.mapDBInfo.tables[tableName]
150  except KeyError:
151  raise gcmd.GException(_("Attribute table <%s> not found. "
152  "For creating the table switch to "
153  "'Manage layers' tab.") % tableName)
154 
155  if not columns:
156  columns = self.mapDBInfo.GetColumns(tableName)
157  else:
158  all = self.mapDBInfo.GetColumns(tableName)
159  for col in columns:
160  if col not in all:
161  wx.MessageBox(parent=self,
162  message=_("Column <%(column)s> not found in "
163  "in the table <%(table)s>.") % \
164  { 'column' : col, 'table' : tableName },
165  caption=_("Error"), style=wx.OK | wx.ICON_ERROR | wx.CENTRE)
166  return
167 
168  try:
169  # for maps connected via v.external
170  keyId = columns.index(keyColumn)
171  except:
172  keyId = -1
173 
174  #
175  # read data
176  #
177  # FIXME: Max. number of rows, while the GUI is still usable
178 
179  # stdout can be very large, do not use PIPE, redirect to temp file
180  # TODO: more effective way should be implemented...
181  outFile = tempfile.NamedTemporaryFile(mode='w+b')
182 
183  if sql:
184  ret = gcmd.RunCommand('db.select',
185  quiet = True,
186  parent = self,
187  flags = 'c',
188  sql = sql,
189  output = outFile.name)
190  else:
191  if columns:
192  ret = gcmd.RunCommand('v.db.select',
193  quiet = True,
194  flags = 'c',
195  map = self.mapDBInfo.map,
196  layer = layer,
197  columns = ','.join(columns),
198  where = where,
199  stdout=outFile)
200  else:
201  ret = gcmd.RunCommand('v.db.select',
202  quiet = True,
203  flags = 'c',
204  map = self.mapDBInfo.map,
205  layer = layer,
206  where = where,
207  stdout=outFile)
208 
209  # These two should probably be passed to init more cleanly
210  # setting the numbers of items = number of elements in the dictionary
211  self.itemDataMap = {}
212  self.itemIndexMap = []
213  self.itemCatsMap = {}
214 
215  self.DeleteAllItems()
216 
217  # self.ClearAll()
218  for i in range(self.GetColumnCount()):
219  self.DeleteColumn(0)
220 
221  i = 0
222  info = wx.ListItem()
223  info.m_mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE | wx.LIST_MASK_FORMAT
224  info.m_image = -1
225  info.m_format = 0
226  for column in columns:
227  info.m_text = column
228  self.InsertColumnInfo(i, info)
229  i += 1
230 
231  if i >= 256:
232  self.log.write(_("Can display only 256 columns."))
233 
234  i = 0
235  outFile.seek(0)
236 
237  while True:
238  # os.linesep doesn't work here (MSYS)
239  record = outFile.readline().replace('\n', '')
240 
241  if not record:
242  break
243 
244  self.AddDataRow(i, record, columns, keyId)
245 
246  i += 1
247  if i >= 100000:
248  self.log.write(_("Limit 100000 records."))
249  break
250 
251  self.SetItemCount(i)
252 
253  i = 0
254  for col in columns:
255  width = self.columns[col]['length'] * 6 # FIXME
256  if width < 60:
257  width = 60
258  if width > 300:
259  width = 300
260  self.SetColumnWidth(col=i, width=width)
261  i += 1
262 
263  self.SendSizeEvent()
264 
265  self.log.write(_("Number of loaded records: %d") % \
266  self.GetItemCount())
267 
268  return keyId
269 
270  def AddDataRow(self, i, record, columns, keyId):
271  """!Add row to the data list"""
272  self.itemDataMap[i] = []
273  keyColumn = self.mapDBInfo.layers[self.layer]['key']
274  j = 0
275  cat = None
276 
277  if keyColumn == 'OGC_FID':
278  self.itemDataMap[i].append(i+1)
279  j += 1
280  cat = i + 1
281 
282  for value in record.split('|'):
283  if self.columns[columns[j]]['ctype'] != types.StringType:
284  try:
285  ### casting disabled (2009/03)
286  ### self.itemDataMap[i].append(self.columns[columns[j]]['ctype'](value))
287  self.itemDataMap[i].append(value)
288  except ValueError:
289  self.itemDataMap[i].append(_('Unknown value'))
290  else:
291  # encode string values
292  try:
293  self.itemDataMap[i].append(dbm_base.unicodeValue(value))
294  except UnicodeDecodeError:
295  self.itemDataMap[i].append(_("Unable to decode value. "
296  "Set encoding in GUI preferences ('Attributes')."))
297 
298  if not cat and keyId > -1 and keyId == j:
299  try:
300  cat = self.columns[columns[j]]['ctype'] (value)
301  except ValueError, e:
302  cat = -1
303  gcmd.GError(parent = self,
304  message=_("Error loading attribute data. "
305  "Record number: %(rec)d. Unable to convert value '%(val)s' in "
306  "key column (%(key)s) to integer.\n\n"
307  "Details: %(detail)s") % \
308  { 'rec' : i + 1, 'val' : value,
309  'key' : keyColumn, 'detail' : e})
310  j += 1
311 
312  self.itemIndexMap.append(i)
313  if keyId > -1: # load cats only when LoadData() is called first time
314  self.itemCatsMap[i] = cat
315 
316  def OnItemSelected(self, event):
317  """!Item selected. Add item to selected cats..."""
318  # cat = int(self.GetItemText(event.m_itemIndex))
319  # if cat not in self.selectedCats:
320  # self.selectedCats.append(cat)
321  # self.selectedCats.sort()
322 
323  event.Skip()
324 
325  def OnItemDeselected(self, event):
326  """!Item deselected. Remove item from selected cats..."""
327  # cat = int(self.GetItemText(event.m_itemIndex))
328  # if cat in self.selectedCats:
329  # self.selectedCats.remove(cat)
330  # self.selectedCats.sort()
331 
332  event.Skip()
333 
334  def GetSelectedItems(self):
335  """!Return list of selected items (category numbers)"""
336  cats = []
337  item = self.GetFirstSelected()
338  while item != -1:
339  cats.append(self.GetItemText(item))
340  item = self.GetNextSelected(item)
341 
342  return cats
343 
344  def GetColumnText(self, index, col):
345  """!Return column text"""
346  item = self.GetItem(index, col)
347  return item.GetText()
348 
349  def GetListCtrl(self):
350  """!Returt list"""
351  return self
352 
353  def OnGetItemText(self, item, col):
354  """!Get item text"""
355  index = self.itemIndexMap[item]
356  s = self.itemDataMap[index][col]
357  return s
358 
359  def OnGetItemAttr(self, item):
360  """!Get item attributes"""
361  if ( item % 2) == 0:
362  return self.attr2
363  else:
364  return self.attr1
365 
366  def OnColumnMenu(self, event):
367  """!Column heading right mouse button -> pop-up menu"""
368  self._col = event.GetColumn()
369 
370  popupMenu = wx.Menu()
371 
372  if not hasattr (self, "popupID1"):
373  self.popupID1 = wx.NewId()
374  self.popupID2 = wx.NewId()
375  self.popupID3 = wx.NewId()
376  self.popupID4 = wx.NewId()
377  self.popupID5 = wx.NewId()
378  self.popupID6 = wx.NewId()
379  self.popupID7 = wx.NewId()
380  self.popupID8 = wx.NewId()
381  self.popupID9 = wx.NewId()
382  self.popupID10 = wx.NewId()
383  self.popupID11 = wx.NewId()
384  self.popupID12 = wx.NewId()
385 
386  popupMenu.Append(self.popupID1, text=_("Sort ascending"))
387  popupMenu.Append(self.popupID2, text=_("Sort descending"))
388  popupMenu.AppendSeparator()
389  subMenu = wx.Menu()
390  popupMenu.AppendMenu(self.popupID3, _("Calculate (only numeric columns)"),
391  subMenu)
392  if not self.log.parent.editable or \
393  self.columns[self.GetColumn(self._col).GetText()]['ctype'] not in (types.IntType, types.FloatType):
394  popupMenu.Enable(self.popupID3, False)
395 
396  subMenu.Append(self.popupID4, text=_("Area size"))
397  subMenu.Append(self.popupID5, text=_("Line length"))
398  subMenu.Append(self.popupID6, text=_("Compactness of an area"))
399  subMenu.Append(self.popupID7, text=_("Fractal dimension of boundary defining a polygon"))
400  subMenu.Append(self.popupID8, text=_("Perimeter length of an area"))
401  subMenu.Append(self.popupID9, text=_("Number of features for each category"))
402  subMenu.Append(self.popupID10, text=_("Slope steepness of 3D line"))
403  subMenu.Append(self.popupID11, text=_("Line sinuousity"))
404  subMenu.Append(self.popupID12, text=_("Line azimuth"))
405 
406  self.Bind (wx.EVT_MENU, self.OnColumnSortAsc, id=self.popupID1)
407  self.Bind (wx.EVT_MENU, self.OnColumnSortDesc, id=self.popupID2)
408  for id in (self.popupID4, self.popupID5, self.popupID6,
409  self.popupID7, self.popupID8, self.popupID9,
410  self.popupID10, self.popupID11, self.popupID12):
411  self.Bind(wx.EVT_MENU, self.OnColumnCompute, id = id)
412 
413  self.PopupMenu(popupMenu)
414  popupMenu.Destroy()
415 
416  def OnColumnSort(self, event):
417  """!Column heading left mouse button -> sorting"""
418  self._col = event.GetColumn()
419 
420  self.ColumnSort()
421 
422  event.Skip()
423 
424  def OnColumnSortAsc(self, event):
425  """!Sort values of selected column (ascending)"""
426  self.SortListItems(col = self._col, ascending = True)
427  event.Skip()
428 
429  def OnColumnSortDesc(self, event):
430  """!Sort values of selected column (descending)"""
431  self.SortListItems(col = self._col, ascending = False)
432  event.Skip()
433 
434  def OnColumnCompute(self, event):
435  """!Compute values of selected column"""
436  id = event.GetId()
437 
438  option = None
439  if id == self.popupID4:
440  option = 'area'
441  elif id == self.popupID5:
442  option = 'length'
443  elif id == self.popupID6:
444  option = 'compact'
445  elif id == self.popupID7:
446  option = 'fd'
447  elif id == self.popupID8:
448  option = 'perimeter'
449  elif id == self.popupID9:
450  option = 'count'
451  elif id == self.popupID10:
452  option = 'slope'
453  elif id == self.popupID11:
454  option = 'sinuous'
455  elif id == self.popupID12:
456  option = 'azimuth'
457 
458  if not option:
459  return
460 
461  gcmd.RunCommand('v.to.db',
462  parent = self.parent,
463  map = self.mapDBInfo.map,
464  layer = self.layer,
465  option = option,
466  columns = self.GetColumn(self._col).GetText())
467 
468  self.LoadData(self.layer)
469 
470  def ColumnSort(self):
471  """!Sort values of selected column (self._col)"""
472  # remove duplicated arrow symbol from column header
473  # FIXME: should be done automatically
474  info = wx.ListItem()
475  info.m_mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE
476  info.m_image = -1
477  for column in range(self.GetColumnCount()):
478  info.m_text = self.GetColumn(column).GetText()
479  self.SetColumn(column, info)
480 
481  def SortItems(self, sorter=cmp):
482  """!Sort items"""
483  items = list(self.itemDataMap.keys())
484  items.sort(self.Sorter)
485  self.itemIndexMap = items
486 
487  # redraw the list
488  self.Refresh()
489 
490  def Sorter(self, key1, key2):
491  colName = self.GetColumn(self._col).GetText()
492  ascending = self._colSortFlag[self._col]
493  try:
494  item1 = self.columns[colName]["ctype"](self.itemDataMap[key1][self._col])
495  item2 = self.columns[colName]["ctype"](self.itemDataMap[key2][self._col])
496  except ValueError:
497  item1 = self.itemDataMap[key1][self._col]
498  item2 = self.itemDataMap[key2][self._col]
499 
500  if type(item1) == type('') or type(item2) == type(''):
501  cmpVal = locale.strcoll(str(item1), str(item2))
502  else:
503  cmpVal = cmp(item1, item2)
504 
505 
506  # If the items are equal then pick something else to make the sort value unique
507  if cmpVal == 0:
508  cmpVal = apply(cmp, self.GetSecondarySortValues(self._col, key1, key2))
509 
510  if ascending:
511  return cmpVal
512  else:
513  return -cmpVal
514 
515  def GetSortImages(self):
516  """!Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py"""
517  return (self.sm_dn, self.sm_up)
518 
519  def IsEmpty(self):
520  """!Check if list if empty"""
521  if self.columns:
522  return False
523 
524  return True
525 
526 class AttributeManager(wx.Frame):
527  """
528  GRASS Attribute manager main window
529  """
530  def __init__(self, parent, id=wx.ID_ANY,
531  size = wx.DefaultSize, style = wx.DEFAULT_FRAME_STYLE,
532  title=None, vectorName=None, item=None, log=None, selection = 0):
533 
534  self.vectorName = vectorName
535  self.parent = parent # GMFrame
536  self.treeItem = item # item in layer tree
537  if self.parent and self.parent.GetName() == "LayerManager" and \
538  self.treeItem and not self.vectorName:
539  maptree = self.parent.curr_page.maptree
540  name = maptree.GetPyData(self.treeItem)[0]['maplayer'].GetName()
541  self.vectorName = name
542 
543  # vector attributes can be changed only if vector map is in
544  # the current mapset
545  if grass.find_file(name = self.vectorName, element = 'vector')['mapset'] == grass.gisenv()['MAPSET']:
546  self.editable = True
547  else:
548  self.editable = False
549 
550  # FIXME: editing is currently broken on wingrass (bug #1270)
551  if sys.platform == 'win32':
552  self.editable = False
553 
554  self.cmdLog = log # self.parent.goutput
555 
556  wx.Frame.__init__(self, parent, id, style=style)
557 
558  # title
559  if not title:
560  self.SetTitle("%s - <%s>" % (_("GRASS GIS Attribute Table Manager"),
561  self.vectorName))
562  else:
563  self.SetTitle(title)
564 
565  # icon
566  self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass_sql.ico'), wx.BITMAP_TYPE_ICO))
567 
568  self.panel = wx.Panel(parent=self, id=wx.ID_ANY)
569 
570  try:
571  self.map = self.parent.curr_page.maptree.Map
572  self.mapdisplay = self.parent.curr_page.maptree.mapdisplay
573  except:
574  self.map = self.mapdisplay = None
575 
576  # status bar log class
577  self.log = Log(self) # -> statusbar
578 
579  # query map layer (if parent (GMFrame) is given)
580  self.qlayer = None
581 
582  # -> layers / tables description
584 
585  # sqlbuilder
586  self.builder = None
587 
588  if len(self.mapDBInfo.layers.keys()) == 0:
589  wx.MessageBox(parent=self.parent,
590  message=_("Database connection for vector map <%s> "
591  "is not defined in DB file. "
592  "You can define new connection in "
593  "'Manage layers' tab.") % self.vectorName,
594  caption=_("Attribute Table Manager"),
595  style=wx.OK | wx.ICON_INFORMATION | wx.CENTRE)
596 
597  #
598  # list of command/SQL statements to be performed
599  #
600  self.listOfCommands = []
602 
603  self.CreateStatusBar(number=1)
604 
605  # set up virtual lists (each layer)
606  ### {layer: list, widgets...}
607  self.layerPage = {}
608 
609  # auinotebook (browse, manage, settings)
610  #self.notebook = wx.aui.AuiNotebook(parent=self, id=wx.ID_ANY,
611  # style=wx.aui.AUI_NB_BOTTOM)
612  # really needed (ML)
613  # self.notebook.SetFont(wx.Font(10, wx.FONTFAMILY_MODERN, wx.NORMAL, wx.NORMAL, 0, ''))
614 
615  if globalvar.hasAgw:
616  self.notebook = FN.FlatNotebook(parent = self.panel, id = wx.ID_ANY,
617  agwStyle = FN.FNB_BOTTOM |
618  FN.FNB_NO_NAV_BUTTONS |
619  FN.FNB_FANCY_TABS | FN.FNB_NO_X_BUTTON)
620  else:
621  self.notebook = FN.FlatNotebook(parent = self.panel, id = wx.ID_ANY,
622  style = FN.FNB_BOTTOM |
623  FN.FNB_NO_NAV_BUTTONS |
624  FN.FNB_FANCY_TABS | FN.FNB_NO_X_BUTTON)
625 
626  if globalvar.hasAgw:
627  dbmStyle = { 'agwStyle' : globalvar.FNPageStyle }
628  else:
629  dbmStyle = { 'style' : globalvar.FNPageStyle }
630 
631  self.browsePage = FN.FlatNotebook(self.panel, id = wx.ID_ANY,
632  **dbmStyle)
633  # self.notebook.AddPage(self.browsePage, caption=_("Browse data"))
634  self.notebook.AddPage(self.browsePage, text=_("Browse data")) # FN
635  self.browsePage.SetTabAreaColour(globalvar.FNPageColor)
636 
637  self.manageTablePage = FN.FlatNotebook(self.panel, id = wx.ID_ANY,
638  **dbmStyle)
639  #self.notebook.AddPage(self.manageTablePage, caption=_("Manage tables"))
640  self.notebook.AddPage(self.manageTablePage, text=_("Manage tables")) # FN
641  if not self.editable:
642  self.notebook.GetPage(self.notebook.GetPageCount()-1).Enable(False)
643  self.manageTablePage.SetTabAreaColour(globalvar.FNPageColor)
644 
645  self.manageLayerPage = FN.FlatNotebook(self.panel, id = wx.ID_ANY,
646  **dbmStyle)
647  #self.notebook.AddPage(self.manageLayerPage, caption=_("Manage layers"))
648  self.notebook.AddPage(self.manageLayerPage, text=_("Manage layers")) # FN
649  self.manageLayerPage.SetTabAreaColour(globalvar.FNPageColor)
650  if not self.editable:
651  self.notebook.GetPage(self.notebook.GetPageCount()-1).Enable(False)
652 
653  self.__createBrowsePage()
656  wx.CallAfter(self.notebook.SetSelection, selection)
657 
658  #
659  # buttons
660  #
661  self.btnQuit = wx.Button(parent=self.panel, id=wx.ID_EXIT)
662  self.btnQuit.SetToolTipString(_("Close Attribute Table Manager"))
663  self.btnReload = wx.Button(parent=self.panel, id=wx.ID_REFRESH)
664  self.btnReload.SetToolTipString(_("Reload attribute data (selected layer only)"))
665 
666  # events
667  self.btnQuit.Bind(wx.EVT_BUTTON, self.OnCloseWindow)
668  self.btnReload.Bind(wx.EVT_BUTTON, self.OnDataReload)
669  self.notebook.Bind(FN.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.OnPageChanged)
670  self.Bind(FN.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.OnLayerPageChanged, self.browsePage)
671  self.Bind(FN.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.OnLayerPageChanged, self.manageTablePage)
672 
673  # do layout
674  self.__layout()
675 
676  # self.SetMinSize(self.GetBestSize())
677  self.SetSize((680, 550)) # FIXME hard-coded size
678  self.SetMinSize(self.GetSize())
679 
680  def __createBrowsePage(self, onlyLayer=-1):
681  """!Create browse tab page"""
682  for layer in self.mapDBInfo.layers.keys():
683  if onlyLayer > 0 and layer != onlyLayer:
684  continue
685 
686  panel = wx.Panel(parent=self.browsePage, id=wx.ID_ANY)
687 
688  #IMPORTANT NOTE: wx.StaticBox MUST be defined BEFORE any of the
689  # controls that are placed IN the wx.StaticBox, or it will freeze
690  # on the Mac
691 
692  listBox = wx.StaticBox(parent=panel, id=wx.ID_ANY,
693  label=" %s " % _("Attribute data - right-click to edit/manage records"))
694  listSizer = wx.StaticBoxSizer(listBox, wx.VERTICAL)
695 
696  win = VirtualAttributeList(panel, self.log,
697  self.mapDBInfo, layer)
698  if win.IsEmpty():
699  del panel
700  continue
701 
702  win.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnDataItemActivated)
703 
704  self.layerPage[layer] = {'browsePage': panel.GetId()}
705 
706  self.browsePage.AddPage(page=panel, text=" %d / %s %s" % \
707  (layer, _("Table"), self.mapDBInfo.layers[layer]['table']))
708 
709  pageSizer = wx.BoxSizer(wx.VERTICAL)
710 
711  # attribute data
712  sqlBox = wx.StaticBox(parent=panel, id=wx.ID_ANY,
713  label=" %s " % _("SQL Query"))
714 
715  sqlSizer = wx.StaticBoxSizer(sqlBox, wx.VERTICAL)
716 
717  win.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnDataRightUp) #wxMSW
718  win.Bind(wx.EVT_RIGHT_UP, self.OnDataRightUp) #wxGTK
719  if UserSettings.Get(group='atm', key='leftDbClick', subkey='selection') == 0:
720  win.Bind(wx.EVT_LEFT_DCLICK, self.OnDataItemEdit)
721  win.Bind(wx.EVT_COMMAND_LEFT_DCLICK, self.OnDataItemEdit)
722  else:
723  win.Bind(wx.EVT_LEFT_DCLICK, self.OnDataDrawSelected)
724  win.Bind(wx.EVT_COMMAND_LEFT_DCLICK, self.OnDataDrawSelected)
725 
726 
727  listSizer.Add(item=win, proportion=1,
728  flag=wx.EXPAND | wx.ALL,
729  border=3)
730 
731  # sql statement box
732  btnApply = wx.Button(parent=panel, id=wx.ID_APPLY)
733  btnApply.SetToolTipString(_("Apply SELECT statement and reload data records"))
734  btnApply.Bind(wx.EVT_BUTTON, self.OnApplySqlStatement)
735  btnSqlBuilder = wx.Button(parent=panel, id=wx.ID_ANY, label=_("SQL Builder"))
736  btnSqlBuilder.Bind(wx.EVT_BUTTON, self.OnBuilder)
737 
738  sqlSimple = wx.RadioButton(parent=panel, id=wx.ID_ANY,
739  label=_("Simple"))
740  sqlSimple.SetValue(True)
741  sqlAdvanced = wx.RadioButton(parent=panel, id=wx.ID_ANY,
742  label=_("Advanced"))
743  sqlSimple.Bind(wx.EVT_RADIOBUTTON, self.OnChangeSql)
744  sqlAdvanced.Bind(wx.EVT_RADIOBUTTON, self.OnChangeSql)
745 
746  sqlWhereColumn = wx.Choice(parent=panel, id=wx.ID_ANY,
747  size=(100,-1),
748  choices=self.mapDBInfo.GetColumns(self.mapDBInfo.layers[layer]['table']))
749  sqlWhereValue = wx.TextCtrl(parent=panel, id=wx.ID_ANY, value="",
750  style=wx.TE_PROCESS_ENTER)
751  sqlWhereValue.SetToolTipString(_("Example: %s") % "MULTILANE = 'no' AND OBJECTID < 10")
752 
753  sqlStatement = wx.TextCtrl(parent=panel, id=wx.ID_ANY,
754  value="SELECT * FROM %s" % \
755  self.mapDBInfo.layers[layer]['table'],
756  style=wx.TE_PROCESS_ENTER)
757  sqlStatement.SetToolTipString(_("Example: %s") % "SELECT * FROM roadsmajor WHERE MULTILANE = 'no' AND OBJECTID < 10")
758  sqlWhereValue.Bind(wx.EVT_TEXT_ENTER, self.OnApplySqlStatement)
759  sqlStatement.Bind(wx.EVT_TEXT_ENTER, self.OnApplySqlStatement)
760 
761  sqlLabel = wx.StaticText(parent=panel, id=wx.ID_ANY,
762  label="SELECT * FROM %s WHERE " % \
763  self.mapDBInfo.layers[layer]['table'])
764  label_query = wx.StaticText(parent=panel, id=wx.ID_ANY,
765  label="")
766 
767  sqlFlexSizer = wx.FlexGridSizer (cols=3, hgap=5, vgap=5)
768  sqlFlexSizer.AddGrowableCol(1)
769 
770  sqlFlexSizer.Add(item=sqlSimple,
771  flag=wx.ALIGN_CENTER_VERTICAL)
772  sqlSimpleSizer = wx.BoxSizer(wx.HORIZONTAL)
773  sqlSimpleSizer.Add(item=sqlLabel,
774  flag=wx.ALIGN_CENTER_VERTICAL)
775  sqlSimpleSizer.Add(item=sqlWhereColumn,
776  flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL)
777  sqlSimpleSizer.Add(item=sqlWhereValue, proportion=1,
778  flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL)
779  sqlFlexSizer.Add(item=sqlSimpleSizer,
780  flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND)
781  sqlFlexSizer.Add(item=btnApply,
782  flag=wx.ALIGN_RIGHT)
783  sqlFlexSizer.Add(item=sqlAdvanced,
784  flag=wx.ALIGN_CENTER_VERTICAL)
785  sqlFlexSizer.Add(item=sqlStatement,
786  flag=wx.EXPAND)
787  sqlFlexSizer.Add(item=btnSqlBuilder,
788  flag=wx.ALIGN_RIGHT)
789 
790  sqlSizer.Add(item=sqlFlexSizer,
791  flag=wx.ALL | wx.EXPAND,
792  border=3)
793 
794  pageSizer.Add(item=listSizer,
795  proportion=1,
796  flag=wx.ALL | wx.EXPAND,
797  border=5)
798 
799  pageSizer.Add(item=sqlSizer,
800  proportion=0,
801  flag=wx.BOTTOM | wx.LEFT | wx.RIGHT | wx.EXPAND,
802  border=5)
803 
804  panel.SetSizer(pageSizer)
805 
806  self.layerPage[layer]['data'] = win.GetId()
807  self.layerPage[layer]['simple'] = sqlSimple.GetId()
808  self.layerPage[layer]['advanced'] = sqlAdvanced.GetId()
809  self.layerPage[layer]['whereColumn'] = sqlWhereColumn.GetId()
810  self.layerPage[layer]['where'] = sqlWhereValue.GetId()
811  self.layerPage[layer]['builder'] = btnSqlBuilder.GetId()
812  self.layerPage[layer]['statement'] = sqlStatement.GetId()
813 
814 
815  self.browsePage.SetSelection(0) # select first layer
816  try:
817  self.layer = self.mapDBInfo.layers.keys()[0]
818  self.OnChangeSql(None)
819  self.log.write(_("Number of loaded records: %d") % \
820  self.FindWindowById(self.layerPage[self.layer]['data']).GetItemCount())
821  except (IndexError, KeyError):
822  self.layer = None
823 
824  def __createManageTablePage(self, onlyLayer=-1):
825  """!Create manage page (create/link and alter tables)"""
826  for layer in self.mapDBInfo.layers.keys():
827  if onlyLayer > 0 and layer != onlyLayer:
828  continue
829 
830  if not layer in self.layerPage:
831  continue
832 
833  panel = wx.Panel(parent=self.manageTablePage, id=wx.ID_ANY)
834  self.layerPage[layer]['tablePage'] = panel.GetId()
835  self.manageTablePage.AddPage(page=panel,
836  text=" %d / %s %s" % (layer,
837  _("Table"),
838  self.mapDBInfo.layers[layer]['table']))
839 
840  pageSizer = wx.BoxSizer(wx.VERTICAL)
841 
842  #
843  # dbInfo
844  #
845  dbBox = wx.StaticBox(parent=panel, id=wx.ID_ANY,
846  label=" %s " % _("Database connection"))
847  dbSizer = wx.StaticBoxSizer(dbBox, wx.VERTICAL)
848  dbSizer.Add(item=dbm_base.createDbInfoDesc(panel, self.mapDBInfo, layer),
849  proportion=1,
850  flag=wx.EXPAND | wx.ALL,
851  border=3)
852 
853  #
854  # table description
855  #
856  table = self.mapDBInfo.layers[layer]['table']
857  tableBox = wx.StaticBox(parent=panel, id=wx.ID_ANY,
858  label=" %s " % _("Table <%s> - right-click to delete column(s)") % table)
859 
860  tableSizer = wx.StaticBoxSizer(tableBox, wx.VERTICAL)
861 
862  list = self.__createTableDesc(panel, table)
863  list.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnTableRightUp) #wxMSW
864  list.Bind(wx.EVT_RIGHT_UP, self.OnTableRightUp) #wxGTK
865  self.layerPage[layer]['tableData'] = list.GetId()
866 
867  # manage columns (add)
868  addBox = wx.StaticBox(parent = panel, id = wx.ID_ANY,
869  label = " %s " % _("Add column"))
870  addSizer = wx.StaticBoxSizer(addBox, wx.HORIZONTAL)
871 
872  column = wx.TextCtrl(parent = panel, id = wx.ID_ANY, value = '',
873  size=(150, -1), style = wx.TE_PROCESS_ENTER)
874  column.Bind(wx.EVT_TEXT, self.OnTableAddColumnName)
875  column.Bind(wx.EVT_TEXT_ENTER, self.OnTableItemAdd)
876  self.layerPage[layer]['addColName'] = column.GetId()
877  addSizer.Add(item= wx.StaticText(parent = panel, id = wx.ID_ANY, label=_("Column")),
878  flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
879  border = 5)
880  addSizer.Add(item = column, proportion = 1,
881  flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
882  border = 5)
883 
884  ctype = wx.Choice (parent=panel, id=wx.ID_ANY,
885  choices = ["integer",
886  "double",
887  "varchar",
888  "date"]) # FIXME
889  ctype.SetSelection(0)
890  ctype.Bind(wx.EVT_CHOICE, self.OnTableChangeType)
891  self.layerPage[layer]['addColType'] = ctype.GetId()
892  addSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY, label=_("Type")),
893  flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
894  border = 5)
895  addSizer.Add(item = ctype,
896  flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
897  border = 5)
898 
899  length = wx.SpinCtrl(parent = panel, id = wx.ID_ANY, size = (65, -1),
900  initial = 250,
901  min = 1, max = 1e6)
902  length.Enable(False)
903  self.layerPage[layer]['addColLength'] = length.GetId()
904  addSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY, label = _("Length")),
905  flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
906  border = 5)
907  addSizer.Add(item = length,
908  flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
909  border = 5)
910 
911  btnAddCol = wx.Button(parent = panel, id = wx.ID_ADD)
912  btnAddCol.Bind(wx.EVT_BUTTON, self.OnTableItemAdd)
913  btnAddCol.Enable(False)
914  self.layerPage[layer]['addColButton'] = btnAddCol.GetId()
915  addSizer.Add(item = btnAddCol, flag = wx.ALL | wx.ALIGN_RIGHT | wx.EXPAND,
916  border = 3)
917 
918  # manage columns (rename)
919  renameBox = wx.StaticBox(parent = panel, id = wx.ID_ANY,
920  label = " %s " % _("Rename column"))
921  renameSizer = wx.StaticBoxSizer(renameBox, wx.HORIZONTAL)
922 
923  column = wx.ComboBox(parent = panel, id = wx.ID_ANY, size = (150, -1),
924  style = wx.CB_SIMPLE | wx.CB_READONLY,
925  choices = self.mapDBInfo.GetColumns(table))
926  column.SetSelection(0)
927  self.layerPage[layer]['renameCol'] = column.GetId()
928  renameSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY, label = _("Column")),
929  flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
930  border = 5)
931  renameSizer.Add(item = column, proportion = 1,
932  flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
933  border = 5)
934 
935  columnTo = wx.TextCtrl(parent = panel, id = wx.ID_ANY, value = '',
936  size = (150, -1), style = wx.TE_PROCESS_ENTER)
937  columnTo.Bind(wx.EVT_TEXT, self.OnTableRenameColumnName)
938  columnTo.Bind(wx.EVT_TEXT_ENTER, self.OnTableItemChange)
939  self.layerPage[layer]['renameColTo'] = columnTo.GetId()
940  renameSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY, label = _("To")),
941  flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
942  border = 5)
943  renameSizer.Add(item = columnTo, proportion = 1,
944  flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
945  border = 5)
946 
947  btnRenameCol = wx.Button(parent = panel, id = wx.ID_ANY, label = _("&Rename"))
948  btnRenameCol.Bind(wx.EVT_BUTTON, self.OnTableItemChange)
949  btnRenameCol.Enable(False)
950  self.layerPage[layer]['renameColButton'] = btnRenameCol.GetId()
951  renameSizer.Add(item = btnRenameCol, flag = wx.ALL | wx.ALIGN_RIGHT | wx.EXPAND,
952  border = 3)
953 
954  tableSizer.Add(item = list,
955  flag = wx.ALL | wx.EXPAND,
956  proportion = 1,
957  border = 3)
958 
959  pageSizer.Add(item=dbSizer,
960  flag = wx.ALL | wx.EXPAND,
961  proportion = 0,
962  border = 3)
963 
964  pageSizer.Add(item = tableSizer,
965  flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND,
966  proportion = 1,
967  border = 3)
968 
969  pageSizer.Add(item = addSizer,
970  flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND,
971  proportion = 0,
972  border = 3)
973  pageSizer.Add(item = renameSizer,
974  flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND,
975  proportion = 0,
976  border = 3)
977 
978  panel.SetSizer(pageSizer)
979 
980  self.manageTablePage.SetSelection(0) # select first layer
981  try:
982  self.layer = self.mapDBInfo.layers.keys()[0]
983  except IndexError:
984  self.layer = None
985 
986  def __createTableDesc(self, parent, table):
987  """!Create list with table description"""
988  list = TableListCtrl(parent=parent, id=wx.ID_ANY,
989  table=self.mapDBInfo.tables[table],
990  columns=self.mapDBInfo.GetColumns(table))
991  list.Populate()
992  # sorter
993  # itemDataMap = list.Populate()
994  # listmix.ColumnSorterMixin.__init__(self, 2)
995 
996  return list
997 
998  def __createManageLayerPage(self):
999  """!Create manage page"""
1000  splitterWin = wx.SplitterWindow(parent=self.manageLayerPage, id=wx.ID_ANY)
1001  splitterWin.SetMinimumPaneSize(100)
1002 
1003  self.manageLayerPage.AddPage(page=splitterWin,
1004  text=_("Layers of vector map")) # dummy page
1005 
1006  #
1007  # list of layers
1008  #
1009  panelList = wx.Panel(parent=splitterWin, id=wx.ID_ANY)
1010 
1011  panelListSizer = wx.BoxSizer(wx.VERTICAL)
1012  layerBox = wx.StaticBox(parent=panelList, id=wx.ID_ANY,
1013  label=" %s " % _("List of layers"))
1014  layerSizer = wx.StaticBoxSizer(layerBox, wx.VERTICAL)
1015 
1016  self.layerList = self.__createLayerDesc(panelList)
1017  self.layerList.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnLayerRightUp) #wxMSW
1018  self.layerList.Bind(wx.EVT_RIGHT_UP, self.OnLayerRightUp) #wxGTK
1019 
1020  layerSizer.Add(item=self.layerList,
1021  flag=wx.ALL | wx.EXPAND,
1022  proportion=1,
1023  border=3)
1024 
1025  panelListSizer.Add(item=layerSizer,
1026  flag=wx.ALL | wx.EXPAND,
1027  proportion=1,
1028  border=3)
1029 
1030  panelList.SetSizer(panelListSizer)
1031 
1032  #
1033  # manage part
1034  #
1035  panelManage = wx.Panel(parent=splitterWin, id=wx.ID_ANY)
1036 
1037  manageSizer = wx.BoxSizer(wx.VERTICAL)
1038 
1039  self.manageLayerBook = LayerBook(parent=panelManage, id=wx.ID_ANY,
1040  parentDialog=self)
1041 
1042  manageSizer.Add(item=self.manageLayerBook,
1043  proportion=1,
1044  flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND,
1045  border=5)
1046 
1047  panelManage.SetSizer(manageSizer)
1048  splitterWin.SplitHorizontally(panelList, panelManage, 100)
1049  splitterWin.Fit()
1050 
1051  def __createLayerDesc(self, parent):
1052  """!Create list of linked layers"""
1053  list = LayerListCtrl(parent=parent, id=wx.ID_ANY,
1054  layers=self.mapDBInfo.layers)
1055 
1056  list.Populate()
1057  # sorter
1058  # itemDataMap = list.Populate()
1059  # listmix.ColumnSorterMixin.__init__(self, 2)
1060 
1061  return list
1062 
1063  def __layout(self):
1064  """!Do layout"""
1065  # frame body
1066  mainSizer = wx.BoxSizer(wx.VERTICAL)
1067 
1068  # buttons
1069  btnSizer = wx.BoxSizer(wx.HORIZONTAL)
1070  btnSizer.Add(item=self.btnReload, proportion=1,
1071  flag=wx.ALL | wx.ALIGN_RIGHT, border=5)
1072  btnSizer.Add(item=self.btnQuit, proportion=1,
1073  flag=wx.ALL | wx.ALIGN_RIGHT, border=5)
1074 
1075  mainSizer.Add(item=self.notebook, proportion=1, flag=wx.EXPAND)
1076  mainSizer.Add(item=btnSizer, flag=wx.ALIGN_RIGHT | wx.ALL, border=5)
1077 
1078  self.panel.SetAutoLayout(True)
1079  self.panel.SetSizer(mainSizer)
1080  mainSizer.Fit(self.panel)
1081  self.Layout()
1082 
1083  def OnDataRightUp(self, event):
1084  """!Table description area, context menu"""
1085  if not hasattr(self, "popupDataID1"):
1086  self.popupDataID1 = wx.NewId()
1087  self.popupDataID2 = wx.NewId()
1088  self.popupDataID3 = wx.NewId()
1089  self.popupDataID4 = wx.NewId()
1090  self.popupDataID5 = wx.NewId()
1091  self.popupDataID6 = wx.NewId()
1092  self.popupDataID7 = wx.NewId()
1093  self.popupDataID8 = wx.NewId()
1094  self.popupDataID9 = wx.NewId()
1095  self.popupDataID10 = wx.NewId()
1096  self.popupDataID11 = wx.NewId()
1097 
1098  self.Bind(wx.EVT_MENU, self.OnDataItemEdit, id=self.popupDataID1)
1099  self.Bind(wx.EVT_MENU, self.OnDataItemAdd, id=self.popupDataID2)
1100  self.Bind(wx.EVT_MENU, self.OnDataItemDelete, id=self.popupDataID3)
1101  self.Bind(wx.EVT_MENU, self.OnDataItemDeleteAll, id=self.popupDataID4)
1102  self.Bind(wx.EVT_MENU, self.OnDataSelectAll, id=self.popupDataID5)
1103  self.Bind(wx.EVT_MENU, self.OnDataSelectNone, id=self.popupDataID6)
1104  self.Bind(wx.EVT_MENU, self.OnDataDrawSelected, id=self.popupDataID7)
1105  self.Bind(wx.EVT_MENU, self.OnDataDrawSelectedZoom, id=self.popupDataID8)
1106  self.Bind(wx.EVT_MENU, self.OnExtractSelected, id=self.popupDataID9)
1107  self.Bind(wx.EVT_MENU, self.OnDeleteSelected, id=self.popupDataID11)
1108  self.Bind(wx.EVT_MENU, self.OnDataReload, id=self.popupDataID10)
1109 
1110  list = self.FindWindowById(self.layerPage[self.layer]['data'])
1111  # generate popup-menu
1112  menu = wx.Menu()
1113  menu.Append(self.popupDataID1, _("Edit selected record"))
1114  selected = list.GetFirstSelected()
1115  if not self.editable or selected == -1 or list.GetNextSelected(selected) != -1:
1116  menu.Enable(self.popupDataID1, False)
1117  menu.Append(self.popupDataID2, _("Insert new record"))
1118  menu.Append(self.popupDataID3, _("Delete selected record(s)"))
1119  menu.Append(self.popupDataID4, _("Delete all records"))
1120  if not self.editable:
1121  menu.Enable(self.popupDataID2, False)
1122  menu.Enable(self.popupDataID3, False)
1123  menu.Enable(self.popupDataID4, False)
1124  menu.AppendSeparator()
1125  menu.Append(self.popupDataID5, _("Select all"))
1126  menu.Append(self.popupDataID6, _("Deselect all"))
1127  menu.AppendSeparator()
1128  menu.Append(self.popupDataID7, _("Highlight selected features"))
1129  menu.Append(self.popupDataID8, _("Highlight selected features and zoom"))
1130  if not self.map or len(list.GetSelectedItems()) == 0:
1131  menu.Enable(self.popupDataID7, False)
1132  menu.Enable(self.popupDataID8, False)
1133  menu.Append(self.popupDataID9, _("Extract selected features"))
1134  menu.Append(self.popupDataID11, _("Delete selected features"))
1135  if not self.editable:
1136  menu.Enable(self.popupDataID11, False)
1137  if list.GetFirstSelected() == -1:
1138  menu.Enable(self.popupDataID3, False)
1139  menu.Enable(self.popupDataID9, False)
1140  menu.Enable(self.popupDataID11, False)
1141  menu.AppendSeparator()
1142  menu.Append(self.popupDataID10, _("Reload"))
1143 
1144  self.PopupMenu(menu)
1145  menu.Destroy()
1146 
1147  # update statusbar
1148  self.log.write(_("Number of loaded records: %d") % \
1149  list.GetItemCount())
1150 
1151  def OnDataItemDelete(self, event):
1152  """!Delete selected item(s) from the list (layer/category pair)"""
1153  list = self.FindWindowById(self.layerPage[self.layer]['data'])
1154  item = list.GetFirstSelected()
1155 
1156  table = self.mapDBInfo.layers[self.layer]["table"]
1157  key = self.mapDBInfo.layers[self.layer]["key"]
1158 
1159  indeces = []
1160  # collect SQL statements
1161  while item != -1:
1162  index = list.itemIndexMap[item]
1163  indeces.append(index)
1164 
1165  cat = list.itemCatsMap[index]
1166 
1167  self.listOfSQLStatements.append('DELETE FROM %s WHERE %s=%d' % \
1168  (table, key, cat))
1169 
1170  item = list.GetNextSelected(item)
1171 
1172  if UserSettings.Get(group='atm', key='askOnDeleteRec', subkey='enabled'):
1173  deleteDialog = wx.MessageBox(parent=self,
1174  message=_("Selected data records (%d) will be permanently deleted "
1175  "from table. Do you want to delete them?") % \
1176  (len(self.listOfSQLStatements)),
1177  caption=_("Delete records"),
1178  style=wx.YES_NO | wx.CENTRE)
1179  if deleteDialog != wx.YES:
1180  self.listOfSQLStatements = []
1181  return False
1182 
1183  # restore maps
1184  i = 0
1185  indexTemp = copy.copy(list.itemIndexMap)
1186  list.itemIndexMap = []
1187  dataTemp = copy.deepcopy(list.itemDataMap)
1188  list.itemDataMap = {}
1189  catsTemp = copy.deepcopy(list.itemCatsMap)
1190  list.itemCatsMap = {}
1191 
1192  i = 0
1193  for index in indexTemp:
1194  if index in indeces:
1195  continue
1196  list.itemIndexMap.append(i)
1197  list.itemDataMap[i] = dataTemp[index]
1198  list.itemCatsMap[i] = catsTemp[index]
1199 
1200  i += 1
1201 
1202  list.SetItemCount(len(list.itemIndexMap))
1203 
1204  # deselect items
1205  item = list.GetFirstSelected()
1206  while item != -1:
1207  list.SetItemState(item, 0, wx.LIST_STATE_SELECTED | wx.LIST_STATE_FOCUSED)
1208  item = list.GetNextSelected(item)
1209 
1210  # submit SQL statements
1211  self.ApplyCommands()
1212 
1213  return True
1214 
1215  def OnDataItemDeleteAll(self, event):
1216  """!Delete all items from the list"""
1217  list = self.FindWindowById(self.layerPage[self.layer]['data'])
1218  if UserSettings.Get(group='atm', key='askOnDeleteRec', subkey='enabled'):
1219  deleteDialog = wx.MessageBox(parent=self,
1220  message=_("All data records (%d) will be permanently deleted "
1221  "from table. Do you want to delete them?") % \
1222  (len(list.itemIndexMap)),
1223  caption=_("Delete records"),
1224  style=wx.YES_NO | wx.CENTRE)
1225  if deleteDialog != wx.YES:
1226  return
1227 
1228  list.DeleteAllItems()
1229  list.itemDataMap = {}
1230  list.itemIndexMap = []
1231  list.SetItemCount(0)
1232 
1233  table = self.mapDBInfo.layers[self.layer]["table"]
1234  self.listOfSQLStatements.append('DELETE FROM %s' % table)
1235 
1236  self.ApplyCommands()
1237 
1238  event.Skip()
1239 
1240  def _drawSelected(self, zoom):
1241  """!Highlight selected features"""
1242  if not self.map or not self.mapdisplay:
1243  return
1244 
1245  list = self.FindWindowById(self.layerPage[self.layer]['data'])
1246  cats = map(int, list.GetSelectedItems())
1247 
1248  digitToolbar = self.mapdisplay.toolbars['vdigit']
1249  if digitToolbar and digitToolbar.GetLayer() and \
1250  digitToolbar.GetLayer().GetName() == self.vectorName:
1251 
1252  self.mapdisplay.digit.driver.SetSelected(cats, field=self.layer)
1253  if zoom:
1254  n, s, w, e = self.mapdisplay.digit.driver.GetRegionSelected()
1255  self.mapdisplay.Map.GetRegion(n=n, s=s, w=w, e=e,
1256  update=True)
1257  else:
1258  # add map layer with higlighted vector features
1259  self.AddQueryMapLayer() # -> self.qlayer
1260 
1261  # set opacity based on queried layer
1262  if self.parent and self.parent.GetName() == "LayerManager" and \
1263  self.treeItem:
1264  maptree = self.parent.curr_page.maptree
1265  opacity = maptree.GetPyData(self.treeItem)[0]['maplayer'].GetOpacity(float=True)
1266  self.qlayer.SetOpacity(opacity)
1267  if zoom:
1268  keyColumn = self.mapDBInfo.layers[self.layer]['key']
1269  where = ''
1270  for range in utils.ListOfCatsToRange(cats).split(','):
1271  if '-' in range:
1272  min, max = range.split('-')
1273  where += '%s >= %d and %s <= %d or ' % \
1274  (keyColumn, int(min),
1275  keyColumn, int(max))
1276  else:
1277  where += '%s = %d or ' % (keyColumn, int(range))
1278  where = where.rstrip('or ')
1279 
1280  select = gcmd.RunCommand('v.db.select',
1281  parent = self,
1282  read = True,
1283  quiet = True,
1284  flags = 'r',
1285  map = self.mapDBInfo.map,
1286  layer = int(self.layer),
1287  where = where)
1288 
1289  region = {}
1290  for line in select.splitlines():
1291  key, value = line.split('=')
1292  region[key.strip()] = float(value.strip())
1293 
1294  self.mapdisplay.Map.GetRegion(n=region['n'], s=region['s'],
1295  w=region['w'], e=region['e'],
1296  update=True)
1297 
1298  if zoom:
1299  self.mapdisplay.Map.AdjustRegion() # adjust resolution
1300  self.mapdisplay.Map.AlignExtentFromDisplay() # adjust extent
1301  self.mapdisplay.MapWindow.UpdateMap(render=True, renderVector=True)
1302  else:
1303  self.mapdisplay.MapWindow.UpdateMap(render=False, renderVector=True)
1304 
1305  def OnDataDrawSelected(self, event):
1306  """!Reload table description"""
1307  self._drawSelected(zoom=False)
1308  event.Skip()
1309 
1310  def OnDataDrawSelectedZoom(self, event):
1311  self._drawSelected(zoom=True)
1312  event.Skip()
1313 
1314  def OnDataItemAdd(self, event):
1315  """!Add new record to the attribute table"""
1316  list = self.FindWindowById(self.layerPage[self.layer]['data'])
1317  table = self.mapDBInfo.layers[self.layer]['table']
1318  keyColumn = self.mapDBInfo.layers[self.layer]['key']
1319 
1320  # (column name, value)
1321  data = []
1322 
1323  # collect names of all visible columns
1324  columnName = []
1325  for i in range(list.GetColumnCount()):
1326  columnName.append(list.GetColumn(i).GetText())
1327 
1328  # maximal category number
1329  if len(list.itemCatsMap.values()) > 0:
1330  maxCat = max(list.itemCatsMap.values())
1331  else:
1332  maxCat = 0 # starting category '1'
1333 
1334  # key column must be always presented
1335  if keyColumn not in columnName:
1336  columnName.insert(0, keyColumn) # insert key column on first position
1337  data.append((keyColumn, str(maxCat + 1)))
1338  missingKey = True
1339  else:
1340  missingKey = False
1341 
1342  # add other visible columns
1343  colIdx = 0
1344  keyId = -1
1345  for col in columnName:
1346  if col == keyColumn: # key
1347  if missingKey is False:
1348  data.append((col, str(maxCat + 1)))
1349  keyId = colIdx
1350  else:
1351  data.append((col, ''))
1352  colIdx += 1
1353 
1354  dlg = ModifyTableRecord(parent = self,
1355  title = _("Insert new record"),
1356  data = data, keyEditable = (keyId, True))
1357 
1358  if dlg.ShowModal() == wx.ID_OK:
1359  try: # get category number
1360  cat = int(dlg.GetValues(columns=[keyColumn])[0])
1361  except:
1362  cat = -1
1363 
1364  try:
1365  if cat in list.itemCatsMap.values():
1366  raise ValueError(_("Record with category number %d "
1367  "already exists in the table.") % cat)
1368 
1369  values = dlg.GetValues() # values (need to be casted)
1370  columnsString = ''
1371  valuesString = ''
1372 
1373  for i in range(len(values)):
1374  if len(values[i]) == 0: # NULL
1375  if columnName[i] == keyColumn:
1376  raise ValueError(_("Category number (column %s)"
1377  " is missing.") % keyColumn)
1378  else:
1379  continue
1380 
1381  try:
1382  if list.columns[columnName[i]]['ctype'] == int:
1383  # values[i] is stored as text.
1384  value = float(values[i])
1385  else:
1386  value = values[i]
1387  values[i] = list.columns[columnName[i]]['ctype'] (value)
1388 
1389  except:
1390  raise ValueError(_("Value '%(value)s' needs to be entered as %(type)s.") %
1391  {'value' : str(values[i]),
1392  'type' : list.columns[columnName[i]]['type']})
1393  columnsString += '%s,' % columnName[i]
1394  if list.columns[columnName[i]]['ctype'] == str:
1395  valuesString += "'%s'," % values[i]
1396  else:
1397  valuesString += "%s," % values[i]
1398 
1399  except ValueError, err:
1400  wx.MessageBox(parent=self,
1401  message="%s%s%s" % (_("Unable to insert new record."),
1402  os.linesep, err),
1403  caption=_("Error"), style=wx.OK | wx.ICON_ERROR | wx.CENTRE)
1404  return
1405 
1406  # remove category if need
1407  if missingKey is True:
1408  del values[0]
1409 
1410  # add new item to the list
1411  if len(list.itemIndexMap) > 0:
1412  index = max(list.itemIndexMap) + 1
1413  else:
1414  index = 0
1415 
1416  list.itemIndexMap.append(index)
1417  list.itemDataMap[index] = values
1418  list.itemCatsMap[index] = cat
1419  list.SetItemCount(list.GetItemCount() + 1)
1420 
1421  self.listOfSQLStatements.append('INSERT INTO %s (%s) VALUES(%s)' % \
1422  (table,
1423  columnsString.strip(','),
1424  valuesString.strip(',')))
1425  self.ApplyCommands()
1426 
1427  def OnDataItemEdit(self, event):
1428  """!Edit selected record of the attribute table"""
1429  list = self.FindWindowById(self.layerPage[self.layer]['data'])
1430  item = list.GetFirstSelected()
1431  if item == -1:
1432  return
1433 
1434  table = self.mapDBInfo.layers[self.layer]['table']
1435  keyColumn = self.mapDBInfo.layers[self.layer]['key']
1436  cat = list.itemCatsMap[list.itemIndexMap[item]]
1437 
1438  # (column name, value)
1439  data = []
1440 
1441  # collect names of all visible columns
1442  columnName = []
1443  for i in range(list.GetColumnCount()):
1444  columnName.append(list.GetColumn(i).GetText())
1445 
1446 
1447  # key column must be always presented
1448  if keyColumn not in columnName:
1449  columnName.insert(0, keyColumn) # insert key column on first position
1450  data.append((keyColumn, str(cat)))
1451  keyId = 0
1452  missingKey = True
1453  else:
1454  missingKey = False
1455 
1456  # add other visible columns
1457  for i in range(len(columnName)):
1458  if columnName[i] == keyColumn: # key
1459  if missingKey is False:
1460  data.append((columnName[i], str(cat)))
1461  keyId = i
1462  else:
1463  if missingKey is True:
1464  value = list.GetItem(item, i-1).GetText()
1465  else:
1466  value = list.GetItem(item, i).GetText()
1467  data.append((columnName[i], value))
1468 
1469  dlg = ModifyTableRecord(parent = self,
1470  title = _("Update existing record"),
1471  data = data, keyEditable = (keyId, False))
1472 
1473  if dlg.ShowModal() == wx.ID_OK:
1474  values = dlg.GetValues() # string
1475  updateString = ''
1476  try:
1477  for i in range(len(values)):
1478  if i == keyId: # skip key column
1479  continue
1480  if list.GetItem(item, i).GetText() != values[i]:
1481  if len(values[i]) > 0:
1482  try:
1483  if missingKey is True:
1484  idx = i - 1
1485  else:
1486  idx = i
1487  if list.columns[columnName[i]]['ctype'] != type(''):
1488  if list.columns[columnName[i]]['ctype'] == int:
1489  value = float(values[i])
1490  else:
1491  value = values[i]
1492  list.itemDataMap[item][idx] = \
1493  list.columns[columnName[i]]['ctype'] (value)
1494  else:
1495  list.itemDataMap[item][idx] = values[i]
1496  except:
1497  raise ValueError(_("Value '%(value)s' needs to be entered as %(type)s.") % \
1498  {'value' : str(values[i]),
1499  'type' : list.columns[columnName[i]]['type']})
1500 
1501  if list.columns[columnName[i]]['ctype'] == str:
1502  updateString += "%s='%s'," % (columnName[i], values[i])
1503  else:
1504  updateString += "%s=%s," % (columnName[i], values[i])
1505  else: # NULL
1506  updateString += "%s=NULL," % (columnName[i])
1507 
1508  except ValueError, err:
1509  wx.MessageBox(parent=self,
1510  message="%s%s%s" % (_("Unable to update existing record."),
1511  os.linesep, err),
1512  caption=_("Error"), style=wx.OK | wx.ICON_ERROR | wx.CENTRE)
1513  return
1514 
1515  if len(updateString) > 0:
1516  self.listOfSQLStatements.append('UPDATE %s SET %s WHERE %s=%d' % \
1517  (table, updateString.strip(','),
1518  keyColumn, cat))
1519  self.ApplyCommands()
1520 
1521  list.Update(self.mapDBInfo)
1522 
1523  def OnDataReload(self, event):
1524  """!Reload list of records"""
1525  self.OnApplySqlStatement(None)
1526  self.listOfSQLStatements = []
1527 
1528  def OnDataSelectAll(self, event):
1529  """!Select all items"""
1530  list = self.FindWindowById(self.layerPage[self.layer]['data'])
1531  item = -1
1532 
1533  while True:
1534  item = list.GetNextItem(item)
1535  if item == -1:
1536  break
1537  list.SetItemState(item, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED)
1538 
1539  event.Skip()
1540 
1541  def OnDataSelectNone(self, event):
1542  """!Deselect items"""
1543  list = self.FindWindowById(self.layerPage[self.layer]['data'])
1544  item = -1
1545 
1546  while True:
1547  item = list.GetNextItem(item, wx.LIST_STATE_SELECTED)
1548  if item == -1:
1549  break
1550  list.SetItemState(item, 0, wx.LIST_STATE_SELECTED | wx.LIST_STATE_FOCUSED)
1551 
1552  event.Skip()
1553 
1554 
1555  def OnTableChangeType(self, event):
1556  """!Data type for new column changed. Enable or disable
1557  data length widget"""
1558  win = self.FindWindowById(self.layerPage[self.layer]['addColLength'])
1559  if event.GetString() == "varchar":
1560  win.Enable(True)
1561  else:
1562  win.Enable(False)
1563 
1564  def OnTableRenameColumnName(self, event):
1565  """!Editing column name to be added to the table"""
1566  btn = self.FindWindowById(self.layerPage[self.layer]['renameColButton'])
1567  col = self.FindWindowById(self.layerPage[self.layer]['renameCol'])
1568  colTo = self.FindWindowById(self.layerPage[self.layer]['renameColTo'])
1569  if len(col.GetValue()) > 0 and len(colTo.GetValue()) > 0:
1570  btn.Enable(True)
1571  else:
1572  btn.Enable(False)
1573 
1574  event.Skip()
1575 
1576  def OnTableAddColumnName(self, event):
1577  """!Editing column name to be added to the table"""
1578  btn = self.FindWindowById(self.layerPage[self.layer]['addColButton'])
1579  if len(event.GetString()) > 0:
1580  btn.Enable(True)
1581  else:
1582  btn.Enable(False)
1583 
1584  event.Skip()
1585 
1586  def OnTableItemChange(self, event):
1587  """!Rename column in the table"""
1588  list = self.FindWindowById(self.layerPage[self.layer]['tableData'])
1589  name = self.FindWindowById(self.layerPage[self.layer]['renameCol']).GetValue()
1590  nameTo = self.FindWindowById(self.layerPage[self.layer]['renameColTo']).GetValue()
1591 
1592  table = self.mapDBInfo.layers[self.layer]["table"]
1593 
1594  if not name or not nameTo:
1595  wx.MessageBox(self=self,
1596  message=_("Unable to rename column. "
1597  "No column name defined."),
1598  caption=_("Error"), style=wx.OK | wx.ICON_ERROR | wx.CENTRE)
1599  return
1600  else:
1601  item = list.FindItem(start=-1, str=name)
1602  if item > -1:
1603  if list.FindItem(start=-1, str=nameTo) > -1:
1604  wx.MessageBox(parent=self,
1605  message=_("Unable to rename column <%(column)s> to "
1606  "<%(columnTo)s>. Column already exists "
1607  "in the table <%(table)s>.") % \
1608  {'column' : name, 'columnTo' : nameTo,
1609  'table' : table},
1610  caption=_("Error"), style=wx.OK | wx.ICON_ERROR | wx.CENTRE)
1611  return
1612  else:
1613  list.SetItemText(item, nameTo)
1614 
1615  self.listOfCommands.append(('v.db.renamecol',
1616  { 'map' : self.vectorName,
1617  'layer' : self.layer,
1618  'column' : '%s,%s' % (name, nameTo) }
1619  ))
1620  else:
1621  wx.MessageBox(parent=self,
1622  message=_("Unable to rename column. "
1623  "Column <%(column)s> doesn't exist in the table <%(table)s>.") %
1624  {'column' : name, 'table' : table},
1625  caption=_("Error"), style=wx.OK | wx.ICON_ERROR | wx.CENTRE)
1626  return
1627 
1628  # apply changes
1629  self.ApplyCommands()
1630 
1631  # update widgets
1632  self.FindWindowById(self.layerPage[self.layer]['renameCol']).SetItems(self.mapDBInfo.GetColumns(table))
1633  self.FindWindowById(self.layerPage[self.layer]['renameCol']).SetSelection(0)
1634  self.FindWindowById(self.layerPage[self.layer]['renameColTo']).SetValue('')
1635 
1636  event.Skip()
1637 
1638  def OnTableRightUp(self, event):
1639  """!Table description area, context menu"""
1640  if not hasattr(self, "popupTableID"):
1641  self.popupTableID1 = wx.NewId()
1642  self.popupTableID2 = wx.NewId()
1643  self.popupTableID3 = wx.NewId()
1644  self.Bind(wx.EVT_MENU, self.OnTableItemDelete, id=self.popupTableID1)
1645  self.Bind(wx.EVT_MENU, self.OnTableItemDeleteAll, id=self.popupTableID2)
1646  self.Bind(wx.EVT_MENU, self.OnTableReload, id=self.popupTableID3)
1647 
1648  # generate popup-menu
1649  menu = wx.Menu()
1650  menu.Append(self.popupTableID1, _("Drop selected column"))
1651  if self.FindWindowById(self.layerPage[self.layer]['tableData']).GetFirstSelected() == -1:
1652  menu.Enable(self.popupTableID1, False)
1653  menu.Append(self.popupTableID2, _("Drop all columns"))
1654  menu.AppendSeparator()
1655  menu.Append(self.popupTableID3, _("Reload"))
1656 
1657  self.PopupMenu(menu)
1658  menu.Destroy()
1659 
1660  def OnTableItemDelete(self, event):
1661  """!Delete selected item(s) from the list"""
1662  list = self.FindWindowById(self.layerPage[self.layer]['tableData'])
1663 
1664  item = list.GetFirstSelected()
1665 
1666  if UserSettings.Get(group='atm', key='askOnDeleteRec', subkey='enabled'):
1667  deleteDialog = wx.MessageBox(parent=self,
1668  message=_("Selected column '%s' will PERMANENTLY removed "
1669  "from table. Do you want to drop the column?") % \
1670  (list.GetItemText(item)),
1671  caption=_("Drop column(s)"),
1672  style=wx.YES_NO | wx.CENTRE)
1673  if deleteDialog != wx.YES:
1674  return False
1675 
1676  while item != -1:
1677  self.listOfCommands.append(('v.db.dropcol',
1678  { 'map' : self.vectorName,
1679  'layer' : self.layer,
1680  'column' : list.GetItemText(item) }
1681  ))
1682  list.DeleteItem(item)
1683  item = list.GetFirstSelected()
1684 
1685  # apply changes
1686  self.ApplyCommands()
1687 
1688  # update widgets
1689  table = self.mapDBInfo.layers[self.layer]['table']
1690  self.FindWindowById(self.layerPage[self.layer]['renameCol']).SetItems(self.mapDBInfo.GetColumns(table))
1691  self.FindWindowById(self.layerPage[self.layer]['renameCol']).SetSelection(0)
1692 
1693  event.Skip()
1694 
1695  def OnTableItemDeleteAll(self, event):
1696  """!Delete all items from the list"""
1697  table = self.mapDBInfo.layers[self.layer]['table']
1698  cols = self.mapDBInfo.GetColumns(table)
1699  keyColumn = self.mapDBInfo.layers[self.layer]['key']
1700  if keyColumn in cols:
1701  cols.remove(keyColumn)
1702 
1703  if UserSettings.Get(group='atm', key='askOnDeleteRec', subkey='enabled'):
1704  deleteDialog = wx.MessageBox(parent=self,
1705  message=_("Selected columns\n%s\nwill PERMANENTLY removed "
1706  "from table. Do you want to drop the columns?") % \
1707  ('\n'.join(cols)),
1708  caption=_("Drop column(s)"),
1709  style=wx.YES_NO | wx.CENTRE)
1710  if deleteDialog != wx.YES:
1711  return False
1712 
1713  for col in cols:
1714  self.listOfCommands.append(('v.db.dropcol',
1715  { 'map' : self.vectorName,
1716  'layer' : self.layer,
1717  'column' : col }
1718  ))
1719  self.FindWindowById(self.layerPage[self.layer]['tableData']).DeleteAllItems()
1720 
1721  # apply changes
1722  self.ApplyCommands()
1723 
1724  # update widgets
1725  table = self.mapDBInfo.layers[self.layer]['table']
1726  self.FindWindowById(self.layerPage[self.layer]['renameCol']).SetItems(self.mapDBInfo.GetColumns(table))
1727  self.FindWindowById(self.layerPage[self.layer]['renameCol']).SetSelection(0)
1728 
1729  event.Skip()
1730 
1731  def OnTableReload(self, event=None):
1732  """!Reload table description"""
1733  self.FindWindowById(self.layerPage[self.layer]['tableData']).Populate(update=True)
1734  self.listOfCommands = []
1735 
1736  def OnTableItemAdd(self, event):
1737  """!Add new column to the table"""
1738  table = self.mapDBInfo.layers[self.layer]['table']
1739  name = self.FindWindowById(self.layerPage[self.layer]['addColName']).GetValue()
1740 
1741  if not name:
1742  gcmd.GError(parent = self,
1743  message = _("Unable to add column to the table. "
1744  "No column name defined."))
1745  return
1746 
1747  ctype = self.FindWindowById(self.layerPage[self.layer]['addColType']). \
1748  GetStringSelection()
1749 
1750  # cast type if needed
1751  if ctype == 'double':
1752  ctype = 'double precision'
1753  if ctype == 'varchar':
1754  length = int(self.FindWindowById(self.layerPage[self.layer]['addColLength']). \
1755  GetValue())
1756  else:
1757  length = '' # FIXME
1758 
1759  # add item to the list of table columns
1760  tlist = self.FindWindowById(self.layerPage[self.layer]['tableData'])
1761  # check for duplicate items
1762  if tlist.FindItem(start=-1, str=name) > -1:
1763  gcmd.GError(parent = self,
1764  message = _("Column <%(column)s> already exists in table <%(table)s>.") % \
1765  {'column' : name, 'table' : self.mapDBInfo.layers[self.layer]["table"]}
1766  )
1767  return
1768  index = tlist.InsertStringItem(sys.maxint, str(name))
1769  tlist.SetStringItem(index, 0, str(name))
1770  tlist.SetStringItem(index, 1, str(ctype))
1771  tlist.SetStringItem(index, 2, str(length))
1772 
1773  # add v.db.addcol command to the list
1774  if ctype == 'varchar':
1775  ctype += ' (%d)' % length
1776  self.listOfCommands.append(('v.db.addcol',
1777  { 'map' : self.vectorName,
1778  'layer' : self.layer,
1779  'columns' : '%s %s' % (name, ctype) }
1780  ))
1781  # apply changes
1782  self.ApplyCommands()
1783 
1784  # update widgets
1785  self.FindWindowById(self.layerPage[self.layer]['addColName']).SetValue('')
1786  self.FindWindowById(self.layerPage[self.layer]['renameCol']).SetItems(self.mapDBInfo.GetColumns(table))
1787  self.FindWindowById(self.layerPage[self.layer]['renameCol']).SetSelection(0)
1788 
1789  event.Skip()
1790 
1791  def OnLayerPageChanged(self, event):
1792  """!Layer tab changed"""
1793  pageNum = event.GetSelection()
1794  self.layer = self.mapDBInfo.layers.keys()[pageNum]
1795 
1796  try:
1797  idCol = self.layerPage[self.layer]['whereColumn']
1798  except KeyError:
1799  idCol = None
1800 
1801  try:
1802  self.OnChangeSql(None)
1803  # update statusbar
1804  self.log.write(_("Number of loaded records: %d") % \
1805  self.FindWindowById(self.layerPage[self.layer]['data']).\
1806  GetItemCount())
1807  except:
1808  pass
1809 
1810  if idCol:
1811  winCol = self.FindWindowById(idCol)
1812  table = self.mapDBInfo.layers[self.layer]["table"]
1813  self.mapDBInfo.GetColumns(table)
1814 
1815  event.Skip()
1816 
1817  def OnPageChanged(self, event):
1818  try:
1819  id = self.layerPage[self.layer]['data']
1820  except KeyError:
1821  id = None
1822 
1823  if event.GetSelection() == 0 and id:
1824  win = self.FindWindowById(id)
1825  if win:
1826  self.log.write(_("Number of loaded records: %d") % win.GetItemCount())
1827  else:
1828  self.log.write("")
1829  self.btnReload.Enable()
1830  else:
1831  self.log.write("")
1832  self.btnReload.Enable(False)
1833 
1834  event.Skip()
1835 
1836  def OnLayerRightUp(self, event):
1837  """!Layer description area, context menu"""
1838  pass
1839 
1840  def OnChangeSql(self, event):
1841  """!Switch simple/advanced sql statement"""
1842  if self.FindWindowById(self.layerPage[self.layer]['simple']).GetValue():
1843  self.FindWindowById(self.layerPage[self.layer]['where']).Enable(True)
1844  self.FindWindowById(self.layerPage[self.layer]['statement']).Enable(False)
1845  self.FindWindowById(self.layerPage[self.layer]['builder']).Enable(False)
1846  else:
1847  self.FindWindowById(self.layerPage[self.layer]['where']).Enable(False)
1848  self.FindWindowById(self.layerPage[self.layer]['statement']).Enable(True)
1849  self.FindWindowById(self.layerPage[self.layer]['builder']).Enable(True)
1850 
1851  def ApplyCommands(self):
1852  """!Apply changes"""
1853  # perform GRASS commands (e.g. v.db.addcol)
1854  wx.BeginBusyCursor()
1855 
1856  if len(self.listOfCommands) > 0:
1857  for cmd in self.listOfCommands:
1858  gcmd.RunCommand(prog = cmd[0],
1859  quiet = True,
1860  parent = self,
1861  **cmd[1])
1862 
1864  table = self.mapDBInfo.layers[self.layer]['table']
1865 
1866  # update table description
1867  list = self.FindWindowById(self.layerPage[self.layer]['tableData'])
1868  list.Update(table=self.mapDBInfo.tables[table],
1869  columns=self.mapDBInfo.GetColumns(table))
1870  self.OnTableReload(None)
1871 
1872  # update data list
1873  list = self.FindWindowById(self.layerPage[self.layer]['data'])
1874  list.Update(self.mapDBInfo)
1875 
1876  # reset list of commands
1877  self.listOfCommands = []
1878 
1879  # perform SQL non-select statements (e.g. 'delete from table where cat=1')
1880  if len(self.listOfSQLStatements) > 0:
1881  sqlFile = tempfile.NamedTemporaryFile(mode="wt")
1882  for sql in self.listOfSQLStatements:
1883  enc = UserSettings.Get(group='atm', key='encoding', subkey='value')
1884  if not enc and 'GRASS_DB_ENCODING' in os.environ:
1885  enc = os.environ['GRASS_DB_ENCODING']
1886  if enc:
1887  sqlFile.file.write(sql.encode(enc) + ';')
1888  else:
1889  sqlFile.file.write(sql + ';')
1890  sqlFile.file.write(os.linesep)
1891  sqlFile.file.flush()
1892 
1893  driver = self.mapDBInfo.layers[self.layer]["driver"]
1894  database = self.mapDBInfo.layers[self.layer]["database"]
1895 
1896  Debug.msg(3, 'AttributeManger.ApplyCommands(): %s' %
1897  ';'.join(["%s" % s for s in self.listOfSQLStatements]))
1898 
1899  gcmd.RunCommand('db.execute',
1900  parent = self,
1901  input = sqlFile.name,
1902  driver = driver,
1903  database = database)
1904 
1905  # reset list of statements
1906  self.listOfSQLStatements = []
1907 
1908  wx.EndBusyCursor()
1909 
1910  def OnApplySqlStatement(self, event):
1911  """!Apply simple/advanced sql statement"""
1912  keyColumn = -1 # index of key column
1913  listWin = self.FindWindowById(self.layerPage[self.layer]['data'])
1914  sql = None
1915 
1916  wx.BeginBusyCursor()
1917 
1918  if self.FindWindowById(self.layerPage[self.layer]['simple']).GetValue():
1919  # simple sql statement
1920  whereCol = self.FindWindowById(self.layerPage[self.layer]['whereColumn']).GetStringSelection()
1921  whereVal = self.FindWindowById(self.layerPage[self.layer]['where']).GetValue().strip()
1922  try:
1923  if len(whereVal) > 0:
1924  keyColumn = listWin.LoadData(self.layer, where=whereCol + whereVal)
1925  else:
1926  keyColumn = listWin.LoadData(self.layer)
1927  except gcmd.GException, e:
1928  gcmd.GError(parent = self,
1929  message = _("Loading attribute data failed.\n\n%s") % e.value)
1930  self.FindWindowById(self.layerPage[self.layer]['where']).SetValue('')
1931  else:
1932  # advanced sql statement
1933  win = self.FindWindowById(self.layerPage[self.layer]['statement'])
1934  try:
1935  cols, where = self.ValidateSelectStatement(win.GetValue())
1936  if cols is None and where is None:
1937  sql = win.GetValue()
1938  except TypeError:
1939  wx.MessageBox(parent=self,
1940  message=_("Loading attribute data failed.\n"
1941  "Invalid SQL select statement.\n\n%s") % win.GetValue(),
1942  caption=_("Error"), style=wx.OK | wx.ICON_ERROR | wx.CENTRE)
1943  win.SetValue("SELECT * FROM %s" % self.mapDBInfo.layers[self.layer]['table'])
1944  cols = None
1945  where = None
1946 
1947  if cols or where or sql:
1948  try:
1949  keyColumn = listWin.LoadData(self.layer, columns=cols,
1950  where=where, sql=sql)
1951  except gcmd.GException, e:
1952  gcmd.GError(parent = self,
1953  message = _("Loading attribute data failed.\n\n%s") % e.value)
1954  win.SetValue("SELECT * FROM %s" % self.mapDBInfo.layers[self.layer]['table'])
1955 
1956  # sort by key column
1957  if sql and 'order by' in sql.lower():
1958  pass # don't order by key column
1959  else:
1960  if keyColumn > -1:
1961  listWin.SortListItems(col=keyColumn, ascending=True)
1962  else:
1963  listWin.SortListItems(col=0, ascending=True)
1964 
1965  wx.EndBusyCursor()
1966 
1967  # update statusbar
1968  self.log.write(_("Number of loaded records: %d") % \
1969  self.FindWindowById(self.layerPage[self.layer]['data']).GetItemCount())
1970 
1971  def ValidateSelectStatement(self, statement):
1972  """!Validate SQL select statement
1973 
1974  @return (columns, where)
1975  @return None on error
1976  """
1977  if statement[0:7].lower() != 'select ':
1978  return None
1979 
1980  cols = ''
1981  index = 7
1982  for c in statement[index:]:
1983  if c == ' ':
1984  break
1985  cols += c
1986  index += 1
1987  if cols == '*':
1988  cols = None
1989  else:
1990  cols = cols.split(',')
1991 
1992  tablelen = len(self.mapDBInfo.layers[self.layer]['table'])
1993 
1994  if statement[index+1:index+6].lower() != 'from ' or \
1995  statement[index+6:index+6+tablelen] != '%s' % \
1996  (self.mapDBInfo.layers[self.layer]['table']):
1997  return None
1998 
1999  if len(statement[index+7+tablelen:]) > 0:
2000  index = statement.lower().find('where ')
2001  if index > -1:
2002  where = statement[index+6:]
2003  else:
2004  where = None
2005  else:
2006  where = None
2007 
2008  return (cols, where)
2009 
2010  def OnCloseWindow(self, event):
2011  """!Cancel button pressed"""
2012  self.Close()
2013  if self.parent and self.parent.GetName() == 'LayerManager':
2014  # deregister ATM
2015  self.parent.dialogs['atm'].remove(self)
2016 
2017  event.Skip()
2018 
2019  def OnBuilder(self,event):
2020  """!SQL Builder button pressed -> show the SQLBuilder dialog"""
2021  if not self.builder:
2022  self.builder = sqlbuilder.SQLFrame(parent = self, id = wx.ID_ANY,
2023  title = _("SQL Builder"),
2024  vectmap = self.vectorName,
2025  evtheader = self.OnBuilderEvt)
2026  self.builder.Show()
2027  else:
2028  self.builder.Raise()
2029 
2030  def OnBuilderEvt(self, event):
2031  if event == 'apply':
2032  sqlstr = self.builder.GetSQLStatement()
2033  self.FindWindowById(self.layerPage[self.layer]['statement']).SetValue(sqlstr)
2034  if self.builder.CloseOnApply():
2035  self.builder = None
2036  elif event == 'close':
2037  self.builder = None
2038 
2039  def OnTextEnter(self, event):
2040  pass
2041 
2042  def OnDataItemActivated(self, event):
2043  """!Item activated, highlight selected item"""
2044  self.OnDataDrawSelected(event)
2045 
2046  event.Skip()
2047 
2048  def OnExtractSelected(self, event):
2049  """!Extract vector objects selected in attribute browse window
2050  to new vector map
2051  """
2052  list = self.FindWindowById(self.layerPage[self.layer]['data'])
2053  # cats = list.selectedCats[:]
2054  cats = list.GetSelectedItems()
2055  if len(cats) == 0:
2056  wx.MessageBox(parent=self,
2057  message=_('Nothing to extract.'),
2058  caption=_('Message'), style=wx.CENTRE)
2059  return
2060  else:
2061  # dialog to get file name
2062  dlg = gdialogs.CreateNewVector(parent = self, title = _('Extract selected features'),
2063  log = self.cmdLog,
2064  cmd = (('v.extract',
2065  { 'input' : self.vectorName,
2066  'list' : utils.ListOfCatsToRange(cats) },
2067  'output')),
2068  disableTable = True)
2069  if not dlg:
2070  return
2071 
2072  name = dlg.GetName(full = True)
2073  if name and dlg.IsChecked('add'):
2074  # add layer to map layer tree
2075  self.parent.curr_page.maptree.AddLayer(ltype = 'vector',
2076  lname = name,
2077  lcmd = ['d.vect', 'map=%s' % name])
2078  dlg.Destroy()
2079 
2080  def OnDeleteSelected(self, event):
2081  """
2082  Delete vector objects selected in attribute browse window
2083  (attribures and geometry)
2084  """
2085  list = self.FindWindowById(self.layerPage[self.layer]['data'])
2086  cats = list.GetSelectedItems()
2087  if len(cats) == 0:
2088  wx.MessageBox(parent=self,
2089  message=_('Nothing to delete.'),
2090  caption=_('Message'), style=wx.CENTRE)
2091 
2092  if self.OnDataItemDelete(None):
2093  digitToolbar = self.mapdisplay.toolbars['vdigit']
2094  if digitToolbar and digitToolbar.GetLayer() and \
2095  digitToolbar.GetLayer().GetName() == self.vectorName:
2096  self.mapdisplay.digit.driver.SetSelected(map(int, cats), field=self.layer)
2097  self.mapdisplay.digit.DeleteSelectedLines()
2098  else:
2099  gcmd.RunCommand('v.edit',
2100  parent = self,
2101  quiet = True,
2102  map = self.vectorName,
2103  tool = 'delete',
2104  cats = utils.ListOfCatsToRange(cats))
2105 
2106  self.mapdisplay.MapWindow.UpdateMap(render=True, renderVector=True)
2107 
2108  def AddQueryMapLayer(self):
2109  """!Redraw a map
2110 
2111  Return True if map has been redrawn, False if no map is given
2112  """
2113  list = self.FindWindowById(self.layerPage[self.layer]['data'])
2114  cats = {
2115  self.layer : list.GetSelectedItems()
2116  }
2117 
2118  if self.mapdisplay.Map.GetLayerIndex(self.qlayer) < 0:
2119  self.qlayer = None
2120 
2121  if self.qlayer:
2122  self.qlayer.SetCmd(self.mapdisplay.AddTmpVectorMapLayer(self.vectorName, cats, addLayer=False))
2123  else:
2124  self.qlayer = self.mapdisplay.AddTmpVectorMapLayer(self.vectorName, cats)
2125 
2126  return self.qlayer
2127 
2128  def UpdateDialog(self, layer):
2129  """!Updates dialog layout for given layer"""
2130  #
2131  # delete page
2132  #
2133  if layer in self.mapDBInfo.layers.keys():
2134  # delete page
2135  # draging pages disallowed
2136  # if self.browsePage.GetPageText(page).replace('Layer ', '').strip() == str(layer):
2137  # self.browsePage.DeletePage(page)
2138  # break
2139  self.browsePage.DeletePage(self.mapDBInfo.layers.keys().index(layer))
2140  self.manageTablePage.DeletePage(self.mapDBInfo.layers.keys().index(layer))
2141  # set current page selection
2142  self.notebook.SetSelection(2)
2143 
2144  # fetch fresh db info
2146 
2147  #
2148  # add new page
2149  #
2150  if layer in self.mapDBInfo.layers.keys():
2151  # 'browse data' page
2152  self.__createBrowsePage(layer)
2153  # 'manage tables' page
2154  self.__createManageTablePage(layer)
2155  # set current page selection
2156  self.notebook.SetSelection(2)
2157 
2158  #
2159  # 'manage layers' page
2160  #
2161  # update list of layers
2162  self.layerList.Update(self.mapDBInfo.layers)
2163  self.layerList.Populate(update=True)
2164  # update selected widgets
2165  listOfLayers = map(str, self.mapDBInfo.layers.keys())
2166  ### delete layer page
2167  self.manageLayerBook.deleteLayer.SetItems(listOfLayers)
2168  if len(listOfLayers) > 0:
2169  self.manageLayerBook.deleteLayer.SetStringSelection(listOfLayers[0])
2170  tableName = self.mapDBInfo.layers[int(listOfLayers[0])]['table']
2171  maxLayer = max(self.mapDBInfo.layers.keys())
2172  else:
2173  tableName = ''
2174  maxLayer = 0
2175  self.manageLayerBook.deleteTable.SetLabel( \
2176  _('Drop also linked attribute table (%s)') % \
2177  tableName)
2178  ### add layer page
2179  self.manageLayerBook.addLayerWidgets['layer'][1].SetValue(\
2180  maxLayer+1)
2181  ### modify layer
2182  self.manageLayerBook.modifyLayerWidgets['layer'][1].SetItems(listOfLayers)
2183  self.manageLayerBook.OnChangeLayer(event=None)
2184 
2185  def GetVectorName(self):
2186  """!Get vector name"""
2187  return self.vectorName
2188 
2189  def LoadData(self, layer, columns=None, where=None, sql=None):
2190  """!Load data into list
2191 
2192  @param layer layer number
2193  @param columns list of columns for output
2194  @param where where statement
2195  @param sql full sql statement
2196 
2197  @return id of key column
2198  @return -1 if key column is not displayed
2199  """
2200  listWin = self.FindWindowById(self.layerPage[layer]['data'])
2201  return listWin.LoadData(layer, columns, where, sql)
2202 
2203 class TableListCtrl(wx.ListCtrl,
2204  listmix.ListCtrlAutoWidthMixin):
2205  # listmix.TextEditMixin):
2206  """!Table description list"""
2207 
2208  def __init__(self, parent, id, table, columns, pos=wx.DefaultPosition,
2209  size=wx.DefaultSize):
2210 
2211  self.parent = parent
2212  self.table = table
2213  self.columns = columns
2214  wx.ListCtrl.__init__(self, parent, id, pos, size,
2215  style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES |
2216  wx.BORDER_NONE)
2217 
2218  listmix.ListCtrlAutoWidthMixin.__init__(self)
2219  # listmix.TextEditMixin.__init__(self)
2220 
2221  def Update(self, table, columns):
2222  """!Update column description"""
2223  self.table = table
2224  self.columns = columns
2225 
2226  def Populate(self, update=False):
2227  """!Populate the list"""
2228  itemData = {} # requested by sorter
2229 
2230  if not update:
2231  headings = [_("Column name"), _("Data type"), _("Data length")]
2232  i = 0
2233  for h in headings:
2234  self.InsertColumn(col=i, heading=h)
2235  self.SetColumnWidth(col=i, width=150)
2236  i += 1
2237  else:
2238  self.DeleteAllItems()
2239 
2240  i = 0
2241  for column in self.columns:
2242  index = self.InsertStringItem(sys.maxint, str(column))
2243  self.SetStringItem(index, 0, str(column))
2244  self.SetStringItem(index, 1, str(self.table[column]['type']))
2245  self.SetStringItem(index, 2, str(self.table[column]['length']))
2246  self.SetItemData(index, i)
2247  itemData[i] = (str(column),
2248  str(self.table[column]['type']),
2249  int(self.table[column]['length']))
2250  i = i + 1
2251 
2252  self.SendSizeEvent()
2253 
2254  return itemData
2255 
2256 class LayerListCtrl(wx.ListCtrl,
2257  listmix.ListCtrlAutoWidthMixin):
2258  # listmix.ColumnSorterMixin):
2259  # listmix.TextEditMixin):
2260  """!Layer description list"""
2261 
2262  def __init__(self, parent, id, layers,
2263  pos=wx.DefaultPosition,
2264  size=wx.DefaultSize):
2265 
2266  self.parent = parent
2267  self.layers = layers
2268  wx.ListCtrl.__init__(self, parent, id, pos, size,
2269  style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES |
2270  wx.BORDER_NONE)
2271 
2272  listmix.ListCtrlAutoWidthMixin.__init__(self)
2273  # listmix.TextEditMixin.__init__(self)
2274 
2275  def Update(self, layers):
2276  """!Update description"""
2277  self.layers = layers
2278 
2279  def Populate(self, update=False):
2280  """!Populate the list"""
2281  itemData = {} # requested by sorter
2282 
2283  if not update:
2284  headings = [_("Layer"), _("Driver"), _("Database"), _("Table"), _("Key")]
2285  i = 0
2286  for h in headings:
2287  self.InsertColumn(col=i, heading=h)
2288  i += 1
2289  else:
2290  self.DeleteAllItems()
2291 
2292  i = 0
2293  for layer in self.layers.keys():
2294  index = self.InsertStringItem(sys.maxint, str(layer))
2295  self.SetStringItem(index, 0, str(layer))
2296  database = str(self.layers[layer]['database'])
2297  driver = str(self.layers[layer]['driver'])
2298  table = str(self.layers[layer]['table'])
2299  key = str(self.layers[layer]['key'])
2300  self.SetStringItem(index, 1, driver)
2301  self.SetStringItem(index, 2, database)
2302  self.SetStringItem(index, 3, table)
2303  self.SetStringItem(index, 4, key)
2304  self.SetItemData(index, i)
2305  itemData[i] = (str(layer),
2306  driver,
2307  database,
2308  table,
2309  key)
2310  i += 1
2311 
2312  for i in range(self.GetColumnCount()):
2313  self.SetColumnWidth(col=i, width=wx.LIST_AUTOSIZE)
2314  if self.GetColumnWidth(col=i) < 60:
2315  self.SetColumnWidth(col=i, width=60)
2316 
2317  self.SendSizeEvent()
2318 
2319  return itemData
2320 
2321 class LayerBook(wx.Notebook):
2322  """!Manage layers (add, delete, modify)"""
2323  def __init__(self, parent, id,
2324  parentDialog,
2325  style=wx.BK_DEFAULT):
2326  wx.Notebook.__init__(self, parent, id, style=style)
2327 
2328  self.parent = parent
2329  self.parentDialog = parentDialog
2330  self.mapDBInfo = self.parentDialog.mapDBInfo
2331 
2332  #
2333  # drivers
2334  #
2335  drivers = gcmd.RunCommand('db.drivers',
2336  quiet = True,
2337  read = True,
2338  flags = 'p')
2339 
2340  self.listOfDrivers = []
2341  for drv in drivers.splitlines():
2342  self.listOfDrivers.append(drv.strip())
2343 
2344  #
2345  # get default values
2346  #
2347  self.defaultConnect = {}
2348  connect = gcmd.RunCommand('db.connect',
2349  flags = 'p',
2350  read = True,
2351  quiet = True)
2352 
2353  for line in connect.splitlines():
2354  item, value = line.split(':', 1)
2355  self.defaultConnect[item.strip()] = value.strip()
2356 
2357  if len(self.defaultConnect['driver']) == 0 or \
2358  len(self.defaultConnect['database']) == 0:
2359  wx.MessageBox(parent=self.parent,
2360  message=_("Unknown default DB connection. "
2361  "Please define DB connection using db.connect module."),
2362  caption=_("Warning"),
2363  style=wx.OK | wx.ICON_WARNING | wx.CENTRE)
2364 
2365  self.defaultTables = self.__getTables(self.defaultConnect['driver'],
2366  self.defaultConnect['database'])
2367  try:
2368  self.defaultColumns = self.__getColumns(self.defaultConnect['driver'],
2369  self.defaultConnect['database'],
2370  self.defaultTables[0])
2371  except IndexError:
2372  self.defaultColumns = []
2373 
2374  self.__createAddPage()
2375  self.__createDeletePage()
2376  self.__createModifyPage()
2377 
2378  def __createAddPage(self):
2379  """!Add new layer"""
2380  self.addPanel = wx.Panel(parent=self, id=wx.ID_ANY)
2381  self.AddPage(page=self.addPanel, text=_("Add layer"))
2382 
2383  try:
2384  maxLayer = max(self.mapDBInfo.layers.keys())
2385  except ValueError:
2386  maxLayer = 0
2387 
2388  # layer description
2389 
2390  layerBox = wx.StaticBox (parent=self.addPanel, id=wx.ID_ANY,
2391  label=" %s " % (_("Layer description")))
2392  layerSizer = wx.StaticBoxSizer(layerBox, wx.VERTICAL)
2393 
2394  #
2395  # list of layer widgets (label, value)
2396  #
2397  self.addLayerWidgets = {'layer':
2398  (wx.StaticText(parent=self.addPanel, id=wx.ID_ANY,
2399  label='%s:' % _("Layer")),
2400  wx.SpinCtrl(parent=self.addPanel, id=wx.ID_ANY, size=(65, -1),
2401  initial=maxLayer+1,
2402  min=1, max=1e6)),
2403  'driver':
2404  (wx.StaticText(parent=self.addPanel, id=wx.ID_ANY,
2405  label='%s:' % _("Driver")),
2406  wx.Choice(parent=self.addPanel, id=wx.ID_ANY, size=(200, -1),
2407  choices=self.listOfDrivers)),
2408  'database':
2409  (wx.StaticText(parent=self.addPanel, id=wx.ID_ANY,
2410  label='%s:' % _("Database")),
2411  wx.TextCtrl(parent=self.addPanel, id=wx.ID_ANY,
2412  value='',
2413  style=wx.TE_PROCESS_ENTER)),
2414  'table':
2415  (wx.StaticText(parent=self.addPanel, id=wx.ID_ANY,
2416  label='%s:' % _("Table")),
2417  wx.Choice(parent=self.addPanel, id=wx.ID_ANY, size=(200, -1),
2418  choices=self.defaultTables)),
2419  'key':
2420  (wx.StaticText(parent=self.addPanel, id=wx.ID_ANY,
2421  label='%s:' % _("Key column")),
2422  wx.Choice(parent=self.addPanel, id=wx.ID_ANY, size=(200, -1),
2423  choices=self.defaultColumns)),
2424  'addCat':
2425  (wx.CheckBox(parent=self.addPanel, id=wx.ID_ANY,
2426  label=_("Insert record for each category into table")),
2427  None),
2428  }
2429 
2430  # set default values for widgets
2431  self.addLayerWidgets['driver'][1].SetStringSelection(self.defaultConnect['driver'])
2432  self.addLayerWidgets['database'][1].SetValue(self.defaultConnect['database'])
2433  self.addLayerWidgets['table'][1].SetSelection(0)
2434  self.addLayerWidgets['key'][1].SetSelection(0)
2435  # events
2436  self.addLayerWidgets['driver'][1].Bind(wx.EVT_CHOICE, self.OnDriverChanged)
2437  self.addLayerWidgets['database'][1].Bind(wx.EVT_TEXT_ENTER, self.OnDatabaseChanged)
2438  self.addLayerWidgets['table'][1].Bind(wx.EVT_CHOICE, self.OnTableChanged)
2439 
2440  # tooltips
2441  self.addLayerWidgets['addCat'][0].SetToolTipString(_("You need to add categories "
2442  "by v.category module."))
2443  #
2444  # list of table widgets
2445  #
2446  keyCol = UserSettings.Get(group='atm', key='keycolumn', subkey='value')
2447  self.tableWidgets = {'table': (wx.StaticText(parent=self.addPanel, id=wx.ID_ANY,
2448  label='%s:' % _("Table name")),
2449  wx.TextCtrl(parent=self.addPanel, id=wx.ID_ANY,
2450  value='',
2451  style=wx.TE_PROCESS_ENTER)),
2452  'key': (wx.StaticText(parent=self.addPanel, id=wx.ID_ANY,
2453  label='%s:' % _("Key column")),
2454  wx.TextCtrl(parent=self.addPanel, id=wx.ID_ANY,
2455  value=keyCol,
2456  style=wx.TE_PROCESS_ENTER))}
2457  # events
2458  self.tableWidgets['table'][1].Bind(wx.EVT_TEXT_ENTER, self.OnCreateTable)
2459  self.tableWidgets['key'][1].Bind(wx.EVT_TEXT_ENTER, self.OnCreateTable)
2460 
2461  btnTable = wx.Button(self.addPanel, wx.ID_ANY, _("&Create table"),
2462  size=(125,-1))
2463  btnTable.Bind(wx.EVT_BUTTON, self.OnCreateTable)
2464 
2465  btnLayer = wx.Button(self.addPanel, wx.ID_ANY, _("&Add layer"),
2466  size=(125,-1))
2467  btnLayer.Bind(wx.EVT_BUTTON, self.OnAddLayer)
2468 
2469  btnDefault = wx.Button(self.addPanel, wx.ID_ANY, _("&Set default"),
2470  size=(125,-1))
2471  btnDefault.Bind(wx.EVT_BUTTON, self.OnSetDefault)
2472 
2473  # do layout
2474 
2475  pageSizer = wx.BoxSizer(wx.HORIZONTAL)
2476 
2477  # data area
2478  dataSizer = wx.GridBagSizer(hgap=5, vgap=5)
2479  dataSizer.AddGrowableCol(1)
2480  row = 0
2481  for key in ('layer', 'driver', 'database', 'table', 'key', 'addCat'):
2482  label, value = self.addLayerWidgets[key]
2483  if not value:
2484  span = (1, 2)
2485  else:
2486  span = (1, 1)
2487  dataSizer.Add(item=label,
2488  flag=wx.ALIGN_CENTER_VERTICAL, pos=(row, 0),
2489  span=span)
2490 
2491  if not value:
2492  row += 1
2493  continue
2494 
2495  if label.GetLabel() == "Layer:":
2496  style = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT
2497  else:
2498  style = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND
2499 
2500  dataSizer.Add(item=value,
2501  flag=style, pos=(row, 1))
2502 
2503  row += 1
2504 
2505  layerSizer.Add(item=dataSizer,
2506  proportion=1,
2507  flag=wx.ALL | wx.EXPAND,
2508  border=5)
2509 
2510  btnSizer = wx.BoxSizer(wx.HORIZONTAL)
2511  btnSizer.Add(item=btnDefault,
2512  proportion=0,
2513  flag=wx.ALL | wx.ALIGN_LEFT,
2514  border=5)
2515 
2516  btnSizer.Add(item=(5, 5),
2517  proportion=1,
2518  flag=wx.ALL | wx.EXPAND,
2519  border=5)
2520 
2521  btnSizer.Add(item=btnLayer,
2522  proportion=0,
2523  flag=wx.ALL | wx.ALIGN_RIGHT,
2524  border=5)
2525 
2526  layerSizer.Add(item=btnSizer,
2527  proportion=0,
2528  flag=wx.ALL | wx.EXPAND,
2529  border=0)
2530 
2531  # table description
2532  tableBox = wx.StaticBox (parent=self.addPanel, id=wx.ID_ANY,
2533  label=" %s " % (_("Table description")))
2534  tableSizer = wx.StaticBoxSizer(tableBox, wx.VERTICAL)
2535 
2536  # data area
2537  dataSizer = wx.FlexGridSizer(cols=2, hgap=5, vgap=5)
2538  dataSizer.AddGrowableCol(1)
2539  for key in ['table', 'key']:
2540  label, value = self.tableWidgets[key]
2541  dataSizer.Add(item=label,
2542  flag=wx.ALIGN_CENTER_VERTICAL)
2543  dataSizer.Add(item=value,
2544  flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND)
2545 
2546  tableSizer.Add(item=dataSizer,
2547  proportion=1,
2548  flag=wx.ALL | wx.EXPAND,
2549  border=5)
2550 
2551  tableSizer.Add(item=btnTable,
2552  proportion=0,
2553  flag=wx.ALL | wx.ALIGN_BOTTOM | wx.ALIGN_RIGHT,
2554  border=5)
2555 
2556  pageSizer.Add(item=layerSizer,
2557  proportion=3,
2558  flag=wx.ALL | wx.EXPAND,
2559  border=3)
2560 
2561  pageSizer.Add(item=tableSizer,
2562  proportion=2,
2563  flag=wx.TOP | wx.BOTTOM | wx.RIGHT | wx.EXPAND,
2564  border=3)
2565 
2566  layerSizer.SetVirtualSizeHints(self.addPanel)
2567  self.addPanel.SetAutoLayout(True)
2568  self.addPanel.SetSizer(pageSizer)
2569  pageSizer.Fit(self.addPanel)
2570 
2571  def __createDeletePage(self):
2572  """!Delete layer"""
2573  self.deletePanel = wx.Panel(parent=self, id=wx.ID_ANY)
2574  self.AddPage(page=self.deletePanel, text=_("Remove layer"))
2575 
2576  label = wx.StaticText(parent=self.deletePanel, id=wx.ID_ANY,
2577  label='%s:' % _("Layer to remove"))
2578 
2579  self.deleteLayer = wx.ComboBox(parent=self.deletePanel, id=wx.ID_ANY, size=(100, -1),
2580  style=wx.CB_SIMPLE | wx.CB_READONLY,
2581  choices=map(str, self.mapDBInfo.layers.keys()))
2582  self.deleteLayer.SetSelection(0)
2583  self.deleteLayer.Bind(wx.EVT_COMBOBOX, self.OnChangeLayer)
2584 
2585  try:
2586  tableName = self.mapDBInfo.layers[int(self.deleteLayer.GetStringSelection())]['table']
2587  except ValueError:
2588  tableName = ''
2589 
2590  self.deleteTable = wx.CheckBox(parent=self.deletePanel, id=wx.ID_ANY,
2591  label=_('Drop also linked attribute table (%s)') % \
2592  tableName)
2593 
2594  if tableName == '':
2595  self.deleteLayer.Enable(False)
2596  self.deleteTable.Enable(False)
2597 
2598  btnDelete = wx.Button(self.deletePanel, wx.ID_DELETE, _("&Remove layer"),
2599  size=(125,-1))
2600  btnDelete.Bind(wx.EVT_BUTTON, self.OnDeleteLayer)
2601 
2602  #
2603  # do layout
2604  #
2605  pageSizer = wx.BoxSizer(wx.VERTICAL)
2606 
2607  dataSizer = wx.BoxSizer(wx.VERTICAL)
2608 
2609  flexSizer = wx.FlexGridSizer(cols=2, hgap=5, vgap=5)
2610  flexSizer.AddGrowableCol(2)
2611 
2612  flexSizer.Add(item=label,
2613  flag=wx.ALIGN_CENTER_VERTICAL)
2614  flexSizer.Add(item=self.deleteLayer,
2615  flag=wx.ALIGN_CENTER_VERTICAL)
2616 
2617  dataSizer.Add(item=flexSizer,
2618  proportion=0,
2619  flag=wx.ALL | wx.EXPAND,
2620  border=1)
2621 
2622  dataSizer.Add(item=self.deleteTable,
2623  proportion=0,
2624  flag=wx.ALL | wx.EXPAND,
2625  border=1)
2626 
2627  pageSizer.Add(item=dataSizer,
2628  proportion=1,
2629  flag=wx.ALL | wx.EXPAND,
2630  border=5)
2631 
2632  pageSizer.Add(item=btnDelete,
2633  proportion=0,
2634  flag=wx.ALL | wx.ALIGN_RIGHT,
2635  border=5)
2636 
2637  self.deletePanel.SetSizer(pageSizer)
2638 
2639  def __createModifyPage(self):
2640  """!Modify layer"""
2641  self.modifyPanel = wx.Panel(parent=self, id=wx.ID_ANY)
2642  self.AddPage(page=self.modifyPanel, text=_("Modify layer"))
2643 
2644  #
2645  # list of layer widgets (label, value)
2646  #
2647  self.modifyLayerWidgets = {'layer':
2648  (wx.StaticText(parent=self.modifyPanel, id=wx.ID_ANY,
2649  label='%s:' % _("Layer")),
2650  wx.ComboBox(parent=self.modifyPanel, id=wx.ID_ANY,
2651  size=(100, -1),
2652  style=wx.CB_SIMPLE | wx.CB_READONLY,
2653  choices=map(str,
2654  self.mapDBInfo.layers.keys()))),
2655  'driver':
2656  (wx.StaticText(parent=self.modifyPanel, id=wx.ID_ANY,
2657  label='%s:' % _("Driver")),
2658  wx.Choice(parent=self.modifyPanel, id=wx.ID_ANY,
2659  size=(200, -1),
2660  choices=self.listOfDrivers)),
2661  'database':
2662  (wx.StaticText(parent=self.modifyPanel, id=wx.ID_ANY,
2663  label='%s:' % _("Database")),
2664  wx.TextCtrl(parent=self.modifyPanel, id=wx.ID_ANY,
2665  value='', size=(350, -1),
2666  style=wx.TE_PROCESS_ENTER)),
2667  'table':
2668  (wx.StaticText(parent=self.modifyPanel, id=wx.ID_ANY,
2669  label='%s:' % _("Table")),
2670  wx.Choice(parent=self.modifyPanel, id=wx.ID_ANY,
2671  size=(200, -1),
2672  choices=self.defaultTables)),
2673  'key':
2674  (wx.StaticText(parent=self.modifyPanel, id=wx.ID_ANY,
2675  label='%s:' % _("Key column")),
2676  wx.Choice(parent=self.modifyPanel, id=wx.ID_ANY,
2677  size=(200, -1),
2678  choices=self.defaultColumns))}
2679 
2680  # set default values for widgets
2681  self.modifyLayerWidgets['layer'][1].SetSelection(0)
2682  try:
2683  layer = int(self.modifyLayerWidgets['layer'][1].GetStringSelection())
2684  except ValueError:
2685  layer = None
2686  for label in self.modifyLayerWidgets.keys():
2687  self.modifyLayerWidgets[label][1].Enable(False)
2688 
2689  if layer:
2690  driver = self.mapDBInfo.layers[layer]['driver']
2691  database = self.mapDBInfo.layers[layer]['database']
2692  table = self.mapDBInfo.layers[layer]['table']
2693 
2694  listOfColumns = self.__getColumns(driver, database, table)
2695  self.modifyLayerWidgets['driver'][1].SetStringSelection(driver)
2696  self.modifyLayerWidgets['database'][1].SetValue(database)
2697  if table in self.modifyLayerWidgets['table'][1].GetItems():
2698  self.modifyLayerWidgets['table'][1].SetStringSelection(table)
2699  else:
2700  if self.defaultConnect['schema'] != '':
2701  table = self.defaultConnect['schema'] + table # try with default schema
2702  else:
2703  table = 'public.' + table # try with 'public' schema
2704  self.modifyLayerWidgets['table'][1].SetStringSelection(table)
2705  self.modifyLayerWidgets['key'][1].SetItems(listOfColumns)
2706  self.modifyLayerWidgets['key'][1].SetSelection(0)
2707 
2708  # events
2709  self.modifyLayerWidgets['layer'][1].Bind(wx.EVT_COMBOBOX, self.OnChangeLayer)
2710  # self.modifyLayerWidgets['driver'][1].Bind(wx.EVT_CHOICE, self.OnDriverChanged)
2711  # self.modifyLayerWidgets['database'][1].Bind(wx.EVT_TEXT_ENTER, self.OnDatabaseChanged)
2712  # self.modifyLayerWidgets['table'][1].Bind(wx.EVT_CHOICE, self.OnTableChanged)
2713 
2714  btnModify = wx.Button(self.modifyPanel, wx.ID_DELETE, _("&Modify layer"),
2715  size=(125,-1))
2716  btnModify.Bind(wx.EVT_BUTTON, self.OnModifyLayer)
2717 
2718  #
2719  # do layout
2720  #
2721  pageSizer = wx.BoxSizer(wx.VERTICAL)
2722 
2723  # data area
2724  dataSizer = wx.FlexGridSizer(cols=2, hgap=5, vgap=5)
2725  dataSizer.AddGrowableCol(1)
2726  for key in ('layer', 'driver', 'database', 'table', 'key'):
2727  label, value = self.modifyLayerWidgets[key]
2728  dataSizer.Add(item=label,
2729  flag=wx.ALIGN_CENTER_VERTICAL)
2730  if label.GetLabel() == "Layer:":
2731  dataSizer.Add(item=value,
2732  flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
2733  else:
2734  dataSizer.Add(item=value,
2735  flag=wx.ALIGN_CENTER_VERTICAL)
2736 
2737  pageSizer.Add(item=dataSizer,
2738  proportion=1,
2739  flag=wx.ALL | wx.EXPAND,
2740  border=5)
2741 
2742  pageSizer.Add(item=btnModify,
2743  proportion=0,
2744  flag=wx.ALL | wx.ALIGN_RIGHT,
2745  border=5)
2746 
2747  self.modifyPanel.SetSizer(pageSizer)
2748 
2749  def __getTables(self, driver, database):
2750  """!Get list of tables for given driver and database"""
2751  tables = []
2752 
2753  ret = gcmd.RunCommand('db.tables',
2754  parent = self,
2755  read = True,
2756  flags = 'p',
2757  driver = driver,
2758  database = database)
2759 
2760  if ret is None:
2761  wx.MessageBox(parent=self,
2762  message=_("Unable to get list of tables.\n"
2763  "Please use db.connect to set database parameters."),
2764  caption=_("Error"), style=wx.OK | wx.ICON_ERROR | wx.CENTRE)
2765 
2766  return tables
2767 
2768  for table in ret.splitlines():
2769  tables.append(table)
2770 
2771  return tables
2772 
2773  def __getColumns(self, driver, database, table):
2774  """!Get list of column of given table"""
2775  columns = []
2776 
2777  ret = gcmd.RunCommand('db.columns',
2778  parent = self,
2779  quiet = True,
2780  read = True,
2781  driver = driver,
2782  database = database,
2783  table = table)
2784 
2785  if ret == None:
2786  return columns
2787 
2788  for column in ret.splitlines():
2789  columns.append(column)
2790 
2791  return columns
2792 
2793  def OnDriverChanged(self, event):
2794  """!Driver selection changed, update list of tables"""
2795  driver = event.GetString()
2796  database = self.addLayerWidgets['database'][1].GetValue()
2797 
2798  winTable = self.addLayerWidgets['table'][1]
2799  winKey = self.addLayerWidgets['key'][1]
2800  tables = self.__getTables(driver, database)
2801 
2802  winTable.SetItems(tables)
2803  winTable.SetSelection(0)
2804 
2805  if len(tables) == 0:
2806  winKey.SetItems([])
2807 
2808  event.Skip()
2809 
2810  def OnDatabaseChanged(self, event):
2811  """!Database selection changed, update list of tables"""
2812  event.Skip()
2813 
2814  def OnTableChanged(self, event):
2815  """!Table name changed, update list of columns"""
2816  driver = self.addLayerWidgets['driver'][1].GetStringSelection()
2817  database = self.addLayerWidgets['database'][1].GetValue()
2818  table = event.GetString()
2819 
2820  win = self.addLayerWidgets['key'][1]
2821  cols = self.__getColumns(driver, database, table)
2822  win.SetItems(cols)
2823  win.SetSelection(0)
2824 
2825  event.Skip()
2826 
2827  def OnSetDefault(self, event):
2828  """!Set default values"""
2829  driver = self.addLayerWidgets['driver'][1]
2830  database = self.addLayerWidgets['database'][1]
2831  table = self.addLayerWidgets['table'][1]
2832  key = self.addLayerWidgets['key'][1]
2833 
2834  driver.SetStringSelection(self.defaultConnect['driver'])
2835  database.SetValue(self.defaultConnect['database'])
2836  tables = self.__getTables(self.defaultConnect['driver'],
2837  self.defaultConnect['database'])
2838  table.SetItems(tables)
2839  table.SetSelection(0)
2840  if len(tables) == 0:
2841  key.SetItems([])
2842  else:
2843  cols = self.__getColumns(self.defaultConnect['driver'],
2844  self.defaultConnect['database'],
2845  tables[0])
2846  key.SetItems(cols)
2847  key.SetSelection(0)
2848 
2849  event.Skip()
2850 
2851  def OnCreateTable(self, event):
2852  """!Create new table (name and key column given)"""
2853  driver = self.addLayerWidgets['driver'][1].GetStringSelection()
2854  database = self.addLayerWidgets['database'][1].GetValue()
2855  table = self.tableWidgets['table'][1].GetValue()
2856  key = self.tableWidgets['key'][1].GetValue()
2857 
2858  if not table or not key:
2859  wx.MessageBox(parent=self,
2860  message=_("Unable to create new table. "
2861  "Table name or key column name is missing."),
2862  caption=_("Error"), style=wx.OK | wx.ICON_ERROR | wx.CENTRE)
2863  return
2864 
2865  if table in self.addLayerWidgets['table'][1].GetItems():
2866  wx.MessageBox(parent=self,
2867  message=_("Unable to create new table. "
2868  "Table <%s> already exists in the database.") % table,
2869  caption=_("Error"), style=wx.OK | wx.ICON_ERROR | wx.CENTRE)
2870  return
2871 
2872  # create table
2873  sql = 'CREATE TABLE %s (%s INTEGER)' % (table, key)
2874 
2875  gcmd.RunCommand('db.execute',
2876  quiet = True,
2877  parent = self,
2878  stdin = sql,
2879  driver = driver,
2880  database = database)
2881 
2882  # update list of tables
2883  tableList = self.addLayerWidgets['table'][1]
2884  tableList.SetItems(self.__getTables(driver, database))
2885  tableList.SetStringSelection(table)
2886 
2887  # update key column selection
2888  keyList = self.addLayerWidgets['key'][1]
2889  keyList.SetItems(self.__getColumns(driver, database, table))
2890  keyList.SetStringSelection(key)
2891 
2892  event.Skip()
2893 
2894  def OnAddLayer(self, event):
2895  """!Add new layer to vector map"""
2896  layer = int(self.addLayerWidgets['layer'][1].GetValue())
2897  layerWin = self.addLayerWidgets['layer'][1]
2898  driver = self.addLayerWidgets['driver'][1].GetStringSelection()
2899  database = self.addLayerWidgets['database'][1].GetValue()
2900  table = self.addLayerWidgets['table'][1].GetStringSelection()
2901  key = self.addLayerWidgets['key'][1].GetStringSelection()
2902 
2903  if layer in self.mapDBInfo.layers.keys():
2904  wx.MessageBox(parent=self,
2905  message=_("Unable to add new layer to vector map <%(vector)s>. "
2906  "Layer %(layer)d already exists.") %
2907  {'vector' : self.mapDBInfo.map, 'layer' : layer},
2908  caption=_("Error"), style=wx.OK | wx.ICON_ERROR | wx.CENTRE)
2909  return
2910 
2911  # add new layer
2912  ret = gcmd.RunCommand('v.db.connect',
2913  parent = self,
2914  quiet = True,
2915  map = self.mapDBInfo.map,
2916  driver = driver,
2917  database = database,
2918  table = table,
2919  key = key,
2920  layer = layer)
2921 
2922  # insert records into table if required
2923  if self.addLayerWidgets['addCat'][0].IsChecked():
2924  gcmd.RunCommand('v.to.db',
2925  parent = self,
2926  quiet = True,
2927  map = self.mapDBInfo.map,
2928  layer = layer,
2929  qlayer = layer,
2930  option = 'cat',
2931  columns = key)
2932 
2933  if ret == 0:
2934  # update dialog (only for new layer)
2935  self.parentDialog.UpdateDialog(layer=layer)
2936  # update db info
2937  self.mapDBInfo = self.parentDialog.mapDBInfo
2938  # increase layer number
2939  layerWin.SetValue(layer+1)
2940 
2941  if len(self.mapDBInfo.layers.keys()) == 1:
2942  # first layer add --- enable previously disabled widgets
2943  self.deleteLayer.Enable()
2944  self.deleteTable.Enable()
2945  for label in self.modifyLayerWidgets.keys():
2946  self.modifyLayerWidgets[label][1].Enable()
2947 
2948  def OnDeleteLayer(self, event):
2949  """!Delete layer"""
2950  try:
2951  layer = int(self.deleteLayer.GetValue())
2952  except:
2953  return
2954 
2955  gcmd.RunCommand('v.db.connect',
2956  parent = self,
2957  flags = 'd',
2958  map = self.mapDBInfo.map,
2959  layer = layer)
2960 
2961  # drop also table linked to layer which is deleted
2962  if self.deleteTable.IsChecked():
2963  driver = self.addLayerWidgets['driver'][1].GetStringSelection()
2964  database = self.addLayerWidgets['database'][1].GetValue()
2965  table = self.mapDBInfo.layers[layer]['table']
2966  sql = 'DROP TABLE %s' % (table)
2967 
2968  gcmd.RunCommand('db.execute',
2969  parent = self,
2970  stdin = sql,
2971  quiet = True,
2972  driver = driver,
2973  database = database)
2974 
2975  # update list of tables
2976  tableList = self.addLayerWidgets['table'][1]
2977  tableList.SetItems(self.__getTables(driver, database))
2978  tableList.SetStringSelection(table)
2979 
2980  # update dialog
2981  self.parentDialog.UpdateDialog(layer=layer)
2982  # update db info
2983  self.mapDBInfo = self.parentDialog.mapDBInfo
2984 
2985  if len(self.mapDBInfo.layers.keys()) == 0:
2986  # disable selected widgets
2987  self.deleteLayer.Enable(False)
2988  self.deleteTable.Enable(False)
2989  for label in self.modifyLayerWidgets.keys():
2990  self.modifyLayerWidgets[label][1].Enable(False)
2991 
2992  event.Skip()
2993 
2994  def OnChangeLayer(self, event):
2995  """!Layer number of layer to be deleted is changed"""
2996  try:
2997  layer = int(event.GetString())
2998  except:
2999  try:
3000  layer = self.mapDBInfo.layers.keys()[0]
3001  except:
3002  return
3003 
3004  if self.GetCurrentPage() == self.modifyPanel:
3005  driver = self.mapDBInfo.layers[layer]['driver']
3006  database = self.mapDBInfo.layers[layer]['database']
3007  table = self.mapDBInfo.layers[layer]['table']
3008  listOfColumns = self.__getColumns(driver, database, table)
3009  self.modifyLayerWidgets['driver'][1].SetStringSelection(driver)
3010  self.modifyLayerWidgets['database'][1].SetValue(database)
3011  self.modifyLayerWidgets['table'][1].SetStringSelection(table)
3012  self.modifyLayerWidgets['key'][1].SetItems(listOfColumns)
3013  self.modifyLayerWidgets['key'][1].SetSelection(0)
3014  else:
3015  self.deleteTable.SetLabel(_('Drop also linked attribute table (%s)') % \
3016  self.mapDBInfo.layers[layer]['table'])
3017  if event:
3018  event.Skip()
3019 
3020  def OnModifyLayer(self, event):
3021  """!Modify layer connection settings"""
3022 
3023  layer = int(self.modifyLayerWidgets['layer'][1].GetStringSelection())
3024 
3025  modify = False
3026  if self.modifyLayerWidgets['driver'][1].GetStringSelection() != \
3027  self.mapDBInfo.layers[layer]['driver'] or \
3028  self.modifyLayerWidgets['database'][1].GetStringSelection() != \
3029  self.mapDBInfo.layers[layer]['database'] or \
3030  self.modifyLayerWidgets['table'][1].GetStringSelection() != \
3031  self.mapDBInfo.layers[layer]['table'] or \
3032  self.modifyLayerWidgets['key'][1].GetStringSelection() != \
3033  self.mapDBInfo.layers[layer]['key']:
3034  modify = True
3035 
3036  if modify:
3037  # delete layer
3038  gcmd.RunCommand('v.db.connect',
3039  parent = self,
3040  quiet = True,
3041  flag = 'd',
3042  map = self.mapDBInfo.map,
3043  layer = layer)
3044 
3045  # add modified layer
3046  gcmd.RunCommand('v.db.connect',
3047  quiet = True,
3048  map = self.mapDBInfo.map,
3049  driver = self.modifyLayerWidgets['driver'][1].GetStringSelection(),
3050  database = self.modifyLayerWidgets['database'][1].GetValue(),
3051  table = self.modifyLayerWidgets['table'][1].GetStringSelection(),
3052  key = self.modifyLayerWidgets['key'][1].GetStringSelection(),
3053  layer = int(layer))
3054 
3055  # update dialog (only for new layer)
3056  self.parentDialog.UpdateDialog(layer=layer)
3057  # update db info
3058  self.mapDBInfo = self.parentDialog.mapDBInfo
3059 
3060  event.Skip()
3061 
3062 def main(argv=None):
3063  if argv is None:
3064  argv = sys.argv
3065 
3066  if len(argv) != 2:
3067  print >> sys.stderr, __doc__
3068  sys.exit()
3069 
3070  # Command line arguments of the script to be run are preserved by the
3071  # hotswap.py wrapper but hotswap.py and its options are removed that
3072  # sys.argv looks as if no wrapper was present.
3073  #print "argv:", `argv`
3074 
3075  #some applications might require image handlers
3076  wx.InitAllImageHandlers()
3077 
3078  app = wx.PySimpleApp()
3079  f = AttributeManager(parent=None, id=wx.ID_ANY,
3080  title="%s - <%s>" % (_("GRASS GIS Attribute Table Manager"),
3081  argv[1]),
3082  size=(900,600), vectorName=argv[1])
3083  f.Show()
3084 
3085  app.MainLoop()
3086 
3087 if __name__ == '__main__':
3088  main()