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