class VDigitCategoryDialog(wx.Dialog, listmix.ColumnSorterMixin): def __init__(self, parent, title, vectorName, query=None, cats=None, style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs): """Dialog used to display/modify categories of vector objects :param parent: :param title: dialog title :param query: {coordinates, qdist} - used by v.edit/v.what :param cats: directory of lines (layer/categories) - used by vdigit :param style: dialog style """ self.parent = parent # map window class instance self.digit = parent.digit # map name self.vectorName = vectorName # line : {layer: [categories]} self.cats = {} # do not display dialog if no line is found (-> self.cats) if cats is None: if self._getCategories(query[0], query[1]) == 0 or not self.line: Debug.msg(3, "VDigitCategoryDialog(): nothing found!") else: self.cats = cats for line in cats.keys(): for layer in cats[line].keys(): self.cats[line][layer] = list(cats[line][layer]) layers = [] for layer in self.digit.GetLayers(): layers.append(str(layer)) # make copy of cats (used for 'reload') self.cats_orig = copy.deepcopy(self.cats) wx.Dialog.__init__(self, parent=self.parent, id=wx.ID_ANY, title=title, style=style, **kwargs) # list of categories box = StaticBox(parent=self, id=wx.ID_ANY, label=" %s " % _("List of categories - right-click to delete")) listSizer = wx.StaticBoxSizer(box, wx.VERTICAL) self.list = CategoryListCtrl(parent=self, id=wx.ID_ANY, style=wx.LC_REPORT | wx.BORDER_NONE | wx.LC_SORT_ASCENDING | wx.LC_HRULES | wx.LC_VRULES) # sorter self.fid = list(self.cats.keys())[0] self.itemDataMap = self.list.Populate(self.cats[self.fid]) listmix.ColumnSorterMixin.__init__(self, 2) self.fidMulti = wx.Choice(parent=self, id=wx.ID_ANY, size=(150, -1)) self.fidMulti.Bind(wx.EVT_CHOICE, self.OnFeature) self.fidText = StaticText(parent=self, id=wx.ID_ANY) if len(self.cats.keys()) == 1: self.fidMulti.Show(False) self.fidText.SetLabel(str(self.fid)) else: self.fidText.Show(False) choices = [] for fid in self.cats.keys(): choices.append(str(fid)) self.fidMulti.SetItems(choices) self.fidMulti.SetSelection(0) listSizer.Add(self.list, proportion=1, flag=wx.EXPAND) # add new category box = StaticBox(parent=self, id=wx.ID_ANY, label=" %s " % _("Add new category")) addSizer = wx.StaticBoxSizer(box, wx.VERTICAL) flexSizer = wx.FlexGridSizer(cols=5, hgap=5, vgap=5) flexSizer.AddGrowableCol(3) layerNewTxt = StaticText(parent=self, id=wx.ID_ANY, label="%s:" % _("Layer")) self.layerNew = wx.Choice(parent=self, id=wx.ID_ANY, size=(75, -1), choices=layers) if len(layers) > 0: self.layerNew.SetSelection(0) catNewTxt = StaticText(parent=self, id=wx.ID_ANY, label="%s:" % _("Category")) try: newCat = max(self.cats[self.fid][1]) + 1 except KeyError: newCat = 1 self.catNew = SpinCtrl(parent=self, id=wx.ID_ANY, size=(75, -1), initial=newCat, min=0, max=1e9) btnAddCat = Button(self, wx.ID_ADD) flexSizer.Add(layerNewTxt, proportion=0, flag=wx.FIXED_MINSIZE | wx.ALIGN_CENTER_VERTICAL) flexSizer.Add(self.layerNew, proportion=0, flag=wx.FIXED_MINSIZE | wx.ALIGN_CENTER_VERTICAL) flexSizer.Add(catNewTxt, proportion=0, flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.LEFT, border=10) flexSizer.Add(self.catNew, proportion=0, flag=wx.FIXED_MINSIZE | wx.ALIGN_CENTER_VERTICAL) flexSizer.Add(btnAddCat, proportion=0, flag=wx.EXPAND | wx.ALIGN_RIGHT | wx.FIXED_MINSIZE) addSizer.Add(flexSizer, proportion=1, flag=wx.ALL | wx.EXPAND, border=5) # buttons btnApply = Button(self, wx.ID_APPLY) btnApply.SetToolTip(_("Apply changes")) btnCancel = Button(self, wx.ID_CANCEL) btnCancel.SetToolTip(_("Ignore changes and close dialog")) btnOk = Button(self, wx.ID_OK) btnOk.SetToolTip(_("Apply changes and close dialog")) btnOk.SetDefault() # sizers btnSizer = wx.StdDialogButtonSizer() btnSizer.AddButton(btnCancel) # btnSizer.AddButton(btnReload) # btnSizer.SetNegativeButton(btnReload) btnSizer.AddButton(btnApply) btnSizer.AddButton(btnOk) btnSizer.Realize() mainSizer = wx.BoxSizer(wx.VERTICAL) mainSizer.Add(listSizer, proportion=1, flag=wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border=5) mainSizer.Add(addSizer, proportion=0, flag=wx.EXPAND | wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT | wx.BOTTOM, border=5) fidSizer = wx.BoxSizer(wx.HORIZONTAL) fidSizer.Add(StaticText(parent=self, id=wx.ID_ANY, label=_("Feature id:")), proportion=0, border=5, flag=wx.ALIGN_CENTER_VERTICAL) fidSizer.Add(self.fidMulti, proportion=0, flag=wx.EXPAND | wx.ALL, border=5) fidSizer.Add(self.fidText, proportion=0, flag=wx.EXPAND | wx.ALL, border=5) mainSizer.Add(fidSizer, proportion=0, flag=wx.EXPAND | wx.ALL, border=5) mainSizer.Add(btnSizer, proportion=0, flag=wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border=5) self.SetSizer(mainSizer) mainSizer.Fit(self) self.SetAutoLayout(True) # set min size for dialog self.SetMinSize(self.GetBestSize()) # bindings btnApply.Bind(wx.EVT_BUTTON, self.OnApply) btnOk.Bind(wx.EVT_BUTTON, self.OnOK) btnAddCat.Bind(wx.EVT_BUTTON, self.OnAddCat) btnCancel.Bind(wx.EVT_BUTTON, self.OnCancel) self.Bind(wx.EVT_CLOSE, lambda evt: self.Hide()) # list self.list.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnRightUp) # wxMSW self.list.Bind(wx.EVT_RIGHT_UP, self.OnRightUp) # wxGTK self.Bind(wx.EVT_LIST_BEGIN_LABEL_EDIT, self.OnBeginEdit, self.list) self.Bind(wx.EVT_LIST_END_LABEL_EDIT, self.OnEndEdit, self.list) self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick, self.list) def GetListCtrl(self): """Used by ColumnSorterMixin """ return self.list def OnColClick(self, event): """Click on column header (order by) """ event.Skip() def OnBeginEdit(self, event): """Editing of item started """ event.Allow() def OnEndEdit(self, event): """Finish editing of item """ itemIndex = event.GetIndex() layerOld = int(self.list.GetItem(itemIndex, 0).GetText()) catOld = int(self.list.GetItem(itemIndex, 1).GetText()) if event.GetColumn() == 0: layerNew = int(event.GetLabel()) catNew = catOld else: layerNew = layerOld catNew = int(event.GetLabel()) try: if layerNew not in self.cats[self.fid].keys(): self.cats[self.fid][layerNew] = [] self.cats[self.fid][layerNew].append(catNew) self.cats[self.fid][layerOld].remove(catOld) except: event.Veto() self.list.SetStringItem(itemIndex, 0, str(layerNew)) self.list.SetStringItem(itemIndex, 1, str(catNew)) dlg = wx.MessageDialog( self, _("Unable to add new layer/category <%(layer)s/%(category)s>.\n" "Layer and category number must be integer.\n" "Layer number must be greater than zero.") % { 'layer': self.layerNew.GetStringSelection(), 'category': str(self.catNew.GetValue()) }, _("Error"), wx.OK | wx.ICON_ERROR) dlg.ShowModal() dlg.Destroy() return False def OnRightDown(self, event): """Mouse right button down """ x = event.GetX() y = event.GetY() item, flags = self.list.HitTest((x, y)) if item != wx.NOT_FOUND and \ flags & wx.LIST_HITTEST_ONITEM: self.list.Select(item) event.Skip() def OnRightUp(self, event): """Mouse right button up """ if not hasattr(self, "popupID1"): self.popupID1 = wx.NewId() self.popupID2 = wx.NewId() self.popupID3 = wx.NewId() self.Bind(wx.EVT_MENU, self.OnItemDelete, id=self.popupID1) self.Bind(wx.EVT_MENU, self.OnItemDeleteAll, id=self.popupID2) self.Bind(wx.EVT_MENU, self.OnReload, id=self.popupID3) # generate popup-menu menu = Menu() menu.Append(self.popupID1, _("Delete selected")) if self.list.GetFirstSelected() == -1: menu.Enable(self.popupID1, False) menu.Append(self.popupID2, _("Delete all")) menu.AppendSeparator() menu.Append(self.popupID3, _("Reload")) self.PopupMenu(menu) menu.Destroy() def OnItemSelected(self, event): """Item selected """ event.Skip() def OnItemDelete(self, event): """Delete selected item(s) from the list (layer/category pair) """ item = self.list.GetFirstSelected() while item != -1: layer = int(self.list.GetItem(item, 0).GetText()) cat = int(self.list.GetItem(item, 1).GetText()) self.list.DeleteItem(item) self.cats[self.fid][layer].remove(cat) item = self.list.GetFirstSelected() event.Skip() def OnItemDeleteAll(self, event): """Delete all items from the list """ self.list.DeleteAllItems() self.cats[self.fid] = {} event.Skip() def OnFeature(self, event): """Feature id changed (on duplicates) """ self.fid = int(event.GetString()) self.itemDataMap = self.list.Populate(self.cats[self.fid], update=True) try: newCat = max(self.cats[self.fid][1]) + 1 except KeyError: newCat = 1 self.catNew.SetValue(newCat) event.Skip() def _getCategories(self, coords, qdist): """Get layer/category pairs for all available layers :return: True line found or False if not found """ ret = RunCommand('v.what', parent=self, quiet=True, map=self.vectorName, east_north='%f,%f' % (float(coords[0]), float(coords[1])), distance=qdist) if not ret: return False for item in ret.splitlines(): litem = item.lower() if "id:" in litem: # get line id self.line = int(item.split(':')[1].strip()) elif "layer:" in litem: # add layer layer = int(item.split(':')[1].strip()) if layer not in self.cats.keys(): self.cats[layer] = [] elif "category:" in litem: # add category self.cats[layer].append(int(item.split(':')[1].strip())) return True def OnReload(self, event): """Reload button pressed """ # restore original list self.cats = copy.deepcopy(self.cats_orig) # polulate list self.itemDataMap = self.list.Populate(self.cats[self.fid], update=True) event.Skip() def OnCancel(self, event): """Cancel button pressed """ self.parent.parent.dialogs['category'] = None if self.digit: self.digit.GetDisplay().SetSelected([]) self.parent.UpdateMap(render=False) else: self.parent.parent.OnRender(None) self.Close() def OnApply(self, event): """Apply button pressed """ for fid in self.cats.keys(): newfid = self.ApplyChanges(fid) if fid == self.fid and newfid > 0: self.fid = newfid def ApplyChanges(self, fid): """Apply changes :param fid: feature id """ cats = self.cats[fid] cats_orig = self.cats_orig[fid] # action : (catsFrom, catsTo) check = {'catadd': (cats, cats_orig), 'catdel': (cats_orig, cats)} newfid = -1 # add/delete new category for action, catsCurr in six.iteritems(check): for layer in catsCurr[0].keys(): catList = [] for cat in catsCurr[0][layer]: if layer not in catsCurr[1].keys() or \ cat not in catsCurr[1][layer]: catList.append(cat) if catList != []: if action == 'catadd': add = True else: add = False newfid = self.digit.SetLineCats(fid, layer, catList, add) if len(self.cats.keys()) == 1: self.fidText.SetLabel("%d" % newfid) else: choices = self.fidMulti.GetItems() choices[choices.index(str(fid))] = str(newfid) self.fidMulti.SetItems(choices) self.fidMulti.SetStringSelection(str(newfid)) self.cats[newfid] = self.cats[fid] del self.cats[fid] fid = newfid if self.fid < 0: wx.MessageBox( parent=self, message=_("Unable to update vector map."), caption=_("Error"), style=wx.OK | wx.ICON_ERROR) self.cats_orig[fid] = copy.deepcopy(cats) return newfid def OnOK(self, event): """OK button pressed """ self.OnApply(event) self.OnCancel(event) def OnAddCat(self, event): """Button 'Add' new category pressed """ try: layer = int(self.layerNew.GetStringSelection()) cat = int(self.catNew.GetValue()) if layer <= 0: raise ValueError except ValueError: GError( parent=self, message= _("Unable to add new layer/category <%(layer)s/%(category)s>.\n" "Layer and category number must be integer.\n" "Layer number must be greater than zero.") % { 'layer': str(self.layerNew.GetValue()), 'category': str(self.catNew.GetValue()) }) return False if layer not in self.cats[self.fid].keys(): self.cats[self.fid][layer] = [] self.cats[self.fid][layer].append(cat) # reload list self.itemDataMap = self.list.Populate(self.cats[self.fid], update=True) # update category number for add self.catNew.SetValue(cat + 1) event.Skip() return True def GetLine(self): """Get id of selected line of 'None' if no line is selected """ return self.cats.keys() def UpdateDialog(self, query=None, cats=None): """Update dialog :param query: {coordinates, distance} - v.what :param cats: directory layer/cats - vdigit :return: True if updated otherwise False """ # line: {layer: [categories]} self.cats = {} # do not display dialog if no line is found (-> self.cats) if cats is None: ret = self._getCategories(query[0], query[1]) else: self.cats = cats for line in cats.keys(): for layer in cats[line].keys(): self.cats[line][layer] = list(cats[line][layer]) ret = 1 if ret == 0 or len(self.cats.keys()) < 1: Debug.msg(3, "VDigitCategoryDialog(): nothing found!") return False # make copy of cats (used for 'reload') self.cats_orig = copy.deepcopy(self.cats) # polulate list self.fid = list(self.cats.keys())[0] self.itemDataMap = self.list.Populate(self.cats[self.fid], update=True) try: newCat = max(self.cats[self.fid][1]) + 1 except KeyError: newCat = 1 self.catNew.SetValue(newCat) if len(self.cats.keys()) == 1: self.fidText.Show(True) self.fidMulti.Show(False) self.fidText.SetLabel("%d" % self.fid) else: self.fidText.Show(False) self.fidMulti.Show(True) choices = [] for fid in self.cats.keys(): choices.append(str(fid)) self.fidMulti.SetItems(choices) self.fidMulti.SetSelection(0) self.Layout() return True
class DisplayAttributesDialog(wx.Dialog): def __init__(self, parent, map, query=None, cats=None, line=None, style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, pos=wx.DefaultPosition, action="add", ignoreError=False): """Standard dialog used to add/update/display attributes linked to the vector map. Attribute data can be selected based on layer and category number or coordinates. :param parent: :param map: vector map :param query: query coordinates and distance (used for v.edit) :param cats: {layer: cats} :param line: feature id (requested for cats) :param style: :param pos: :param action: (add, update, display) :param ignoreError: True to ignore errors """ self.parent = parent # mapdisplay.BufferedWindow self.map = map self.action = action # ids/cats of selected features # fid : {layer : cats} self.cats = {} self.fid = -1 # feature id # get layer/table/column information self.mapDBInfo = VectorDBInfo(self.map) layers = self.mapDBInfo.layers.keys() # get available layers # check if db connection / layer exists if len(layers) <= 0: if not ignoreError: dlg = wx.MessageDialog( parent=self.parent, message=_("No attribute table found.\n\n" "Do you want to create a new attribute table " "and defined a link to vector map <%s>?") % self.map, caption=_("Create table?"), style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION) if dlg.ShowModal() == wx.ID_YES: lmgr = self.parent.lmgr lmgr.OnShowAttributeTable(event=None, selection='layers') dlg.Destroy() self.mapDBInfo = None wx.Dialog.__init__(self, parent=self.parent, id=wx.ID_ANY, title="", style=style, pos=pos) # dialog body mainSizer = wx.BoxSizer(wx.VERTICAL) # notebook self.notebook = wx.Notebook(parent=self, id=wx.ID_ANY, style=wx.BK_DEFAULT) self.closeDialog = wx.CheckBox(parent=self, id=wx.ID_ANY, label=_("Close dialog on submit")) self.closeDialog.SetValue(True) if self.action == 'display': self.closeDialog.Enable(False) # feature id (text/choice for duplicates) self.fidMulti = wx.Choice(parent=self, id=wx.ID_ANY, size=(150, -1)) self.fidMulti.Bind(wx.EVT_CHOICE, self.OnFeature) self.fidText = StaticText(parent=self, id=wx.ID_ANY) self.noFoundMsg = StaticText(parent=self, id=wx.ID_ANY, label=_("No attributes found")) self.UpdateDialog(query=query, cats=cats) # set title if self.action == "update": self.SetTitle(_("Update attributes")) elif self.action == "add": self.SetTitle(_("Define attributes")) else: self.SetTitle(_("Display attributes")) # buttons btnCancel = Button(self, wx.ID_CANCEL) btnReset = Button(self, wx.ID_UNDO, _("&Reload")) btnSubmit = Button(self, wx.ID_OK, _("&Submit")) if self.action == 'display': btnSubmit.Enable(False) btnSizer = wx.StdDialogButtonSizer() btnSizer.AddButton(btnCancel) btnSizer.AddButton(btnReset) btnSizer.SetNegativeButton(btnReset) btnSubmit.SetDefault() btnSizer.AddButton(btnSubmit) btnSizer.Realize() mainSizer.Add(self.noFoundMsg, proportion=0, flag=wx.EXPAND | wx.ALL, border=5) mainSizer.Add(self.notebook, proportion=1, flag=wx.EXPAND | wx.ALL, border=5) fidSizer = wx.BoxSizer(wx.HORIZONTAL) fidSizer.Add(StaticText(parent=self, id=wx.ID_ANY, label=_("Feature id:")), proportion=0, border=5, flag=wx.ALIGN_CENTER_VERTICAL) fidSizer.Add(self.fidMulti, proportion=0, flag=wx.EXPAND | wx.ALL, border=5) fidSizer.Add(self.fidText, proportion=0, flag=wx.EXPAND | wx.ALL, border=5) mainSizer.Add(fidSizer, proportion=0, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=5) mainSizer.Add(self.closeDialog, proportion=0, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=5) mainSizer.Add(btnSizer, proportion=0, flag=wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border=5) # bindigs btnReset.Bind(wx.EVT_BUTTON, self.OnReset) btnSubmit.Bind(wx.EVT_BUTTON, self.OnSubmit) btnCancel.Bind(wx.EVT_BUTTON, self.OnClose) self.Bind(wx.EVT_CLOSE, self.OnClose) self.SetSizer(mainSizer) mainSizer.Fit(self) # set min size for dialog w, h = self.GetBestSize() w += 50 if h < 200: self.SetMinSize((w, 200)) else: self.SetMinSize((w, h)) if self.notebook.GetPageCount() == 0: Debug.msg(2, "DisplayAttributesDialog(): Nothing found!") ### self.mapDBInfo = None def OnSQLStatement(self, event): """Update SQL statement""" pass def IsFound(self): """Check for status :return: True on attributes found :return: False attributes not found """ return bool(self.mapDBInfo and self.notebook.GetPageCount() > 0) def GetSQLString(self, updateValues=False): """Create SQL statement string based on self.sqlStatement Show error message when invalid values are entered. If updateValues is True, update dataFrame according to values in textfields. """ sqlCommands = [] # find updated values for each layer/category for layer in self.mapDBInfo.layers.keys(): # for each layer table = self.mapDBInfo.GetTable(layer) key = self.mapDBInfo.GetKeyColumn(layer) columns = self.mapDBInfo.GetTableDesc(table) for idx in range(len(columns[key]['values'])): # for each category updatedColumns = [] updatedValues = [] for name in columns.keys(): if name == key: cat = columns[name]['values'][idx] continue ctype = columns[name]['ctype'] value = columns[name]['values'][idx] id = columns[name]['ids'][idx] try: newvalue = self.FindWindowById(id).GetValue() except: newvalue = self.FindWindowById(id).GetLabel() if newvalue: try: if ctype == int: newvalue = int(newvalue) elif ctype == float: newvalue = float(newvalue) except ValueError: GError( parent=self, message= _("Column <%(col)s>: Value '%(value)s' needs to be entered as %(type)s." ) % { 'col': name, 'value': str(newvalue), 'type': columns[name]['type'].lower() }, showTraceback=False) sqlCommands.append(None) continue else: if self.action == 'add': continue if newvalue != value: updatedColumns.append(name) if newvalue == '': updatedValues.append('NULL') else: if ctype != str: updatedValues.append(str(newvalue)) else: updatedValues.append( "'" + newvalue.replace("'", "''") + "'") columns[name]['values'][idx] = newvalue if self.action != "add" and len(updatedValues) == 0: continue if self.action == "add": sqlString = "INSERT INTO %s (%s," % (table, key) else: sqlString = "UPDATE %s SET " % table for idx in range(len(updatedColumns)): name = updatedColumns[idx] if self.action == "add": sqlString += name + "," else: sqlString += name + "=" + updatedValues[idx] + "," sqlString = sqlString[:-1] # remove last comma if self.action == "add": sqlString += ") VALUES (%s," % cat for value in updatedValues: sqlString += value + "," sqlString = sqlString[:-1] # remove last comma sqlString += ")" else: sqlString += " WHERE %s=%s" % (key, cat) sqlCommands.append(sqlString) # for each category # for each layer END Debug.msg(3, "DisplayAttributesDialog.GetSQLString(): %s" % sqlCommands) return sqlCommands def OnReset(self, event=None): """Reset form""" for layer in self.mapDBInfo.layers.keys(): table = self.mapDBInfo.layers[layer]["table"] key = self.mapDBInfo.layers[layer]["key"] columns = self.mapDBInfo.tables[table] for idx in range(len(columns[key]['values'])): for name in columns.keys(): type = columns[name]['type'] value = columns[name]['values'][idx] if value is None: value = '' try: id = columns[name]['ids'][idx] except IndexError: id = wx.NOT_FOUND if name != key and id != wx.NOT_FOUND: self.FindWindowById(id).SetValue(str(value)) def OnClose(self, event): """Closes dialog and removes query layer. """ frame = self.parent.parent frame.dialogs['attributes'] = None if hasattr(self, "digit"): self.parent.digit.GetDisplay().SetSelected([]) if frame.IsAutoRendered(): self.parent.UpdateMap(render=False) elif frame.IsAutoRendered(): frame.RemoveQueryLayer() self.parent.UpdateMap(render=True) if self.IsModal(): self.EndModal(wx.ID_OK) else: self.Destroy() def OnSubmit(self, event): """Submit records""" layer = 1 close = True enc = GetDbEncoding() for sql in self.GetSQLString(updateValues=True): if not sql: close = False continue sql = sql.encode(enc) driver, database = self.mapDBInfo.GetDbSettings(layer) Debug.msg(1, "SQL: %s" % sql) RunCommand('db.execute', parent=self, quiet=True, input='-', stdin=sql, driver=driver, database=database) layer += 1 if close and self.closeDialog.IsChecked(): self.OnClose(event) def OnFeature(self, event): self.fid = int(event.GetString()) self.UpdateDialog(cats=self.cats, fid=self.fid) def GetCats(self): """Get id of selected vector object or 'None' if nothing selected :param id: if true return ids otherwise cats """ if self.fid < 0: return None return self.cats[self.fid] def GetFid(self): """Get selected feature id""" return self.fid def UpdateDialog(self, map=None, query=None, cats=None, fid=-1, action=None): """Update dialog :param map: name of vector map :param query: :param cats: :param fid: feature id :param action: add, update, display or None :return: True if updated :return: False """ if action: self.action = action if action == 'display': enabled = False else: enabled = True self.closeDialog.Enable(enabled) self.FindWindowById(wx.ID_OK).Enable(enabled) if map: self.map = map # get layer/table/column information self.mapDBInfo = VectorDBInfo(self.map) if not self.mapDBInfo: return False self.mapDBInfo.Reset() layers = self.mapDBInfo.layers.keys() # get available layers # id of selected line if query: # select by position data = self.mapDBInfo.SelectByPoint(query[0], query[1]) self.cats = {} if data and 'Layer' in data: idx = 0 for layer in data['Layer']: layer = int(layer) if data['Id'][idx] is not None: tfid = int(data['Id'][idx]) else: tfid = 0 # Area / Volume if tfid not in self.cats: self.cats[tfid] = {} if layer not in self.cats[tfid]: self.cats[tfid][layer] = [] cat = int(data['Category'][idx]) self.cats[tfid][layer].append(cat) idx += 1 else: self.cats = cats if fid > 0: self.fid = fid elif len(self.cats.keys()) > 0: self.fid = list(self.cats.keys())[0] else: self.fid = -1 if len(self.cats.keys()) == 1: self.fidMulti.Show(False) self.fidText.Show(True) if self.fid > 0: self.fidText.SetLabel("%d" % self.fid) else: self.fidText.SetLabel(_("Unknown")) else: self.fidMulti.Show(True) self.fidText.Show(False) choices = [] for tfid in self.cats.keys(): choices.append(str(tfid)) self.fidMulti.SetItems(choices) self.fidMulti.SetStringSelection(str(self.fid)) # reset notebook self.notebook.DeleteAllPages() for layer in layers: # for each layer if not query: # select by layer/cat if self.fid > 0 and layer in self.cats[self.fid]: for cat in self.cats[self.fid][layer]: nselected = self.mapDBInfo.SelectFromTable( layer, where="%s=%d" % (self.mapDBInfo.layers[layer]['key'], cat)) else: nselected = 0 # if nselected <= 0 and self.action != "add": # continue # nothing selected ... if self.action == "add": if nselected <= 0: if layer in self.cats[self.fid]: table = self.mapDBInfo.layers[layer]["table"] key = self.mapDBInfo.layers[layer]["key"] columns = self.mapDBInfo.tables[table] for name in columns.keys(): if name == key: for cat in self.cats[self.fid][layer]: self.mapDBInfo.tables[table][name][ 'values'].append(cat) else: self.mapDBInfo.tables[table][name][ 'values'].append(None) else: # change status 'add' -> 'update' self.action = "update" table = self.mapDBInfo.layers[layer]["table"] key = self.mapDBInfo.layers[layer]["key"] columns = self.mapDBInfo.tables[table] for idx in range(len(columns[key]['values'])): for name in columns.keys(): if name == key: cat = int(columns[name]['values'][idx]) break # use scrolled panel instead (and fix initial max height of the # window to 480px) panel = scrolled.ScrolledPanel(parent=self.notebook, id=wx.ID_ANY, size=(-1, 150)) panel.SetupScrolling(scroll_x=False) self.notebook.AddPage(page=panel, text=" %s %d / %s %d" % (_("Layer"), layer, _("Category"), cat)) # notebook body border = wx.BoxSizer(wx.VERTICAL) flexSizer = wx.FlexGridSizer(cols=3, hgap=3, vgap=3) flexSizer.AddGrowableCol(2) # columns (sorted by index) names = [''] * len(columns.keys()) for name in columns.keys(): names[columns[name]['index']] = name for name in names: if name == key: # skip key column (category) continue vtype = columns[name]['type'].lower() ctype = columns[name]['ctype'] if columns[name]['values'][idx] is not None: if not isinstance(columns[name]['ctype'], six.string_types): value = str(columns[name]['values'][idx]) else: value = columns[name]['values'][idx] else: value = '' colName = StaticText(parent=panel, id=wx.ID_ANY, label=name) colType = StaticText(parent=panel, id=wx.ID_ANY, label="[%s]:" % vtype) colValue = TextCtrl(parent=panel, id=wx.ID_ANY, value=value) colValue.SetName(name) if ctype == int: colValue.SetValidator(IntegerValidator()) elif ctype == float: colValue.SetValidator(FloatValidator()) self.Bind(wx.EVT_TEXT, self.OnSQLStatement, colValue) if self.action == 'display': colValue.SetWindowStyle(wx.TE_READONLY) flexSizer.Add(colName, proportion=0, flag=wx.ALIGN_CENTER_VERTICAL) flexSizer.Add(colType, proportion=0, flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT) flexSizer.Add(colValue, proportion=1, flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL) # add widget reference to self.columns columns[name]['ids'].append( colValue.GetId()) # name, type, values, id # for each attribute (including category) END border.Add(flexSizer, proportion=1, flag=wx.ALL | wx.EXPAND, border=5) panel.SetSizer(border) # for each category END # for each layer END if self.notebook.GetPageCount() == 0: self.noFoundMsg.Show(True) else: self.noFoundMsg.Show(False) self.Layout() return True def SetColumnValue(self, layer, column, value): """Set attrbute value :param column: column name :param value: value """ table = self.mapDBInfo.GetTable(layer) columns = self.mapDBInfo.GetTableDesc(table) for key, col in six.iteritems(columns): if key == column: col['values'] = [ col['ctype'](value), ] break