GRASS Programmer's Manual  6.4.2(2012)
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
dbm_dialogs.py
Go to the documentation of this file.
1 """!
2 @package dbm_dialogs.py
3 
4 @brief DBM-related dialogs
5 
6 List of classes:
7  - DisplayAttributesDialog
8  - ModifyTableRecord
9 
10 (C) 2007-2011 by the GRASS Development Team
11 
12 This program is free software under the GNU General Public
13 License (>=v2). Read the file COPYING that comes with GRASS
14 for details.
15 
16 @author Martin Landa <landa.martin gmail.com>
17 """
18 
19 import os
20 
21 import globalvar
22 import wx
23 import wx.lib.scrolledpanel as scrolled
24 
25 import gcmd
26 from debug import Debug
27 from preferences import globalSettings as UserSettings
28 from dbm_base import VectorDBInfo
29 
30 class DisplayAttributesDialog(wx.Dialog):
31  def __init__(self, parent, map,
32  query = None, cats = None, line = None,
33  style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER,
34  pos = wx.DefaultPosition,
35  action = "add"):
36  """!Standard dialog used to add/update/display attributes linked
37  to the vector map.
38 
39  Attribute data can be selected based on layer and category number
40  or coordinates.
41 
42  @param parent
43  @param map vector map
44  @param query query coordinates and distance (used for v.edit)
45  @param cats {layer: cats}
46  @param line feature id (requested for cats)
47  @param style
48  @param pos
49  @param action (add, update, display)
50  """
51  self.parent = parent # mapdisplay.BufferedWindow
52  self.map = map
53  self.action = action
54 
55  # ids/cats of selected features
56  # fid : {layer : cats}
57  self.cats = {}
58  self.fid = -1 # feature id
59 
60  # get layer/table/column information
61  self.mapDBInfo = VectorDBInfo(self.map)
62 
63  layers = self.mapDBInfo.layers.keys() # get available layers
64 
65  # check if db connection / layer exists
66  if len(layers) <= 0:
67  label = _("Database connection "
68  "is not defined in DB file.")
69 
70  gcmd.GMessage(parent = self.parent,
71  message = _("No attribute table linked to "
72  "vector map <%(vector)s> found. %(msg)s\n\n"
73  "New attribute table can be created by "
74  "Attribute Table Manager.") %
75  {'vector' : self.map, 'msg' : label})
76  self.mapDBInfo = None
77 
78  wx.Dialog.__init__(self, parent = self.parent, id = wx.ID_ANY,
79  title = "", style = style, pos = pos)
80 
81  # dialog body
82  mainSizer = wx.BoxSizer(wx.VERTICAL)
83 
84  # notebook
85  self.notebook = wx.Notebook(parent = self, id = wx.ID_ANY, style = wx.BK_DEFAULT)
86 
87  self.closeDialog = wx.CheckBox(parent = self, id = wx.ID_ANY,
88  label = _("Close dialog on submit"))
89  self.closeDialog.SetValue(True)
90  if self.action == 'display':
91  self.closeDialog.Enable(False)
92 
93  # feature id (text/choice for duplicates)
94  self.fidMulti = wx.Choice(parent = self, id = wx.ID_ANY,
95  size = (150, -1))
96  self.fidMulti.Bind(wx.EVT_CHOICE, self.OnFeature)
97  self.fidText = wx.StaticText(parent = self, id = wx.ID_ANY)
98 
99  self.noFoundMsg = wx.StaticText(parent = self, id = wx.ID_ANY,
100  label = _("No attributes found"))
101 
102  self.UpdateDialog(query = query, cats = cats)
103 
104  # set title
105  if self.action == "update":
106  self.SetTitle(_("Update attributes"))
107  elif self.action == "add":
108  self.SetTitle(_("Define attributes"))
109  else:
110  self.SetTitle(_("Display attributes"))
111 
112  # buttons
113  btnCancel = wx.Button(self, wx.ID_CANCEL)
114  btnReset = wx.Button(self, wx.ID_UNDO, _("&Reload"))
115  btnSubmit = wx.Button(self, wx.ID_OK, _("&Submit"))
116  if self.action == 'display':
117  btnSubmit.Enable(False)
118 
119  btnSizer = wx.StdDialogButtonSizer()
120  btnSizer.AddButton(btnCancel)
121  btnSizer.AddButton(btnReset)
122  btnSizer.SetNegativeButton(btnReset)
123  btnSubmit.SetDefault()
124  btnSizer.AddButton(btnSubmit)
125  btnSizer.Realize()
126 
127  mainSizer.Add(item = self.noFoundMsg, proportion = 0,
128  flag = wx.EXPAND | wx.ALL, border = 5)
129  mainSizer.Add(item = self.notebook, proportion = 1,
130  flag = wx.EXPAND | wx.ALL, border = 5)
131  fidSizer = wx.BoxSizer(wx.HORIZONTAL)
132  fidSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
133  label = _("Feature id:")),
134  proportion = 0, border = 5,
135  flag = wx.ALIGN_CENTER_VERTICAL)
136  fidSizer.Add(item = self.fidMulti, proportion = 0,
137  flag = wx.EXPAND | wx.ALL, border = 5)
138  fidSizer.Add(item = self.fidText, proportion = 0,
139  flag = wx.EXPAND | wx.ALL, border = 5)
140  mainSizer.Add(item = fidSizer, proportion = 0,
141  flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border = 5)
142  mainSizer.Add(item = self.closeDialog, proportion = 0, flag = wx.EXPAND | wx.LEFT | wx.RIGHT,
143  border = 5)
144  mainSizer.Add(item = btnSizer, proportion = 0,
145  flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)
146 
147  # bindigs
148  btnReset.Bind(wx.EVT_BUTTON, self.OnReset)
149  btnSubmit.Bind(wx.EVT_BUTTON, self.OnSubmit)
150  btnCancel.Bind(wx.EVT_BUTTON, self.OnCancel)
151 
152  self.SetSizer(mainSizer)
153  mainSizer.Fit(self)
154 
155  # set min size for dialog
156  w, h = self.GetBestSize()
157  if h < 200:
158  self.SetMinSize((w, 200))
159  else:
160  self.SetMinSize(self.GetBestSize())
161 
162  if self.notebook.GetPageCount() == 0:
163  Debug.msg(2, "DisplayAttributesDialog(): Nothing found!")
164  ### self.mapDBInfo = None
165 
166  def __SelectAttributes(self, layer):
167  """!Select attributes"""
168  pass
169 
170  def OnSQLStatement(self, event):
171  """!Update SQL statement"""
172  pass
173 
174  def IsFound(self):
175  """!Check for status
176 
177  @return True on attributes found
178  @return False attributes not found
179  """
180  return bool(self.notebook.GetPageCount())
181 
182  def GetSQLString(self, updateValues = False):
183  """!Create SQL statement string based on self.sqlStatement
184 
185  If updateValues is True, update dataFrame according to values
186  in textfields.
187  """
188  sqlCommands = []
189  # find updated values for each layer/category
190  for layer in self.mapDBInfo.layers.keys(): # for each layer
191  table = self.mapDBInfo.GetTable(layer)
192  key = self.mapDBInfo.GetKeyColumn(layer)
193  columns = self.mapDBInfo.GetTableDesc(table)
194  for idx in range(len(columns[key]['values'])): # for each category
195  updatedColumns = []
196  updatedValues = []
197  for name in columns.keys():
198  if name == key:
199  cat = columns[name]['values'][idx]
200  continue
201  type = columns[name]['type']
202  value = columns[name]['values'][idx]
203  id = columns[name]['ids'][idx]
204  try:
205  newvalue = self.FindWindowById(id).GetValue()
206  except:
207  newvalue = self.FindWindowById(id).GetLabel()
208 
209  if newvalue == '':
210  newvalue = None
211 
212  if newvalue != value:
213  updatedColumns.append(name)
214  if newvalue is None:
215  updatedValues.append('NULL')
216  else:
217  if type != 'character':
218  updatedValues.append(newvalue)
219  else:
220  updatedValues.append("'" + newvalue + "'")
221  columns[name]['values'][idx] = newvalue
222 
223  if self.action != "add" and len(updatedValues) == 0:
224  continue
225 
226  if self.action == "add":
227  sqlString = "INSERT INTO %s (%s," % (table, key)
228  else:
229  sqlString = "UPDATE %s SET " % table
230 
231  for idx in range(len(updatedColumns)):
232  name = updatedColumns[idx]
233  if self.action == "add":
234  sqlString += name + ","
235  else:
236  sqlString += name + "=" + updatedValues[idx] + ","
237 
238  sqlString = sqlString[:-1] # remove last comma
239 
240  if self.action == "add":
241  sqlString += ") VALUES (%s," % cat
242  for value in updatedValues:
243  sqlString += str(value) + ","
244  sqlString = sqlString[:-1] # remove last comma
245  sqlString += ")"
246  else:
247  sqlString += " WHERE cat=%s" % cat
248  sqlCommands.append(sqlString)
249  # for each category
250  # for each layer END
251 
252  Debug.msg(3, "DisplayAttributesDialog.GetSQLString(): %s" % sqlCommands)
253 
254  return sqlCommands
255 
256  def OnReset(self, event = None):
257  """!Reset form"""
258  for layer in self.mapDBInfo.layers.keys():
259  table = self.mapDBInfo.layers[layer]["table"]
260  key = self.mapDBInfo.layers[layer]["key"]
261  columns = self.mapDBInfo.tables[table]
262  for idx in range(len(columns[key]['values'])):
263  for name in columns.keys():
264  type = columns[name]['type']
265  value = columns[name]['values'][idx]
266  if value is None:
267  value = ''
268  try:
269  id = columns[name]['ids'][idx]
270  except IndexError:
271  id = wx.NOT_FOUND
272 
273  if name != key and id != wx.NOT_FOUND:
274  self.FindWindowById(id).SetValue(str(value))
275 
276  def OnCancel(self, event):
277  """!Cancel button pressed
278  """
279  self.parent.parent.dialogs['attributes'] = None
280 
281  if hasattr(self, "digit"):
282  self.parent.digit.GetDisplay().SetSelected([])
283  self.parent.UpdateMap(render = False)
284  else:
285  self.parent.parent.OnRender(None)
286 
287  self.Close()
288 
289  def OnSubmit(self, event):
290  """!Submit records"""
291  ret = 0
292  for sql in self.GetSQLString(updateValues = True):
293  enc = UserSettings.Get(group = 'atm', key = 'encoding', subkey = 'value')
294  if not enc and 'GRASS_DB_ENCODING' in os.environ:
295  enc = os.environ['GRASS_DB_ENCODING']
296  if enc:
297  sql = sql.encode(enc)
298 
299  ret += gcmd.RunCommand('db.execute',
300  parent = self,
301  quiet = True,
302  stdin = sql)
303 
304  if ret == 0 and self.closeDialog.IsChecked():
305  self.OnCancel(event)
306 
307  def OnFeature(self, event):
308  self.fid = int(event.GetString())
309  self.UpdateDialog(cats = self.cats, fid = self.fid)
310 
311  def GetCats(self):
312  """!Get id of selected vector object or 'None' if nothing selected
313 
314  @param id if true return ids otherwise cats
315  """
316  if self.fid < 0:
317  return None
318 
319  return self.cats[self.fid]
320 
321  def GetFid(self):
322  """!Get selected feature id"""
323  return self.fid
324 
325  def UpdateDialog(self, map = None, query = None, cats = None, fid = -1,
326  action = None):
327  """!Update dialog
328 
329  @param map name of vector map
330  @param query
331  @param cats
332  @param fid feature id
333  @param action add, update, display or None
334 
335  @return True if updated
336  @return False
337  """
338  if action:
339  self.action = action
340  if action == 'display':
341  enabled = False
342  else:
343  enabled = True
344  self.closeDialog.Enable(enabled)
345  self.FindWindowById(wx.ID_OK).Enable(enabled)
346 
347  if map:
348  self.map = map
349  # get layer/table/column information
350  self.mapDBInfo = VectorDBInfo(self.map)
351 
352  if not self.mapDBInfo:
353  return False
354 
355  self.mapDBInfo.Reset()
356 
357  layers = self.mapDBInfo.layers.keys() # get available layers
358 
359  # id of selected line
360  if query: # select by position
361  data = self.mapDBInfo.SelectByPoint(query[0],
362  query[1])
363  self.cats = {}
364  if data and 'Layer' in data:
365  idx = 0
366  for layer in data['Layer']:
367  layer = int(layer)
368  if 'Id' in data:
369  tfid = int(data['Id'][idx])
370  else:
371  tfid = 0 # Area / Volume
372  if not tfid in self.cats:
373  self.cats[tfid] = {}
374  if not layer in self.cats[tfid]:
375  self.cats[tfid][layer] = []
376  cat = int(data['Category'][idx])
377  self.cats[tfid][layer].append(cat)
378  idx += 1
379  else:
380  self.cats = cats
381 
382  if fid > 0:
383  self.fid = fid
384  elif len(self.cats.keys()) > 0:
385  self.fid = self.cats.keys()[0]
386  else:
387  self.fid = -1
388 
389  if len(self.cats.keys()) == 1:
390  self.fidMulti.Show(False)
391  self.fidText.Show(True)
392  if self.fid > 0:
393  self.fidText.SetLabel("%d" % self.fid)
394  else:
395  self.fidText.SetLabel(_("Unknown"))
396  else:
397  self.fidMulti.Show(True)
398  self.fidText.Show(False)
399  choices = []
400  for tfid in self.cats.keys():
401  choices.append(str(tfid))
402  self.fidMulti.SetItems(choices)
403  self.fidMulti.SetStringSelection(str(self.fid))
404 
405  # reset notebook
406  self.notebook.DeleteAllPages()
407 
408  for layer in layers: # for each layer
409  if not query: # select by layer/cat
410  if self.fid > 0 and layer in self.cats[self.fid]:
411  for cat in self.cats[self.fid][layer]:
412  nselected = self.mapDBInfo.SelectFromTable(layer,
413  where = "%s=%d" % \
414  (self.mapDBInfo.layers[layer]['key'],
415  cat))
416  else:
417  nselected = 0
418 
419  # if nselected <= 0 and self.action != "add":
420  # continue # nothing selected ...
421 
422  if self.action == "add":
423  if nselected <= 0:
424  if layer in self.cats[self.fid]:
425  table = self.mapDBInfo.layers[layer]["table"]
426  key = self.mapDBInfo.layers[layer]["key"]
427  columns = self.mapDBInfo.tables[table]
428  for name in columns.keys():
429  if name == key:
430  for cat in self.cats[self.fid][layer]:
431  self.mapDBInfo.tables[table][name]['values'].append(cat)
432  else:
433  self.mapDBInfo.tables[table][name]['values'].append(None)
434  else: # change status 'add' -> 'update'
435  self.action = "update"
436 
437  table = self.mapDBInfo.layers[layer]["table"]
438  key = self.mapDBInfo.layers[layer]["key"]
439  columns = self.mapDBInfo.tables[table]
440 
441  for idx in range(len(columns[key]['values'])):
442  for name in columns.keys():
443  if name == key:
444  cat = int(columns[name]['values'][idx])
445  break
446 
447  # use scrolled panel instead (and fix initial max height of the window to 480px)
448  panel = scrolled.ScrolledPanel(parent = self.notebook, id = wx.ID_ANY,
449  size = (-1, 150))
450  panel.SetupScrolling(scroll_x = False)
451 
452  self.notebook.AddPage(page = panel, text = " %s %d / %s %d" % (_("Layer"), layer,
453  _("Category"), cat))
454 
455  # notebook body
456  border = wx.BoxSizer(wx.VERTICAL)
457 
458  flexSizer = wx.FlexGridSizer (cols = 4, hgap = 3, vgap = 3)
459  flexSizer.AddGrowableCol(3)
460  # columns (sorted by index)
461  names = [''] * len(columns.keys())
462  for name in columns.keys():
463  names[columns[name]['index']] = name
464 
465  for name in names:
466  if name == key: # skip key column (category)
467  continue
468 
469  vtype = columns[name]['type']
470 
471  if columns[name]['values'][idx] is not None:
472  if columns[name]['ctype'] != type(''):
473  value = str(columns[name]['values'][idx])
474  else:
475  value = columns[name]['values'][idx]
476  else:
477  value = ''
478 
479  colName = wx.StaticText(parent = panel, id = wx.ID_ANY,
480  label = name)
481  colType = wx.StaticText(parent = panel, id = wx.ID_ANY,
482  label = "[" + vtype.lower() + "]")
483  delimiter = wx.StaticText(parent = panel, id = wx.ID_ANY, label = ":")
484 
485  colValue = wx.TextCtrl(parent = panel, id = wx.ID_ANY, value = value)
486  colValue.SetName(name)
487  self.Bind(wx.EVT_TEXT, self.OnSQLStatement, colValue)
488  if self.action == 'display':
489  colValue.SetWindowStyle(wx.TE_READONLY)
490 
491  flexSizer.Add(colName, proportion = 0,
492  flag = wx.FIXED_MINSIZE | wx.ALIGN_CENTER_VERTICAL)
493  flexSizer.Add(colType, proportion = 0,
494  flag = wx.FIXED_MINSIZE | wx.ALIGN_CENTER_VERTICAL)
495  flexSizer.Add(delimiter, proportion = 0,
496  flag = wx.FIXED_MINSIZE | wx.ALIGN_CENTER_VERTICAL)
497  flexSizer.Add(colValue, proportion = 1,
498  flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL)
499  # add widget reference to self.columns
500  columns[name]['ids'].append(colValue.GetId()) # name, type, values, id
501  # for each attribute (including category) END
502  border.Add(item = flexSizer, proportion = 1, flag = wx.ALL | wx.EXPAND, border = 5)
503  panel.SetSizer(border)
504  # for each category END
505  # for each layer END
506 
507  if self.notebook.GetPageCount() == 0:
508  self.noFoundMsg.Show(True)
509  else:
510  self.noFoundMsg.Show(False)
511 
512  self.Layout()
513 
514  return True
515 
516  def SetColumnValue(self, layer, column, value):
517  """!Set attrbute value
518 
519  @param column column name
520  @param value value
521  """
522  table = self.mapDBInfo.GetTable(layer)
523  columns = self.mapDBInfo.GetTableDesc(table)
524 
525  for key, col in columns.iteritems():
526  if key == column:
527  col['values'] = [col['ctype'](value),]
528  break
529 
530 class ModifyTableRecord(wx.Dialog):
531  def __init__(self, parent, title, data, keyEditable = (-1, True),
532  id = wx.ID_ANY, style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER):
533  """!Dialog for inserting/updating table record
534 
535  @param data a list: [(column, value)]
536  @param KeyEditable (id, editable?) indicates if textarea for key column
537  is editable(True) or not
538  """
539  # parent -> VDigitWindow
540  wx.Dialog.__init__(self, parent, id, title, style = style)
541 
542  self.CenterOnParent()
543 
544  self.keyId = keyEditable[0]
545 
546  box = wx.StaticBox(parent = self, id = wx.ID_ANY)
547  box.Hide()
548  self.dataPanel = scrolled.ScrolledPanel(parent = self, id = wx.ID_ANY,
549  style = wx.TAB_TRAVERSAL)
550  self.dataPanel.SetupScrolling(scroll_x = False)
551 
552  # buttons
553  self.btnCancel = wx.Button(self, wx.ID_CANCEL)
554  self.btnSubmit = wx.Button(self, wx.ID_OK, _("&Submit"))
555  self.btnSubmit.SetDefault()
556 
557  # data area
558  self.widgets = []
559  cId = 0
560  self.usebox = False
561  self.cat = None
562  for column, value in data:
563  if self.keyId == cId:
564  self.cat = int(value)
565  if not keyEditable[1]:
566  self.usebox = True
567  box.SetLabel(" %s %d " % (_("Category"), self.cat))
568  box.Show()
569  self.boxSizer = wx.StaticBoxSizer(box, wx.VERTICAL)
570  cId += 1
571  continue
572  else:
573  valueWin = wx.SpinCtrl(parent = self.dataPanel, id = wx.ID_ANY,
574  value = value, min = -1e9, max = 1e9, size = (250, -1))
575  else:
576  valueWin = wx.TextCtrl(parent = self.dataPanel, id = wx.ID_ANY,
577  value = value, size = (250, -1))
578 
579  label = wx.StaticText(parent = self.dataPanel, id = wx.ID_ANY,
580  label = column + ":")
581 
582  self.widgets.append((label.GetId(), valueWin.GetId()))
583 
584  cId += 1
585 
586  self._layout()
587 
588  def _layout(self):
589  """!Do layout"""
590  sizer = wx.BoxSizer(wx.VERTICAL)
591 
592  # data area
593  dataSizer = wx.FlexGridSizer (cols = 2, hgap = 3, vgap = 3)
594  dataSizer.AddGrowableCol(1)
595 
596  for labelId, valueId in self.widgets:
597  label = self.FindWindowById(labelId)
598  value = self.FindWindowById(valueId)
599 
600  dataSizer.Add(label, proportion = 0,
601  flag = wx.ALIGN_CENTER_VERTICAL)
602  dataSizer.Add(value, proportion = 0,
603  flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL)
604 
605  self.dataPanel.SetAutoLayout(True)
606  self.dataPanel.SetSizer(dataSizer)
607  dataSizer.Fit(self.dataPanel)
608 
609  if self.usebox:
610  self.boxSizer.Add(item = self.dataPanel, proportion = 1,
611  flag = wx.EXPAND | wx.ALL, border = 5)
612 
613  # buttons
614  btnSizer = wx.StdDialogButtonSizer()
615  btnSizer.AddButton(self.btnCancel)
616  btnSizer.AddButton(self.btnSubmit)
617  btnSizer.Realize()
618 
619  if not self.usebox:
620  sizer.Add(item = self.dataPanel, proportion = 1,
621  flag = wx.EXPAND | wx.ALL, border = 5)
622  else:
623  sizer.Add(item = self.boxSizer, proportion = 1,
624  flag = wx.EXPAND | wx.ALL, border = 5)
625 
626  sizer.Add(item = btnSizer, proportion = 0,
627  flag = wx.EXPAND | wx.ALL, border = 5)
628 
629  framewidth = self.GetSize()[0]
630  self.SetMinSize((framewidth,250))
631 
632  self.SetAutoLayout(True)
633  self.SetSizer(sizer)
634  sizer.Fit(self)
635 
636  self.Layout()
637 
638  def GetValues(self, columns = None):
639  """!Return list of values (casted to string).
640 
641  If columns is given (list), return only values of given columns.
642  """
643  valueList = []
644  for labelId, valueId in self.widgets:
645  column = self.FindWindowById(labelId).GetLabel().replace(':', '')
646  if columns is None or column in columns:
647  value = str(self.FindWindowById(valueId).GetValue())
648  valueList.append(value)
649 
650  # add key value
651  if self.usebox:
652  valueList.insert(self.keyId, str(self.cat))
653 
654  return valueList