GRASS Programmer's Manual  6.5.svn(2012)-r51648
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
dbmgr/manager.py
Go to the documentation of this file.
00001 """!
00002 @package dbmgr.manager
00003 
00004 @brief GRASS Attribute Table Manager
00005 
00006 This program is based on FileHunter, published in 'The wxPython Linux
00007 Tutorial' on wxPython WIKI pages.
00008 
00009 It also uses some functions at
00010 http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/426407
00011 
00012 @code
00013 python dbm.py vector@mapset
00014 @endcode
00015 
00016 List of classes:
00017  - manager::Log
00018  - manager::VirtualAttributeList
00019  - manager::AttributeManager
00020  - manager::TableListCtrl
00021  - manager::LayerListCtrl
00022  - manager::LayerBook
00023 
00024 (C) 2007-2009, 2011 by the GRASS Development Team
00025 
00026 This program is free software under the GNU General Public License
00027 (>=v2). Read the file COPYING that comes with GRASS for details.
00028 
00029 @author Jachym Cepicky <jachym.cepicky gmail.com>
00030 @author Martin Landa <landa.martin gmail.com>
00031 """
00032 
00033 import sys
00034 import os
00035 import locale
00036 import tempfile
00037 import copy
00038 import types
00039 
00040 if __name__ == "__main__":
00041     sys.path.append(os.path.join(os.getenv('GISBASE'), 'etc', 'wxpython'))
00042 from core import globalvar
00043 import wx
00044 import wx.lib.mixins.listctrl as listmix
00045 import wx.lib.flatnotebook    as FN
00046 
00047 import grass.script as grass
00048 
00049 from dbmgr.sqlbuilder import SQLFrame
00050 from core.gcmd        import RunCommand, GException, GError, GMessage, GWarning
00051 from core.utils       import ListOfCatsToRange
00052 from gui_core.dialogs import CreateNewVector
00053 from dbmgr.vinfo      import VectorDBInfo, unicodeValue, createDbInfoDesc
00054 from core.debug       import Debug
00055 from dbmgr.dialogs    import ModifyTableRecord
00056 from core.settings    import UserSettings
00057 from gui_core.widgets import GNotebook
00058 
00059 class Log:
00060     """
00061     The log output is redirected to the status bar of the containing frame.
00062     """
00063     def __init__(self, parent):
00064         self.parent = parent
00065 
00066     def write(self, text_string):
00067         """!Update status bar"""
00068         self.parent.SetStatusText(text_string.strip())
00069 
00070 
00071 class VirtualAttributeList(wx.ListCtrl,
00072                            listmix.ListCtrlAutoWidthMixin,
00073                            listmix.ColumnSorterMixin):
00074     """
00075     Support virtual list class
00076     """
00077     def __init__(self, parent, log, mapDBInfo, layer):
00078         #
00079         # initialize variables
00080         #
00081         self.parent  = parent
00082         self.log     = log
00083         self.mapDBInfo = mapDBInfo
00084         self.layer   = layer
00085         
00086         self.columns = {} # <- LoadData()
00087 
00088         wx.ListCtrl.__init__(self, parent = parent, id = wx.ID_ANY,
00089                              style = wx.LC_REPORT | wx.LC_HRULES |
00090                              wx.LC_VRULES | wx.LC_VIRTUAL | wx.LC_SORT_ASCENDING)
00091         
00092         try:
00093             keyColumn = self.LoadData(layer)
00094         except GException, e:
00095             GError(parent = self,
00096                    message = e.value)
00097             return
00098         
00099         #
00100         # add some attributes (colourful background for each item rows)
00101         #
00102         self.attr1 = wx.ListItemAttr()
00103         self.attr1.SetBackgroundColour(wx.Colour(238,238,238))
00104         self.attr2 = wx.ListItemAttr()
00105         self.attr2.SetBackgroundColour("white")
00106         self.il = wx.ImageList(16, 16)
00107         self.sm_up = self.il.Add(wx.ArtProvider_GetBitmap(wx.ART_GO_UP,   wx.ART_TOOLBAR,
00108                                                           (16,16)))
00109         self.sm_dn = self.il.Add(wx.ArtProvider_GetBitmap(wx.ART_GO_DOWN, wx.ART_TOOLBAR,
00110                                                           (16,16)))
00111         self.SetImageList(self.il, wx.IMAGE_LIST_SMALL)
00112         
00113         # setup mixins
00114         listmix.ListCtrlAutoWidthMixin.__init__(self)
00115         listmix.ColumnSorterMixin.__init__(self, len(self.columns))
00116 
00117         # sort item by category (id)
00118         if keyColumn > -1:
00119             self.SortListItems(col = keyColumn, ascending = True) 
00120         else:
00121             self.SortListItems(col = 0, ascending = True) 
00122         
00123         # events
00124         self.Bind(wx.EVT_LIST_ITEM_SELECTED,   self.OnItemSelected)
00125         self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnItemDeselected)
00126         self.Bind(wx.EVT_LIST_COL_CLICK,       self.OnColumnSort)     
00127         self.Bind(wx.EVT_LIST_COL_RIGHT_CLICK, self.OnColumnMenu)     
00128         
00129     def Update(self, mapDBInfo):
00130         """!Update list according new mapDBInfo description"""
00131         self.mapDBInfo = mapDBInfo
00132         self.LoadData(self.layer)
00133 
00134     def LoadData(self, layer, columns = None, where = None, sql = None):
00135         """!Load data into list
00136 
00137         @param layer layer number
00138         @param columns list of columns for output (-> v.db.select)
00139         @param where where statement (-> v.db.select)
00140         @param sql full sql statement (-> db.select)
00141         
00142         @return id of key column 
00143         @return -1 if key column is not displayed
00144         """
00145         self.log.write(_("Loading data..."))
00146         
00147         tableName    = self.mapDBInfo.layers[layer]['table']
00148         keyColumn    = self.mapDBInfo.layers[layer]['key']
00149         try:
00150             self.columns = self.mapDBInfo.tables[tableName]
00151         except KeyError:
00152             raise GException(_("Attribute table <%s> not found. "
00153                                "For creating the table switch to "
00154                                "'Manage layers' tab.") % tableName)
00155         
00156         if not columns:
00157             columns = self.mapDBInfo.GetColumns(tableName)
00158         else:
00159             all = self.mapDBInfo.GetColumns(tableName)
00160             for col in columns:
00161                 if col not in all:
00162                     GError(parent = self,
00163                            message = _("Column <%(column)s> not found in "
00164                                        "in the table <%(table)s>.") % \
00165                                { 'column' : col, 'table' : tableName })
00166                     return
00167         
00168         try:
00169             # for maps connected via v.external
00170             keyId = columns.index(keyColumn)
00171         except:
00172             keyId = -1
00173         
00174         #
00175         # read data
00176         #
00177         # FIXME: Max. number of rows, while the GUI is still usable
00178 
00179         # stdout can be very large, do not use PIPE, redirect to temp file
00180         # TODO: more effective way should be implemented...
00181         outFile = tempfile.NamedTemporaryFile(mode = 'w+b')
00182         
00183         if sql:
00184             ret = RunCommand('db.select',
00185                              quiet = True,
00186                              parent = self,
00187                              flags = 'c',
00188                              sql = sql,
00189                              output = outFile.name)
00190         else:
00191             if columns:
00192                 ret = RunCommand('v.db.select',
00193                                  quiet = True,
00194                                  parent = self,
00195                                  flags = 'c',
00196                                  map = self.mapDBInfo.map,
00197                                  layer = layer,
00198                                  columns = ','.join(columns),
00199                                  where = where,
00200                                  stdout = outFile)
00201             else:
00202                 ret = RunCommand('v.db.select',
00203                                  quiet = True,
00204                                  parent = self,
00205                                  flags = 'c',
00206                                  map = self.mapDBInfo.map,
00207                                  layer = layer,
00208                                  where = where,
00209                                  stdout = outFile) 
00210         
00211         # These two should probably be passed to init more cleanly
00212         # setting the numbers of items = number of elements in the dictionary
00213         self.itemDataMap  = {}
00214         self.itemIndexMap = []
00215         self.itemCatsMap  = {}
00216         
00217         self.DeleteAllItems()
00218         
00219         # self.ClearAll()
00220         for i in range(self.GetColumnCount()):
00221             self.DeleteColumn(0)
00222         
00223         i = 0
00224         info = wx.ListItem()
00225         info.m_mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE | wx.LIST_MASK_FORMAT
00226         info.m_image = -1
00227         info.m_format = 0
00228         for column in columns:
00229             info.m_text = column
00230             self.InsertColumnInfo(i, info)
00231             i += 1
00232             
00233             if i >= 256:
00234                 self.log.write(_("Can display only 256 columns."))
00235         
00236         i = 0
00237         outFile.seek(0)
00238         
00239         while True:
00240             # os.linesep doesn't work here (MSYS)
00241             record = outFile.readline().replace('\n', '')
00242             
00243             if not record:
00244                 break
00245            
00246             self.AddDataRow(i, record, columns, keyId)
00247 
00248             i += 1
00249             if i >= 100000:
00250                 self.log.write(_("Limit 100000 records."))
00251                 break
00252         
00253         self.SetItemCount(i)
00254         
00255         i = 0
00256         for col in columns:
00257             width = self.columns[col]['length'] * 6 # FIXME
00258             if width < 60:
00259                 width = 60
00260             if width > 300:
00261                 width = 300
00262             self.SetColumnWidth(col = i, width = width)
00263             i += 1
00264         
00265         self.SendSizeEvent()
00266         
00267         self.log.write(_("Number of loaded records: %d") % \
00268                            self.GetItemCount())
00269         
00270         return keyId
00271     
00272     def AddDataRow(self, i, record, columns, keyId):
00273         """!Add row to the data list"""
00274         self.itemDataMap[i] = []
00275         keyColumn = self.mapDBInfo.layers[self.layer]['key']
00276         j = 0
00277         cat = None
00278         
00279         if keyColumn == 'OGC_FID':
00280             self.itemDataMap[i].append(i+1)
00281             j += 1
00282             cat = i + 1
00283         
00284         for value in record.split('|'):
00285             if self.columns[columns[j]]['ctype'] != types.StringType:
00286                 try:
00287                     ### casting disabled (2009/03)
00288                     ### self.itemDataMap[i].append(self.columns[columns[j]]['ctype'](value))
00289                     self.itemDataMap[i].append(value)
00290                 except ValueError:
00291                     self.itemDataMap[i].append(_('Unknown value'))
00292             else:
00293                 # encode string values
00294                 try:
00295                     self.itemDataMap[i].append(unicodeValue(value))
00296                 except UnicodeDecodeError:
00297                     self.itemDataMap[i].append(_("Unable to decode value. "
00298                                                  "Set encoding in GUI preferences ('Attributes')."))
00299                 
00300             if not cat and keyId > -1 and keyId == j:
00301                 try:
00302                     cat = self.columns[columns[j]]['ctype'] (value)
00303                 except ValueError, e:
00304                     cat = -1
00305                     GError(parent = self,
00306                            message = _("Error loading attribute data. "
00307                                        "Record number: %(rec)d. Unable to convert value '%(val)s' in "
00308                                        "key column (%(key)s) to integer.\n\n"
00309                                        "Details: %(detail)s") % \
00310                                { 'rec' : i + 1, 'val' : value,
00311                                  'key' : keyColumn, 'detail' : e})
00312             j += 1
00313         
00314         self.itemIndexMap.append(i)
00315         if keyId > -1: # load cats only when LoadData() is called first time
00316             self.itemCatsMap[i] = cat
00317         
00318     def OnItemSelected(self, event):
00319         """!Item selected. Add item to selected cats..."""
00320         #         cat = int(self.GetItemText(event.m_itemIndex))
00321         #         if cat not in self.selectedCats:
00322         #             self.selectedCats.append(cat)
00323         #             self.selectedCats.sort()
00324         
00325         event.Skip()
00326 
00327     def OnItemDeselected(self, event):
00328         """!Item deselected. Remove item from selected cats..."""
00329         #         cat = int(self.GetItemText(event.m_itemIndex))
00330         #         if cat in self.selectedCats:
00331         #             self.selectedCats.remove(cat)
00332         #             self.selectedCats.sort()
00333 
00334         event.Skip()
00335 
00336     def GetSelectedItems(self):
00337         """!Return list of selected items (category numbers)"""
00338         cats = []
00339         item = self.GetFirstSelected()
00340         while item != -1:
00341             cats.append(self.GetItemText(item))
00342             item = self.GetNextSelected(item)
00343 
00344         return cats
00345 
00346     def GetColumnText(self, index, col):
00347         """!Return column text"""
00348         item = self.GetItem(index, col)
00349         return item.GetText()
00350 
00351     def GetListCtrl(self):
00352         """!Returt list"""
00353         return self
00354 
00355     def OnGetItemText(self, item, col):
00356         """!Get item text"""
00357         index = self.itemIndexMap[item]
00358         s = self.itemDataMap[index][col]
00359         return s
00360 
00361     def OnGetItemAttr(self, item):
00362         """!Get item attributes"""
00363         if ( item % 2) == 0:
00364             return self.attr2
00365         else:
00366             return self.attr1
00367 
00368     def OnColumnMenu(self, event):
00369         """!Column heading right mouse button -> pop-up menu"""
00370         self._col = event.GetColumn()
00371         
00372         popupMenu = wx.Menu()
00373 
00374         if not hasattr (self, "popupID1"):
00375             self.popupID1 = wx.NewId()
00376             self.popupID2 = wx.NewId()
00377             self.popupID3 = wx.NewId()
00378             self.popupID4 = wx.NewId()
00379             self.popupID5 = wx.NewId()
00380             self.popupID6 = wx.NewId()
00381             self.popupID7 = wx.NewId()
00382             self.popupID8 = wx.NewId()
00383             self.popupID9 = wx.NewId()
00384             self.popupID10 = wx.NewId()
00385             self.popupID11 = wx.NewId()
00386             self.popupID12 = wx.NewId()
00387         
00388         popupMenu.Append(self.popupID1, text = _("Sort ascending"))
00389         popupMenu.Append(self.popupID2, text = _("Sort descending"))
00390         popupMenu.AppendSeparator()
00391         subMenu = wx.Menu()
00392         popupMenu.AppendMenu(self.popupID3, _("Calculate (only numeric columns)"),
00393                              subMenu)
00394         if not self.log.parent.editable or \
00395                 self.columns[self.GetColumn(self._col).GetText()]['ctype'] not in (types.IntType, types.FloatType):
00396             popupMenu.Enable(self.popupID3, False)
00397         
00398         subMenu.Append(self.popupID4,  text = _("Area size"))
00399         subMenu.Append(self.popupID5,  text = _("Line length"))
00400         subMenu.Append(self.popupID6,  text = _("Compactness of an area"))
00401         subMenu.Append(self.popupID7,  text = _("Fractal dimension of boundary defining a polygon"))
00402         subMenu.Append(self.popupID8,  text = _("Perimeter length of an area"))
00403         subMenu.Append(self.popupID9,  text = _("Number of features for each category"))
00404         subMenu.Append(self.popupID10, text = _("Slope steepness of 3D line"))
00405         subMenu.Append(self.popupID11, text = _("Line sinuousity"))
00406         subMenu.Append(self.popupID12, text = _("Line azimuth"))
00407         
00408         self.Bind (wx.EVT_MENU, self.OnColumnSortAsc,  id = self.popupID1)
00409         self.Bind (wx.EVT_MENU, self.OnColumnSortDesc, id = self.popupID2)
00410         for id in (self.popupID4, self.popupID5, self.popupID6,
00411                    self.popupID7, self.popupID8, self.popupID9,
00412                    self.popupID10, self.popupID11, self.popupID12):
00413             self.Bind(wx.EVT_MENU, self.OnColumnCompute, id = id)
00414         
00415         self.PopupMenu(popupMenu)
00416         popupMenu.Destroy()
00417 
00418     def OnColumnSort(self, event):
00419         """!Column heading left mouse button -> sorting"""
00420         self._col = event.GetColumn()
00421         
00422         self.ColumnSort()
00423         
00424         event.Skip()
00425 
00426     def OnColumnSortAsc(self, event):
00427         """!Sort values of selected column (ascending)"""
00428         self.SortListItems(col = self._col, ascending = True)
00429         event.Skip()
00430 
00431     def OnColumnSortDesc(self, event):
00432         """!Sort values of selected column (descending)"""
00433         self.SortListItems(col = self._col, ascending = False)
00434         event.Skip()
00435         
00436     def OnColumnCompute(self, event):
00437         """!Compute values of selected column"""
00438         id = event.GetId()
00439         
00440         option = None
00441         if id == self.popupID4:
00442             option = 'area'
00443         elif id == self.popupID5:
00444             option = 'length'
00445         elif id == self.popupID6:
00446             option = 'compact'
00447         elif id == self.popupID7:
00448             option = 'fd'
00449         elif id == self.popupID8:
00450             option = 'perimeter'
00451         elif id == self.popupID9:
00452             option = 'count'
00453         elif id == self.popupID10:
00454             option = 'slope'
00455         elif id == self.popupID11:
00456             option = 'sinuous'
00457         elif id == self.popupID12:
00458             option = 'azimuth'
00459         
00460         if not option:
00461             return
00462         
00463         RunCommand('v.to.db',
00464                    parent = self.parent,
00465                    map = self.mapDBInfo.map,
00466                    layer = self.layer, 
00467                    option = option,
00468                    columns = self.GetColumn(self._col).GetText())
00469         
00470         self.LoadData(self.layer)
00471         
00472     def ColumnSort(self):
00473         """!Sort values of selected column (self._col)"""
00474         # remove duplicated arrow symbol from column header
00475         # FIXME: should be done automatically
00476         info = wx.ListItem()
00477         info.m_mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE
00478         info.m_image = -1
00479         for column in range(self.GetColumnCount()):
00480             info.m_text = self.GetColumn(column).GetText()
00481             self.SetColumn(column, info)
00482         
00483     def SortItems(self, sorter = cmp):
00484         """!Sort items"""
00485         items = list(self.itemDataMap.keys())
00486         items.sort(self.Sorter)
00487         self.itemIndexMap = items
00488 
00489         # redraw the list
00490         self.Refresh()
00491         
00492     def Sorter(self, key1, key2):
00493         colName = self.GetColumn(self._col).GetText()
00494         ascending = self._colSortFlag[self._col]
00495         try:
00496             item1 = self.columns[colName]["ctype"](self.itemDataMap[key1][self._col])
00497             item2 = self.columns[colName]["ctype"](self.itemDataMap[key2][self._col])
00498         except ValueError:
00499             item1 = self.itemDataMap[key1][self._col]
00500             item2 = self.itemDataMap[key2][self._col]
00501 
00502         if type(item1) == types.StringType or type(item2) == types.StringTypes:
00503             cmpVal = locale.strcoll(str(item1), str(item2))
00504         else:
00505             cmpVal = cmp(item1, item2)
00506 
00507 
00508         # If the items are equal then pick something else to make the sort value unique
00509         if cmpVal == 0:
00510             cmpVal = apply(cmp, self.GetSecondarySortValues(self._col, key1, key2))
00511         
00512         if ascending:
00513             return cmpVal
00514         else:
00515             return -cmpVal
00516 
00517     def GetSortImages(self):
00518         """!Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py"""
00519         return (self.sm_dn, self.sm_up)
00520 
00521     def IsEmpty(self):
00522         """!Check if list if empty"""
00523         if self.columns:
00524             return False
00525         
00526         return True
00527     
00528 class AttributeManager(wx.Frame):
00529     def __init__(self, parent, id = wx.ID_ANY,
00530                  title = None, vectorName = None, item = None, log = None,
00531                  selection = None, **kwargs):
00532         """!GRASS Attribute Table Manager window
00533 
00534         @param parent parent window
00535         @parem id window id
00536         @param title window title or None for default title
00537         @param vetorName name of vector map
00538         @param item item from Layer Tree
00539         @param log log window
00540         @param selection name of page to be selected
00541         @param kwagrs other wx.Frame's arguments
00542         """
00543         self.vectorName = vectorName
00544         self.parent     = parent # GMFrame
00545         self.treeItem   = item   # item in layer tree
00546         if self.parent and self.parent.GetName() == "LayerManager" and \
00547                 self.treeItem and not self.vectorName:
00548             maptree = self.parent.curr_page.maptree
00549             name = maptree.GetPyData(self.treeItem)[0]['maplayer'].GetName()
00550             self.vectorName = name
00551         
00552         # vector attributes can be changed only if vector map is in
00553         # the current mapset
00554         if grass.find_file(name = self.vectorName, element = 'vector')['mapset'] == grass.gisenv()['MAPSET']:
00555             self.editable = True
00556         else:
00557             self.editable = False
00558         
00559         self.cmdLog = log    # self.parent.goutput
00560         
00561         wx.Frame.__init__(self, parent, id, *kwargs)
00562 
00563         # title
00564         if not title:
00565             self.SetTitle("%s - <%s>" % (_("GRASS GIS Attribute Table Manager"),
00566                                          self.vectorName))
00567         else:
00568             self.SetTitle(title)
00569         
00570         # icon
00571         self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass_sql.ico'), wx.BITMAP_TYPE_ICO))
00572 
00573         self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
00574 
00575         try:
00576             self.map        = self.parent.curr_page.maptree.Map
00577             self.mapdisplay = self.parent.curr_page.maptree.mapdisplay
00578         except:
00579             self.map = self.mapdisplay = None
00580         
00581         # status bar log class
00582         self.log = Log(self) # -> statusbar
00583 
00584         # query map layer (if parent (GMFrame) is given)
00585         self.qlayer = None
00586 
00587         # -> layers / tables description
00588         self.mapDBInfo = VectorDBInfo(self.vectorName)
00589 
00590         # sqlbuilder
00591         self.builder = None
00592         
00593         if len(self.mapDBInfo.layers.keys()) == 0:
00594             GMessage(parent = self.parent,
00595                      message = _("Database connection for vector map <%s> "
00596                                  "is not defined in DB file. "
00597                                  "You can define new connection in "
00598                                  "'Manage layers' tab.") % self.vectorName)
00599         
00600         #
00601         # list of command/SQL statements to be performed
00602         #
00603         self.listOfCommands      = []
00604         self.listOfSQLStatements = []
00605 
00606         self.CreateStatusBar(number = 1)
00607 
00608         # set up virtual lists (each layer)
00609         ### {layer: list, widgets...}
00610         self.layerPage = {}
00611 
00612         self.notebook = GNotebook(self.panel, style = globalvar.FNPageDStyle)
00613         
00614         if globalvar.hasAgw:
00615             dbmStyle = { 'agwStyle' : globalvar.FNPageStyle }
00616         else:
00617             dbmStyle = { 'style' : globalvar.FNPageStyle }
00618         
00619         self.browsePage = FN.FlatNotebook(self.panel, id = wx.ID_ANY,
00620                                           **dbmStyle)
00621         self.notebook.AddPage(page = self.browsePage, text = _("Browse data"),
00622                               name = 'browse')
00623         self.browsePage.SetTabAreaColour(globalvar.FNPageColor)
00624 
00625         self.manageTablePage = FN.FlatNotebook(self.panel, id = wx.ID_ANY,
00626                                                **dbmStyle)
00627         self.notebook.AddPage(page = self.manageTablePage, text = _("Manage tables"),
00628                               name = 'table')
00629         if not self.editable:
00630             self.notebook.GetPage(self.notebook.GetPageCount()-1).Enable(False)
00631         self.manageTablePage.SetTabAreaColour(globalvar.FNPageColor)
00632 
00633         self.manageLayerPage = FN.FlatNotebook(self.panel, id = wx.ID_ANY,
00634                                                **dbmStyle)
00635         self.notebook.AddPage(page = self.manageLayerPage, text = _("Manage layers"),
00636                               name = 'layers')
00637         self.manageLayerPage.SetTabAreaColour(globalvar.FNPageColor)
00638         if not self.editable:
00639             self.notebook.GetPage(self.notebook.GetPageCount()-1).Enable(False)
00640         
00641         self._createBrowsePage()
00642         self._createManageTablePage()
00643         self._createManageLayerPage()
00644 
00645         if selection:
00646             wx.CallAfter(self.notebook.SetSelectionByName, selection)
00647         else:
00648             wx.CallAfter(self.notebook.SetSelection, 0) # select browse tab
00649         
00650         # buttons
00651         self.btnQuit   = wx.Button(parent = self.panel, id = wx.ID_EXIT)
00652         self.btnQuit.SetToolTipString(_("Close Attribute Table Manager"))
00653         self.btnReload = wx.Button(parent = self.panel, id = wx.ID_REFRESH)
00654         self.btnReload.SetToolTipString(_("Reload attribute data (selected layer only)"))
00655 
00656         # events
00657         self.btnQuit.Bind(wx.EVT_BUTTON,   self.OnCloseWindow)
00658         self.btnReload.Bind(wx.EVT_BUTTON, self.OnDataReload)
00659         self.notebook.Bind(FN.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.OnPageChanged)
00660         self.Bind(FN.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.OnLayerPageChanged, self.browsePage)
00661         self.Bind(FN.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.OnLayerPageChanged, self.manageTablePage)
00662         self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
00663 
00664         # do layout
00665         self._layout()
00666 
00667         # self.SetMinSize(self.GetBestSize())
00668         self.SetSize((700, 550)) # FIXME hard-coded size
00669         self.SetMinSize(self.GetSize())
00670 
00671     def _createBrowsePage(self, onlyLayer = -1):
00672         """!Create browse tab page"""
00673         for layer in self.mapDBInfo.layers.keys():
00674             if onlyLayer > 0 and layer != onlyLayer:
00675                 continue
00676 
00677             panel = wx.Panel(parent = self.browsePage, id = wx.ID_ANY)
00678             
00679             #IMPORTANT NOTE: wx.StaticBox MUST be defined BEFORE any of the 
00680             #   controls that are placed IN the wx.StaticBox, or it will freeze
00681             #   on the Mac
00682             
00683             listBox = wx.StaticBox(parent = panel, id = wx.ID_ANY,
00684                                    label = " %s " % _("Attribute data - right-click to edit/manage records"))
00685             listSizer = wx.StaticBoxSizer(listBox, wx.VERTICAL)
00686             
00687             win = VirtualAttributeList(panel, self.log,
00688                                        self.mapDBInfo, layer)
00689             if win.IsEmpty():
00690                 del panel
00691                 continue
00692             
00693             win.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnDataItemActivated)
00694 
00695             self.layerPage[layer] = {'browsePage': panel.GetId()}
00696             
00697             label = _("Table")
00698             if not self.editable:
00699                 label += _(" (readonly)")
00700             self.browsePage.AddPage(page = panel, text = " %d / %s %s" % \
00701                                         (layer, label, self.mapDBInfo.layers[layer]['table']))
00702             
00703             pageSizer = wx.BoxSizer(wx.VERTICAL)
00704 
00705             # attribute data            
00706             sqlBox = wx.StaticBox(parent = panel, id = wx.ID_ANY,
00707                                   label = " %s " % _("SQL Query"))
00708 
00709             sqlSizer = wx.StaticBoxSizer(sqlBox, wx.VERTICAL)
00710 
00711             win.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnDataRightUp) #wxMSW
00712             win.Bind(wx.EVT_RIGHT_UP,            self.OnDataRightUp) #wxGTK
00713             if UserSettings.Get(group = 'atm', key = 'leftDbClick', subkey = 'selection') == 0:
00714                 win.Bind(wx.EVT_LEFT_DCLICK, self.OnDataItemEdit)
00715                 win.Bind(wx.EVT_COMMAND_LEFT_DCLICK, self.OnDataItemEdit)
00716             else:
00717                 win.Bind(wx.EVT_LEFT_DCLICK, self.OnDataDrawSelected)
00718                 win.Bind(wx.EVT_COMMAND_LEFT_DCLICK, self.OnDataDrawSelected)
00719             
00720             listSizer.Add(item = win, proportion = 1,
00721                           flag = wx.EXPAND | wx.ALL,
00722                           border = 3)
00723 
00724             # sql statement box
00725             btnApply = wx.Button(parent = panel, id = wx.ID_APPLY)
00726             btnApply.SetToolTipString(_("Apply SELECT statement and reload data records"))
00727             btnApply.Bind(wx.EVT_BUTTON, self.OnApplySqlStatement)
00728             btnSqlBuilder = wx.Button(parent = panel, id = wx.ID_ANY, label = _("SQL Builder"))
00729             btnSqlBuilder.Bind(wx.EVT_BUTTON, self.OnBuilder)
00730 
00731             sqlSimple = wx.RadioButton(parent = panel, id = wx.ID_ANY,
00732                                        label = _("Simple"))
00733             sqlSimple.SetValue(True)
00734             sqlAdvanced = wx.RadioButton(parent = panel, id = wx.ID_ANY,
00735                                          label = _("Advanced"))
00736             sqlSimple.Bind(wx.EVT_RADIOBUTTON,   self.OnChangeSql)
00737             sqlAdvanced.Bind(wx.EVT_RADIOBUTTON, self.OnChangeSql)
00738 
00739             sqlWhereColumn = wx.ComboBox(parent = panel, id = wx.ID_ANY,
00740                                          size = (100,-1),
00741                                          style = wx.CB_SIMPLE | wx.CB_READONLY,
00742                                          choices = self.mapDBInfo.GetColumns(self.mapDBInfo.layers[layer]['table']))
00743             sqlWhereColumn.SetSelection(0)
00744             sqlWhereCond = wx.Choice(parent = panel, id = wx.ID_ANY,
00745                                      size = (55,-1),
00746                                      choices = ['=', '!=', '<', '<=', '>', '>='])
00747             sqlWhereValue = wx.TextCtrl(parent = panel, id = wx.ID_ANY, value = "",
00748                                         style = wx.TE_PROCESS_ENTER)
00749             sqlWhereValue.SetToolTipString(_("Example: %s") % "MULTILANE = 'no' AND OBJECTID < 10")
00750 
00751             sqlStatement = wx.TextCtrl(parent = panel, id = wx.ID_ANY,
00752                                        value = "SELECT * FROM %s" % \
00753                                            self.mapDBInfo.layers[layer]['table'],
00754                                        style = wx.TE_PROCESS_ENTER)
00755             sqlStatement.SetToolTipString(_("Example: %s") % "SELECT * FROM roadsmajor WHERE MULTILANE = 'no' AND OBJECTID < 10")
00756             sqlWhereValue.Bind(wx.EVT_TEXT_ENTER, self.OnApplySqlStatement)
00757             sqlStatement.Bind(wx.EVT_TEXT_ENTER, self.OnApplySqlStatement)
00758 
00759             sqlLabel = wx.StaticText(parent = panel, id = wx.ID_ANY,
00760                                      label = "SELECT * FROM %s WHERE " % \
00761                                          self.mapDBInfo.layers[layer]['table'])
00762             label_query = wx.StaticText(parent = panel, id = wx.ID_ANY,
00763                                         label = "")
00764 
00765             sqlFlexSizer = wx.FlexGridSizer (cols = 3, hgap = 5, vgap = 5)
00766             sqlFlexSizer.AddGrowableCol(1)
00767 
00768             sqlFlexSizer.Add(item = sqlSimple,
00769                              flag = wx.ALIGN_CENTER_VERTICAL)
00770             sqlSimpleSizer = wx.BoxSizer(wx.HORIZONTAL)
00771             sqlSimpleSizer.Add(item = sqlLabel,
00772                                flag = wx.ALIGN_CENTER_VERTICAL)
00773             sqlSimpleSizer.Add(item = sqlWhereColumn,
00774                                flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL)
00775             sqlSimpleSizer.Add(item = sqlWhereCond,
00776                                flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
00777                                border = 3)
00778             sqlSimpleSizer.Add(item = sqlWhereValue, proportion = 1,
00779                                flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL)
00780             sqlFlexSizer.Add(item = sqlSimpleSizer,
00781                              flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND)
00782             sqlFlexSizer.Add(item = btnApply,
00783                              flag = wx.ALIGN_RIGHT)
00784             sqlFlexSizer.Add(item = sqlAdvanced,
00785                              flag = wx.ALIGN_CENTER_VERTICAL)
00786             sqlFlexSizer.Add(item = sqlStatement,
00787                              flag = wx.EXPAND)
00788             sqlFlexSizer.Add(item = btnSqlBuilder,
00789                              flag = wx.ALIGN_RIGHT)
00790 
00791             sqlSizer.Add(item = sqlFlexSizer,
00792                          flag = wx.ALL | wx.EXPAND,
00793                          border = 3)
00794 
00795             pageSizer.Add(item = listSizer,
00796                           proportion = 1,
00797                           flag = wx.ALL | wx.EXPAND,
00798                           border = 5)
00799 
00800             pageSizer.Add(item = sqlSizer,
00801                           proportion = 0,
00802                           flag = wx.BOTTOM | wx.LEFT | wx.RIGHT | wx.EXPAND,
00803                           border = 5)
00804 
00805             panel.SetSizer(pageSizer)
00806 
00807             self.layerPage[layer]['data']      = win.GetId()
00808             self.layerPage[layer]['simple']    = sqlSimple.GetId()
00809             self.layerPage[layer]['advanced']  = sqlAdvanced.GetId()
00810             self.layerPage[layer]['whereColumn'] = sqlWhereColumn.GetId()
00811             self.layerPage[layer]['whereOperator'] = sqlWhereCond.GetId()
00812             self.layerPage[layer]['where']     = sqlWhereValue.GetId()
00813             self.layerPage[layer]['builder']   = btnSqlBuilder.GetId()
00814             self.layerPage[layer]['statement'] = sqlStatement.GetId()
00815 
00816 
00817         self.browsePage.SetSelection(0) # select first layer
00818         try:
00819             self.layer = self.mapDBInfo.layers.keys()[0]
00820             self.OnChangeSql(None)
00821             self.log.write(_("Number of loaded records: %d") % \
00822                            self.FindWindowById(self.layerPage[self.layer]['data']).GetItemCount())
00823         except (IndexError, KeyError):
00824             self.layer = None
00825         
00826     def _createManageTablePage(self, onlyLayer = -1):
00827         """!Create manage page (create/link and alter tables)"""
00828         for layer in self.mapDBInfo.layers.keys():
00829             if onlyLayer > 0 and layer != onlyLayer:
00830                 continue
00831             
00832             if not layer in self.layerPage:
00833                 continue
00834             
00835             panel = wx.Panel(parent = self.manageTablePage, id = wx.ID_ANY)
00836             self.layerPage[layer]['tablePage'] = panel.GetId()
00837             label = _("Table")
00838             if not self.editable:
00839                 label += _(" (readonly)")
00840             self.manageTablePage.AddPage(page = panel,
00841                                          text = " %d / %s %s" % (layer, label,
00842                                                                self.mapDBInfo.layers[layer]['table']))
00843             
00844             pageSizer = wx.BoxSizer(wx.VERTICAL)
00845             
00846             #
00847             # dbInfo
00848             #
00849             dbBox = wx.StaticBox(parent = panel, id = wx.ID_ANY,
00850                                           label = " %s " % _("Database connection"))
00851             dbSizer = wx.StaticBoxSizer(dbBox, wx.VERTICAL)
00852             dbSizer.Add(item = createDbInfoDesc(panel, self.mapDBInfo, layer),
00853                         proportion = 1,
00854                         flag = wx.EXPAND | wx.ALL,
00855                         border = 3)
00856             
00857             #
00858             # table description
00859             #
00860             table = self.mapDBInfo.layers[layer]['table']
00861             tableBox = wx.StaticBox(parent = panel, id = wx.ID_ANY,
00862                                     label = " %s " % _("Table <%s> - right-click to delete column(s)") % table)
00863             
00864             tableSizer = wx.StaticBoxSizer(tableBox, wx.VERTICAL)
00865             
00866             tlist = self._createTableDesc(panel, table)
00867             tlist.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnTableRightUp) #wxMSW
00868             tlist.Bind(wx.EVT_RIGHT_UP,            self.OnTableRightUp) #wxGTK
00869             self.layerPage[layer]['tableData'] = tlist.GetId()
00870             
00871             # manage columns (add)
00872             addBox = wx.StaticBox(parent = panel, id = wx.ID_ANY,
00873                                   label = " %s " % _("Add column"))
00874             addSizer = wx.StaticBoxSizer(addBox, wx.HORIZONTAL)
00875             
00876             column = wx.TextCtrl(parent = panel, id = wx.ID_ANY, value = '',
00877                                  size = (150, -1), style = wx.TE_PROCESS_ENTER)
00878             column.Bind(wx.EVT_TEXT,       self.OnTableAddColumnName)
00879             column.Bind(wx.EVT_TEXT_ENTER, self.OnTableItemAdd)
00880             self.layerPage[layer]['addColName'] = column.GetId()
00881             addSizer.Add(item =  wx.StaticText(parent = panel, id = wx.ID_ANY, label = _("Column")),
00882                          flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
00883                          border = 5)
00884             addSizer.Add(item = column, proportion = 1,
00885                          flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
00886                          border = 5)
00887             
00888             ctype = wx.Choice (parent = panel, id = wx.ID_ANY,
00889                                choices = ["integer",
00890                                           "double",
00891                                           "varchar",
00892                                           "date"]) # FIXME
00893             ctype.SetSelection(0)
00894             ctype.Bind(wx.EVT_CHOICE, self.OnTableChangeType)
00895             self.layerPage[layer]['addColType'] = ctype.GetId()
00896             addSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY, label = _("Type")), 
00897                          flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
00898                          border = 5)
00899             addSizer.Add(item = ctype,
00900                          flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
00901                          border = 5)
00902             
00903             length = wx.SpinCtrl(parent = panel, id = wx.ID_ANY, size = (65, -1),
00904                                  initial = 250,
00905                                  min = 1, max = 1e6)
00906             length.Enable(False)
00907             self.layerPage[layer]['addColLength'] = length.GetId()
00908             addSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY, label = _("Length")),
00909                          flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
00910                          border = 5)
00911             addSizer.Add(item = length,
00912                          flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
00913                          border = 5)
00914             
00915             btnAddCol = wx.Button(parent = panel, id = wx.ID_ADD)
00916             btnAddCol.Bind(wx.EVT_BUTTON, self.OnTableItemAdd)
00917             btnAddCol.Enable(False)
00918             self.layerPage[layer]['addColButton'] = btnAddCol.GetId()
00919             addSizer.Add(item = btnAddCol, flag = wx.ALL | wx.ALIGN_RIGHT | wx.EXPAND,
00920                          border = 3)
00921             
00922             # manage columns (rename)
00923             renameBox = wx.StaticBox(parent = panel, id = wx.ID_ANY,
00924                                      label = " %s " % _("Rename column"))
00925             renameSizer = wx.StaticBoxSizer(renameBox, wx.HORIZONTAL)
00926             
00927             column = wx.ComboBox(parent = panel, id = wx.ID_ANY, size = (150, -1),
00928                                  style = wx.CB_SIMPLE | wx.CB_READONLY,
00929                                  choices = self.mapDBInfo.GetColumns(table))
00930             column.SetSelection(0)
00931             self.layerPage[layer]['renameCol'] = column.GetId()
00932             renameSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY, label = _("Column")),
00933                             flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
00934                             border = 5)
00935             renameSizer.Add(item = column, proportion = 1,
00936                             flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
00937                             border = 5)
00938             
00939             columnTo = wx.TextCtrl(parent = panel, id = wx.ID_ANY, value = '',
00940                                    size = (150, -1), style = wx.TE_PROCESS_ENTER)
00941             columnTo.Bind(wx.EVT_TEXT,       self.OnTableRenameColumnName)
00942             columnTo.Bind(wx.EVT_TEXT_ENTER, self.OnTableItemChange)
00943             self.layerPage[layer]['renameColTo'] = columnTo.GetId()
00944             renameSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY, label = _("To")),
00945                             flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
00946                             border = 5)
00947             renameSizer.Add(item = columnTo, proportion = 1,
00948                             flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
00949                             border = 5)
00950             
00951             btnRenameCol = wx.Button(parent = panel, id = wx.ID_ANY, label = _("&Rename"))
00952             btnRenameCol.Bind(wx.EVT_BUTTON, self.OnTableItemChange)
00953             btnRenameCol.Enable(False)
00954             self.layerPage[layer]['renameColButton'] = btnRenameCol.GetId()
00955             renameSizer.Add(item = btnRenameCol, flag = wx.ALL | wx.ALIGN_RIGHT | wx.EXPAND,
00956                             border = 3)
00957             
00958             tableSizer.Add(item = tlist,
00959                            flag = wx.ALL | wx.EXPAND,
00960                            proportion = 1,
00961                            border = 3)
00962             
00963             pageSizer.Add(item=dbSizer,
00964                           flag = wx.ALL | wx.EXPAND,
00965                           proportion = 0,
00966                           border = 3)
00967             
00968             pageSizer.Add(item = tableSizer,
00969                           flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND,
00970                           proportion = 1,
00971                           border = 3)
00972             
00973             pageSizer.Add(item = addSizer,
00974                           flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND,
00975                           proportion = 0,
00976                           border = 3)
00977             pageSizer.Add(item = renameSizer,
00978                           flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND,
00979                           proportion = 0,
00980                           border = 3)
00981             
00982             panel.SetSizer(pageSizer)
00983         
00984         self.manageTablePage.SetSelection(0) # select first layer
00985         try:
00986             self.layer = self.mapDBInfo.layers.keys()[0]
00987         except IndexError:
00988             self.layer = None
00989         
00990     def _createTableDesc(self, parent, table):
00991         """!Create list with table description"""
00992         tlist = TableListCtrl(parent = parent, id = wx.ID_ANY,
00993                              table = self.mapDBInfo.tables[table],
00994                              columns = self.mapDBInfo.GetColumns(table))
00995         tlist.Populate()
00996         # sorter
00997         # itemDataMap = list.Populate()
00998         # listmix.ColumnSorterMixin.__init__(self, 2)
00999 
01000         return tlist
01001 
01002     def _createManageLayerPage(self):
01003         """!Create manage page"""
01004         splitterWin = wx.SplitterWindow(parent = self.manageLayerPage, id = wx.ID_ANY)
01005         splitterWin.SetMinimumPaneSize(100)
01006         
01007         label = _("Layers of vector map")
01008         if not self.editable:
01009             label += _(" (readonly)")
01010         self.manageLayerPage.AddPage(page = splitterWin,
01011                                      text = label) # dummy page
01012         
01013         #
01014         # list of layers
01015         #
01016         panelList = wx.Panel(parent = splitterWin, id = wx.ID_ANY)
01017 
01018         panelListSizer  = wx.BoxSizer(wx.VERTICAL)
01019         layerBox = wx.StaticBox(parent = panelList, id = wx.ID_ANY,
01020                                 label = " %s " % _("List of layers"))
01021         layerSizer = wx.StaticBoxSizer(layerBox, wx.VERTICAL)
01022 
01023         self.layerList = self._createLayerDesc(panelList)
01024         self.layerList.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnLayerRightUp) #wxMSW
01025         self.layerList.Bind(wx.EVT_RIGHT_UP,            self.OnLayerRightUp) #wxGTK
01026         
01027         layerSizer.Add(item = self.layerList,
01028                        flag = wx.ALL | wx.EXPAND,
01029                        proportion = 1,
01030                        border = 3)
01031 
01032         panelListSizer.Add(item = layerSizer,
01033                            flag = wx.ALL | wx.EXPAND,
01034                            proportion = 1,
01035                            border = 3)
01036 
01037         panelList.SetSizer(panelListSizer)
01038 
01039         #
01040         # manage part
01041         #
01042         panelManage = wx.Panel(parent = splitterWin, id = wx.ID_ANY)
01043          
01044         manageSizer = wx.BoxSizer(wx.VERTICAL)
01045 
01046         self.manageLayerBook = LayerBook(parent = panelManage, id = wx.ID_ANY,
01047                                          parentDialog = self)
01048 
01049         manageSizer.Add(item = self.manageLayerBook,
01050                         proportion = 1,
01051                         flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND,
01052                         border = 5)
01053 
01054         panelManage.SetSizer(manageSizer)
01055         splitterWin.SplitHorizontally(panelList, panelManage, 100) 
01056         splitterWin.Fit()
01057 
01058     def _createLayerDesc(self, parent):
01059         """!Create list of linked layers"""
01060         tlist = LayerListCtrl(parent = parent, id = wx.ID_ANY,
01061                              layers = self.mapDBInfo.layers)
01062         
01063         tlist.Populate()
01064         # sorter
01065         # itemDataMap = list.Populate()
01066         # listmix.ColumnSorterMixin.__init__(self, 2)
01067 
01068         return tlist
01069 
01070     def _layout(self):
01071         """!Do layout"""
01072         # frame body
01073         mainSizer = wx.BoxSizer(wx.VERTICAL)
01074 
01075         # buttons
01076         btnSizer = wx.BoxSizer(wx.HORIZONTAL)
01077         btnSizer.Add(item = self.btnReload, proportion = 1,
01078                      flag = wx.ALL | wx.ALIGN_RIGHT, border = 5)
01079         btnSizer.Add(item = self.btnQuit, proportion = 1,
01080                      flag = wx.ALL | wx.ALIGN_RIGHT, border = 5)
01081 
01082         mainSizer.Add(item = self.notebook, proportion = 1, flag = wx.EXPAND)
01083         mainSizer.Add(item = btnSizer, flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
01084 
01085         self.panel.SetAutoLayout(True)
01086         self.panel.SetSizer(mainSizer)
01087         mainSizer.Fit(self.panel)
01088         self.Layout()
01089         
01090     def OnDataRightUp(self, event):
01091         """!Table description area, context menu"""
01092         if not hasattr(self, "popupDataID1"):
01093             self.popupDataID1 = wx.NewId()
01094             self.popupDataID2 = wx.NewId()
01095             self.popupDataID3 = wx.NewId()
01096             self.popupDataID4 = wx.NewId()
01097             self.popupDataID5 = wx.NewId()
01098             self.popupDataID6 = wx.NewId()
01099             self.popupDataID7 = wx.NewId()
01100             self.popupDataID8 = wx.NewId()
01101             self.popupDataID9 = wx.NewId()
01102             self.popupDataID10 = wx.NewId()
01103             self.popupDataID11 = wx.NewId()
01104 
01105             self.Bind(wx.EVT_MENU, self.OnDataItemEdit,       id = self.popupDataID1)
01106             self.Bind(wx.EVT_MENU, self.OnDataItemAdd,        id = self.popupDataID2)
01107             self.Bind(wx.EVT_MENU, self.OnDataItemDelete,     id = self.popupDataID3)
01108             self.Bind(wx.EVT_MENU, self.OnDataItemDeleteAll,  id = self.popupDataID4)
01109             self.Bind(wx.EVT_MENU, self.OnDataSelectAll,      id = self.popupDataID5)
01110             self.Bind(wx.EVT_MENU, self.OnDataSelectNone,     id = self.popupDataID6)
01111             self.Bind(wx.EVT_MENU, self.OnDataDrawSelected,   id = self.popupDataID7)
01112             self.Bind(wx.EVT_MENU, self.OnDataDrawSelectedZoom, id = self.popupDataID8)
01113             self.Bind(wx.EVT_MENU, self.OnExtractSelected,    id = self.popupDataID9)
01114             self.Bind(wx.EVT_MENU, self.OnDeleteSelected,     id = self.popupDataID11)
01115             self.Bind(wx.EVT_MENU, self.OnDataReload,         id = self.popupDataID10)
01116 
01117         tlist = self.FindWindowById(self.layerPage[self.layer]['data'])
01118         # generate popup-menu
01119         menu = wx.Menu()
01120         menu.Append(self.popupDataID1, _("Edit selected record"))
01121         selected = tlist.GetFirstSelected()
01122         if not self.editable or selected == -1 or tlist.GetNextSelected(selected) != -1:
01123             menu.Enable(self.popupDataID1, False)
01124         menu.Append(self.popupDataID2, _("Insert new record"))
01125         menu.Append(self.popupDataID3, _("Delete selected record(s)"))
01126         menu.Append(self.popupDataID4, _("Delete all records"))
01127         if not self.editable:
01128             menu.Enable(self.popupDataID2, False)
01129             menu.Enable(self.popupDataID3, False)
01130             menu.Enable(self.popupDataID4, False)
01131         menu.AppendSeparator()
01132         menu.Append(self.popupDataID5, _("Select all"))
01133         menu.Append(self.popupDataID6, _("Deselect all"))
01134         menu.AppendSeparator()
01135         menu.Append(self.popupDataID7, _("Highlight selected features"))
01136         menu.Append(self.popupDataID8, _("Highlight selected features and zoom"))
01137         if not self.map or len(tlist.GetSelectedItems()) == 0:
01138             menu.Enable(self.popupDataID7, False)
01139             menu.Enable(self.popupDataID8, False)
01140         menu.Append(self.popupDataID9, _("Extract selected features"))
01141         menu.Append(self.popupDataID11, _("Delete selected features"))
01142         if not self.editable:
01143             menu.Enable(self.popupDataID11, False)
01144         if tlist.GetFirstSelected() == -1:
01145             menu.Enable(self.popupDataID3, False)
01146             menu.Enable(self.popupDataID9, False)
01147             menu.Enable(self.popupDataID11, False)
01148         menu.AppendSeparator()
01149         menu.Append(self.popupDataID10, _("Reload"))
01150 
01151         self.PopupMenu(menu)
01152         menu.Destroy()
01153 
01154         # update statusbar
01155         self.log.write(_("Number of loaded records: %d") % \
01156                            tlist.GetItemCount())
01157 
01158     def OnDataItemDelete(self, event):
01159         """!Delete selected item(s) from the tlist (layer/category pair)"""
01160         dlist = self.FindWindowById(self.layerPage[self.layer]['data'])
01161         item = dlist.GetFirstSelected()
01162         
01163         table    = self.mapDBInfo.layers[self.layer]["table"]
01164         key      = self.mapDBInfo.layers[self.layer]["key"]
01165         
01166         indeces = []
01167         # collect SQL statements
01168         while item != -1:
01169             index = dlist.itemIndexMap[item]
01170             indeces.append(index)
01171             
01172             cat = dlist.itemCatsMap[index]
01173             
01174             self.listOfSQLStatements.append('DELETE FROM %s WHERE %s=%d' % \
01175                                                 (table, key, cat))
01176             
01177             item = dlist.GetNextSelected(item)
01178         
01179         if UserSettings.Get(group = 'atm', key = 'askOnDeleteRec', subkey = 'enabled'):
01180             deleteDialog = wx.MessageBox(parent = self,
01181                                          message = _("Selected data records (%d) will be permanently deleted "
01182                                                    "from table. Do you want to delete them?") % \
01183                                              (len(self.listOfSQLStatements)),
01184                                          caption = _("Delete records"),
01185                                          style = wx.YES_NO | wx.CENTRE)
01186             if deleteDialog != wx.YES:
01187                 self.listOfSQLStatements = []
01188                 return False
01189         
01190         # restore maps
01191         i = 0
01192         indexTemp = copy.copy(dlist.itemIndexMap)
01193         dlist.itemIndexMap = []
01194         dataTemp = copy.deepcopy(dlist.itemDataMap)
01195         dlist.itemDataMap = {}
01196         catsTemp = copy.deepcopy(dlist.itemCatsMap)
01197         dlist.itemCatsMap = {}
01198         
01199         i = 0
01200         for index in indexTemp:
01201             if index in indeces:
01202                 continue
01203             dlist.itemIndexMap.append(i)
01204             dlist.itemDataMap[i] = dataTemp[index]
01205             dlist.itemCatsMap[i] = catsTemp[index]
01206             
01207             i += 1
01208             
01209         dlist.SetItemCount(len(dlist.itemIndexMap))
01210         
01211         # deselect items
01212         item = dlist.GetFirstSelected()
01213         while item != -1:
01214             dlist.SetItemState(item, 0, wx.LIST_STATE_SELECTED | wx.LIST_STATE_FOCUSED)
01215             item = dlist.GetNextSelected(item)
01216         
01217         # submit SQL statements
01218         self.ApplyCommands()
01219         
01220         return True
01221 
01222     def OnDataItemDeleteAll(self, event):
01223         """!Delete all items from the list"""
01224         dlist = self.FindWindowById(self.layerPage[self.layer]['data'])
01225         if UserSettings.Get(group = 'atm', key = 'askOnDeleteRec', subkey = 'enabled'):
01226             deleteDialog = wx.MessageBox(parent = self,
01227                                          message = _("All data records (%d) will be permanently deleted "
01228                                                    "from table. Do you want to delete them?") % \
01229                                              (len(dlist.itemIndexMap)),
01230                                          caption = _("Delete records"),
01231                                          style = wx.YES_NO | wx.CENTRE)
01232             if deleteDialog != wx.YES:
01233                 return
01234 
01235         dlist.DeleteAllItems()
01236         dlist.itemDataMap  = {}
01237         dlist.itemIndexMap = []
01238         dlist.SetItemCount(0)
01239 
01240         table = self.mapDBInfo.layers[self.layer]["table"]
01241         self.listOfSQLStatements.append('DELETE FROM %s' % table)
01242 
01243         self.ApplyCommands()
01244         
01245         event.Skip()
01246 
01247     def _drawSelected(self, zoom):
01248         """!Highlight selected features"""
01249         if not self.map or not self.mapdisplay:
01250             return
01251         
01252         tlist = self.FindWindowById(self.layerPage[self.layer]['data'])
01253         cats = map(int, tlist.GetSelectedItems())
01254 
01255         digitToolbar = self.mapdisplay.toolbars['vdigit']
01256         if digitToolbar and digitToolbar.GetLayer() and \
01257                 digitToolbar.GetLayer().GetName() == self.vectorName:
01258             display = self.mapdisplay.GetMapWindow().GetDisplay()
01259             display.SetSelected(cats, layer = self.layer)
01260             if zoom:
01261                 n, s, w, e = display.GetRegionSelected()
01262                 self.mapdisplay.Map.GetRegion(n = n, s = s, w = w, e = e,
01263                                               update = True)
01264         else:
01265             # add map layer with higlighted vector features
01266             self.AddQueryMapLayer() # -> self.qlayer
01267 
01268             # set opacity based on queried layer
01269             if self.parent and self.parent.GetName() == "LayerManager" and \
01270                     self.treeItem:
01271                 maptree = self.parent.curr_page.maptree
01272                 opacity = maptree.GetPyData(self.treeItem)[0]['maplayer'].GetOpacity(float = True)
01273                 self.qlayer.SetOpacity(opacity)
01274             if zoom:
01275                 keyColumn = self.mapDBInfo.layers[self.layer]['key']
01276                 where = ''
01277                 for range in ListOfCatsToRange(cats).split(','):
01278                     if '-' in range:
01279                         min, max = range.split('-')
01280                         where += '%s >= %d and %s <= %d or ' % \
01281                             (keyColumn, int(min),
01282                              keyColumn, int(max))
01283                     else:
01284                         where += '%s = %d or ' % (keyColumn, int(range))
01285                 where = where.rstrip('or ')
01286                 
01287                 select = RunCommand('v.db.select',
01288                                     parent = self,
01289                                     read = True,
01290                                     quiet = True,
01291                                     flags = 'r',
01292                                     map = self.mapDBInfo.map,
01293                                     layer = int(self.layer),
01294                                     where = where)
01295                 
01296                 region = {}
01297                 for line in select.splitlines():
01298                     key, value = line.split('=')
01299                     region[key.strip()] = float(value.strip())
01300                 
01301                 self.mapdisplay.Map.GetRegion(n = region['n'], s = region['s'],
01302                                               w = region['w'], e = region['e'],
01303                                               update = True)
01304         
01305         if zoom:
01306             self.mapdisplay.Map.AdjustRegion()           # adjust resolution
01307             self.mapdisplay.Map.AlignExtentFromDisplay() # adjust extent
01308             self.mapdisplay.MapWindow.UpdateMap(render = True,  renderVector = True)
01309         else:
01310             self.mapdisplay.MapWindow.UpdateMap(render = False, renderVector = True)
01311         
01312     def OnDataDrawSelected(self, event):
01313         """!Reload table description"""
01314         self._drawSelected(zoom = False)
01315         event.Skip()
01316 
01317     def OnDataDrawSelectedZoom(self, event):
01318         self._drawSelected(zoom = True)
01319         event.Skip()
01320         
01321     def OnDataItemAdd(self, event):
01322         """!Add new record to the attribute table"""
01323         tlist      = self.FindWindowById(self.layerPage[self.layer]['data'])
01324         table     = self.mapDBInfo.layers[self.layer]['table']
01325         keyColumn = self.mapDBInfo.layers[self.layer]['key']
01326         
01327         # (column name, value)
01328         data = []
01329 
01330         # collect names of all visible columns
01331         columnName = []
01332         for i in range(tlist.GetColumnCount()): 
01333             columnName.append(tlist.GetColumn(i).GetText())
01334 
01335         # maximal category number
01336         if len(tlist.itemCatsMap.values()) > 0:
01337             maxCat = max(tlist.itemCatsMap.values())
01338         else:
01339             maxCat = 0 # starting category '1'
01340         
01341         # key column must be always presented
01342         if keyColumn not in columnName:
01343             columnName.insert(0, keyColumn) # insert key column on first position
01344             data.append((keyColumn, str(maxCat + 1)))
01345             missingKey = True
01346         else:
01347             missingKey = False
01348             
01349         # add other visible columns
01350         colIdx = 0
01351         keyId = -1
01352         for col in columnName:
01353             ctype = self.mapDBInfo.tables[table][col]['ctype']
01354             ctypeStr = self.mapDBInfo.tables[table][col]['type']
01355             if col == keyColumn: # key 
01356                 if missingKey is False: 
01357                     data.append((col, ctype, ctypeStr, str(maxCat + 1)))
01358                     keyId = colIdx
01359             else:
01360                 data.append((col, ctype, ctypeStr, ''))
01361             
01362             colIdx += 1
01363                 
01364         dlg = ModifyTableRecord(parent = self,
01365                                 title = _("Insert new record"),
01366                                 data = data, keyEditable = (keyId, True))
01367 
01368         if dlg.ShowModal() == wx.ID_OK:
01369             try: # get category number
01370                 cat = int(dlg.GetValues(columns = [keyColumn])[0])
01371             except:
01372                 cat = -1
01373 
01374             try:
01375                 if cat in tlist.itemCatsMap.values():
01376                     raise ValueError(_("Record with category number %d "
01377                                        "already exists in the table.") % cat)
01378 
01379                 values = dlg.GetValues() # values (need to be casted)
01380                 columnsString = ''
01381                 valuesString   = ''
01382                 
01383                 for i in range(len(values)):
01384                     if len(values[i]) == 0: # NULL
01385                         if columnName[i] == keyColumn:
01386                             raise ValueError(_("Category number (column %s)"
01387                                                " is missing.") % keyColumn)
01388                         else:
01389                             continue
01390 
01391                     try:
01392                         if tlist.columns[columnName[i]]['ctype'] == int:
01393                             # values[i] is stored as text. 
01394                             value = float(values[i])
01395                         else:
01396                             value = values[i]
01397                         values[i] = tlist.columns[columnName[i]]['ctype'] (value)
01398 
01399                     except:
01400                         raise ValueError(_("Value '%(value)s' needs to be entered as %(type)s.") % 
01401                                          {'value' : str(values[i]),
01402                                           'type' : tlist.columns[columnName[i]]['type']})
01403                     columnsString += '%s,' % columnName[i]
01404                     if tlist.columns[columnName[i]]['ctype'] == str:
01405                         valuesString += "'%s'," % values[i]
01406                     else:
01407                         valuesString += "%s," % values[i]
01408 
01409             except ValueError, err:
01410                 GError(parent = self,
01411                        message = _("Unable to insert new record.\n%s") % err,
01412                        showTraceback = False)
01413                 self.OnDataItemAdd(event)
01414                 return
01415             
01416             # remove category if need 
01417             if missingKey is True:
01418                 del values[0]
01419                 
01420             # add new item to the tlist
01421             if len(tlist.itemIndexMap) > 0:
01422                 index = max(tlist.itemIndexMap) + 1
01423             else:
01424                 index = 0
01425             
01426             tlist.itemIndexMap.append(index)
01427             tlist.itemDataMap[index] = values
01428             tlist.itemCatsMap[index] = cat
01429             tlist.SetItemCount(tlist.GetItemCount() + 1)
01430 
01431             self.listOfSQLStatements.append('INSERT INTO %s (%s) VALUES(%s)' % \
01432                                                 (table,
01433                                                  columnsString.strip(','),
01434                                                  valuesString.strip(',')))
01435             self.ApplyCommands()
01436             
01437     def OnDataItemEdit(self, event):
01438         """!Edit selected record of the attribute table"""
01439         tlist      = self.FindWindowById(self.layerPage[self.layer]['data'])
01440         item      = tlist.GetFirstSelected()
01441         if item == -1:
01442             return
01443 
01444         table     = self.mapDBInfo.layers[self.layer]['table']
01445         keyColumn = self.mapDBInfo.layers[self.layer]['key']
01446         cat       = tlist.itemCatsMap[tlist.itemIndexMap[item]]
01447 
01448         # (column name, value)
01449         data = []
01450 
01451         # collect names of all visible columns
01452         columnName = []
01453         for i in range(tlist.GetColumnCount()): 
01454             columnName.append(tlist.GetColumn(i).GetText())
01455 
01456 
01457         # key column must be always presented
01458         if keyColumn not in columnName:
01459             columnName.insert(0, keyColumn) # insert key column on first position
01460             data.append((keyColumn, str(cat)))
01461             keyId = 0
01462             missingKey = True
01463         else:
01464             missingKey = False
01465             
01466         # add other visible columns
01467         for i in range(len(columnName)):
01468             ctype = self.mapDBInfo.tables[table][columnName[i]]['ctype']
01469             ctypeStr = self.mapDBInfo.tables[table][columnName[i]]['type']
01470             if columnName[i] == keyColumn: # key 
01471                 if missingKey is False: 
01472                     data.append((columnName[i], ctype, ctypeStr, str(cat)))
01473                     keyId = i
01474             else:
01475                 if missingKey is True:
01476                     value = tlist.GetItem(item, i-1).GetText()
01477                 else:
01478                     value = tlist.GetItem(item, i).GetText()
01479                 data.append((columnName[i], ctype, ctypeStr, value))
01480 
01481         dlg = ModifyTableRecord(parent = self, 
01482                                 title = _("Update existing record"),
01483                                 data = data, keyEditable = (keyId, False))
01484 
01485         if dlg.ShowModal() == wx.ID_OK:
01486             values = dlg.GetValues() # string
01487             updateString = ''
01488             try:
01489                 for i in range(len(values)): 
01490                     if i == keyId: # skip key column
01491                         continue
01492                     if tlist.GetItem(item, i).GetText() != values[i]:
01493                         if len(values[i]) > 0:
01494                             try:
01495                                 if missingKey is True:
01496                                     idx = i - 1
01497                                 else:
01498                                     idx = i
01499                                 if tlist.columns[columnName[i]]['ctype'] != types.StringType:
01500                                     if tlist.columns[columnName[i]]['ctype'] == int:
01501                                         value = float(values[i])
01502                                     else:
01503                                         value = values[i]
01504                                     tlist.itemDataMap[item][idx] = \
01505                                         tlist.columns[columnName[i]]['ctype'] (value)
01506                                 else:
01507                                     tlist.itemDataMap[item][idx] = values[i]
01508                             except:
01509                                 raise ValueError(_("Value '%(value)s' needs to be entered as %(type)s.") % \
01510                                                      {'value' : str(values[i]),
01511                                                       'type' : tlist.columns[columnName[i]]['type']})
01512 
01513                             if tlist.columns[columnName[i]]['ctype'] == str:
01514                                 updateString += "%s='%s'," % (columnName[i], values[i])
01515                             else:
01516                                 updateString += "%s=%s," % (columnName[i], values[i])
01517                         else: # NULL
01518                             updateString += "%s=NULL," % (columnName[i])
01519                             
01520             except ValueError, err:
01521                 GError(parent = self,
01522                        message = _("Unable to update existing record.\n%s") % err,
01523                        showTraceback = False)
01524                 self.OnDataItemEdit(event)
01525                 return
01526             
01527             if len(updateString) > 0:
01528                 self.listOfSQLStatements.append('UPDATE %s SET %s WHERE %s=%d' % \
01529                                                     (table, updateString.strip(','),
01530                                                      keyColumn, cat))
01531                 self.ApplyCommands()
01532 
01533             tlist.Update(self.mapDBInfo)
01534                         
01535     def OnDataReload(self, event):
01536         """!Reload tlist of records"""
01537         self.OnApplySqlStatement(None)
01538         self.listOfSQLStatements = []
01539 
01540     def OnDataSelectAll(self, event):
01541         """!Select all items"""
01542         tlist = self.FindWindowById(self.layerPage[self.layer]['data'])
01543         item = -1
01544 
01545         while True:
01546             item = tlist.GetNextItem(item)
01547             if item == -1:
01548                 break
01549             tlist.SetItemState(item, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED)
01550 
01551         event.Skip()
01552 
01553     def OnDataSelectNone(self, event):
01554         """!Deselect items"""
01555         tlist = self.FindWindowById(self.layerPage[self.layer]['data'])
01556         item = -1
01557 
01558         while True:
01559             item = tlist.GetNextItem(item, wx.LIST_STATE_SELECTED)
01560             if item == -1:
01561                 break
01562             tlist.SetItemState(item, 0, wx.LIST_STATE_SELECTED | wx.LIST_STATE_FOCUSED)
01563 
01564         event.Skip()
01565 
01566 
01567     def OnTableChangeType(self, event):
01568         """!Data type for new column changed. Enable or disable
01569         data length widget"""
01570         win = self.FindWindowById(self.layerPage[self.layer]['addColLength'])
01571         if event.GetString() == "varchar":
01572             win.Enable(True)
01573         else:
01574             win.Enable(False)
01575 
01576     def OnTableRenameColumnName(self, event):
01577         """!Editing column name to be added to the table"""
01578         btn  = self.FindWindowById(self.layerPage[self.layer]['renameColButton'])
01579         col  = self.FindWindowById(self.layerPage[self.layer]['renameCol'])
01580         colTo = self.FindWindowById(self.layerPage[self.layer]['renameColTo'])
01581         if len(col.GetValue()) > 0 and len(colTo.GetValue()) > 0:
01582             btn.Enable(True)
01583         else:
01584             btn.Enable(False)
01585 
01586         event.Skip()
01587 
01588     def OnTableAddColumnName(self, event):
01589         """!Editing column name to be added to the table"""
01590         btn = self.FindWindowById(self.layerPage[self.layer]['addColButton'])
01591         if len(event.GetString()) > 0:
01592             btn.Enable(True)
01593         else:
01594             btn.Enable(False)
01595 
01596         event.Skip()
01597 
01598     def OnTableItemChange(self, event):
01599         """!Rename column in the table"""
01600         tlist   = self.FindWindowById(self.layerPage[self.layer]['tableData'])
01601         name   = self.FindWindowById(self.layerPage[self.layer]['renameCol']).GetValue()
01602         nameTo = self.FindWindowById(self.layerPage[self.layer]['renameColTo']).GetValue()
01603 
01604         table = self.mapDBInfo.layers[self.layer]["table"]
01605 
01606         if not name or not nameTo:
01607             GError(parent = self,
01608                    message = _("Unable to rename column. "
01609                                "No column name defined."))
01610             return
01611         else:
01612             item = tlist.FindItem(start = -1, str = name)
01613             if item > -1:
01614                 if tlist.FindItem(start = -1, str = nameTo) > -1:
01615                     GError(parent = self,
01616                            message = _("Unable to rename column <%(column)s> to "
01617                                        "<%(columnTo)s>. Column already exists "
01618                                        "in the table <%(table)s>.") % \
01619                                {'column' : name, 'columnTo' : nameTo,
01620                                 'table' : table})
01621                     return
01622                 else:
01623                     tlist.SetItemText(item, nameTo)
01624 
01625                     self.listOfCommands.append(('v.db.renamecol',
01626                                                 { 'map'    : self.vectorName,
01627                                                   'layer'  : self.layer,
01628                                                   'column' : '%s,%s' % (name, nameTo) }
01629                                                 ))
01630             else:
01631                 GError(parent = self,
01632                        message = _("Unable to rename column. "
01633                                    "Column <%(column)s> doesn't exist in the table <%(table)s>.") % 
01634                        {'column' : name, 'table' : table})
01635                 return
01636             
01637         # apply changes
01638         self.ApplyCommands()
01639 
01640         # update widgets
01641         self.FindWindowById(self.layerPage[self.layer]['renameCol']).SetItems(self.mapDBInfo.GetColumns(table))
01642         self.FindWindowById(self.layerPage[self.layer]['renameCol']).SetSelection(0)
01643         self.FindWindowById(self.layerPage[self.layer]['renameColTo']).SetValue('')
01644 
01645         event.Skip()
01646 
01647     def OnTableRightUp(self, event):
01648         """!Table description area, context menu"""
01649         if not hasattr(self, "popupTableID"):
01650             self.popupTableID1 = wx.NewId()
01651             self.popupTableID2 = wx.NewId()
01652             self.popupTableID3 = wx.NewId()
01653             self.Bind(wx.EVT_MENU, self.OnTableItemDelete,    id = self.popupTableID1)
01654             self.Bind(wx.EVT_MENU, self.OnTableItemDeleteAll, id = self.popupTableID2)
01655             self.Bind(wx.EVT_MENU, self.OnTableReload,        id = self.popupTableID3)
01656 
01657         # generate popup-menu
01658         menu = wx.Menu()
01659         menu.Append(self.popupTableID1, _("Drop selected column"))
01660         if self.FindWindowById(self.layerPage[self.layer]['tableData']).GetFirstSelected() == -1:
01661             menu.Enable(self.popupTableID1, False)
01662         menu.Append(self.popupTableID2, _("Drop all columns"))
01663         menu.AppendSeparator()
01664         menu.Append(self.popupTableID3, _("Reload"))
01665 
01666         self.PopupMenu(menu)
01667         menu.Destroy()
01668 
01669     def OnTableItemDelete(self, event):
01670         """!Delete selected item(s) from the list"""
01671         tlist = self.FindWindowById(self.layerPage[self.layer]['tableData'])
01672         
01673         item = tlist.GetFirstSelected()
01674         
01675         if UserSettings.Get(group = 'atm', key = 'askOnDeleteRec', subkey = 'enabled'):
01676             deleteDialog = wx.MessageBox(parent = self,
01677                                          message = _("Selected column '%s' will PERMANENTLY removed "
01678                                                    "from table. Do you want to drop the column?") % \
01679                                              (tlist.GetItemText(item)),
01680                                          caption = _("Drop column(s)"),
01681                                          style = wx.YES_NO | wx.CENTRE)
01682             if deleteDialog != wx.YES:
01683                 return False
01684         
01685         while item != -1:
01686             self.listOfCommands.append(('v.db.dropcol',
01687                                         { 'map' : self.vectorName,
01688                                           'layer' : self.layer,
01689                                           'column' : tlist.GetItemText(item) }
01690                                         ))
01691             tlist.DeleteItem(item)
01692             item = tlist.GetFirstSelected()
01693         
01694         # apply changes
01695         self.ApplyCommands()
01696         
01697         # update widgets
01698         table = self.mapDBInfo.layers[self.layer]['table']
01699         self.FindWindowById(self.layerPage[self.layer]['renameCol']).SetItems(self.mapDBInfo.GetColumns(table))
01700         self.FindWindowById(self.layerPage[self.layer]['renameCol']).SetSelection(0)
01701         
01702         event.Skip()
01703 
01704     def OnTableItemDeleteAll(self, event):
01705         """!Delete all items from the list"""
01706         table     = self.mapDBInfo.layers[self.layer]['table']
01707         cols      = self.mapDBInfo.GetColumns(table)
01708         keyColumn = self.mapDBInfo.layers[self.layer]['key']
01709         if keyColumn in cols:
01710             cols.remove(keyColumn)
01711         
01712         if UserSettings.Get(group = 'atm', key = 'askOnDeleteRec', subkey = 'enabled'):
01713             deleteDialog = wx.MessageBox(parent = self,
01714                                          message = _("Selected columns\n%s\nwill PERMANENTLY removed "
01715                                                    "from table. Do you want to drop the columns?") % \
01716                                              ('\n'.join(cols)),
01717                                          caption = _("Drop column(s)"),
01718                                          style = wx.YES_NO | wx.CENTRE)
01719             if deleteDialog != wx.YES:
01720                 return False
01721         
01722         for col in cols:
01723             self.listOfCommands.append(('v.db.dropcol',
01724                                         { 'map' : self.vectorName,
01725                                           'layer' : self.layer,
01726                                           'column' : col }
01727                                         ))
01728         self.FindWindowById(self.layerPage[self.layer]['tableData']).DeleteAllItems()
01729 
01730         # apply changes
01731         self.ApplyCommands()
01732 
01733         # update widgets
01734         table = self.mapDBInfo.layers[self.layer]['table']
01735         self.FindWindowById(self.layerPage[self.layer]['renameCol']).SetItems(self.mapDBInfo.GetColumns(table))
01736         self.FindWindowById(self.layerPage[self.layer]['renameCol']).SetSelection(0)
01737 
01738         event.Skip()
01739 
01740     def OnTableReload(self, event = None):
01741         """!Reload table description"""
01742         self.FindWindowById(self.layerPage[self.layer]['tableData']).Populate(update = True)
01743         self.listOfCommands = []
01744 
01745     def OnTableItemAdd(self, event):
01746         """!Add new column to the table"""
01747         table = self.mapDBInfo.layers[self.layer]['table']
01748         name = self.FindWindowById(self.layerPage[self.layer]['addColName']).GetValue()
01749         
01750         if not name:
01751             GError(parent = self,
01752                    message = _("Unable to add column to the table. "
01753                                "No column name defined."))
01754             return
01755         
01756         ctype = self.FindWindowById(self.layerPage[self.layer]['addColType']). \
01757             GetStringSelection()
01758         
01759         # cast type if needed
01760         if ctype == 'double':
01761             ctype = 'double precision'
01762         if ctype == 'varchar':
01763             length = int(self.FindWindowById(self.layerPage[self.layer]['addColLength']). \
01764                              GetValue())
01765         else:
01766             length = '' # FIXME
01767         
01768         # add item to the list of table columns
01769         tlist = self.FindWindowById(self.layerPage[self.layer]['tableData'])
01770         # check for duplicate items
01771         if tlist.FindItem(start = -1, str = name) > -1:
01772             GError(parent = self,
01773                    message = _("Column <%(column)s> already exists in table <%(table)s>.") % \
01774                        {'column' : name, 'table' : self.mapDBInfo.layers[self.layer]["table"]})
01775             return
01776         index = tlist.InsertStringItem(sys.maxint, str(name))
01777         tlist.SetStringItem(index, 0, str(name))
01778         tlist.SetStringItem(index, 1, str(ctype))
01779         tlist.SetStringItem(index, 2, str(length))
01780         
01781         # add v.db.addcol command to the list
01782         if ctype == 'varchar':
01783             ctype += ' (%d)' % length
01784         self.listOfCommands.append(('v.db.addcol',
01785                                     { 'map'     : self.vectorName,
01786                                       'layer'   : self.layer,
01787                                       'columns' : '%s %s' % (name, ctype) }
01788                                     ))
01789         # apply changes
01790         self.ApplyCommands()
01791         
01792         # update widgets
01793         self.FindWindowById(self.layerPage[self.layer]['addColName']).SetValue('')
01794         self.FindWindowById(self.layerPage[self.layer]['renameCol']).SetItems(self.mapDBInfo.GetColumns(table))
01795         self.FindWindowById(self.layerPage[self.layer]['renameCol']).SetSelection(0)
01796         
01797         event.Skip()
01798         
01799     def OnLayerPageChanged(self, event):
01800         """!Layer tab changed"""
01801         pageNum = event.GetSelection()
01802         self.layer = self.mapDBInfo.layers.keys()[pageNum]
01803         
01804         try:
01805             idCol = self.layerPage[self.layer]['whereColumn']
01806         except KeyError:
01807             idCol = None
01808         
01809         try:
01810             self.OnChangeSql(None)
01811             # update statusbar
01812             self.log.write(_("Number of loaded records: %d") % \
01813                                self.FindWindowById(self.layerPage[self.layer]['data']).\
01814                                GetItemCount())
01815         except:
01816             pass
01817         
01818         if idCol:
01819             winCol = self.FindWindowById(idCol)
01820             table = self.mapDBInfo.layers[self.layer]["table"]
01821             self.mapDBInfo.GetColumns(table)
01822         
01823         event.Skip()
01824         
01825     def OnPageChanged(self, event):
01826         try:
01827             id = self.layerPage[self.layer]['data']
01828         except KeyError:
01829             id = None
01830         
01831         if event.GetSelection() == 0 and id:
01832             win = self.FindWindowById(id)
01833             if win:
01834                 self.log.write(_("Number of loaded records: %d") % win.GetItemCount())
01835             else:
01836                 self.log.write("")
01837             self.btnReload.Enable()
01838         else:
01839             self.log.write("")
01840             self.btnReload.Enable(False)
01841         
01842         event.Skip()
01843         
01844     def OnLayerRightUp(self, event):
01845         """!Layer description area, context menu"""
01846         pass
01847 
01848     def OnChangeSql(self, event):
01849         """!Switch simple/advanced sql statement"""
01850         if self.FindWindowById(self.layerPage[self.layer]['simple']).GetValue():
01851             self.FindWindowById(self.layerPage[self.layer]['where']).Enable(True)
01852             self.FindWindowById(self.layerPage[self.layer]['statement']).Enable(False)
01853             self.FindWindowById(self.layerPage[self.layer]['builder']).Enable(False)
01854         else:
01855             self.FindWindowById(self.layerPage[self.layer]['where']).Enable(False)
01856             self.FindWindowById(self.layerPage[self.layer]['statement']).Enable(True)
01857             self.FindWindowById(self.layerPage[self.layer]['builder']).Enable(True)
01858 
01859     def ApplyCommands(self):
01860         """!Apply changes"""
01861         # perform GRASS commands (e.g. v.db.addcol)
01862         wx.BeginBusyCursor()
01863         
01864         if len(self.listOfCommands) > 0:
01865             for cmd in self.listOfCommands:
01866                 RunCommand(prog = cmd[0],
01867                            quiet = True,
01868                            parent = self,
01869                            **cmd[1])
01870             
01871             self.mapDBInfo = VectorDBInfo(self.vectorName)
01872             table = self.mapDBInfo.layers[self.layer]['table']
01873 
01874             # update table description
01875             tlist = self.FindWindowById(self.layerPage[self.layer]['tableData'])
01876             tlist.Update(table = self.mapDBInfo.tables[table],
01877                         columns = self.mapDBInfo.GetColumns(table))
01878             self.OnTableReload(None)
01879 
01880             # update data tlist
01881             tlist = self.FindWindowById(self.layerPage[self.layer]['data'])
01882             tlist.Update(self.mapDBInfo)
01883 
01884             # reset list of commands
01885             self.listOfCommands = []
01886         
01887         # perform SQL non-select statements (e.g. 'delete from table where cat=1')
01888         if len(self.listOfSQLStatements) > 0:
01889             sqlFile = tempfile.NamedTemporaryFile(mode = "wt")
01890             for sql in self.listOfSQLStatements:
01891                 enc = UserSettings.Get(group = 'atm', key = 'encoding', subkey = 'value')
01892                 if not enc and 'GRASS_DB_ENCODING' in os.environ:
01893                     enc = os.environ['GRASS_DB_ENCODING']
01894                 if enc:
01895                     sqlFile.file.write(sql.encode(enc) + ';')
01896                 else:
01897                     sqlFile.file.write(sql + ';')
01898                 sqlFile.file.write(os.linesep)
01899                 sqlFile.file.flush()
01900 
01901             driver   = self.mapDBInfo.layers[self.layer]["driver"]
01902             database = self.mapDBInfo.layers[self.layer]["database"]
01903             
01904             Debug.msg(3, 'AttributeManger.ApplyCommands(): %s' %
01905                       ';'.join(["%s" % s for s in self.listOfSQLStatements]))
01906             
01907             RunCommand('db.execute',
01908                        parent = self,
01909                        input = sqlFile.name,
01910                        driver = driver,
01911                        database = database)
01912             
01913             # reset list of statements
01914             self.listOfSQLStatements = []
01915             
01916         wx.EndBusyCursor()
01917         
01918     def OnApplySqlStatement(self, event):
01919         """!Apply simple/advanced sql statement"""
01920         keyColumn = -1 # index of key column
01921         listWin = self.FindWindowById(self.layerPage[self.layer]['data'])
01922         sql = None
01923         win = self.FindWindowById(self.layerPage[self.layer]['simple'])
01924         if not win:
01925             return
01926         
01927         wx.BeginBusyCursor()
01928         if win.GetValue():
01929             # simple sql statement
01930             whereCol = self.FindWindowById(self.layerPage[self.layer]['whereColumn']).GetStringSelection()
01931             whereOpe = self.FindWindowById(self.layerPage[self.layer]['whereOperator']).GetStringSelection()
01932             whereVal = self.FindWindowById(self.layerPage[self.layer]['where']).GetValue().strip()
01933             try:
01934                 if len(whereVal) > 0:
01935                     keyColumn = listWin.LoadData(self.layer, where = whereCol + whereOpe + whereVal)
01936                 else:
01937                     keyColumn = listWin.LoadData(self.layer)
01938             except GException, e:
01939                 GError(parent = self,
01940                        message = _("Loading attribute data failed.\n\n%s") % e.value)
01941                 self.FindWindowById(self.layerPage[self.layer]['where']).SetValue('')
01942         else:
01943             # advanced sql statement
01944             win = self.FindWindowById(self.layerPage[self.layer]['statement'])
01945             try:
01946                 cols, where = self.ValidateSelectStatement(win.GetValue())
01947                 if cols is None and where is None:
01948                     sql = win.GetValue()
01949             except TypeError:
01950                 GError(parent = self,
01951                        message = _("Loading attribute data failed.\n"
01952                                    "Invalid SQL select statement.\n\n%s") % win.GetValue())
01953                 win.SetValue("SELECT * FROM %s" % self.mapDBInfo.layers[self.layer]['table'])
01954                 cols = None
01955                 where = None
01956             
01957             if cols or where or sql:
01958                 try:
01959                     keyColumn = listWin.LoadData(self.layer, columns = cols,
01960                                                  where = where, sql = sql)
01961                 except GException, e:
01962                     GError(parent = self,
01963                            message = _("Loading attribute data failed.\n\n%s") % e.value)
01964                     win.SetValue("SELECT * FROM %s" % self.mapDBInfo.layers[self.layer]['table'])
01965         
01966         # sort by key column
01967         if sql and 'order by' in sql.lower():
01968             pass # don't order by key column
01969         else:
01970             if keyColumn > -1:
01971                 listWin.SortListItems(col = keyColumn, ascending = True)
01972             else:
01973                 listWin.SortListItems(col = 0, ascending = True) 
01974         
01975         wx.EndBusyCursor()
01976         
01977         # update statusbar
01978         self.log.write(_("Number of loaded records: %d") % \
01979                            self.FindWindowById(self.layerPage[self.layer]['data']).GetItemCount())
01980 
01981     def ValidateSelectStatement(self, statement):
01982         """!Validate SQL select statement
01983 
01984         @return (columns, where)
01985         @return None on error
01986         """
01987         if statement[0:7].lower() != 'select ':
01988             return None
01989         
01990         cols = ''
01991         index = 7
01992         for c in statement[index:]:
01993             if c == ' ':
01994                 break
01995             cols += c
01996             index += 1
01997         if cols == '*':
01998             cols = None
01999         else:
02000             cols = cols.split(',')
02001         
02002         tablelen = len(self.mapDBInfo.layers[self.layer]['table'])
02003         
02004         if statement[index+1:index+6].lower() != 'from ' or \
02005                 statement[index+6:index+6+tablelen] != '%s' % \
02006                 (self.mapDBInfo.layers[self.layer]['table']):
02007             return None
02008         
02009         if len(statement[index+7+tablelen:]) > 0:
02010             index = statement.lower().find('where ')
02011             if index > -1:
02012                 where = statement[index+6:]
02013             else:
02014                 where = None
02015         else:
02016             where = None
02017         
02018         return (cols, where)
02019     
02020     def OnCloseWindow(self, event):
02021         """!Cancel button pressed"""
02022         if self.parent and self.parent.GetName() == 'LayerManager':
02023             # deregister ATM
02024             self.parent.dialogs['atm'].remove(self)
02025                     
02026         if not isinstance(event, wx.CloseEvent):
02027             self.Destroy()
02028         
02029         event.Skip()
02030 
02031     def OnBuilder(self,event):
02032         """!SQL Builder button pressed -> show the SQLBuilder dialog"""
02033         if not self.builder:
02034             self.builder = SQLFrame(parent = self, id = wx.ID_ANY,
02035                                     title = _("SQL Builder"),
02036                                     vectmap = self.vectorName,
02037                                     evtheader = self.OnBuilderEvt)
02038             self.builder.Show()
02039         else:
02040             self.builder.Raise()
02041         
02042     def OnBuilderEvt(self, event):
02043         if event == 'apply':
02044             sqlstr = self.builder.GetSQLStatement()
02045             self.FindWindowById(self.layerPage[self.layer]['statement']).SetValue(sqlstr)
02046             if self.builder.CloseOnApply():
02047                 self.builder = None
02048         elif event == 'close':
02049             self.builder = None
02050         
02051     def OnTextEnter(self, event):
02052         pass
02053     
02054     def OnDataItemActivated(self, event):
02055         """!Item activated, highlight selected item"""
02056         self.OnDataDrawSelected(event)
02057 
02058         event.Skip()
02059 
02060     def OnExtractSelected(self, event):
02061         """!Extract vector objects selected in attribute browse window
02062         to new vector map
02063         """
02064         tlist = self.FindWindowById(self.layerPage[self.layer]['data'])
02065         # cats = tlist.selectedCats[:]
02066         cats = tlist.GetSelectedItems()
02067         if len(cats) == 0:
02068             GMessage(parent = self,
02069                      message = _('Nothing to extract.'))
02070             return
02071         else:
02072             # dialog to get file name
02073             dlg = CreateNewVector(parent = self, title = _('Extract selected features'),
02074                                   log = self.cmdLog,
02075                                   cmd = (('v.extract',
02076                                           { 'input' : self.vectorName,
02077                                             'list' : ListOfCatsToRange(cats) },
02078                                           'output')),
02079                                   disableTable = True)
02080             if not dlg:
02081                 return
02082             
02083             name = dlg.GetName(full = True)
02084             if name and dlg.IsChecked('add'):
02085                 # add layer to map layer tree
02086                 self.parent.curr_page.maptree.AddLayer(ltype = 'vector',
02087                                                        lname = name,
02088                                                        lcmd = ['d.vect', 'map=%s' % name])
02089             dlg.Destroy()
02090             
02091     def OnDeleteSelected(self, event):
02092         """!Delete vector objects selected in attribute browse window
02093         (attribures and geometry)
02094         """
02095         tlist = self.FindWindowById(self.layerPage[self.layer]['data'])
02096         cats = tlist.GetSelectedItems()
02097         if len(cats) == 0:
02098             GMessage(parent = self,
02099                      message = _('Nothing to delete.'))
02100         
02101         display = None
02102         if 'vdigit' in self.mapdisplay.toolbars:
02103             digitToolbar = self.mapdisplay.toolbars['vdigit']
02104             if digitToolbar and digitToolbar.GetLayer() and \
02105                     digitToolbar.GetLayer().GetName() == self.vectorName:
02106                 display = self.mapdisplay.GetMapWindow().GetDisplay()
02107                 display.SetSelected(map(int, cats), layer = self.layer)
02108                 self.mapdisplay.MapWindow.UpdateMap(render = True, renderVector = True)
02109         
02110         if self.OnDataItemDelete(None):
02111             if display:
02112                 self.mapdisplay.GetMapWindow().digit.DeleteSelectedLines()
02113             else:
02114                 RunCommand('v.edit',
02115                            parent = self,
02116                            quiet = True,
02117                            map = self.vectorName,
02118                            tool = 'delete',
02119                            cats = ListOfCatsToRange(cats))
02120             
02121             self.mapdisplay.MapWindow.UpdateMap(render = True, renderVector = True)
02122         
02123     def AddQueryMapLayer(self):
02124         """!Redraw a map
02125 
02126         Return True if map has been redrawn, False if no map is given
02127         """
02128         tlist = self.FindWindowById(self.layerPage[self.layer]['data'])
02129         cats = { 
02130             self.layer : tlist.GetSelectedItems()
02131             }
02132         
02133         if self.mapdisplay.Map.GetLayerIndex(self.qlayer) < 0:
02134             self.qlayer = None
02135             
02136         if self.qlayer:
02137             self.qlayer.SetCmd(self.mapdisplay.AddTmpVectorMapLayer(self.vectorName, cats, addLayer = False))
02138         else:
02139             self.qlayer = self.mapdisplay.AddTmpVectorMapLayer(self.vectorName, cats)
02140 
02141         return self.qlayer
02142     
02143     def UpdateDialog(self, layer):
02144         """!Updates dialog layout for given layer"""
02145         # delete page
02146         if layer in self.mapDBInfo.layers.keys():
02147             # delete page
02148             # draging pages disallowed
02149             # if self.browsePage.GetPageText(page).replace('Layer ', '').strip() == str(layer):
02150             # self.browsePage.DeletePage(page)
02151             # break
02152             self.browsePage.DeletePage(self.mapDBInfo.layers.keys().index(layer))
02153             self.manageTablePage.DeletePage(self.mapDBInfo.layers.keys().index(layer))
02154             # set current page selection
02155             self.notebook.SetSelectionByName('layers')
02156             
02157         # fetch fresh db info
02158         self.mapDBInfo = VectorDBInfo(self.vectorName)    
02159 
02160         #
02161         # add new page
02162         #
02163         if layer in self.mapDBInfo.layers.keys():
02164             # 'browse data' page
02165             self._createBrowsePage(layer)
02166             # 'manage tables' page
02167             self._createManageTablePage(layer)
02168             # set current page selection
02169             self.notebook.SetSelectionByName('layers')
02170             
02171         #
02172         # 'manage layers' page
02173         #
02174         # update list of layers
02175         self.layerList.Update(self.mapDBInfo.layers)
02176         self.layerList.Populate(update = True)
02177         # update selected widgets
02178         listOfLayers = map(str, self.mapDBInfo.layers.keys())
02179         ### delete layer page
02180         self.manageLayerBook.deleteLayer.SetItems(listOfLayers)
02181         if len(listOfLayers) > 0:
02182             self.manageLayerBook.deleteLayer.SetStringSelection(listOfLayers[0])
02183             tableName = self.mapDBInfo.layers[int(listOfLayers[0])]['table']
02184             maxLayer = max(self.mapDBInfo.layers.keys())
02185         else:
02186             tableName = ''
02187             maxLayer = 0
02188         self.manageLayerBook.deleteTable.SetLabel( \
02189             _('Drop also linked attribute table (%s)') % \
02190                 tableName)
02191         ### add layer page
02192         self.manageLayerBook.addLayerWidgets['layer'][1].SetValue(\
02193             maxLayer+1)
02194         ### modify layer
02195         self.manageLayerBook.modifyLayerWidgets['layer'][1].SetItems(listOfLayers)
02196         self.manageLayerBook.OnChangeLayer(event = None)
02197 
02198     def GetVectorName(self):
02199         """!Get vector name"""
02200         return self.vectorName
02201     
02202     def LoadData(self, layer, columns = None, where = None, sql = None):
02203         """!Load data into list
02204 
02205         @param layer layer number
02206         @param columns list of columns for output
02207         @param where where statement
02208         @param sql full sql statement
02209 
02210         @return id of key column 
02211         @return -1 if key column is not displayed
02212         """
02213         listWin = self.FindWindowById(self.layerPage[layer]['data'])
02214         return listWin.LoadData(layer, columns, where, sql)
02215     
02216 class TableListCtrl(wx.ListCtrl,
02217                     listmix.ListCtrlAutoWidthMixin):
02218                     #                    listmix.TextEditMixin):
02219     """!Table description list"""
02220 
02221     def __init__(self, parent, id, table, columns, pos = wx.DefaultPosition,
02222                  size = wx.DefaultSize):
02223 
02224         self.parent  = parent
02225         self.table   = table
02226         self.columns = columns
02227         wx.ListCtrl.__init__(self, parent, id, pos, size,
02228                              style = wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES |
02229                              wx.BORDER_NONE)
02230 
02231         listmix.ListCtrlAutoWidthMixin.__init__(self)
02232         # listmix.TextEditMixin.__init__(self)
02233 
02234     def Update(self, table, columns):
02235         """!Update column description"""
02236         self.table   = table
02237         self.columns = columns
02238 
02239     def Populate(self, update = False):
02240         """!Populate the list"""
02241         itemData = {} # requested by sorter
02242 
02243         if not update:
02244             headings = [_("Column name"), _("Data type"), _("Data length")]
02245             i = 0
02246             for h in headings:
02247                 self.InsertColumn(col = i, heading = h)
02248                 i += 1
02249             self.SetColumnWidth(col = 0, width = 350)
02250             self.SetColumnWidth(col = 1, width = 175)
02251         else:
02252             self.DeleteAllItems()
02253 
02254         i = 0
02255         for column in self.columns:
02256             index = self.InsertStringItem(sys.maxint, str(column))
02257             self.SetStringItem(index, 0, str(column))
02258             self.SetStringItem(index, 1, str(self.table[column]['type']))
02259             self.SetStringItem(index, 2, str(self.table[column]['length']))
02260             self.SetItemData(index, i)
02261             itemData[i] = (str(column),
02262                            str(self.table[column]['type']),
02263                            int(self.table[column]['length']))
02264             i = i + 1
02265 
02266         self.SendSizeEvent()
02267         
02268         return itemData
02269 
02270 class LayerListCtrl(wx.ListCtrl,
02271                     listmix.ListCtrlAutoWidthMixin):
02272                     # listmix.ColumnSorterMixin):
02273                     # listmix.TextEditMixin):
02274     """!Layer description list"""
02275 
02276     def __init__(self, parent, id, layers,
02277                  pos = wx.DefaultPosition,
02278                  size = wx.DefaultSize):
02279 
02280         self.parent = parent
02281         self.layers = layers
02282         wx.ListCtrl.__init__(self, parent, id, pos, size,
02283                              style = wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES |
02284                              wx.BORDER_NONE)
02285 
02286         listmix.ListCtrlAutoWidthMixin.__init__(self)
02287         # listmix.TextEditMixin.__init__(self)
02288 
02289     def Update(self, layers):
02290         """!Update description"""
02291         self.layers = layers
02292 
02293     def Populate(self, update = False):
02294         """!Populate the list"""
02295         itemData = {} # requested by sorter
02296 
02297         if not update:
02298             headings = [_("Layer"),  _("Driver"), _("Database"), _("Table"), _("Key")]
02299             i = 0
02300             for h in headings:
02301                 self.InsertColumn(col = i, heading = h)
02302                 i += 1
02303         else:
02304             self.DeleteAllItems()
02305 
02306         i = 0
02307         for layer in self.layers.keys():
02308             index = self.InsertStringItem(sys.maxint, str(layer))
02309             self.SetStringItem(index, 0, str(layer))
02310             database = str(self.layers[layer]['database'])
02311             driver   = str(self.layers[layer]['driver'])
02312             table    = str(self.layers[layer]['table'])
02313             key      = str(self.layers[layer]['key'])
02314             self.SetStringItem(index, 1, driver)
02315             self.SetStringItem(index, 2, database)
02316             self.SetStringItem(index, 3, table)
02317             self.SetStringItem(index, 4, key)
02318             self.SetItemData(index, i)
02319             itemData[i] = (str(layer),
02320                            driver,
02321                            database,
02322                            table,
02323                            key)
02324             i += 1
02325 
02326         for i in range(self.GetColumnCount()):
02327             self.SetColumnWidth(col = i, width = wx.LIST_AUTOSIZE)
02328             if self.GetColumnWidth(col = i) < 60:
02329                 self.SetColumnWidth(col = i, width = 60)
02330 
02331         self.SendSizeEvent()
02332         
02333         return itemData
02334 
02335 class LayerBook(wx.Notebook):
02336     """!Manage layers (add, delete, modify)"""
02337     def __init__(self, parent, id,
02338                  parentDialog,
02339                  style = wx.BK_DEFAULT):
02340         wx.Notebook.__init__(self, parent, id, style = style)
02341 
02342         self.parent       = parent
02343         self.parentDialog = parentDialog
02344         self.mapDBInfo    = self.parentDialog.mapDBInfo
02345 
02346         #
02347         # drivers
02348         #
02349         drivers = RunCommand('db.drivers',
02350                              quiet = True,
02351                              read = True,
02352                              flags = 'p')
02353         
02354         self.listOfDrivers = []
02355         for drv in drivers.splitlines():
02356             self.listOfDrivers.append(drv.strip())
02357         
02358         #
02359         # get default values
02360         #
02361         self.defaultConnect = {}
02362         connect = RunCommand('db.connect',
02363                              flags = 'p',
02364                              read = True,
02365                              quiet = True)
02366         
02367         for line in connect.splitlines():
02368             item, value = line.split(':', 1)
02369             self.defaultConnect[item.strip()] = value.strip()
02370         
02371         if len(self.defaultConnect['driver']) == 0 or \
02372                len(self.defaultConnect['database']) == 0:
02373             GWarning(parent = self.parent,
02374                      message = _("Unknown default DB connection. "
02375                                  "Please define DB connection using db.connect module."))
02376         
02377         self.defaultTables = self._getTables(self.defaultConnect['driver'],
02378                                              self.defaultConnect['database'])
02379         try:
02380             self.defaultColumns = self._getColumns(self.defaultConnect['driver'],
02381                                                     self.defaultConnect['database'],
02382                                                     self.defaultTables[0])
02383         except IndexError:
02384             self.defaultColumns = []
02385 
02386         self._createAddPage()
02387         self._createDeletePage()
02388         self._createModifyPage()
02389 
02390     def _createAddPage(self):
02391         """!Add new layer"""
02392         self.addPanel = wx.Panel(parent = self, id = wx.ID_ANY)
02393         self.AddPage(page = self.addPanel, text = _("Add layer"))
02394         
02395         try:
02396             maxLayer = max(self.mapDBInfo.layers.keys())
02397         except ValueError:
02398             maxLayer = 0
02399 
02400         # layer description
02401         
02402         layerBox = wx.StaticBox (parent = self.addPanel, id = wx.ID_ANY,
02403                                  label = " %s " % (_("Layer description")))
02404         layerSizer = wx.StaticBoxSizer(layerBox, wx.VERTICAL)
02405         
02406         #
02407         # list of layer widgets (label, value)
02408         #
02409         self.addLayerWidgets = {'layer':
02410                                     (wx.StaticText(parent = self.addPanel, id = wx.ID_ANY,
02411                                                    label = '%s:' % _("Layer")),
02412                                      wx.SpinCtrl(parent = self.addPanel, id = wx.ID_ANY, size = (65, -1),
02413                                                  initial = maxLayer+1,
02414                                                  min = 1, max = 1e6)),
02415                                 'driver':
02416                                     (wx.StaticText(parent = self.addPanel, id = wx.ID_ANY,
02417                                                    label = '%s:' % _("Driver")),
02418                                      wx.Choice(parent = self.addPanel, id = wx.ID_ANY, size = (200, -1),
02419                                                choices = self.listOfDrivers)),
02420                                 'database':
02421                                     (wx.StaticText(parent = self.addPanel, id = wx.ID_ANY,
02422                                                    label = '%s:' % _("Database")),
02423                                      wx.TextCtrl(parent = self.addPanel, id = wx.ID_ANY,
02424                                                  value = '',
02425                                                  style = wx.TE_PROCESS_ENTER)),
02426                                 'table':
02427                                     (wx.StaticText(parent = self.addPanel, id = wx.ID_ANY,
02428                                                    label = '%s:' % _("Table")),
02429                                      wx.Choice(parent = self.addPanel, id = wx.ID_ANY, size = (200, -1),
02430                                                choices = self.defaultTables)),
02431                                 'key':
02432                                     (wx.StaticText(parent = self.addPanel, id = wx.ID_ANY,
02433                                                    label = '%s:' % _("Key column")),
02434                                      wx.Choice(parent = self.addPanel, id = wx.ID_ANY, size = (200, -1),
02435                                                choices = self.defaultColumns)),
02436                                 'addCat':
02437                                     (wx.CheckBox(parent = self.addPanel, id = wx.ID_ANY,
02438                                                  label = _("Insert record for each category into table")),
02439                                      None),
02440                                 }
02441         
02442         # set default values for widgets
02443         self.addLayerWidgets['driver'][1].SetStringSelection(self.defaultConnect['driver'])
02444         self.addLayerWidgets['database'][1].SetValue(self.defaultConnect['database'])
02445         self.addLayerWidgets['table'][1].SetSelection(0)
02446         self.addLayerWidgets['key'][1].SetSelection(0)
02447         # events
02448         self.addLayerWidgets['driver'][1].Bind(wx.EVT_CHOICE, self.OnDriverChanged)
02449         self.addLayerWidgets['database'][1].Bind(wx.EVT_TEXT_ENTER, self.OnDatabaseChanged)
02450         self.addLayerWidgets['table'][1].Bind(wx.EVT_CHOICE, self.OnTableChanged)
02451         
02452         # tooltips
02453         self.addLayerWidgets['addCat'][0].SetToolTipString(_("You need to add categories "
02454                                                              "by v.category module."))
02455         #
02456         # list of table widgets
02457         #
02458         keyCol = UserSettings.Get(group = 'atm', key = 'keycolumn', subkey = 'value')
02459         self.tableWidgets = {'table': (wx.StaticText(parent = self.addPanel, id = wx.ID_ANY,
02460                                                      label = '%s:' % _("Table name")),
02461                                        wx.TextCtrl(parent = self.addPanel, id = wx.ID_ANY,
02462                                                    value = '',
02463                                                    style = wx.TE_PROCESS_ENTER)),
02464                              'key': (wx.StaticText(parent = self.addPanel, id = wx.ID_ANY,
02465                                                    label = '%s:' % _("Key column")),
02466                                      wx.TextCtrl(parent = self.addPanel, id = wx.ID_ANY,
02467                                                  value = keyCol,
02468                                                  style = wx.TE_PROCESS_ENTER))}
02469         # events
02470         self.tableWidgets['table'][1].Bind(wx.EVT_TEXT_ENTER, self.OnCreateTable)
02471         self.tableWidgets['key'][1].Bind(wx.EVT_TEXT_ENTER, self.OnCreateTable)
02472         
02473         btnTable   = wx.Button(self.addPanel, wx.ID_ANY, _("&Create table"),
02474                              size = (125,-1))
02475         btnTable.Bind(wx.EVT_BUTTON, self.OnCreateTable)
02476         
02477         btnLayer   = wx.Button(self.addPanel, wx.ID_ANY, _("&Add layer"),
02478                              size = (125,-1))
02479         btnLayer.Bind(wx.EVT_BUTTON, self.OnAddLayer)
02480         
02481         btnDefault = wx.Button(self.addPanel, wx.ID_ANY, _("&Set default"),
02482                                size = (125,-1))
02483         btnDefault.Bind(wx.EVT_BUTTON, self.OnSetDefault)
02484         
02485         # do layout
02486         
02487         pageSizer = wx.BoxSizer(wx.HORIZONTAL)
02488                 
02489         # data area
02490         dataSizer = wx.GridBagSizer(hgap = 5, vgap = 5)
02491         dataSizer.AddGrowableCol(1)
02492         row = 0
02493         for key in ('layer', 'driver', 'database', 'table', 'key', 'addCat'):
02494             label, value = self.addLayerWidgets[key]
02495             if not value:
02496                 span = (1, 2)
02497             else:
02498                 span = (1, 1)
02499             dataSizer.Add(item = label,
02500                           flag = wx.ALIGN_CENTER_VERTICAL, pos = (row, 0),
02501                           span = span)
02502             
02503             if not value:
02504                 row += 1
02505                 continue
02506 
02507             if label.GetLabel() == "Layer:":
02508                 style = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT
02509             else:
02510                 style = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND
02511             
02512             dataSizer.Add(item = value,
02513                           flag = style, pos = (row, 1))
02514             
02515             row += 1
02516         
02517         layerSizer.Add(item = dataSizer,
02518                        proportion = 1,
02519                        flag = wx.ALL | wx.EXPAND,
02520                        border = 5)
02521         
02522         btnSizer = wx.BoxSizer(wx.HORIZONTAL)
02523         btnSizer.Add(item = btnDefault,
02524                      proportion = 0,
02525                      flag = wx.ALL | wx.ALIGN_LEFT,
02526                      border = 5)
02527         
02528         btnSizer.Add(item = (5, 5),
02529                      proportion = 1,
02530                      flag = wx.ALL | wx.EXPAND,
02531                      border = 5)
02532         
02533         btnSizer.Add(item = btnLayer,
02534                      proportion = 0,
02535                      flag = wx.ALL | wx.ALIGN_RIGHT,
02536                      border = 5)
02537         
02538         layerSizer.Add(item = btnSizer,
02539                        proportion = 0,
02540                        flag = wx.ALL | wx.EXPAND,
02541                        border = 0)
02542                 
02543         # table description
02544         tableBox = wx.StaticBox (parent = self.addPanel, id = wx.ID_ANY,
02545                                  label = " %s " % (_("Table description")))
02546         tableSizer = wx.StaticBoxSizer(tableBox, wx.VERTICAL)
02547         
02548         # data area
02549         dataSizer = wx.FlexGridSizer(cols = 2, hgap = 5, vgap = 5)
02550         dataSizer.AddGrowableCol(1)
02551         for key in ['table', 'key']:
02552             label, value = self.tableWidgets[key]
02553             dataSizer.Add(item = label,
02554                           flag = wx.ALIGN_CENTER_VERTICAL)
02555             dataSizer.Add(item = value,
02556                           flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND)
02557 
02558         tableSizer.Add(item = dataSizer,
02559                        proportion = 1,
02560                        flag = wx.ALL | wx.EXPAND,
02561                        border = 5)
02562 
02563         tableSizer.Add(item = btnTable,
02564                        proportion = 0,
02565                        flag = wx.ALL | wx.ALIGN_BOTTOM | wx.ALIGN_RIGHT,
02566                        border = 5)
02567 
02568         pageSizer.Add(item = layerSizer,
02569                       proportion = 3,
02570                       flag = wx.ALL | wx.EXPAND,
02571                       border = 3)
02572         
02573         pageSizer.Add(item = tableSizer,
02574                       proportion = 2,
02575                       flag = wx.TOP | wx.BOTTOM | wx.RIGHT | wx.EXPAND,
02576                       border = 3)
02577         
02578         layerSizer.SetVirtualSizeHints(self.addPanel)
02579         self.addPanel.SetAutoLayout(True)
02580         self.addPanel.SetSizer(pageSizer)
02581         pageSizer.Fit(self.addPanel)
02582         
02583     def _createDeletePage(self):
02584         """!Delete layer"""
02585         self.deletePanel = wx.Panel(parent = self, id = wx.ID_ANY)
02586         self.AddPage(page = self.deletePanel, text = _("Remove layer"))
02587 
02588         label = wx.StaticText(parent = self.deletePanel, id = wx.ID_ANY,
02589                               label = '%s:' % _("Layer to remove"))
02590 
02591         self.deleteLayer = wx.ComboBox(parent = self.deletePanel, id = wx.ID_ANY, size = (100, -1),
02592                                        style = wx.CB_SIMPLE | wx.CB_READONLY,
02593                                        choices = map(str, self.mapDBInfo.layers.keys()))
02594         self.deleteLayer.SetSelection(0)           
02595         self.deleteLayer.Bind(wx.EVT_COMBOBOX, self.OnChangeLayer)
02596 
02597         try:
02598             tableName = self.mapDBInfo.layers[int(self.deleteLayer.GetStringSelection())]['table']
02599         except ValueError:
02600             tableName = ''
02601             
02602         self.deleteTable = wx.CheckBox(parent = self.deletePanel, id = wx.ID_ANY,
02603                                        label = _('Drop also linked attribute table (%s)') % \
02604                                        tableName)
02605 
02606         if tableName == '':
02607             self.deleteLayer.Enable(False)
02608             self.deleteTable.Enable(False)
02609             
02610         btnDelete   = wx.Button(self.deletePanel, wx.ID_DELETE, _("&Remove layer"),
02611                                 size = (125,-1))
02612         btnDelete.Bind(wx.EVT_BUTTON, self.OnDeleteLayer)
02613 
02614         #
02615         # do layout
02616         #
02617         pageSizer = wx.BoxSizer(wx.VERTICAL)
02618 
02619         dataSizer = wx.BoxSizer(wx.VERTICAL)
02620 
02621         flexSizer = wx.FlexGridSizer(cols = 2, hgap = 5, vgap = 5)
02622         flexSizer.AddGrowableCol(2)
02623 
02624         flexSizer.Add(item = label,
02625                       flag = wx.ALIGN_CENTER_VERTICAL)
02626         flexSizer.Add(item = self.deleteLayer,
02627                       flag = wx.ALIGN_CENTER_VERTICAL)
02628 
02629         dataSizer.Add(item = flexSizer,
02630                       proportion = 0,
02631                       flag = wx.ALL | wx.EXPAND,
02632                       border = 1)
02633 
02634         dataSizer.Add(item = self.deleteTable,
02635                       proportion = 0,
02636                       flag = wx.ALL | wx.EXPAND,
02637                       border = 1)
02638 
02639         pageSizer.Add(item = dataSizer,
02640                       proportion = 1,
02641                       flag = wx.ALL | wx.EXPAND,
02642                       border = 5)
02643 
02644         pageSizer.Add(item = btnDelete,
02645                       proportion = 0,
02646                       flag = wx.ALL | wx.ALIGN_RIGHT,
02647                       border = 5)
02648 
02649         self.deletePanel.SetSizer(pageSizer)
02650 
02651     def _createModifyPage(self):
02652         """!Modify layer"""
02653         self.modifyPanel = wx.Panel(parent = self, id = wx.ID_ANY)
02654         self.AddPage(page = self.modifyPanel, text = _("Modify layer"))
02655 
02656         #
02657         # list of layer widgets (label, value)
02658         #
02659         self.modifyLayerWidgets = {'layer':
02660                                        (wx.StaticText(parent = self.modifyPanel, id = wx.ID_ANY,
02661                                                       label = '%s:' % _("Layer")),
02662                                         wx.ComboBox(parent = self.modifyPanel, id = wx.ID_ANY,
02663                                                     size = (100, -1),
02664                                                     style = wx.CB_SIMPLE | wx.CB_READONLY,
02665                                                     choices = map(str, 
02666                                                                 self.mapDBInfo.layers.keys()))),
02667                                    'driver':
02668                                        (wx.StaticText(parent = self.modifyPanel, id = wx.ID_ANY,
02669                                                       label = '%s:' % _("Driver")),
02670                                         wx.Choice(parent = self.modifyPanel, id = wx.ID_ANY,
02671                                                   size = (200, -1),
02672                                                   choices = self.listOfDrivers)),
02673                                    'database':
02674                                        (wx.StaticText(parent = self.modifyPanel, id = wx.ID_ANY,
02675                                                       label = '%s:' % _("Database")),
02676                                         wx.TextCtrl(parent = self.modifyPanel, id = wx.ID_ANY,
02677                                                     value = '', size = (350, -1),
02678                                                     style = wx.TE_PROCESS_ENTER)),
02679                                    'table':
02680                                        (wx.StaticText(parent = self.modifyPanel, id = wx.ID_ANY,
02681                                                       label = '%s:' % _("Table")),
02682                                         wx.Choice(parent = self.modifyPanel, id = wx.ID_ANY,
02683                                                   size = (200, -1),
02684                                                   choices = self.defaultTables)),
02685                                    'key':
02686                                        (wx.StaticText(parent = self.modifyPanel, id = wx.ID_ANY,
02687                                                       label = '%s:' % _("Key column")),
02688                                         wx.Choice(parent = self.modifyPanel, id = wx.ID_ANY,
02689                                                   size = (200, -1),
02690                                                   choices = self.defaultColumns))}
02691         
02692         # set default values for widgets
02693         self.modifyLayerWidgets['layer'][1].SetSelection(0)
02694         try:
02695             layer = int(self.modifyLayerWidgets['layer'][1].GetStringSelection())
02696         except ValueError:
02697             layer = None
02698             for label in self.modifyLayerWidgets.keys():
02699                 self.modifyLayerWidgets[label][1].Enable(False)
02700 
02701         if layer:
02702             driver   = self.mapDBInfo.layers[layer]['driver']
02703             database = self.mapDBInfo.layers[layer]['database']
02704             table    = self.mapDBInfo.layers[layer]['table']
02705 
02706             listOfColumns = self._getColumns(driver, database, table)
02707             self.modifyLayerWidgets['driver'][1].SetStringSelection(driver)
02708             self.modifyLayerWidgets['database'][1].SetValue(database)
02709             if table in self.modifyLayerWidgets['table'][1].GetItems():
02710                 self.modifyLayerWidgets['table'][1].SetStringSelection(table)
02711             else:
02712                 if self.defaultConnect['schema'] != '':
02713                     table = self.defaultConnect['schema'] + table # try with default schema
02714                 else:
02715                     table = 'public.' + table # try with 'public' schema
02716                 self.modifyLayerWidgets['table'][1].SetStringSelection(table)
02717             self.modifyLayerWidgets['key'][1].SetItems(listOfColumns)
02718             self.modifyLayerWidgets['key'][1].SetSelection(0)
02719 
02720         # events
02721         self.modifyLayerWidgets['layer'][1].Bind(wx.EVT_COMBOBOX, self.OnChangeLayer)
02722         # self.modifyLayerWidgets['driver'][1].Bind(wx.EVT_CHOICE, self.OnDriverChanged)
02723         # self.modifyLayerWidgets['database'][1].Bind(wx.EVT_TEXT_ENTER, self.OnDatabaseChanged)
02724         # self.modifyLayerWidgets['table'][1].Bind(wx.EVT_CHOICE, self.OnTableChanged)
02725 
02726         btnModify = wx.Button(self.modifyPanel, wx.ID_DELETE, _("&Modify layer"),
02727                               size = (125,-1))
02728         btnModify.Bind(wx.EVT_BUTTON, self.OnModifyLayer)
02729 
02730         #
02731         # do layout
02732         #
02733         pageSizer = wx.BoxSizer(wx.VERTICAL)
02734 
02735         # data area
02736         dataSizer = wx.FlexGridSizer(cols = 2, hgap = 5, vgap = 5)
02737         dataSizer.AddGrowableCol(1)
02738         for key in ('layer', 'driver', 'database', 'table', 'key'):
02739             label, value = self.modifyLayerWidgets[key]
02740             dataSizer.Add(item = label,
02741                           flag = wx.ALIGN_CENTER_VERTICAL)
02742             if label.GetLabel() == "Layer:":
02743                 dataSizer.Add(item = value,
02744                               flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
02745             else:
02746                 dataSizer.Add(item = value,
02747                               flag = wx.ALIGN_CENTER_VERTICAL)
02748 
02749         pageSizer.Add(item = dataSizer,
02750                       proportion = 1,
02751                       flag = wx.ALL | wx.EXPAND,
02752                       border = 5)
02753 
02754         pageSizer.Add(item = btnModify,
02755                       proportion = 0,
02756                       flag = wx.ALL | wx.ALIGN_RIGHT,
02757                       border = 5)
02758 
02759         self.modifyPanel.SetSizer(pageSizer)
02760 
02761     def _getTables(self, driver, database):
02762         """!Get list of tables for given driver and database"""
02763         tables = []
02764 
02765         ret = RunCommand('db.tables',
02766                          parent = self,
02767                          read = True,
02768                          flags = 'p',
02769                          driver = driver,
02770                          database = database)
02771         
02772         if ret is None:
02773             GError(parent = self,
02774                    message = _("Unable to get list of tables.\n"
02775                                "Please use db.connect to set database parameters."))
02776             
02777             return tables
02778         
02779         for table in ret.splitlines():
02780             tables.append(table)
02781         
02782         return tables
02783 
02784     def _getColumns(self, driver, database, table):
02785         """!Get list of column of given table"""
02786         columns = []
02787 
02788         ret = RunCommand('db.columns',
02789                          parent = self,
02790                          quiet = True,
02791                          read = True,
02792                          driver = driver,
02793                          database = database,
02794                          table = table)
02795         
02796         if ret == None:
02797             return columns
02798         
02799         for column in ret.splitlines():
02800             columns.append(column)
02801         
02802         return columns
02803 
02804     def OnDriverChanged(self, event):
02805         """!Driver selection changed, update list of tables"""
02806         driver = event.GetString()
02807         database = self.addLayerWidgets['database'][1].GetValue()
02808 
02809         winTable = self.addLayerWidgets['table'][1]
02810         winKey   = self.addLayerWidgets['key'][1]
02811         tables   = self._getTables(driver, database)
02812 
02813         winTable.SetItems(tables)
02814         winTable.SetSelection(0)
02815 
02816         if len(tables) == 0:
02817             winKey.SetItems([])
02818 
02819         event.Skip()
02820 
02821     def OnDatabaseChanged(self, event):
02822         """!Database selection changed, update list of tables"""
02823         event.Skip()
02824 
02825     def OnTableChanged(self, event):
02826         """!Table name changed, update list of columns"""
02827         driver   = self.addLayerWidgets['driver'][1].GetStringSelection()
02828         database = self.addLayerWidgets['database'][1].GetValue()
02829         table    = event.GetString()
02830 
02831         win  = self.addLayerWidgets['key'][1]
02832         cols = self._getColumns(driver, database, table)
02833         win.SetItems(cols)
02834         win.SetSelection(0)
02835 
02836         event.Skip()
02837 
02838     def OnSetDefault(self, event):
02839         """!Set default values"""
02840         driver   = self.addLayerWidgets['driver'][1]
02841         database = self.addLayerWidgets['database'][1]
02842         table    = self.addLayerWidgets['table'][1]
02843         key      = self.addLayerWidgets['key'][1]
02844 
02845         driver.SetStringSelection(self.defaultConnect['driver'])
02846         database.SetValue(self.defaultConnect['database'])
02847         tables = self._getTables(self.defaultConnect['driver'],
02848                                   self.defaultConnect['database'])
02849         table.SetItems(tables)
02850         table.SetSelection(0)
02851         if len(tables) == 0:
02852             key.SetItems([])
02853         else:
02854             cols = self._getColumns(self.defaultConnect['driver'],
02855                                      self.defaultConnect['database'],
02856                                      tables[0])
02857             key.SetItems(cols)
02858             key.SetSelection(0)
02859 
02860         event.Skip()
02861 
02862     def OnCreateTable(self, event):
02863         """!Create new table (name and key column given)"""
02864         driver   = self.addLayerWidgets['driver'][1].GetStringSelection()
02865         database = self.addLayerWidgets['database'][1].GetValue()
02866         table    = self.tableWidgets['table'][1].GetValue()
02867         key      = self.tableWidgets['key'][1].GetValue()
02868         
02869         if not table or not key:
02870             GError(parent = self,
02871                    message = _("Unable to create new table. "
02872                                "Table name or key column name is missing."))
02873             return
02874 
02875         if table in self.addLayerWidgets['table'][1].GetItems():
02876             GError(parent = self,
02877                    message = _("Unable to create new table. "
02878                                "Table <%s> already exists in the database.") % table)
02879             return
02880         
02881         # create table
02882         sql = 'CREATE TABLE %s (%s INTEGER)' % (table, key)
02883 
02884         RunCommand('db.execute',
02885                    quiet = True,
02886                    parent = self,
02887                    stdin = sql,
02888                    driver = driver,
02889                    database = database)
02890         
02891         # update list of tables
02892         tableList = self.addLayerWidgets['table'][1]
02893         tableList.SetItems(self._getTables(driver, database))
02894         tableList.SetStringSelection(table)
02895 
02896         # update key column selection
02897         keyList = self.addLayerWidgets['key'][1]
02898         keyList.SetItems(self._getColumns(driver, database, table))
02899         keyList.SetStringSelection(key)
02900         
02901         event.Skip()
02902 
02903     def OnAddLayer(self, event):
02904         """!Add new layer to vector map"""
02905         layer    = int(self.addLayerWidgets['layer'][1].GetValue())
02906         layerWin = self.addLayerWidgets['layer'][1]
02907         driver   = self.addLayerWidgets['driver'][1].GetStringSelection()
02908         database = self.addLayerWidgets['database'][1].GetValue()
02909         table    = self.addLayerWidgets['table'][1].GetStringSelection()
02910         key      = self.addLayerWidgets['key'][1].GetStringSelection()
02911         
02912         if layer in self.mapDBInfo.layers.keys():
02913             GError(parent = self,
02914                    message = _("Unable to add new layer to vector map <%(vector)s>. "
02915                                "Layer %(layer)d already exists.") % \
02916                        {'vector' : self.mapDBInfo.map, 'layer' : layer})
02917             return
02918 
02919         # add new layer
02920         ret = RunCommand('v.db.connect',
02921                          parent = self,
02922                          quiet = True,
02923                          map = self.mapDBInfo.map,
02924                          driver = driver,
02925                          database = database,
02926                          table = table,
02927                          key = key,
02928                          layer = layer)
02929         
02930         # insert records into table if required
02931         if self.addLayerWidgets['addCat'][0].IsChecked():
02932             RunCommand('v.to.db',
02933                        parent = self,
02934                        quiet = True,
02935                        map = self.mapDBInfo.map,
02936                        layer = layer,
02937                        qlayer = layer,
02938                        option = 'cat',
02939                        columns = key)
02940 
02941         if ret == 0:
02942             # update dialog (only for new layer)
02943             self.parentDialog.UpdateDialog(layer = layer) 
02944             # update db info
02945             self.mapDBInfo = self.parentDialog.mapDBInfo
02946             # increase layer number
02947             layerWin.SetValue(layer+1)
02948 
02949         if len(self.mapDBInfo.layers.keys()) == 1:
02950             # first layer add --- enable previously disabled widgets
02951             self.deleteLayer.Enable()
02952             self.deleteTable.Enable()
02953             for label in self.modifyLayerWidgets.keys():
02954                 self.modifyLayerWidgets[label][1].Enable()
02955             
02956     def OnDeleteLayer(self, event):
02957         """!Delete layer"""
02958         try:
02959             layer = int(self.deleteLayer.GetValue())
02960         except:
02961             return
02962 
02963         RunCommand('v.db.connect',
02964                    parent = self,
02965                    flags = 'd',
02966                    map = self.mapDBInfo.map,
02967                    layer = layer)
02968 
02969         # drop also table linked to layer which is deleted
02970         if self.deleteTable.IsChecked():
02971             driver   = self.addLayerWidgets['driver'][1].GetStringSelection()
02972             database = self.addLayerWidgets['database'][1].GetValue()
02973             table    = self.mapDBInfo.layers[layer]['table']
02974             sql      = 'DROP TABLE %s' % (table)
02975 
02976             RunCommand('db.execute',
02977                        parent = self,
02978                        stdin = sql,
02979                        quiet = True,
02980                        driver = driver,
02981                        database = database)
02982             
02983             # update list of tables
02984             tableList = self.addLayerWidgets['table'][1]
02985             tableList.SetItems(self._getTables(driver, database))
02986             tableList.SetStringSelection(table)
02987         
02988         # update dialog
02989         self.parentDialog.UpdateDialog(layer = layer) 
02990         # update db info
02991         self.mapDBInfo = self.parentDialog.mapDBInfo
02992 
02993         if len(self.mapDBInfo.layers.keys()) == 0:
02994             # disable selected widgets
02995             self.deleteLayer.Enable(False)
02996             self.deleteTable.Enable(False)
02997             for label in self.modifyLayerWidgets.keys():
02998                 self.modifyLayerWidgets[label][1].Enable(False)
02999             
03000         event.Skip()
03001 
03002     def OnChangeLayer(self, event):
03003         """!Layer number of layer to be deleted is changed"""
03004         try:
03005             layer = int(event.GetString())
03006         except:
03007             try:
03008                 layer = self.mapDBInfo.layers.keys()[0]
03009             except:
03010                 return
03011 
03012         if self.GetCurrentPage() == self.modifyPanel:
03013             driver   = self.mapDBInfo.layers[layer]['driver']
03014             database = self.mapDBInfo.layers[layer]['database']
03015             table    = self.mapDBInfo.layers[layer]['table']
03016             listOfColumns = self._getColumns(driver, database, table)
03017             self.modifyLayerWidgets['driver'][1].SetStringSelection(driver)
03018             self.modifyLayerWidgets['database'][1].SetValue(database)
03019             self.modifyLayerWidgets['table'][1].SetStringSelection(table)
03020             self.modifyLayerWidgets['key'][1].SetItems(listOfColumns)
03021             self.modifyLayerWidgets['key'][1].SetSelection(0)
03022         else:
03023             self.deleteTable.SetLabel(_('Drop also linked attribute table (%s)') % \
03024                                           self.mapDBInfo.layers[layer]['table'])
03025         if event:
03026             event.Skip()
03027 
03028     def OnModifyLayer(self, event):
03029         """!Modify layer connection settings"""
03030 
03031         layer = int(self.modifyLayerWidgets['layer'][1].GetStringSelection())
03032 
03033         modify = False
03034         if self.modifyLayerWidgets['driver'][1].GetStringSelection() != \
03035                 self.mapDBInfo.layers[layer]['driver'] or \
03036                 self.modifyLayerWidgets['database'][1].GetStringSelection() != \
03037                 self.mapDBInfo.layers[layer]['database'] or \
03038                 self.modifyLayerWidgets['table'][1].GetStringSelection() != \
03039                 self.mapDBInfo.layers[layer]['table'] or \
03040                 self.modifyLayerWidgets['key'][1].GetStringSelection() != \
03041                 self.mapDBInfo.layers[layer]['key']:
03042             modify = True
03043 
03044         if modify:
03045             # delete layer
03046             RunCommand('v.db.connect',
03047                        parent = self,
03048                        quiet = True,
03049                        flag = 'd',
03050                        map = self.mapDBInfo.map,
03051                        layer = layer)
03052             
03053             # add modified layer
03054             RunCommand('v.db.connect',
03055                        quiet = True,
03056                        map = self.mapDBInfo.map,
03057                        driver = self.modifyLayerWidgets['driver'][1].GetStringSelection(),
03058                        database = self.modifyLayerWidgets['database'][1].GetValue(),
03059                        table = self.modifyLayerWidgets['table'][1].GetStringSelection(),
03060                        key = self.modifyLayerWidgets['key'][1].GetStringSelection(),
03061                        layer = int(layer))
03062             
03063             # update dialog (only for new layer)
03064             self.parentDialog.UpdateDialog(layer = layer) 
03065             # update db info
03066             self.mapDBInfo = self.parentDialog.mapDBInfo
03067 
03068         event.Skip()
03069 
03070 def main(argv = None):
03071     import gettext
03072     gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode = True)
03073     
03074     if argv is None:
03075         argv = sys.argv
03076     
03077     if len(argv) != 2:
03078         print >> sys.stderr, __doc__
03079         sys.exit()
03080     
03081     #some applications might require image handlers
03082     wx.InitAllImageHandlers()
03083     
03084     app = wx.PySimpleApp()
03085     f = AttributeManager(parent = None, id = wx.ID_ANY,
03086                          title = "%s - <%s>" % (_("GRASS GIS Attribute Table Manager"),
03087                                               argv[1]),
03088                          size = (900,600), vectorName = argv[1])
03089     f.Show()
03090     
03091     app.MainLoop()
03092     
03093 if __name__ == '__main__':
03094     main()