GRASS Programmer's Manual
6.4.2(2012)
|
00001 """! 00002 @package dbm_dialogs.py 00003 00004 @brief DBM-related dialogs 00005 00006 List of classes: 00007 - DisplayAttributesDialog 00008 - ModifyTableRecord 00009 00010 (C) 2007-2011 by the GRASS Development Team 00011 00012 This program is free software under the GNU General Public 00013 License (>=v2). Read the file COPYING that comes with GRASS 00014 for details. 00015 00016 @author Martin Landa <landa.martin gmail.com> 00017 """ 00018 00019 import os 00020 00021 import globalvar 00022 import wx 00023 import wx.lib.scrolledpanel as scrolled 00024 00025 import gcmd 00026 from debug import Debug 00027 from preferences import globalSettings as UserSettings 00028 from dbm_base import VectorDBInfo 00029 00030 class DisplayAttributesDialog(wx.Dialog): 00031 def __init__(self, parent, map, 00032 query = None, cats = None, line = None, 00033 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, 00034 pos = wx.DefaultPosition, 00035 action = "add"): 00036 """!Standard dialog used to add/update/display attributes linked 00037 to the vector map. 00038 00039 Attribute data can be selected based on layer and category number 00040 or coordinates. 00041 00042 @param parent 00043 @param map vector map 00044 @param query query coordinates and distance (used for v.edit) 00045 @param cats {layer: cats} 00046 @param line feature id (requested for cats) 00047 @param style 00048 @param pos 00049 @param action (add, update, display) 00050 """ 00051 self.parent = parent # mapdisplay.BufferedWindow 00052 self.map = map 00053 self.action = action 00054 00055 # ids/cats of selected features 00056 # fid : {layer : cats} 00057 self.cats = {} 00058 self.fid = -1 # feature id 00059 00060 # get layer/table/column information 00061 self.mapDBInfo = VectorDBInfo(self.map) 00062 00063 layers = self.mapDBInfo.layers.keys() # get available layers 00064 00065 # check if db connection / layer exists 00066 if len(layers) <= 0: 00067 label = _("Database connection " 00068 "is not defined in DB file.") 00069 00070 gcmd.GMessage(parent = self.parent, 00071 message = _("No attribute table linked to " 00072 "vector map <%(vector)s> found. %(msg)s\n\n" 00073 "New attribute table can be created by " 00074 "Attribute Table Manager.") % 00075 {'vector' : self.map, 'msg' : label}) 00076 self.mapDBInfo = None 00077 00078 wx.Dialog.__init__(self, parent = self.parent, id = wx.ID_ANY, 00079 title = "", style = style, pos = pos) 00080 00081 # dialog body 00082 mainSizer = wx.BoxSizer(wx.VERTICAL) 00083 00084 # notebook 00085 self.notebook = wx.Notebook(parent = self, id = wx.ID_ANY, style = wx.BK_DEFAULT) 00086 00087 self.closeDialog = wx.CheckBox(parent = self, id = wx.ID_ANY, 00088 label = _("Close dialog on submit")) 00089 self.closeDialog.SetValue(True) 00090 if self.action == 'display': 00091 self.closeDialog.Enable(False) 00092 00093 # feature id (text/choice for duplicates) 00094 self.fidMulti = wx.Choice(parent = self, id = wx.ID_ANY, 00095 size = (150, -1)) 00096 self.fidMulti.Bind(wx.EVT_CHOICE, self.OnFeature) 00097 self.fidText = wx.StaticText(parent = self, id = wx.ID_ANY) 00098 00099 self.noFoundMsg = wx.StaticText(parent = self, id = wx.ID_ANY, 00100 label = _("No attributes found")) 00101 00102 self.UpdateDialog(query = query, cats = cats) 00103 00104 # set title 00105 if self.action == "update": 00106 self.SetTitle(_("Update attributes")) 00107 elif self.action == "add": 00108 self.SetTitle(_("Define attributes")) 00109 else: 00110 self.SetTitle(_("Display attributes")) 00111 00112 # buttons 00113 btnCancel = wx.Button(self, wx.ID_CANCEL) 00114 btnReset = wx.Button(self, wx.ID_UNDO, _("&Reload")) 00115 btnSubmit = wx.Button(self, wx.ID_OK, _("&Submit")) 00116 if self.action == 'display': 00117 btnSubmit.Enable(False) 00118 00119 btnSizer = wx.StdDialogButtonSizer() 00120 btnSizer.AddButton(btnCancel) 00121 btnSizer.AddButton(btnReset) 00122 btnSizer.SetNegativeButton(btnReset) 00123 btnSubmit.SetDefault() 00124 btnSizer.AddButton(btnSubmit) 00125 btnSizer.Realize() 00126 00127 mainSizer.Add(item = self.noFoundMsg, proportion = 0, 00128 flag = wx.EXPAND | wx.ALL, border = 5) 00129 mainSizer.Add(item = self.notebook, proportion = 1, 00130 flag = wx.EXPAND | wx.ALL, border = 5) 00131 fidSizer = wx.BoxSizer(wx.HORIZONTAL) 00132 fidSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY, 00133 label = _("Feature id:")), 00134 proportion = 0, border = 5, 00135 flag = wx.ALIGN_CENTER_VERTICAL) 00136 fidSizer.Add(item = self.fidMulti, proportion = 0, 00137 flag = wx.EXPAND | wx.ALL, border = 5) 00138 fidSizer.Add(item = self.fidText, proportion = 0, 00139 flag = wx.EXPAND | wx.ALL, border = 5) 00140 mainSizer.Add(item = fidSizer, proportion = 0, 00141 flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border = 5) 00142 mainSizer.Add(item = self.closeDialog, proportion = 0, flag = wx.EXPAND | wx.LEFT | wx.RIGHT, 00143 border = 5) 00144 mainSizer.Add(item = btnSizer, proportion = 0, 00145 flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5) 00146 00147 # bindigs 00148 btnReset.Bind(wx.EVT_BUTTON, self.OnReset) 00149 btnSubmit.Bind(wx.EVT_BUTTON, self.OnSubmit) 00150 btnCancel.Bind(wx.EVT_BUTTON, self.OnCancel) 00151 00152 self.SetSizer(mainSizer) 00153 mainSizer.Fit(self) 00154 00155 # set min size for dialog 00156 w, h = self.GetBestSize() 00157 if h < 200: 00158 self.SetMinSize((w, 200)) 00159 else: 00160 self.SetMinSize(self.GetBestSize()) 00161 00162 if self.notebook.GetPageCount() == 0: 00163 Debug.msg(2, "DisplayAttributesDialog(): Nothing found!") 00164 ### self.mapDBInfo = None 00165 00166 def __SelectAttributes(self, layer): 00167 """!Select attributes""" 00168 pass 00169 00170 def OnSQLStatement(self, event): 00171 """!Update SQL statement""" 00172 pass 00173 00174 def IsFound(self): 00175 """!Check for status 00176 00177 @return True on attributes found 00178 @return False attributes not found 00179 """ 00180 return bool(self.notebook.GetPageCount()) 00181 00182 def GetSQLString(self, updateValues = False): 00183 """!Create SQL statement string based on self.sqlStatement 00184 00185 If updateValues is True, update dataFrame according to values 00186 in textfields. 00187 """ 00188 sqlCommands = [] 00189 # find updated values for each layer/category 00190 for layer in self.mapDBInfo.layers.keys(): # for each layer 00191 table = self.mapDBInfo.GetTable(layer) 00192 key = self.mapDBInfo.GetKeyColumn(layer) 00193 columns = self.mapDBInfo.GetTableDesc(table) 00194 for idx in range(len(columns[key]['values'])): # for each category 00195 updatedColumns = [] 00196 updatedValues = [] 00197 for name in columns.keys(): 00198 if name == key: 00199 cat = columns[name]['values'][idx] 00200 continue 00201 type = columns[name]['type'] 00202 value = columns[name]['values'][idx] 00203 id = columns[name]['ids'][idx] 00204 try: 00205 newvalue = self.FindWindowById(id).GetValue() 00206 except: 00207 newvalue = self.FindWindowById(id).GetLabel() 00208 00209 if newvalue == '': 00210 newvalue = None 00211 00212 if newvalue != value: 00213 updatedColumns.append(name) 00214 if newvalue is None: 00215 updatedValues.append('NULL') 00216 else: 00217 if type != 'character': 00218 updatedValues.append(newvalue) 00219 else: 00220 updatedValues.append("'" + newvalue + "'") 00221 columns[name]['values'][idx] = newvalue 00222 00223 if self.action != "add" and len(updatedValues) == 0: 00224 continue 00225 00226 if self.action == "add": 00227 sqlString = "INSERT INTO %s (%s," % (table, key) 00228 else: 00229 sqlString = "UPDATE %s SET " % table 00230 00231 for idx in range(len(updatedColumns)): 00232 name = updatedColumns[idx] 00233 if self.action == "add": 00234 sqlString += name + "," 00235 else: 00236 sqlString += name + "=" + updatedValues[idx] + "," 00237 00238 sqlString = sqlString[:-1] # remove last comma 00239 00240 if self.action == "add": 00241 sqlString += ") VALUES (%s," % cat 00242 for value in updatedValues: 00243 sqlString += str(value) + "," 00244 sqlString = sqlString[:-1] # remove last comma 00245 sqlString += ")" 00246 else: 00247 sqlString += " WHERE cat=%s" % cat 00248 sqlCommands.append(sqlString) 00249 # for each category 00250 # for each layer END 00251 00252 Debug.msg(3, "DisplayAttributesDialog.GetSQLString(): %s" % sqlCommands) 00253 00254 return sqlCommands 00255 00256 def OnReset(self, event = None): 00257 """!Reset form""" 00258 for layer in self.mapDBInfo.layers.keys(): 00259 table = self.mapDBInfo.layers[layer]["table"] 00260 key = self.mapDBInfo.layers[layer]["key"] 00261 columns = self.mapDBInfo.tables[table] 00262 for idx in range(len(columns[key]['values'])): 00263 for name in columns.keys(): 00264 type = columns[name]['type'] 00265 value = columns[name]['values'][idx] 00266 if value is None: 00267 value = '' 00268 try: 00269 id = columns[name]['ids'][idx] 00270 except IndexError: 00271 id = wx.NOT_FOUND 00272 00273 if name != key and id != wx.NOT_FOUND: 00274 self.FindWindowById(id).SetValue(str(value)) 00275 00276 def OnCancel(self, event): 00277 """!Cancel button pressed 00278 """ 00279 self.parent.parent.dialogs['attributes'] = None 00280 00281 if hasattr(self, "digit"): 00282 self.parent.digit.GetDisplay().SetSelected([]) 00283 self.parent.UpdateMap(render = False) 00284 else: 00285 self.parent.parent.OnRender(None) 00286 00287 self.Close() 00288 00289 def OnSubmit(self, event): 00290 """!Submit records""" 00291 ret = 0 00292 for sql in self.GetSQLString(updateValues = True): 00293 enc = UserSettings.Get(group = 'atm', key = 'encoding', subkey = 'value') 00294 if not enc and 'GRASS_DB_ENCODING' in os.environ: 00295 enc = os.environ['GRASS_DB_ENCODING'] 00296 if enc: 00297 sql = sql.encode(enc) 00298 00299 ret += gcmd.RunCommand('db.execute', 00300 parent = self, 00301 quiet = True, 00302 stdin = sql) 00303 00304 if ret == 0 and self.closeDialog.IsChecked(): 00305 self.OnCancel(event) 00306 00307 def OnFeature(self, event): 00308 self.fid = int(event.GetString()) 00309 self.UpdateDialog(cats = self.cats, fid = self.fid) 00310 00311 def GetCats(self): 00312 """!Get id of selected vector object or 'None' if nothing selected 00313 00314 @param id if true return ids otherwise cats 00315 """ 00316 if self.fid < 0: 00317 return None 00318 00319 return self.cats[self.fid] 00320 00321 def GetFid(self): 00322 """!Get selected feature id""" 00323 return self.fid 00324 00325 def UpdateDialog(self, map = None, query = None, cats = None, fid = -1, 00326 action = None): 00327 """!Update dialog 00328 00329 @param map name of vector map 00330 @param query 00331 @param cats 00332 @param fid feature id 00333 @param action add, update, display or None 00334 00335 @return True if updated 00336 @return False 00337 """ 00338 if action: 00339 self.action = action 00340 if action == 'display': 00341 enabled = False 00342 else: 00343 enabled = True 00344 self.closeDialog.Enable(enabled) 00345 self.FindWindowById(wx.ID_OK).Enable(enabled) 00346 00347 if map: 00348 self.map = map 00349 # get layer/table/column information 00350 self.mapDBInfo = VectorDBInfo(self.map) 00351 00352 if not self.mapDBInfo: 00353 return False 00354 00355 self.mapDBInfo.Reset() 00356 00357 layers = self.mapDBInfo.layers.keys() # get available layers 00358 00359 # id of selected line 00360 if query: # select by position 00361 data = self.mapDBInfo.SelectByPoint(query[0], 00362 query[1]) 00363 self.cats = {} 00364 if data and 'Layer' in data: 00365 idx = 0 00366 for layer in data['Layer']: 00367 layer = int(layer) 00368 if 'Id' in data: 00369 tfid = int(data['Id'][idx]) 00370 else: 00371 tfid = 0 # Area / Volume 00372 if not tfid in self.cats: 00373 self.cats[tfid] = {} 00374 if not layer in self.cats[tfid]: 00375 self.cats[tfid][layer] = [] 00376 cat = int(data['Category'][idx]) 00377 self.cats[tfid][layer].append(cat) 00378 idx += 1 00379 else: 00380 self.cats = cats 00381 00382 if fid > 0: 00383 self.fid = fid 00384 elif len(self.cats.keys()) > 0: 00385 self.fid = self.cats.keys()[0] 00386 else: 00387 self.fid = -1 00388 00389 if len(self.cats.keys()) == 1: 00390 self.fidMulti.Show(False) 00391 self.fidText.Show(True) 00392 if self.fid > 0: 00393 self.fidText.SetLabel("%d" % self.fid) 00394 else: 00395 self.fidText.SetLabel(_("Unknown")) 00396 else: 00397 self.fidMulti.Show(True) 00398 self.fidText.Show(False) 00399 choices = [] 00400 for tfid in self.cats.keys(): 00401 choices.append(str(tfid)) 00402 self.fidMulti.SetItems(choices) 00403 self.fidMulti.SetStringSelection(str(self.fid)) 00404 00405 # reset notebook 00406 self.notebook.DeleteAllPages() 00407 00408 for layer in layers: # for each layer 00409 if not query: # select by layer/cat 00410 if self.fid > 0 and layer in self.cats[self.fid]: 00411 for cat in self.cats[self.fid][layer]: 00412 nselected = self.mapDBInfo.SelectFromTable(layer, 00413 where = "%s=%d" % \ 00414 (self.mapDBInfo.layers[layer]['key'], 00415 cat)) 00416 else: 00417 nselected = 0 00418 00419 # if nselected <= 0 and self.action != "add": 00420 # continue # nothing selected ... 00421 00422 if self.action == "add": 00423 if nselected <= 0: 00424 if layer in self.cats[self.fid]: 00425 table = self.mapDBInfo.layers[layer]["table"] 00426 key = self.mapDBInfo.layers[layer]["key"] 00427 columns = self.mapDBInfo.tables[table] 00428 for name in columns.keys(): 00429 if name == key: 00430 for cat in self.cats[self.fid][layer]: 00431 self.mapDBInfo.tables[table][name]['values'].append(cat) 00432 else: 00433 self.mapDBInfo.tables[table][name]['values'].append(None) 00434 else: # change status 'add' -> 'update' 00435 self.action = "update" 00436 00437 table = self.mapDBInfo.layers[layer]["table"] 00438 key = self.mapDBInfo.layers[layer]["key"] 00439 columns = self.mapDBInfo.tables[table] 00440 00441 for idx in range(len(columns[key]['values'])): 00442 for name in columns.keys(): 00443 if name == key: 00444 cat = int(columns[name]['values'][idx]) 00445 break 00446 00447 # use scrolled panel instead (and fix initial max height of the window to 480px) 00448 panel = scrolled.ScrolledPanel(parent = self.notebook, id = wx.ID_ANY, 00449 size = (-1, 150)) 00450 panel.SetupScrolling(scroll_x = False) 00451 00452 self.notebook.AddPage(page = panel, text = " %s %d / %s %d" % (_("Layer"), layer, 00453 _("Category"), cat)) 00454 00455 # notebook body 00456 border = wx.BoxSizer(wx.VERTICAL) 00457 00458 flexSizer = wx.FlexGridSizer (cols = 4, hgap = 3, vgap = 3) 00459 flexSizer.AddGrowableCol(3) 00460 # columns (sorted by index) 00461 names = [''] * len(columns.keys()) 00462 for name in columns.keys(): 00463 names[columns[name]['index']] = name 00464 00465 for name in names: 00466 if name == key: # skip key column (category) 00467 continue 00468 00469 vtype = columns[name]['type'] 00470 00471 if columns[name]['values'][idx] is not None: 00472 if columns[name]['ctype'] != type(''): 00473 value = str(columns[name]['values'][idx]) 00474 else: 00475 value = columns[name]['values'][idx] 00476 else: 00477 value = '' 00478 00479 colName = wx.StaticText(parent = panel, id = wx.ID_ANY, 00480 label = name) 00481 colType = wx.StaticText(parent = panel, id = wx.ID_ANY, 00482 label = "[" + vtype.lower() + "]") 00483 delimiter = wx.StaticText(parent = panel, id = wx.ID_ANY, label = ":") 00484 00485 colValue = wx.TextCtrl(parent = panel, id = wx.ID_ANY, value = value) 00486 colValue.SetName(name) 00487 self.Bind(wx.EVT_TEXT, self.OnSQLStatement, colValue) 00488 if self.action == 'display': 00489 colValue.SetWindowStyle(wx.TE_READONLY) 00490 00491 flexSizer.Add(colName, proportion = 0, 00492 flag = wx.FIXED_MINSIZE | wx.ALIGN_CENTER_VERTICAL) 00493 flexSizer.Add(colType, proportion = 0, 00494 flag = wx.FIXED_MINSIZE | wx.ALIGN_CENTER_VERTICAL) 00495 flexSizer.Add(delimiter, proportion = 0, 00496 flag = wx.FIXED_MINSIZE | wx.ALIGN_CENTER_VERTICAL) 00497 flexSizer.Add(colValue, proportion = 1, 00498 flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL) 00499 # add widget reference to self.columns 00500 columns[name]['ids'].append(colValue.GetId()) # name, type, values, id 00501 # for each attribute (including category) END 00502 border.Add(item = flexSizer, proportion = 1, flag = wx.ALL | wx.EXPAND, border = 5) 00503 panel.SetSizer(border) 00504 # for each category END 00505 # for each layer END 00506 00507 if self.notebook.GetPageCount() == 0: 00508 self.noFoundMsg.Show(True) 00509 else: 00510 self.noFoundMsg.Show(False) 00511 00512 self.Layout() 00513 00514 return True 00515 00516 def SetColumnValue(self, layer, column, value): 00517 """!Set attrbute value 00518 00519 @param column column name 00520 @param value value 00521 """ 00522 table = self.mapDBInfo.GetTable(layer) 00523 columns = self.mapDBInfo.GetTableDesc(table) 00524 00525 for key, col in columns.iteritems(): 00526 if key == column: 00527 col['values'] = [col['ctype'](value),] 00528 break 00529 00530 class ModifyTableRecord(wx.Dialog): 00531 def __init__(self, parent, title, data, keyEditable = (-1, True), 00532 id = wx.ID_ANY, style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER): 00533 """!Dialog for inserting/updating table record 00534 00535 @param data a list: [(column, value)] 00536 @param KeyEditable (id, editable?) indicates if textarea for key column 00537 is editable(True) or not 00538 """ 00539 # parent -> VDigitWindow 00540 wx.Dialog.__init__(self, parent, id, title, style = style) 00541 00542 self.CenterOnParent() 00543 00544 self.keyId = keyEditable[0] 00545 00546 box = wx.StaticBox(parent = self, id = wx.ID_ANY) 00547 box.Hide() 00548 self.dataPanel = scrolled.ScrolledPanel(parent = self, id = wx.ID_ANY, 00549 style = wx.TAB_TRAVERSAL) 00550 self.dataPanel.SetupScrolling(scroll_x = False) 00551 00552 # buttons 00553 self.btnCancel = wx.Button(self, wx.ID_CANCEL) 00554 self.btnSubmit = wx.Button(self, wx.ID_OK, _("&Submit")) 00555 self.btnSubmit.SetDefault() 00556 00557 # data area 00558 self.widgets = [] 00559 cId = 0 00560 self.usebox = False 00561 self.cat = None 00562 for column, value in data: 00563 if self.keyId == cId: 00564 self.cat = int(value) 00565 if not keyEditable[1]: 00566 self.usebox = True 00567 box.SetLabel(" %s %d " % (_("Category"), self.cat)) 00568 box.Show() 00569 self.boxSizer = wx.StaticBoxSizer(box, wx.VERTICAL) 00570 cId += 1 00571 continue 00572 else: 00573 valueWin = wx.SpinCtrl(parent = self.dataPanel, id = wx.ID_ANY, 00574 value = value, min = -1e9, max = 1e9, size = (250, -1)) 00575 else: 00576 valueWin = wx.TextCtrl(parent = self.dataPanel, id = wx.ID_ANY, 00577 value = value, size = (250, -1)) 00578 00579 label = wx.StaticText(parent = self.dataPanel, id = wx.ID_ANY, 00580 label = column + ":") 00581 00582 self.widgets.append((label.GetId(), valueWin.GetId())) 00583 00584 cId += 1 00585 00586 self._layout() 00587 00588 def _layout(self): 00589 """!Do layout""" 00590 sizer = wx.BoxSizer(wx.VERTICAL) 00591 00592 # data area 00593 dataSizer = wx.FlexGridSizer (cols = 2, hgap = 3, vgap = 3) 00594 dataSizer.AddGrowableCol(1) 00595 00596 for labelId, valueId in self.widgets: 00597 label = self.FindWindowById(labelId) 00598 value = self.FindWindowById(valueId) 00599 00600 dataSizer.Add(label, proportion = 0, 00601 flag = wx.ALIGN_CENTER_VERTICAL) 00602 dataSizer.Add(value, proportion = 0, 00603 flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL) 00604 00605 self.dataPanel.SetAutoLayout(True) 00606 self.dataPanel.SetSizer(dataSizer) 00607 dataSizer.Fit(self.dataPanel) 00608 00609 if self.usebox: 00610 self.boxSizer.Add(item = self.dataPanel, proportion = 1, 00611 flag = wx.EXPAND | wx.ALL, border = 5) 00612 00613 # buttons 00614 btnSizer = wx.StdDialogButtonSizer() 00615 btnSizer.AddButton(self.btnCancel) 00616 btnSizer.AddButton(self.btnSubmit) 00617 btnSizer.Realize() 00618 00619 if not self.usebox: 00620 sizer.Add(item = self.dataPanel, proportion = 1, 00621 flag = wx.EXPAND | wx.ALL, border = 5) 00622 else: 00623 sizer.Add(item = self.boxSizer, proportion = 1, 00624 flag = wx.EXPAND | wx.ALL, border = 5) 00625 00626 sizer.Add(item = btnSizer, proportion = 0, 00627 flag = wx.EXPAND | wx.ALL, border = 5) 00628 00629 framewidth = self.GetSize()[0] 00630 self.SetMinSize((framewidth,250)) 00631 00632 self.SetAutoLayout(True) 00633 self.SetSizer(sizer) 00634 sizer.Fit(self) 00635 00636 self.Layout() 00637 00638 def GetValues(self, columns = None): 00639 """!Return list of values (casted to string). 00640 00641 If columns is given (list), return only values of given columns. 00642 """ 00643 valueList = [] 00644 for labelId, valueId in self.widgets: 00645 column = self.FindWindowById(labelId).GetLabel().replace(':', '') 00646 if columns is None or column in columns: 00647 value = str(self.FindWindowById(valueId).GetValue()) 00648 valueList.append(value) 00649 00650 # add key value 00651 if self.usebox: 00652 valueList.insert(self.keyId, str(self.cat)) 00653 00654 return valueList