source: grass/trunk/gui/wxpython/dbmgr/base.py

Last change on this file was 74307, checked in by neteler, 5 years ago

i18N: cleanup gettext usage for Python code (fixes #3790) (contributed by pmav99)

  • Property svn:eol-style set to native
  • Property svn:mime-type set to text/x-python
File size: 143.1 KB
Line 
1"""
2@package dbmgr.base
3
4@brief GRASS Attribute Table Manager base classes
5
6List of classes:
7 - base::Log
8 - base::VirtualAttributeList
9 - base::DbMgrBase
10 - base::DbMgrNotebookBase
11 - base::DbMgrBrowsePage
12 - base::DbMgrTablesPage
13 - base::DbMgrLayersPage
14 - base::TableListCtrl
15 - base::LayerListCtrl
16 - base::LayerBook
17 - base::FieldStatistics
18
19.. todo::
20 Implement giface class
21
22(C) 2007-2014 by the GRASS Development Team
23
24This program is free software under the GNU General Public License
25(>=v2). Read the file COPYING that comes with GRASS for details.
26
27@author Jachym Cepicky <jachym.cepicky gmail.com>
28@author Martin Landa <landa.martin gmail.com>
29@author Refactoring by Stepan Turek <stepan.turek seznam.cz> (GSoC 2012, mentor: Martin Landa)
30"""
31
32import sys
33import os
34import locale
35import tempfile
36import copy
37import types
38import math
39import functools
40
41from core import globalvar
42import wx
43import wx.lib.mixins.listctrl as listmix
44
45if globalvar.wxPythonPhoenix:
46 try:
47 import agw.flatnotebook as FN
48 except ImportError: # if it's not there locally, try the wxPython lib.
49 import wx.lib.agw.flatnotebook as FN
50else:
51 import wx.lib.flatnotebook as FN
52import wx.lib.scrolledpanel as scrolled
53
54import grass.script as grass
55from grass.script.utils import decode
56
57from dbmgr.sqlbuilder import SQLBuilderSelect, SQLBuilderUpdate
58from core.gcmd import RunCommand, GException, GError, GMessage, GWarning
59from core.utils import ListOfCatsToRange
60from gui_core.dialogs import CreateNewVector
61from dbmgr.vinfo import VectorDBInfo, GetUnicodeValue, CreateDbInfoDesc
62from core.debug import Debug
63from dbmgr.dialogs import ModifyTableRecord, AddColumnDialog
64from core.settings import UserSettings
65from gui_core.wrap import SpinCtrl, Button, TextCtrl, ListCtrl, CheckBox, \
66 StaticText, StaticBox, Menu
67from core.utils import cmp
68
69if sys.version_info.major >= 3:
70 unicode = str
71
72
73class Log:
74 """The log output SQL is redirected to the status bar of the
75 containing frame.
76 """
77
78 def __init__(self, parent):
79 self.parent = parent
80
81 def write(self, text_string):
82 """Update status bar"""
83 if self.parent:
84 self.parent.SetStatusText(text_string.strip())
85
86
87class VirtualAttributeList(ListCtrl,
88 listmix.ListCtrlAutoWidthMixin,
89 listmix.ColumnSorterMixin):
90 """Support virtual list class for Attribute Table Manager (browse page)
91 """
92
93 def __init__(self, parent, log, dbMgrData, layer, pages):
94 # initialize variables
95 self.parent = parent
96 self.log = log
97 self.dbMgrData = dbMgrData
98 self.mapDBInfo = self.dbMgrData['mapDBInfo']
99 self.layer = layer
100 self.pages = pages
101
102 self.fieldCalc = None
103 self.fieldStats = None
104 self.columns = {} # <- LoadData()
105
106 self.sqlFilter = {}
107
108 ListCtrl.__init__(self, parent=parent, id=wx.ID_ANY,
109 style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES |
110 wx.LC_VIRTUAL | wx.LC_SORT_ASCENDING)
111
112 try:
113 keyColumn = self.LoadData(layer)
114 except GException as e:
115 GError(parent=self,
116 message=e.value)
117 return
118
119 # add some attributes (colourful background for each item rows)
120 self.attr1 = wx.ListItemAttr()
121 self.attr1.SetBackgroundColour(wx.Colour(238, 238, 238))
122 self.attr2 = wx.ListItemAttr()
123 self.attr2.SetBackgroundColour("white")
124 self.il = wx.ImageList(16, 16)
125 self.sm_up = self.il.Add(
126 wx.ArtProvider.GetBitmap(
127 wx.ART_GO_UP, wx.ART_TOOLBAR, (16, 16)))
128 self.sm_dn = self.il.Add(
129 wx.ArtProvider.GetBitmap(
130 wx.ART_GO_DOWN, wx.ART_TOOLBAR, (16, 16)))
131 self.SetImageList(self.il, wx.IMAGE_LIST_SMALL)
132
133 # setup mixins
134 listmix.ListCtrlAutoWidthMixin.__init__(self)
135 listmix.ColumnSorterMixin.__init__(self, len(self.columns))
136
137 # sort item by category (id)
138 if keyColumn > -1:
139 self.SortListItems(col=keyColumn, ascending=True)
140 elif keyColumn:
141 self.SortListItems(col=0, ascending=True)
142
143 # events
144 self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected)
145 self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnItemDeselected)
146 self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColumnSort)
147 self.Bind(wx.EVT_LIST_COL_RIGHT_CLICK, self.OnColumnMenu)
148
149 def Update(self, mapDBInfo=None):
150 """Update list according new mapDBInfo description"""
151 if mapDBInfo:
152 self.mapDBInfo = mapDBInfo
153 self.LoadData(self.layer)
154 else:
155 self.LoadData(self.layer, **self.sqlFilter)
156
157 def LoadData(self, layer, columns=None, where=None, sql=None):
158 """Load data into list
159
160 :param layer: layer number
161 :param columns: list of columns for output (-> v.db.select)
162 :param where: where statement (-> v.db.select)
163 :param sql: full sql statement (-> db.select)
164
165 :return: id of key column
166 :return: -1 if key column is not displayed
167 """
168 self.log.write(_("Loading data..."))
169
170 tableName = self.mapDBInfo.layers[layer]['table']
171 keyColumn = self.mapDBInfo.layers[layer]['key']
172 try:
173 self.columns = self.mapDBInfo.tables[tableName]
174 except KeyError:
175 raise GException(_("Attribute table <%s> not found. "
176 "For creating the table switch to "
177 "'Manage layers' tab.") % tableName)
178
179 if not columns:
180 columns = self.mapDBInfo.GetColumns(tableName)
181 else:
182 all = self.mapDBInfo.GetColumns(tableName)
183 for col in columns:
184 if col not in all:
185 GError(parent=self,
186 message=_("Column <%(column)s> not found in "
187 "in the table <%(table)s>.") %
188 {'column': col, 'table': tableName})
189 return
190
191 try:
192 # for maps connected via v.external
193 keyId = columns.index(keyColumn)
194 except:
195 keyId = -1
196
197 # read data
198 # FIXME: Max. number of rows, while the GUI is still usable
199
200 # stdout can be very large, do not use PIPE, redirect to temp file
201 # TODO: more effective way should be implemented...
202
203 # split on field sep breaks if varchar() column contains the
204 # values, so while sticking with ASCII we make it something
205 # highly unlikely to exist naturally.
206 fs = '{_sep_}'
207
208 outFile = tempfile.NamedTemporaryFile(mode='w+b')
209
210 cmdParams = dict(quiet=True,
211 parent=self,
212 flags='c',
213 separator=fs)
214
215 if sql:
216 cmdParams.update(dict(sql=sql,
217 output=outFile.name,
218 overwrite=True))
219 ret = RunCommand('db.select',
220 **cmdParams)
221 self.sqlFilter = {"sql": sql}
222 else:
223 cmdParams.update(dict(map=self.mapDBInfo.map,
224 layer=layer,
225 where=where,
226 stdout=outFile))
227
228 self.sqlFilter = {"where": where}
229
230 if columns:
231 cmdParams.update(dict(columns=','.join(columns)))
232
233 ret = RunCommand('v.db.select',
234 **cmdParams)
235
236 # These two should probably be passed to init more cleanly
237 # setting the numbers of items = number of elements in the dictionary
238 self.itemDataMap = {}
239 self.itemIndexMap = []
240 self.itemCatsMap = {}
241
242 self.DeleteAllItems()
243
244 # self.ClearAll()
245 for i in range(self.GetColumnCount()):
246 self.DeleteColumn(0)
247
248 i = 0
249 info = wx.ListItem()
250 if globalvar.wxPythonPhoenix:
251 info.Mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE | wx.LIST_MASK_FORMAT
252 info.Image = -1
253 info.Format = 0
254 else:
255 info.m_mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE | wx.LIST_MASK_FORMAT
256 info.m_image = -1
257 info.m_format = 0
258 for column in columns:
259 if globalvar.wxPythonPhoenix:
260 info.Text = column
261 self.InsertColumn(i, info)
262 else:
263 info.m_text = column
264 self.InsertColumnInfo(i, info)
265 i += 1
266 if i >= 256:
267 self.log.write(_("Can display only 256 columns."))
268
269 i = 0
270 outFile.seek(0)
271
272 while True:
273 # os.linesep doesn't work here (MSYS)
274 # not sure what the replace is for?
275 # but we need strip to get rid of the ending newline
276 # which on windows leaves \r in a last empty attribute table cell
277 # and causes error
278 record = decode(outFile.readline().strip()).replace('\n', '')
279
280 if not record:
281 break
282
283 record = record.split(fs)
284 if len(columns) != len(record):
285 GError(parent=self,
286 message=_("Inconsistent number of columns "
287 "in the table <%(table)s>.") %
288 {'table': tableName})
289 self.columns = {} # because of IsEmpty method
290 return
291
292 self.AddDataRow(i, record, columns, keyId)
293
294 i += 1
295 if i >= 100000:
296 self.log.write(_("Viewing limit: 100000 records."))
297 break
298
299 self.SetItemCount(i)
300
301 if where:
302 item = -1
303 while True:
304 item = self.GetNextItem(item)
305 if item == -1:
306 break
307 self.SetItemState(
308 item,
309 wx.LIST_STATE_SELECTED,
310 wx.LIST_STATE_SELECTED)
311
312 i = 0
313 for col in columns:
314 width = self.columns[col]['length'] * 6 # FIXME
315 if width < 60:
316 width = 60
317 if width > 300:
318 width = 300
319 self.SetColumnWidth(col=i, width=width)
320 i += 1
321
322 self.SendSizeEvent()
323
324 self.log.write(_("Number of loaded records: %d") %
325 self.GetItemCount())
326
327 return keyId
328
329 def AddDataRow(self, i, record, columns, keyId):
330 """Add row to the data list"""
331 self.itemDataMap[i] = []
332 keyColumn = self.mapDBInfo.layers[self.layer]['key']
333 j = 0
334 cat = None
335
336 if keyColumn == 'OGC_FID':
337 self.itemDataMap[i].append(i + 1)
338 j += 1
339 cat = i + 1
340
341 for value in record:
342 if self.columns[columns[j]]['ctype'] != str:
343 try:
344 # casting disabled (2009/03)
345 # self.itemDataMap[i].append(self.columns[columns[j]]['ctype'](value))
346 self.itemDataMap[i].append(value)
347 except ValueError:
348 self.itemDataMap[i].append(_('Unknown value'))
349 else:
350 # encode string values
351 try:
352 self.itemDataMap[i].append(GetUnicodeValue(value))
353 except UnicodeDecodeError:
354 self.itemDataMap[i].append(_("Unable to decode value. "
355 "Set encoding in GUI preferences ('Attributes')."))
356
357 if not cat and keyId > -1 and keyId == j:
358 try:
359 cat = self.columns[columns[j]]['ctype'](value)
360 except ValueError as e:
361 cat = -1
362 GError(
363 parent=self,
364 message=_(
365 "Error loading attribute data. "
366 "Record number: %(rec)d. Unable to convert value '%(val)s' in "
367 "key column (%(key)s) to integer.\n\n"
368 "Details: %(detail)s") % {
369 'rec': i + 1,
370 'val': value,
371 'key': keyColumn,
372 'detail': e})
373 j += 1
374
375 self.itemIndexMap.append(i)
376 if keyId > -1: # load cats only when LoadData() is called first time
377 self.itemCatsMap[i] = cat
378
379 def OnItemSelected(self, event):
380 """Item selected. Add item to selected cats..."""
381 # cat = int(self.GetItemText(event.m_itemIndex))
382 # if cat not in self.selectedCats:
383 # self.selectedCats.append(cat)
384 # self.selectedCats.sort()
385
386 event.Skip()
387
388 def OnItemDeselected(self, event):
389 """Item deselected. Remove item from selected cats..."""
390 # cat = int(self.GetItemText(event.m_itemIndex))
391 # if cat in self.selectedCats:
392 # self.selectedCats.remove(cat)
393 # self.selectedCats.sort()
394
395 event.Skip()
396
397 def GetSelectedItems(self):
398 """Return list of selected items (category numbers)"""
399 cats = []
400 item = self.GetFirstSelected()
401 while item != -1:
402 cats.append(self.GetItemText(item))
403 item = self.GetNextSelected(item)
404
405 return cats
406
407 def GetItems(self):
408 """Return list of items (category numbers)"""
409 cats = []
410 for item in range(self.GetItemCount()):
411 cats.append(self.GetItemText(item))
412
413 return cats
414
415 def GetColumnText(self, index, col):
416 """Return column text"""
417 item = self.GetItem(index, col)
418 return item.GetText()
419
420 def GetListCtrl(self):
421 """Returt list"""
422 return self
423
424 def OnGetItemText(self, item, col):
425 """Get item text"""
426 index = self.itemIndexMap[item]
427 s = self.itemDataMap[index][col]
428 return s
429
430 def OnGetItemAttr(self, item):
431 """Get item attributes"""
432 if (item % 2) == 0:
433 return self.attr2
434 else:
435 return self.attr1
436
437 def OnColumnMenu(self, event):
438 """Column heading right mouse button -> pop-up menu"""
439 self._col = event.GetColumn()
440
441 popupMenu = Menu()
442
443 if not hasattr(self, "popupID"):
444 self.popupId = {'sortAsc': wx.NewId(),
445 'sortDesc': wx.NewId(),
446 'calculate': wx.NewId(),
447 'area': wx.NewId(),
448 'length': wx.NewId(),
449 'compact': wx.NewId(),
450 'fractal': wx.NewId(),
451 'perimeter': wx.NewId(),
452 'ncats': wx.NewId(),
453 'slope': wx.NewId(),
454 'lsin': wx.NewId(),
455 'lazimuth': wx.NewId(),
456 'calculator': wx.NewId(),
457 'stats': wx.NewId()}
458
459 popupMenu.Append(self.popupId['sortAsc'], text=_("Sort ascending"))
460 popupMenu.Append(self.popupId['sortDesc'], text=_("Sort descending"))
461 popupMenu.AppendSeparator()
462 subMenu = Menu()
463 popupMenu.AppendMenu(self.popupId['calculate'], _(
464 "Calculate (only numeric columns)"), subMenu)
465 popupMenu.Append(
466 self.popupId['calculator'],
467 text=_("Field calculator"))
468 popupMenu.AppendSeparator()
469 popupMenu.Append(self.popupId['stats'], text=_("Statistics"))
470
471 if not self.pages['manageTable']:
472 popupMenu.AppendSeparator()
473 self.popupId['addCol'] = wx.NewId()
474 popupMenu.Append(self.popupId['addCol'], text=_("Add column"))
475 if not self.dbMgrData['editable']:
476 popupMenu.Enable(self.popupId['addCol'], False)
477
478 if not self.dbMgrData['editable']:
479 popupMenu.Enable(self.popupId['calculator'], False)
480
481 if not self.dbMgrData['editable'] or self.columns[
482 self.GetColumn(self._col).GetText()]['ctype'] not in (
483 types.IntType, types.FloatType):
484 popupMenu.Enable(self.popupId['calculate'], False)
485
486 subMenu.Append(self.popupId['area'], text=_("Area size"))
487 subMenu.Append(self.popupId['length'], text=_("Line length"))
488 subMenu.Append(
489 self.popupId['compact'],
490 text=_("Compactness of an area"))
491 subMenu.Append(self.popupId['fractal'], text=_(
492 "Fractal dimension of boundary defining a polygon"))
493 subMenu.Append(
494 self.popupId['perimeter'],
495 text=_("Perimeter length of an area"))
496 subMenu.Append(self.popupId['ncats'], text=_(
497 "Number of features for each category"))
498 subMenu.Append(
499 self.popupId['slope'],
500 text=_("Slope steepness of 3D line"))
501 subMenu.Append(self.popupId['lsin'], text=_("Line sinuousity"))
502 subMenu.Append(self.popupId['lazimuth'], text=_("Line azimuth"))
503
504 self.Bind(
505 wx.EVT_MENU,
506 self.OnColumnSortAsc,
507 id=self.popupId['sortAsc'])
508 self.Bind(
509 wx.EVT_MENU,
510 self.OnColumnSortDesc,
511 id=self.popupId['sortDesc'])
512 self.Bind(
513 wx.EVT_MENU,
514 self.OnFieldCalculator,
515 id=self.popupId['calculator'])
516 self.Bind(
517 wx.EVT_MENU,
518 self.OnFieldStatistics,
519 id=self.popupId['stats'])
520 if not self.pages['manageTable']:
521 self.Bind(wx.EVT_MENU, self.OnAddColumn, id=self.popupId['addCol'])
522
523 for id in (
524 self.popupId['area'],
525 self.popupId['length'],
526 self.popupId['compact'],
527 self.popupId['fractal'],
528 self.popupId['perimeter'],
529 self.popupId['ncats'],
530 self.popupId['slope'],
531 self.popupId['lsin'],
532 self.popupId['lazimuth']):
533 self.Bind(wx.EVT_MENU, self.OnColumnCompute, id=id)
534
535 self.PopupMenu(popupMenu)
536 popupMenu.Destroy()
537
538 def OnColumnSort(self, event):
539 """Column heading left mouse button -> sorting"""
540 self._col = event.GetColumn()
541
542 self.ColumnSort()
543
544 event.Skip()
545
546 def OnColumnSortAsc(self, event):
547 """Sort values of selected column (ascending)"""
548 self.SortListItems(col=self._col, ascending=True)
549 event.Skip()
550
551 def OnColumnSortDesc(self, event):
552 """Sort values of selected column (descending)"""
553 self.SortListItems(col=self._col, ascending=False)
554 event.Skip()
555
556 def OnColumnCompute(self, event):
557 """Compute values of selected column"""
558 id = event.GetId()
559
560 option = None
561 if id == self.popupId['area']:
562 option = 'area'
563 elif id == self.popupId['length']:
564 option = 'length'
565 elif id == self.popupId['compact']:
566 option = 'compact'
567 elif id == self.popupId['fractal']:
568 option = 'fd'
569 elif id == self.popupId['perimeter']:
570 option = 'perimeter'
571 elif id == self.popupId['ncats']:
572 option = 'count'
573 elif id == self.popupId['slope']:
574 option = 'slope'
575 elif id == self.popupId['lsin']:
576 option = 'sinuous'
577 elif id == self.popupId['lazimuth']:
578 option = 'azimuth'
579
580 if not option:
581 return
582
583 RunCommand('v.to.db',
584 parent=self.parent,
585 map=self.mapDBInfo.map,
586 layer=self.layer,
587 option=option,
588 columns=self.GetColumn(self._col).GetText())
589
590 self.LoadData(self.layer)
591
592 def ColumnSort(self):
593 """Sort values of selected column (self._col)"""
594 # remove duplicated arrow symbol from column header
595 # FIXME: should be done automatically
596 info = wx.ListItem()
597 info.m_mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE
598 info.m_image = -1
599 for column in range(self.GetColumnCount()):
600 info.m_text = self.GetColumn(column).GetText()
601 self.SetColumn(column, info)
602
603 def OnFieldCalculator(self, event):
604 """Calls SQLBuilderUpdate instance"""
605 if not self.fieldCalc:
606 self.fieldCalc = SQLBuilderUpdate(
607 parent=self,
608 id=wx.ID_ANY,
609 vectmap=self.dbMgrData['vectName'],
610 layer=self.layer,
611 column=self.GetColumn(
612 self._col).GetText())
613 self.fieldCalc.Show()
614 else:
615 self.fieldCalc.Raise()
616
617 def OnFieldStatistics(self, event):
618 """Calls FieldStatistics instance"""
619 if not self.fieldStats:
620 self.fieldStats = FieldStatistics(parent=self, id=wx.ID_ANY)
621 self.fieldStats.Show()
622 else:
623 self.fieldStats.Raise()
624
625 selLayer = self.dbMgrData['mapDBInfo'].layers[self.layer]
626 self.fieldStats.Update(driver=selLayer['driver'],
627 database=selLayer['database'],
628 table=selLayer['table'],
629 column=self.GetColumn(self._col).GetText())
630
631 def OnAddColumn(self, event):
632 """Add column into table"""
633 table = self.dbMgrData['mapDBInfo'].layers[self.layer]['table']
634 dlg = AddColumnDialog(
635 parent=self,
636 title=_('Add column to table <%s>') %
637 table)
638 if not dlg:
639 return
640 if dlg.ShowModal() == wx.ID_OK:
641 data = dlg.GetData()
642 self.pages['browse'].AddColumn(name=data['name'],
643 ctype=data['ctype'],
644 length=data['length'])
645 dlg.Destroy()
646
647 def SortItems(self, sorter=cmp):
648 """Sort items"""
649 wx.BeginBusyCursor()
650 items = list(self.itemDataMap.keys())
651 items.sort(key=functools.cmp_to_key(self.Sorter))
652 self.itemIndexMap = items
653
654 # redraw the list
655 self.Refresh()
656 wx.EndBusyCursor()
657
658 def Sorter(self, key1, key2):
659 colName = self.GetColumn(self._col).GetText()
660 ascending = self._colSortFlag[self._col]
661 try:
662 item1 = self.columns[colName]["ctype"](
663 self.itemDataMap[key1][self._col])
664 item2 = self.columns[colName]["ctype"](
665 self.itemDataMap[key2][self._col])
666 except ValueError:
667 item1 = self.itemDataMap[key1][self._col]
668 item2 = self.itemDataMap[key2][self._col]
669
670 if isinstance(
671 item1, str) or isinstance(
672 item2, unicode):
673 cmpVal = locale.strcoll(GetUnicodeValue(item1), GetUnicodeValue(item2))
674 else:
675 cmpVal = cmp(item1, item2)
676
677 # If the items are equal then pick something else to make the sort
678 # value unique
679 if cmpVal == 0:
680 cmpVal = cmp(*self.GetSecondarySortValues(self._col, key1, key2))
681
682 if ascending:
683 return cmpVal
684 else:
685 return -cmpVal
686
687 def GetSortImages(self):
688 """Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py"""
689 return (self.sm_dn, self.sm_up)
690
691 def OnGetItemImage(self, item):
692 return -1
693
694 def IsEmpty(self):
695 """Check if list if empty"""
696 if self.columns:
697 return False
698
699 return True
700
701
702class DbMgrBase:
703
704 def __init__(self, id=wx.ID_ANY, mapdisplay=None,
705 vectorName=None, item=None, giface=None,
706 statusbar=None,
707 **kwargs):
708 """Base class, which enables usage of separate pages of Attribute Table Manager
709
710 :param id: window id
711 :param mapdisplay: MapFrame instance
712 :param vectorName: name of vector map
713 :param item: item from Layer Tree
714 :param log: log window
715 :param statusbar: widget with statusbar
716 :param kwagrs: other wx.Frame's arguments
717 """
718
719 # stores all data, which are shared by pages
720 self.dbMgrData = {}
721 self.dbMgrData['vectName'] = vectorName
722 self.dbMgrData['treeItem'] = item # item in layer tree
723
724 self.mapdisplay = mapdisplay
725
726 if self.mapdisplay:
727 self.map = mapdisplay.Map
728 else:
729 self.map = None
730
731 if not self.mapdisplay:
732 pass
733 elif self.mapdisplay.tree and \
734 self.dbMgrData['treeItem'] and not self.dbMgrData['vectName']:
735 maptree = self.mapdisplay.tree
736 name = maptree.GetLayerInfo(
737 self.dbMgrData['treeItem'],
738 key='maplayer').GetName()
739 self.dbMgrData['vectName'] = name
740
741 # vector attributes can be changed only if vector map is in
742 # the current mapset
743 mapInfo = None
744 if self.dbMgrData['vectName']:
745 mapInfo = grass.find_file(
746 name=self.dbMgrData['vectName'],
747 element='vector')
748 if not mapInfo or mapInfo['mapset'] != grass.gisenv()['MAPSET']:
749 self.dbMgrData['editable'] = False
750 else:
751 self.dbMgrData['editable'] = True
752
753 self.giface = giface
754
755 # status bar log class
756 self.log = Log(statusbar) # -> statusbar
757
758 # -> layers / tables description
759 self.dbMgrData['mapDBInfo'] = VectorDBInfo(self.dbMgrData['vectName'])
760
761 # store information, which pages were initialized
762 self.pages = {
763 'browse': None,
764 'manageTable': None,
765 'manageLayer': None
766 }
767
768 def ChangeVectorMap(self, vectorName):
769 """Change of vector map
770
771 Does not import layers of new vector map into pages.
772 For the import use methods addLayer in DbMgrBrowsePage and DbMgrTablesPage
773 """
774 if self.pages['browse']:
775 self.pages['browse'].DeleteAllPages()
776 if self.pages['manageTable']:
777 self.pages['manageTable'].DeleteAllPages()
778
779 self.dbMgrData['vectName'] = vectorName
780
781 # fetch fresh db info
782 self.dbMgrData['mapDBInfo'] = VectorDBInfo(self.dbMgrData['vectName'])
783
784 # vector attributes can be changed only if vector map is in
785 # the current mapset
786 mapInfo = grass.find_file(
787 name=self.dbMgrData['vectName'],
788 element='vector')
789 if not mapInfo or mapInfo['mapset'] != grass.gisenv()['MAPSET']:
790 self.dbMgrData['editable'] = False
791 else:
792 self.dbMgrData['editable'] = True
793
794 # 'manage layers page
795 if self.pages['manageLayer']:
796 self.pages['manageLayer'].UpdatePage()
797
798 def CreateDbMgrPage(self, parent, pageName, onlyLayer=-1):
799 """Creates chosen page
800
801 :param pageName: can be 'browse' or 'manageTable' or
802 'manageLayer' which corresponds with pages in
803 Attribute Table Manager
804 :return: created instance of page, if the page has been already
805 created returns the previously created instance
806 :return: None if wrong identifier was passed
807 """
808 if pageName == 'browse':
809 if not self.pages['browse']:
810 self.pages[pageName] = DbMgrBrowsePage(
811 parent=parent, parentDbMgrBase=self, onlyLayer=onlyLayer)
812 return self.pages[pageName]
813 if pageName == 'manageTable':
814 if not self.pages['manageTable']:
815 self.pages[pageName] = DbMgrTablesPage(
816 parent=parent, parentDbMgrBase=self, onlyLayer=onlyLayer)
817 return self.pages[pageName]
818 if pageName == 'manageLayer':
819 if not self.pages['manageLayer']:
820 self.pages[pageName] = DbMgrLayersPage(
821 parent=parent, parentDbMgrBase=self)
822 return self.pages[pageName]
823 return None
824
825 def UpdateDialog(self, layer):
826 """Updates dialog layout for given layer"""
827 # delete page
828 if layer in self.dbMgrData['mapDBInfo'].layers.keys():
829 # delete page
830 # draging pages disallowed
831 # if self.browsePage.GetPageText(page).replace('Layer ', '').strip() == str(layer):
832 # self.browsePage.DeletePage(page)
833 # break
834 if self.pages['browse']:
835 self.pages['browse'].DeletePage(layer)
836 if self.pages['manageTable']:
837 self.pages['manageTable'].DeletePage(layer)
838
839 # fetch fresh db info
840 self.dbMgrData['mapDBInfo'] = VectorDBInfo(self.dbMgrData['vectName'])
841
842 #
843 # add new page
844 #
845 if layer in self.dbMgrData['mapDBInfo'].layers.keys():
846 # 'browse data' page
847 if self.pages['browse']:
848 self.pages['browse'].AddLayer(layer)
849 # 'manage tables' page
850 if self.pages['manageTable']:
851 self.pages['manageTable'].AddLayer(layer)
852
853 # manage layers page
854 if self.pages['manageLayer']:
855 self.pages['manageLayer'].UpdatePage()
856
857 def GetVectorName(self):
858 """Get vector name"""
859 return self.dbMgrData['vectName']
860
861 def GetVectorLayers(self):
862 """Get layers of vector map which have table"""
863 return self.dbMgrData['mapDBInfo'].layers.keys()
864
865
866class DbMgrNotebookBase(FN.FlatNotebook):
867
868 def __init__(self, parent, parentDbMgrBase):
869 """Base class for notebook with attribute tables in tabs
870
871 :param parent: GUI parent
872 :param parentDbMgrBase: instance of DbMgrBase class
873 """
874
875 self.parent = parent
876 self.parentDbMgrBase = parentDbMgrBase
877
878 self.log = self.parentDbMgrBase.log
879 self.giface = self.parentDbMgrBase.giface
880
881 self.map = self.parentDbMgrBase.map
882 self.mapdisplay = self.parentDbMgrBase.mapdisplay
883
884 # TODO no need to have it in class scope make it local?
885 self.listOfCommands = []
886 self.listOfSQLStatements = []
887
888 # initializet pages
889 self.pages = self.parentDbMgrBase.pages
890
891 # shared data among pages
892 self.dbMgrData = self.parentDbMgrBase.dbMgrData
893
894 # set up virtual lists (each layer)
895 # {layer: list, widgets...}
896 self.layerPage = {}
897
898 # currently selected layer
899 self.selLayer = None
900
901 # list which represents layers numbers in order of tabs
902 self.layers = []
903
904 if globalvar.hasAgw:
905 dbmStyle = {'agwStyle': globalvar.FNPageStyle}
906 else:
907 dbmStyle = {'style': globalvar.FNPageStyle}
908
909 FN.FlatNotebook.__init__(self, parent=self.parent, id=wx.ID_ANY,
910 **dbmStyle)
911
912 self.Bind(FN.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.OnLayerPageChanged)
913
914 def OnLayerPageChanged(self, event):
915 """Layer tab changed"""
916
917 # because of SQL Query notebook
918 if event.GetEventObject() != self:
919 return
920
921 pageNum = self.GetSelection()
922 self.selLayer = self.layers[pageNum]
923 try:
924 idCol = self.layerPage[self.selLayer]['whereColumn']
925 except KeyError:
926 idCol = None
927
928 try:
929 # update statusbar
930 self.log.write(
931 _("Number of loaded records: %d") %
932 self.FindWindowById(
933 self.layerPage[
934 self.selLayer]['data']). GetItemCount())
935 except:
936 pass
937
938 if idCol:
939 winCol = self.FindWindowById(idCol)
940 table = self.dbMgrData['mapDBInfo'].layers[self.selLayer]["table"]
941 self.dbMgrData['mapDBInfo'].GetColumns(table)
942
943 def ApplyCommands(self, listOfCommands, listOfSQLStatements):
944 """Apply changes
945
946 .. todo::
947 this part should be _completely_ redesigned
948 """
949 # perform GRASS commands (e.g. v.db.addcolumn)
950 wx.BeginBusyCursor()
951
952 if len(listOfCommands) > 0:
953 for cmd in listOfCommands:
954 RunCommand(prog=cmd[0],
955 quiet=True,
956 parent=self,
957 **cmd[1])
958
959 self.dbMgrData['mapDBInfo'] = VectorDBInfo(
960 self.dbMgrData['vectName'])
961 if self.pages['manageTable']:
962 self.pages['manageTable'].UpdatePage(self.selLayer)
963
964 if self.pages['browse']:
965 self.pages['browse'].UpdatePage(self.selLayer)
966 # reset list of commands
967 listOfCommands = []
968
969 # perform SQL non-select statements (e.g. 'delete from table where
970 # cat=1')
971 if len(listOfSQLStatements) > 0:
972 fd, sqlFilePath = tempfile.mkstemp(text=True)
973 sqlFile = open(sqlFilePath, 'w')
974 for sql in listOfSQLStatements:
975 enc = UserSettings.Get(
976 group='atm', key='encoding', subkey='value')
977 if not enc and 'GRASS_DB_ENCODING' in os.environ:
978 enc = os.environ['GRASS_DB_ENCODING']
979 if enc:
980 sqlFile.write(sql.encode(enc) + ';')
981 else:
982 sqlFile.write(sql.encode('utf-8') + ';')
983 sqlFile.write(os.linesep)
984 sqlFile.close()
985
986 driver = self.dbMgrData['mapDBInfo'].layers[
987 self.selLayer]["driver"]
988 database = self.dbMgrData['mapDBInfo'].layers[
989 self.selLayer]["database"]
990
991 Debug.msg(3, 'AttributeManger.ApplyCommands(): %s' %
992 ';'.join(["%s" % s for s in listOfSQLStatements]))
993
994 RunCommand('db.execute',
995 parent=self,
996 input=sqlFilePath,
997 driver=driver,
998 database=database)
999
1000 os.close(fd)
1001 os.remove(sqlFilePath)
1002 # reset list of statements
1003 self.listOfSQLStatements = []
1004
1005 wx.EndBusyCursor()
1006
1007 def DeletePage(self, layer):
1008 """Removes layer page"""
1009 if layer not in self.layers:
1010 return False
1011
1012 FN.FlatNotebook.DeletePage(self, self.layers.index(layer))
1013
1014 self.layers.remove(layer)
1015 del self.layerPage[layer]
1016
1017 if self.GetSelection() >= 0:
1018 self.selLayer = self.layers[self.GetSelection()]
1019 else:
1020 self.selLayer = None
1021
1022 return True
1023
1024 def DeleteAllPages(self):
1025 """Removes all layer pages"""
1026 FN.FlatNotebook.DeleteAllPages(self)
1027 self.layerPage = {}
1028 self.layers = []
1029 self.selLayer = None
1030
1031 def AddColumn(self, name, ctype, length):
1032 """Add new column to the table"""
1033 table = self.dbMgrData['mapDBInfo'].layers[self.selLayer]['table']
1034
1035 if not name:
1036 GError(parent=self,
1037 message=_("Unable to add column to the table. "
1038 "No column name defined."))
1039 return False
1040
1041 # cast type if needed
1042 if ctype == 'double':
1043 ctype = 'double precision'
1044 if ctype != 'varchar':
1045 length = '' # FIXME
1046
1047 # check for duplicate items
1048 if name in self.dbMgrData['mapDBInfo'].GetColumns(table):
1049 GError(
1050 parent=self,
1051 message=_("Column <%(column)s> already exists in table <%(table)s>.") % {
1052 'column': name,
1053 'table': self.dbMgrData['mapDBInfo'].layers[
1054 self.selLayer]["table"]})
1055 return False
1056
1057 # add v.db.addcolumn command to the list
1058 if ctype == 'varchar':
1059 ctype += ' (%d)' % length
1060 self.listOfCommands.append(('v.db.addcolumn',
1061 {'map': self.dbMgrData['vectName'],
1062 'layer': self.selLayer,
1063 'columns': '%s %s' % (name, ctype)}
1064 ))
1065 # apply changes
1066 self.ApplyCommands(self.listOfCommands, self.listOfSQLStatements)
1067
1068 return True
1069
1070 def GetAddedLayers(self):
1071 """Get list of added layers"""
1072 return self.layers[:]
1073
1074
1075class DbMgrBrowsePage(DbMgrNotebookBase):
1076
1077 def __init__(self, parent, parentDbMgrBase, onlyLayer=-1):
1078 """Browse page class
1079
1080 :param parent: GUI parent
1081 :param parentDbMgrBase: instance of DbMgrBase class
1082 :param onlyLayer: create only tab of given layer, if -1 creates
1083 tabs of all layers
1084 """
1085
1086 DbMgrNotebookBase.__init__(self, parent=parent,
1087 parentDbMgrBase=parentDbMgrBase)
1088
1089 # for Sql Query notebook adaptation on current width
1090 self.sqlBestSize = None
1091
1092 for layer in self.dbMgrData['mapDBInfo'].layers.keys():
1093 if onlyLayer > 0 and layer != onlyLayer:
1094 continue
1095 self.AddLayer(layer)
1096
1097 if self.layers:
1098 self.SetSelection(0)
1099 self.selLayer = self.layers[0]
1100 self.log.write(
1101 _("Number of loaded records: %d") %
1102 self.FindWindowById(
1103 self.layerPage[
1104 self.selLayer]['data']).GetItemCount())
1105
1106 # query map layer (if parent (GMFrame) is given)
1107 self.qlayer = None
1108
1109 # sqlbuilder
1110 self.builder = None
1111
1112 def AddLayer(self, layer, pos=-1):
1113 """Adds tab which represents table and enables browse it
1114
1115 :param layer: vector map layer conntected to table
1116 :param pos: position of tab, if -1 it is added to end
1117
1118 :return: True if layer was added
1119 :return: False if layer was not added - layer has been already
1120 added or has empty table or does not exist
1121 """
1122 if layer in self.layers or \
1123 layer not in self.parentDbMgrBase.GetVectorLayers():
1124 return False
1125
1126 panel = wx.Panel(parent=self, id=wx.ID_ANY)
1127
1128 # IMPORTANT NOTE: wx.StaticBox MUST be defined BEFORE any of the
1129 # controls that are placed IN the wx.StaticBox, or it will freeze
1130 # on the Mac
1131
1132 listBox = StaticBox(
1133 parent=panel, id=wx.ID_ANY, label=" %s " %
1134 _("Attribute data - right-click to edit/manage records"))
1135 listSizer = wx.StaticBoxSizer(listBox, wx.VERTICAL)
1136
1137 win = VirtualAttributeList(panel, self.log,
1138 self.dbMgrData, layer, self.pages)
1139 if win.IsEmpty():
1140 panel.Destroy()
1141 return False
1142
1143 self.layers.append(layer)
1144
1145 win.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnDataItemActivated)
1146
1147 self.layerPage[layer] = {'browsePage': panel.GetId()}
1148
1149 label = _("Table")
1150 if not self.dbMgrData['editable']:
1151 label += _(" (readonly)")
1152
1153 if pos == -1:
1154 pos = self.GetPageCount()
1155 self.InsertPage(
1156 pos, page=panel, text=" %d / %s %s" %
1157 (layer, label, self.dbMgrData['mapDBInfo'].layers[layer]['table']))
1158
1159 pageSizer = wx.BoxSizer(wx.VERTICAL)
1160
1161 sqlQueryPanel = wx.Panel(parent=panel, id=wx.ID_ANY)
1162
1163 # attribute data
1164 sqlBox = StaticBox(parent=sqlQueryPanel, id=wx.ID_ANY,
1165 label=" %s " % _("SQL Query"))
1166
1167 sqlSizer = wx.StaticBoxSizer(sqlBox, wx.VERTICAL)
1168
1169 win.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnDataRightUp) # wxMSW
1170 win.Bind(wx.EVT_RIGHT_UP, self.OnDataRightUp) # wxGTK
1171 if UserSettings.Get(group='atm', key='leftDbClick',
1172 subkey='selection') == 0:
1173 win.Bind(wx.EVT_LEFT_DCLICK, self.OnDataItemEdit)
1174 win.Bind(wx.EVT_COMMAND_LEFT_DCLICK, self.OnDataItemEdit)
1175 else:
1176 win.Bind(wx.EVT_LEFT_DCLICK, self.OnDataDrawSelected)
1177 win.Bind(wx.EVT_COMMAND_LEFT_DCLICK, self.OnDataDrawSelected)
1178
1179 listSizer.Add(win, proportion=1,
1180 flag=wx.EXPAND | wx.ALL,
1181 border=3)
1182
1183 # sql statement box
1184 FNPageStyle = FN.FNB_NO_NAV_BUTTONS | \
1185 FN.FNB_NO_X_BUTTON
1186 if globalvar.hasAgw:
1187 dbmStyle = {'agwStyle': FNPageStyle}
1188 else:
1189 dbmStyle = {'style': FNPageStyle}
1190 sqlNtb = FN.FlatNotebook(parent=sqlQueryPanel, id=wx.ID_ANY,
1191 **dbmStyle)
1192 # Simple tab
1193 simpleSqlPanel = wx.Panel(parent=sqlNtb, id=wx.ID_ANY)
1194 sqlNtb.AddPage(page=simpleSqlPanel,
1195 text=_('Simple'))
1196
1197 btnApply = Button(
1198 parent=simpleSqlPanel,
1199 id=wx.ID_APPLY,
1200 name='btnApply')
1201 btnApply.SetToolTip(
1202 _("Apply SELECT statement and reload data records"))
1203 btnApply.Bind(wx.EVT_BUTTON, self.OnApplySqlStatement)
1204
1205 whereSimpleSqlPanel = wx.Panel(
1206 parent=simpleSqlPanel,
1207 id=wx.ID_ANY,
1208 name='wherePanel')
1209 sqlWhereColumn = wx.ComboBox(
1210 parent=whereSimpleSqlPanel, id=wx.ID_ANY, size=(150, -1),
1211 style=wx.CB_SIMPLE | wx.CB_READONLY,
1212 choices=self.dbMgrData['mapDBInfo'].GetColumns(
1213 self.dbMgrData['mapDBInfo'].layers[layer]['table']))
1214 sqlWhereColumn.SetSelection(0)
1215 sqlWhereCond = wx.Choice(parent=whereSimpleSqlPanel, id=wx.ID_ANY,
1216 size=(55, -1),
1217 choices=['=', '!=', '<', '<=', '>', '>='])
1218 sqlWhereCond.SetSelection(0)
1219 sqlWhereValue = TextCtrl(
1220 parent=whereSimpleSqlPanel,
1221 id=wx.ID_ANY,
1222 value="",
1223 style=wx.TE_PROCESS_ENTER)
1224 sqlWhereValue.SetToolTip(
1225 _("Example: %s") %
1226 "MULTILANE = 'no' AND OBJECTID < 10")
1227
1228 sqlLabel = StaticText(
1229 parent=simpleSqlPanel,
1230 id=wx.ID_ANY,
1231 label="SELECT * FROM %s WHERE " %
1232 self.dbMgrData['mapDBInfo'].layers[layer]['table'])
1233 # Advanced tab
1234 advancedSqlPanel = wx.Panel(parent=sqlNtb, id=wx.ID_ANY)
1235 sqlNtb.AddPage(page=advancedSqlPanel,
1236 text=_('Builder'))
1237
1238 btnSqlBuilder = Button(
1239 parent=advancedSqlPanel,
1240 id=wx.ID_ANY,
1241 label=_("SQL Builder"))
1242 btnSqlBuilder.Bind(wx.EVT_BUTTON, self.OnBuilder)
1243
1244 sqlStatement = TextCtrl(
1245 parent=advancedSqlPanel,
1246 id=wx.ID_ANY,
1247 value="SELECT * FROM %s" %
1248 self.dbMgrData['mapDBInfo'].layers[layer]['table'],
1249 style=wx.TE_PROCESS_ENTER)
1250 sqlStatement.SetToolTip(
1251 _("Example: %s") %
1252 "SELECT * FROM roadsmajor WHERE MULTILANE = 'no' AND OBJECTID < 10")
1253 sqlWhereValue.Bind(wx.EVT_TEXT_ENTER, self.OnApplySqlStatement)
1254 sqlStatement.Bind(wx.EVT_TEXT_ENTER, self.OnApplySqlStatement)
1255
1256 # Simple tab layout
1257 simpleSqlSizer = wx.GridBagSizer(hgap=5, vgap=5)
1258
1259 sqlSimpleWhereSizer = wx.BoxSizer(wx.HORIZONTAL)
1260
1261 sqlSimpleWhereSizer.Add(
1262 sqlWhereColumn,
1263 flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.LEFT,
1264 border=3)
1265 sqlSimpleWhereSizer.Add(
1266 sqlWhereCond,
1267 flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.LEFT,
1268 border=3)
1269 sqlSimpleWhereSizer.Add(
1270 sqlWhereValue,
1271 proportion=1,
1272 flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.LEFT,
1273 border=3)
1274 whereSimpleSqlPanel.SetSizer(sqlSimpleWhereSizer)
1275 simpleSqlSizer.Add(sqlLabel, border=5, pos=(0, 0),
1276 flag=wx.ALIGN_CENTER_VERTICAL | wx.TOP | wx.LEFT)
1277 simpleSqlSizer.Add(whereSimpleSqlPanel, border=5, pos=(0, 1),
1278 flag=wx.ALIGN_CENTER_VERTICAL | wx.TOP | wx.EXPAND)
1279 simpleSqlSizer.Add(btnApply, border=5, pos=(0, 2),
1280 flag=wx.ALIGN_CENTER_VERTICAL | wx.TOP)
1281 simpleSqlSizer.AddGrowableCol(1)
1282
1283 simpleSqlPanel.SetSizer(simpleSqlSizer)
1284
1285 # Advanced tab layout
1286 advancedSqlSizer = wx.FlexGridSizer(cols=2, hgap=5, vgap=5)
1287 advancedSqlSizer.AddGrowableCol(0)
1288
1289 advancedSqlSizer.Add(sqlStatement,
1290 flag=wx.EXPAND | wx.ALL, border=5)
1291 advancedSqlSizer.Add(
1292 btnSqlBuilder,
1293 flag=wx.ALIGN_RIGHT | wx.TOP | wx.RIGHT | wx.BOTTOM,
1294 border=5)
1295
1296 sqlSizer.Add(sqlNtb,
1297 flag=wx.ALL | wx.EXPAND,
1298 border=3)
1299
1300 advancedSqlPanel.SetSizer(advancedSqlSizer)
1301
1302 pageSizer.Add(listSizer,
1303 proportion=1,
1304 flag=wx.ALL | wx.EXPAND,
1305 border=5)
1306
1307 sqlQueryPanel.SetSizer(sqlSizer)
1308
1309 pageSizer.Add(sqlQueryPanel,
1310 proportion=0,
1311 flag=wx.BOTTOM | wx.LEFT | wx.RIGHT | wx.EXPAND,
1312 border=5)
1313
1314 panel.SetSizer(pageSizer)
1315
1316 sqlNtb.Bind(wx.EVT_SIZE, self.OnSqlQuerySizeWrap(layer))
1317
1318 self.layerPage[layer]['data'] = win.GetId()
1319 self.layerPage[layer]['sqlNtb'] = sqlNtb.GetId()
1320 self.layerPage[layer]['whereColumn'] = sqlWhereColumn.GetId()
1321 self.layerPage[layer]['whereOperator'] = sqlWhereCond.GetId()
1322 self.layerPage[layer]['where'] = sqlWhereValue.GetId()
1323 self.layerPage[layer]['builder'] = btnSqlBuilder.GetId()
1324 self.layerPage[layer]['statement'] = sqlStatement.GetId()
1325 # for SQL Query adaptation on width
1326 self.layerPage[layer]['sqlIsReduced'] = False
1327
1328 return True
1329
1330 def OnSqlQuerySizeWrap(self, layer):
1331 """Helper function"""
1332 return lambda event: self.OnSqlQuerySize(event, layer)
1333
1334 def OnSqlQuerySize(self, event, layer):
1335 """Adapts SQL Query Simple tab on current width"""
1336
1337 sqlNtb = event.GetEventObject()
1338 if not self.sqlBestSize:
1339 self.sqlBestSize = sqlNtb.GetBestSize()
1340
1341 size = sqlNtb.GetSize()
1342 sqlReduce = self.sqlBestSize[0] > size[0]
1343 if (sqlReduce and self.layerPage[layer]['sqlIsReduced']) or \
1344 (not sqlReduce and not self.layerPage[layer]['sqlIsReduced']):
1345 event.Skip()
1346 return
1347
1348 wherePanel = sqlNtb.FindWindowByName('wherePanel')
1349 btnApply = sqlNtb.FindWindowByName('btnApply')
1350 sqlSimpleSizer = btnApply.GetContainingSizer()
1351
1352 if sqlReduce:
1353 self.layerPage[layer]['sqlIsReduced'] = True
1354 sqlSimpleSizer.AddGrowableCol(0)
1355 sqlSimpleSizer.RemoveGrowableCol(1)
1356 sqlSimpleSizer.SetItemPosition(wherePanel, (1, 0))
1357 sqlSimpleSizer.SetItemPosition(btnApply, (1, 1))
1358 else:
1359 self.layerPage[layer]['sqlIsReduced'] = False
1360 sqlSimpleSizer.AddGrowableCol(1)
1361 sqlSimpleSizer.RemoveGrowableCol(0)
1362 sqlSimpleSizer.SetItemPosition(wherePanel, (0, 1))
1363 sqlSimpleSizer.SetItemPosition(btnApply, (0, 2))
1364
1365 event.Skip()
1366
1367 def OnDataItemActivated(self, event):
1368 """Item activated, highlight selected item"""
1369 self.OnDataDrawSelected(event)
1370
1371 event.Skip()
1372
1373 def OnDataRightUp(self, event):
1374 """Table description area, context menu"""
1375 if not hasattr(self, "popupDataID1"):
1376 self.popupDataID1 = wx.NewId()
1377 self.popupDataID2 = wx.NewId()
1378 self.popupDataID3 = wx.NewId()
1379 self.popupDataID4 = wx.NewId()
1380 self.popupDataID5 = wx.NewId()
1381 self.popupDataID6 = wx.NewId()
1382 self.popupDataID7 = wx.NewId()
1383 self.popupDataID8 = wx.NewId()
1384 self.popupDataID9 = wx.NewId()
1385 self.popupDataID10 = wx.NewId()
1386 self.popupDataID11 = wx.NewId()
1387
1388 self.Bind(wx.EVT_MENU, self.OnDataItemEdit, id=self.popupDataID1)
1389 self.Bind(wx.EVT_MENU, self.OnDataItemAdd, id=self.popupDataID2)
1390 self.Bind(wx.EVT_MENU, self.OnDataItemDelete, id=self.popupDataID3)
1391 self.Bind(
1392 wx.EVT_MENU,
1393 self.OnDataItemDeleteAll,
1394 id=self.popupDataID4)
1395 self.Bind(wx.EVT_MENU, self.OnDataSelectAll, id=self.popupDataID5)
1396 self.Bind(wx.EVT_MENU, self.OnDataSelectNone, id=self.popupDataID6)
1397 self.Bind(
1398 wx.EVT_MENU,
1399 self.OnDataDrawSelected,
1400 id=self.popupDataID7)
1401 self.Bind(
1402 wx.EVT_MENU,
1403 self.OnDataDrawSelectedZoom,
1404 id=self.popupDataID8)
1405 self.Bind(
1406 wx.EVT_MENU,
1407 self.OnExtractSelected,
1408 id=self.popupDataID9)
1409 self.Bind(
1410 wx.EVT_MENU,
1411 self.OnDeleteSelected,
1412 id=self.popupDataID11)
1413 self.Bind(wx.EVT_MENU, self.OnDataReload, id=self.popupDataID10)
1414
1415 tlist = self.FindWindowById(self.layerPage[self.selLayer]['data'])
1416 # generate popup-menu
1417 menu = Menu()
1418 menu.Append(self.popupDataID1, _("Edit selected record"))
1419 selected = tlist.GetFirstSelected()
1420 if not self.dbMgrData[
1421 'editable'] or selected == -1 or tlist.GetNextSelected(selected) != -1:
1422 menu.Enable(self.popupDataID1, False)
1423 menu.Append(self.popupDataID2, _("Insert new record"))
1424 menu.Append(self.popupDataID3, _("Delete selected record(s)"))
1425 menu.Append(self.popupDataID4, _("Delete all records"))
1426 if not self.dbMgrData['editable']:
1427 menu.Enable(self.popupDataID2, False)
1428 menu.Enable(self.popupDataID3, False)
1429 menu.Enable(self.popupDataID4, False)
1430 menu.AppendSeparator()
1431 menu.Append(self.popupDataID5, _("Select all"))
1432 menu.Append(self.popupDataID6, _("Deselect all"))
1433 menu.AppendSeparator()
1434 menu.Append(self.popupDataID7, _("Highlight selected features"))
1435 menu.Append(
1436 self.popupDataID8,
1437 _("Highlight selected features and zoom"))
1438 if not self.map or len(tlist.GetSelectedItems()) == 0:
1439 menu.Enable(self.popupDataID7, False)
1440 menu.Enable(self.popupDataID8, False)
1441 menu.Append(self.popupDataID9, _("Extract selected features"))
1442 menu.Append(self.popupDataID11, _("Delete selected features"))
1443 if not self.dbMgrData['editable']:
1444 menu.Enable(self.popupDataID11, False)
1445 if tlist.GetFirstSelected() == -1:
1446 menu.Enable(self.popupDataID3, False)
1447 menu.Enable(self.popupDataID9, False)
1448 menu.Enable(self.popupDataID11, False)
1449 menu.AppendSeparator()
1450 menu.Append(self.popupDataID10, _("Reload"))
1451
1452 self.PopupMenu(menu)
1453 menu.Destroy()
1454
1455 # update statusbar
1456 self.log.write(_("Number of loaded records: %d") %
1457 tlist.GetItemCount())
1458
1459 def OnDataItemEdit(self, event):
1460 """Edit selected record of the attribute table"""
1461 tlist = self.FindWindowById(self.layerPage[self.selLayer]['data'])
1462 item = tlist.GetFirstSelected()
1463 if item == -1:
1464 return
1465 table = self.dbMgrData['mapDBInfo'].layers[self.selLayer]['table']
1466 keyColumn = self.dbMgrData['mapDBInfo'].layers[self.selLayer]['key']
1467 cat = tlist.itemCatsMap[tlist.itemIndexMap[item]]
1468
1469 # (column name, value)
1470 data = []
1471
1472 # collect names of all visible columns
1473 columnName = []
1474 for i in range(tlist.GetColumnCount()):
1475 columnName.append(tlist.GetColumn(i).GetText())
1476
1477 # key column must be always presented
1478 if keyColumn not in columnName:
1479 # insert key column on first position
1480 columnName.insert(0, keyColumn)
1481 data.append((keyColumn, str(cat)))
1482 keyId = 0
1483 missingKey = True
1484 else:
1485 missingKey = False
1486
1487 # add other visible columns
1488 for i in range(len(columnName)):
1489 ctype = self.dbMgrData['mapDBInfo'].tables[
1490 table][columnName[i]]['ctype']
1491 ctypeStr = self.dbMgrData['mapDBInfo'].tables[
1492 table][columnName[i]]['type']
1493 if columnName[i] == keyColumn: # key
1494 if missingKey is False:
1495 data.append((columnName[i], ctype, ctypeStr, str(cat)))
1496 keyId = i
1497 else:
1498 if missingKey is True:
1499 value = tlist.GetItem(item, i - 1).GetText()
1500 else:
1501 value = tlist.GetItem(item, i).GetText()
1502 data.append((columnName[i], ctype, ctypeStr, value))
1503
1504 dlg = ModifyTableRecord(parent=self,
1505 title=_("Update existing record"),
1506 data=data, keyEditable=(keyId, False))
1507
1508 if dlg.ShowModal() == wx.ID_OK:
1509 values = dlg.GetValues() # string
1510 updateList = list()
1511 try:
1512 for i in range(len(values)):
1513 if i == keyId: # skip key column
1514 continue
1515 if tlist.GetItem(item, i).GetText() == values[i]:
1516 continue # no change
1517
1518 column = tlist.columns[columnName[i]]
1519 if len(values[i]) > 0:
1520 try:
1521 if missingKey is True:
1522 idx = i - 1
1523 else:
1524 idx = i
1525
1526 if column['ctype'] != types.StringType:
1527 tlist.itemDataMap[item][
1528 idx] = column['ctype'](values[i])
1529 else: # -> string
1530 tlist.itemDataMap[item][idx] = values[i]
1531 except ValueError:
1532 raise ValueError(_("Value '%(value)s' needs to be entered as %(type)s.") %
1533 {'value': str(values[i]),
1534 'type': column['type']})
1535
1536 if column['ctype'] == types.StringType:
1537 if "'" in values[i]: # replace "'" -> "''"
1538 values[i] = values[i].replace("'", "''")
1539 updateList.append(
1540 "%s='%s'" %
1541 (columnName[i], values[i]))
1542 else:
1543 updateList.append(
1544 "%s=%s" %
1545 (columnName[i], values[i]))
1546 else: # -> NULL
1547 updateList.append("%s=NULL" % (columnName[i]))
1548 except ValueError as err:
1549 GError(
1550 parent=self,
1551 message=_("Unable to update existing record.\n%s") %
1552 err,
1553 showTraceback=False)
1554 self.OnDataItemEdit(event)
1555 return
1556
1557 if updateList:
1558 self.listOfSQLStatements.append(
1559 'UPDATE %s SET %s WHERE %s=%d' %
1560 (table, ','.join(updateList), keyColumn, cat))
1561 self.ApplyCommands(
1562 self.listOfCommands,
1563 self.listOfSQLStatements)
1564
1565 tlist.Update()
1566
1567 def OnDataItemAdd(self, event):
1568 """Add new record to the attribute table"""
1569 tlist = self.FindWindowById(self.layerPage[self.selLayer]['data'])
1570 table = self.dbMgrData['mapDBInfo'].layers[self.selLayer]['table']
1571 keyColumn = self.dbMgrData['mapDBInfo'].layers[self.selLayer]['key']
1572
1573 # (column name, value)
1574 data = []
1575
1576 # collect names of all visible columns
1577 columnName = []
1578 for i in range(tlist.GetColumnCount()):
1579 columnName.append(tlist.GetColumn(i).GetText())
1580
1581 # maximal category number
1582 if len(tlist.itemCatsMap.values()) > 0:
1583 maxCat = max(tlist.itemCatsMap.values())
1584 else:
1585 maxCat = 0 # starting category '1'
1586
1587 # key column must be always presented
1588 if keyColumn not in columnName:
1589 # insert key column on first position
1590 columnName.insert(0, keyColumn)
1591 data.append((keyColumn, str(maxCat + 1)))
1592 missingKey = True
1593 else:
1594 missingKey = False
1595
1596 # add other visible columns
1597 colIdx = 0
1598 keyId = -1
1599 for col in columnName:
1600 ctype = self.dbMgrData['mapDBInfo'].tables[table][col]['ctype']
1601 ctypeStr = self.dbMgrData['mapDBInfo'].tables[table][col]['type']
1602 if col == keyColumn: # key
1603 if missingKey is False:
1604 data.append((col, ctype, ctypeStr, str(maxCat + 1)))
1605 keyId = colIdx
1606 else:
1607 data.append((col, ctype, ctypeStr, ''))
1608
1609 colIdx += 1
1610
1611 dlg = ModifyTableRecord(parent=self,
1612 title=_("Insert new record"),
1613 data=data, keyEditable=(keyId, True))
1614
1615 if dlg.ShowModal() == wx.ID_OK:
1616 try: # get category number
1617 cat = int(dlg.GetValues(columns=[keyColumn])[0])
1618 except:
1619 cat = -1
1620
1621 try:
1622 if cat in tlist.itemCatsMap.values():
1623 raise ValueError(_("Record with category number %d "
1624 "already exists in the table.") % cat)
1625
1626 values = dlg.GetValues() # values (need to be casted)
1627 columnsString = ''
1628 valuesString = ''
1629
1630 for i in range(len(values)):
1631 if len(values[i]) == 0: # NULL
1632 if columnName[i] == keyColumn:
1633 raise ValueError(_("Category number (column %s)"
1634 " is missing.") % keyColumn)
1635 else:
1636 continue
1637
1638 try:
1639 if tlist.columns[columnName[i]]['ctype'] == int:
1640 # values[i] is stored as text.
1641 values[i] = int(float(values[i]))
1642 elif tlist.columns[columnName[i]]['ctype'] == float:
1643 values[i] = float(values[i])
1644 except:
1645 raise ValueError(_("Value '%(value)s' needs to be entered as %(type)s.") %
1646 {'value': values[i],
1647 'type': tlist.columns[columnName[i]]['type']})
1648 columnsString += '%s,' % columnName[i]
1649
1650 if tlist.columns[columnName[i]]['ctype'] == str:
1651 valuesString += "'%s'," % values[i].replace("'", "''")
1652 else:
1653 valuesString += "%s," % values[i]
1654
1655 except ValueError as err:
1656 GError(parent=self,
1657 message=_("Unable to insert new record.\n%s") % err,
1658 showTraceback=False)
1659 self.OnDataItemAdd(event)
1660 return
1661
1662 # remove category if need
1663 if missingKey is True:
1664 del values[0]
1665
1666 # add new item to the tlist
1667 if len(tlist.itemIndexMap) > 0:
1668 index = max(tlist.itemIndexMap) + 1
1669 else:
1670 index = 0
1671
1672 tlist.itemIndexMap.append(index)
1673 tlist.itemDataMap[index] = values
1674 tlist.itemCatsMap[index] = cat
1675 tlist.SetItemCount(tlist.GetItemCount() + 1)
1676
1677 self.listOfSQLStatements.append('INSERT INTO %s (%s) VALUES(%s)' %
1678 (table,
1679 columnsString.rstrip(','),
1680 valuesString.rstrip(',')))
1681
1682 self.ApplyCommands(self.listOfCommands, self.listOfSQLStatements)
1683
1684 def OnDataItemDelete(self, event):
1685 """Delete selected item(s) from the tlist (layer/category pair)"""
1686 dlist = self.FindWindowById(self.layerPage[self.selLayer]['data'])
1687 item = dlist.GetFirstSelected()
1688
1689 table = self.dbMgrData['mapDBInfo'].layers[self.selLayer]["table"]
1690 key = self.dbMgrData['mapDBInfo'].layers[self.selLayer]["key"]
1691
1692 indices = []
1693 # collect SQL statements
1694 while item != -1:
1695 index = dlist.itemIndexMap[item]
1696 indices.append(index)
1697
1698 cat = dlist.itemCatsMap[index]
1699
1700 self.listOfSQLStatements.append('DELETE FROM %s WHERE %s=%d' %
1701 (table, key, cat))
1702
1703 item = dlist.GetNextSelected(item)
1704
1705 if UserSettings.Get(
1706 group='atm', key='askOnDeleteRec', subkey='enabled'):
1707 deleteDialog = wx.MessageBox(
1708 parent=self,
1709 message=_(
1710 "Selected data records (%d) will be permanently deleted "
1711 "from table. Do you want to delete them?") %
1712 (len(self.listOfSQLStatements)),
1713 caption=_("Delete records"),
1714 style=wx.YES_NO | wx.CENTRE)
1715 if deleteDialog != wx.YES:
1716 self.listOfSQLStatements = []
1717 return False
1718
1719 # restore maps
1720 i = 0
1721 indexTemp = copy.copy(dlist.itemIndexMap)
1722 dlist.itemIndexMap = []
1723 dataTemp = copy.deepcopy(dlist.itemDataMap)
1724 dlist.itemDataMap = {}
1725 catsTemp = copy.deepcopy(dlist.itemCatsMap)
1726 dlist.itemCatsMap = {}
1727
1728 i = 0
1729 for index in indexTemp:
1730 if index in indices:
1731 continue
1732 dlist.itemIndexMap.append(i)
1733 dlist.itemDataMap[i] = dataTemp[index]
1734 dlist.itemCatsMap[i] = catsTemp[index]
1735
1736 i += 1
1737
1738 dlist.SetItemCount(len(dlist.itemIndexMap))
1739
1740 # deselect items
1741 item = dlist.GetFirstSelected()
1742 while item != -1:
1743 dlist.SetItemState(
1744 item, 0, wx.LIST_STATE_SELECTED | wx.LIST_STATE_FOCUSED)
1745 item = dlist.GetNextSelected(item)
1746
1747 # submit SQL statements
1748 self.ApplyCommands(self.listOfCommands, self.listOfSQLStatements)
1749
1750 return True
1751
1752 def OnDataItemDeleteAll(self, event):
1753 """Delete all items from the list"""
1754 dlist = self.FindWindowById(self.layerPage[self.selLayer]['data'])
1755 if UserSettings.Get(
1756 group='atm', key='askOnDeleteRec', subkey='enabled'):
1757 deleteDialog = wx.MessageBox(
1758 parent=self,
1759 message=_(
1760 "All data records (%d) will be permanently deleted "
1761 "from table. Do you want to delete them?") %
1762 (len(dlist.itemIndexMap)),
1763 caption=_("Delete records"),
1764 style=wx.YES_NO | wx.CENTRE)
1765 if deleteDialog != wx.YES:
1766 return
1767
1768 dlist.DeleteAllItems()
1769 dlist.itemDataMap = {}
1770 dlist.itemIndexMap = []
1771 dlist.SetItemCount(0)
1772
1773 table = self.dbMgrData['mapDBInfo'].layers[self.selLayer]["table"]
1774 self.listOfSQLStatements.append('DELETE FROM %s' % table)
1775
1776 self.ApplyCommands(self.listOfCommands, self.listOfSQLStatements)
1777
1778 event.Skip()
1779
1780 def _drawSelected(self, zoom, selectedOnly=True):
1781 """Highlight selected features"""
1782 if not self.map or not self.mapdisplay:
1783 return
1784
1785 tlist = self.FindWindowById(self.layerPage[self.selLayer]['data'])
1786 if selectedOnly:
1787 fn = tlist.GetSelectedItems
1788 else:
1789 fn = tlist.GetItems
1790
1791 cats = list(map(int, fn()))
1792
1793 digitToolbar = None
1794 if 'vdigit' in self.mapdisplay.toolbars:
1795 digitToolbar = self.mapdisplay.toolbars['vdigit']
1796 if digitToolbar and digitToolbar.GetLayer() and digitToolbar.GetLayer(
1797 ).GetName() == self.dbMgrData['vectName']:
1798 display = self.mapdisplay.GetMapWindow().GetDisplay()
1799 display.SetSelected(cats, layer=self.selLayer)
1800 if zoom:
1801 n, s, w, e = display.GetRegionSelected()
1802 self.mapdisplay.Map.GetRegion(n=n, s=s, w=w, e=e,
1803 update=True)
1804 else:
1805 # add map layer with higlighted vector features
1806 self.AddQueryMapLayer(selectedOnly) # -> self.qlayer
1807
1808 # set opacity based on queried layer
1809 if self.parent and self.mapdisplay.tree and \
1810 self.dbMgrData['treeItem']:
1811 maptree = self.mapdisplay.tree # TODO: giface
1812 opacity = maptree.GetLayerInfo(
1813 self.dbMgrData['treeItem'],
1814 key='maplayer').GetOpacity()
1815 self.qlayer.SetOpacity(opacity)
1816 if zoom:
1817 keyColumn = self.dbMgrData[
1818 'mapDBInfo'].layers[self.selLayer]['key']
1819 where = ''
1820 for range in ListOfCatsToRange(cats).split(','):
1821 if '-' in range:
1822 min, max = range.split('-')
1823 where += '%s >= %d and %s <= %d or ' % \
1824 (keyColumn, int(min),
1825 keyColumn, int(max))
1826 else:
1827 where += '%s = %d or ' % (keyColumn, int(range))
1828 where = where.rstrip('or ')
1829
1830 select = RunCommand('v.db.select',
1831 parent=self,
1832 read=True,
1833 quiet=True,
1834 flags='r',
1835 map=self.dbMgrData['mapDBInfo'].map,
1836 layer=int(self.selLayer),
1837 where=where)
1838
1839 region = {}
1840 for line in select.splitlines():
1841 key, value = line.split('=')
1842 region[key.strip()] = float(value.strip())
1843
1844 nsdist = ewdist = 0
1845 renderer = self.mapdisplay.GetMap()
1846 nsdist = 10 * ((renderer.GetCurrentRegion()
1847 ['n'] - renderer.GetCurrentRegion()['s']) / renderer.height)
1848 ewdist = 10 * ((renderer.GetCurrentRegion()
1849 ['e'] - renderer.GetCurrentRegion()['w']) / renderer.width)
1850 north = region['n'] + nsdist
1851 south = region['s'] - nsdist
1852 west = region['w'] - ewdist
1853 east = region['e'] + ewdist
1854 renderer.GetRegion(
1855 n=north, s=south, w=west, e=east, update=True)
1856 self.mapdisplay.GetMapWindow().ZoomHistory(n=north, s=south, w=west, e=east)
1857
1858 if zoom:
1859 self.mapdisplay.Map.AdjustRegion() # adjust resolution
1860 self.mapdisplay.Map.AlignExtentFromDisplay() # adjust extent
1861 self.mapdisplay.MapWindow.UpdateMap(render=True, renderVector=True)
1862 else:
1863 self.mapdisplay.MapWindow.UpdateMap(
1864 render=False, renderVector=True)
1865
1866 def AddQueryMapLayer(self, selectedOnly=True):
1867 """Redraw a map
1868
1869 :return: True if map has been redrawn, False if no map is given
1870 """
1871 tlist = self.FindWindowById(self.layerPage[self.selLayer]['data'])
1872 if selectedOnly:
1873 fn = tlist.GetSelectedItems
1874 else:
1875 fn = tlist.GetItems
1876
1877 cats = {self.selLayer: fn()}
1878
1879 if self.mapdisplay.Map.GetLayerIndex(self.qlayer) < 0:
1880 self.qlayer = None
1881
1882 if self.qlayer:
1883 self.qlayer.SetCmd(
1884 self.mapdisplay.AddTmpVectorMapLayer(
1885 self.dbMgrData['vectName'],
1886 cats, addLayer=False))
1887 else:
1888 self.qlayer = self.mapdisplay.AddTmpVectorMapLayer(
1889 self.dbMgrData['vectName'], cats)
1890
1891 return self.qlayer
1892
1893 def OnDataReload(self, event):
1894 """Reload tlist of records"""
1895 self.OnApplySqlStatement(None)
1896 self.listOfSQLStatements = []
1897
1898 def OnDataSelectAll(self, event):
1899 """Select all items"""
1900 tlist = self.FindWindowById(self.layerPage[self.selLayer]['data'])
1901 item = -1
1902
1903 while True:
1904 item = tlist.GetNextItem(item)
1905 if item == -1:
1906 break
1907 tlist.SetItemState(
1908 item,
1909 wx.LIST_STATE_SELECTED,
1910 wx.LIST_STATE_SELECTED)
1911
1912 event.Skip()
1913
1914 def OnDataSelectNone(self, event):
1915 """Deselect items"""
1916 tlist = self.FindWindowById(self.layerPage[self.selLayer]['data'])
1917 item = -1
1918
1919 while True:
1920 item = tlist.GetNextItem(item, wx.LIST_STATE_SELECTED)
1921 if item == -1:
1922 break
1923 tlist.SetItemState(
1924 item, 0, wx.LIST_STATE_SELECTED | wx.LIST_STATE_FOCUSED)
1925 tlist.Focus(0)
1926
1927 event.Skip()
1928
1929 def OnDataDrawSelected(self, event):
1930 """Reload table description"""
1931 self._drawSelected(zoom=False)
1932 event.Skip()
1933
1934 def OnDataDrawSelectedZoom(self, event):
1935 self._drawSelected(zoom=True)
1936 event.Skip()
1937
1938 def OnExtractSelected(self, event):
1939 """Extract vector objects selected in attribute browse window
1940 to new vector map
1941 """
1942 tlist = self.FindWindowById(self.layerPage[self.selLayer]['data'])
1943 # cats = tlist.selectedCats[:]
1944 cats = tlist.GetSelectedItems()
1945 if len(cats) == 0:
1946 GMessage(parent=self,
1947 message=_('Nothing to extract.'))
1948 return
1949 else:
1950 # dialog to get file name
1951 dlg = CreateNewVector(
1952 parent=self, title=_('Extract selected features'),
1953 giface=self.giface,
1954 cmd=(('v.extract',
1955 {'input': self.dbMgrData['vectName'],
1956 'cats': ListOfCatsToRange(cats)},
1957 'output')),
1958 disableTable=True)
1959 if not dlg:
1960 return
1961
1962 name = dlg.GetName(full=True)
1963
1964 if not self.mapdisplay and self.mapdisplay.tree:
1965 pass
1966 elif name and dlg.IsChecked('add'):
1967 # add layer to map layer tree
1968 self.mapdisplay.tree.AddLayer(ltype='vector',
1969 lname=name,
1970 lcmd=['d.vect', 'map=%s' % name])
1971 dlg.Destroy()
1972
1973 def OnDeleteSelected(self, event):
1974 """Delete vector objects selected in attribute browse window
1975 (attribures and geometry)
1976 """
1977 tlist = self.FindWindowById(self.layerPage[self.selLayer]['data'])
1978 cats = tlist.GetSelectedItems()
1979 if len(cats) == 0:
1980 GMessage(parent=self,
1981 message=_('Nothing to delete.'))
1982
1983 return
1984
1985 display = None
1986 if not self.mapdisplay:
1987 pass
1988 elif 'vdigit' in self.mapdisplay.toolbars:
1989 digitToolbar = self.mapdisplay.toolbars['vdigit']
1990 if digitToolbar and digitToolbar.GetLayer() and digitToolbar.GetLayer(
1991 ).GetName() == self.dbMgrData['vectName']:
1992 display = self.mapdisplay.GetMapWindow().GetDisplay()
1993 display.SetSelected(list(map(int, cats)), layer=self.selLayer)
1994 self.mapdisplay.MapWindow.UpdateMap(
1995 render=True, renderVector=True)
1996
1997 if self.OnDataItemDelete(None) and self.mapdisplay:
1998 if display:
1999 self.mapdisplay.GetMapWindow().digit.DeleteSelectedLines()
2000 else:
2001 RunCommand('v.edit',
2002 parent=self,
2003 quiet=True,
2004 map=self.dbMgrData['vectName'],
2005 tool='delete',
2006 cats=ListOfCatsToRange(cats))
2007
2008 self.mapdisplay.MapWindow.UpdateMap(render=True, renderVector=True)
2009
2010 def OnApplySqlStatement(self, event):
2011 """Apply simple/advanced sql statement"""
2012 keyColumn = -1 # index of key column
2013 listWin = self.FindWindowById(self.layerPage[self.selLayer]['data'])
2014 sql = None
2015 win = self.FindWindowById(self.layerPage[self.selLayer]['sqlNtb'])
2016 if not win:
2017 return
2018
2019 showSelected = False
2020 wx.BeginBusyCursor()
2021 if win.GetSelection() == 0:
2022 # simple sql statement
2023 whereCol = self.FindWindowById(self.layerPage[self.selLayer][
2024 'whereColumn']).GetStringSelection()
2025 whereOpe = self.FindWindowById(self.layerPage[self.selLayer][
2026 'whereOperator']).GetStringSelection()
2027 whereWin = self.FindWindowById(
2028 self.layerPage[self.selLayer]['where'])
2029 whereVal = whereWin.GetValue().strip()
2030 table = self.dbMgrData['mapDBInfo'].layers[self.selLayer]["table"]
2031 if self.dbMgrData['mapDBInfo'].tables[
2032 table][whereCol]['ctype'] == str:
2033 # string attribute, check for quotes
2034 whereVal = whereVal.replace('"', "'")
2035 if whereVal:
2036 if not whereVal.startswith("'"):
2037 whereVal = "'" + whereVal
2038 if not whereVal.endswith("'"):
2039 whereVal += "'"
2040 whereWin.SetValue(whereVal)
2041
2042 try:
2043 if len(whereVal) > 0:
2044 showSelected = True
2045 keyColumn = listWin.LoadData(
2046 self.selLayer, where=whereCol + whereOpe + whereVal)
2047 else:
2048 keyColumn = listWin.LoadData(self.selLayer)
2049 except GException as e:
2050 GError(
2051 parent=self,
2052 message=_("Loading attribute data failed.\n\n%s") %
2053 e.value)
2054 self.FindWindowById(
2055 self.layerPage[
2056 self.selLayer]['where']).SetValue('')
2057 else:
2058 # advanced sql statement
2059 win = self.FindWindowById(
2060 self.layerPage[
2061 self.selLayer]['statement'])
2062 try:
2063 cols, where = self.ValidateSelectStatement(win.GetValue())
2064 if cols is None and where is None:
2065 sql = win.GetValue()
2066 if where:
2067 showSelected = True
2068 except TypeError:
2069 GError(
2070 parent=self, message=_(
2071 "Loading attribute data failed.\n"
2072 "Invalid SQL select statement.\n\n%s") %
2073 win.GetValue())
2074 win.SetValue(
2075 "SELECT * FROM %s" %
2076 self.dbMgrData['mapDBInfo'].layers[
2077 self.selLayer]['table'])
2078 cols = None
2079 where = None
2080
2081 if cols or where or sql:
2082 try:
2083 keyColumn = listWin.LoadData(self.selLayer, columns=cols,
2084 where=where, sql=sql)
2085 except GException as e:
2086 GError(
2087 parent=self,
2088 message=_("Loading attribute data failed.\n\n%s") %
2089 e.value)
2090 win.SetValue(
2091 "SELECT * FROM %s" %
2092 self.dbMgrData['mapDBInfo'].layers[
2093 self.selLayer]['table'])
2094
2095 # sort by key column
2096 if sql and 'order by' in sql.lower():
2097 pass # don't order by key column
2098 else:
2099 if keyColumn > -1:
2100 listWin.SortListItems(col=keyColumn, ascending=True)
2101 else:
2102 listWin.SortListItems(col=0, ascending=True)
2103
2104 wx.EndBusyCursor()
2105
2106 # update statusbar
2107 self.log.write(
2108 _("Number of loaded records: %d") %
2109 self.FindWindowById(
2110 self.layerPage[
2111 self.selLayer]['data']).GetItemCount())
2112
2113 # update map display if needed
2114 if self.mapdisplay and \
2115 UserSettings.Get(group='atm', key='highlight', subkey='auto'):
2116 # TODO: replace by signals
2117 if showSelected:
2118 self._drawSelected(zoom=False, selectedOnly=False)
2119 else:
2120 self.mapdisplay.RemoveQueryLayer()
2121 self.mapdisplay.MapWindow.UpdateMap(
2122 render=False) # TODO: replace by signals
2123
2124 def OnBuilder(self, event):
2125 """SQL Builder button pressed -> show the SQLBuilder dialog"""
2126 if not self.builder:
2127 self.builder = SQLBuilderSelect(parent=self, id=wx.ID_ANY,
2128 vectmap=self.dbMgrData['vectName'],
2129 layer=self.selLayer,
2130 evtHandler=self.OnBuilderEvt)
2131 self.builder.Show()
2132 else:
2133 self.builder.Raise()
2134
2135 def OnBuilderEvt(self, event):
2136 if event == 'apply':
2137 sqlstr = self.builder.GetSQLStatement()
2138 self.FindWindowById(self.layerPage[self.selLayer][
2139 'statement']).SetValue(sqlstr)
2140 # apply query
2141 # self.listOfSQLStatements.append(sqlstr) #TODO probably it was bug
2142 self.OnApplySqlStatement(None)
2143 # close builder on apply
2144 if self.builder.CloseOnApply():
2145 self.builder = None
2146 elif event == 'close':
2147 self.builder = None
2148
2149 def ValidateSelectStatement(self, statement):
2150 """Validate SQL select statement
2151
2152 :return: (columns, where)
2153 :return: None on error
2154 """
2155 if statement[0:7].lower() != 'select ':
2156 return None
2157
2158 cols = ''
2159 index = 7
2160 for c in statement[index:]:
2161 if c == ' ':
2162 break
2163 cols += c
2164 index += 1
2165 if cols == '*':
2166 cols = None
2167 else:
2168 cols = cols.split(',')
2169
2170 tablelen = len(
2171 self.dbMgrData['mapDBInfo'].layers[
2172 self.selLayer]['table'])
2173
2174 if statement[index + 1:index + 6].lower() != 'from ' or \
2175 statement[index + 6:index + 6 + tablelen] != '%s' % \
2176 (self.dbMgrData['mapDBInfo'].layers[self.selLayer]['table']):
2177 return None
2178
2179 if len(statement[index + 7 + tablelen:]) > 0:
2180 index = statement.lower().find('where ')
2181 if index > -1:
2182 where = statement[index + 6:]
2183 else:
2184 where = None
2185 else:
2186 where = None
2187
2188 return (cols, where)
2189
2190 def LoadData(self, layer, columns=None, where=None, sql=None):
2191 """Load data into list
2192
2193 :param int layer: layer number
2194 :param list columns: list of columns for output
2195 :param str where: where statement
2196 :param str sql: full sql statement
2197
2198 :return: id of key column
2199 :return: -1 if key column is not displayed
2200 """
2201 listWin = self.FindWindowById(self.layerPage[layer]['data'])
2202 return listWin.LoadData(layer, columns, where, sql)
2203
2204 def UpdatePage(self, layer):
2205 # update data tlist
2206 if layer in self.layerPage.keys():
2207 tlist = self.FindWindowById(self.layerPage[layer]['data'])
2208 tlist.Update(self.dbMgrData['mapDBInfo'])
2209
2210 def ResetPage(self, layer=None):
2211 if not layer:
2212 layer = self.selLayer
2213 if layer not in self.layerPage.keys():
2214 return
2215 win = self.FindWindowById(self.layerPage[self.selLayer]['sqlNtb'])
2216 if win.GetSelection() == 0:
2217 self.FindWindowById(
2218 self.layerPage[layer]['whereColumn']).SetSelection(0)
2219 self.FindWindowById(
2220 self.layerPage[layer]['whereOperator']).SetSelection(0)
2221 self.FindWindowById(self.layerPage[layer]['where']).SetValue('')
2222 else:
2223 sqlWin = self.FindWindowById(
2224 self.layerPage[self.selLayer]['statement'])
2225 sqlWin.SetValue("SELECT * FROM %s" %
2226 self.dbMgrData['mapDBInfo'].layers[layer]['table'])
2227
2228 self.UpdatePage(layer)
2229
2230
2231class DbMgrTablesPage(DbMgrNotebookBase):
2232
2233 def __init__(self, parent, parentDbMgrBase, onlyLayer=-1):
2234 """Page for managing tables
2235
2236 :param parent: GUI parent
2237 :param parentDbMgrBase: instance of DbMgrBase class
2238 :param onlyLayer: create only tab of given layer, if -1
2239 creates tabs of all layers
2240 """
2241
2242 DbMgrNotebookBase.__init__(self, parent=parent,
2243 parentDbMgrBase=parentDbMgrBase)
2244
2245 for layer in self.dbMgrData['mapDBInfo'].layers.keys():
2246 if onlyLayer > 0 and layer != onlyLayer:
2247 continue
2248 self.AddLayer(layer)
2249
2250 if self.layers:
2251 self.SetSelection(0) # select first layer
2252 self.selLayer = self.layers[0]
2253
2254 def AddLayer(self, layer, pos=-1):
2255 """Adds tab which represents table
2256
2257 :param layer: vector map layer connected to table
2258 :param pos: position of tab, if -1 it is added to end
2259
2260 :return: True if layer was added
2261 :return: False if layer was not added - layer has been already added or does not exist
2262 """
2263 if layer in self.layers or \
2264 layer not in self.parentDbMgrBase.GetVectorLayers():
2265 return False
2266
2267 self.layers.append(layer)
2268
2269 self.layerPage[layer] = {}
2270 panel = wx.Panel(parent=self, id=wx.ID_ANY)
2271 self.layerPage[layer]['tablePage'] = panel.GetId()
2272 label = _("Table")
2273 if not self.dbMgrData['editable']:
2274 label += _(" (readonly)")
2275
2276 if pos == -1:
2277 pos = self.GetPageCount()
2278 self.InsertPage(
2279 pos, page=panel, text=" %d / %s %s" %
2280 (layer, label, self.dbMgrData['mapDBInfo'].layers[layer]['table']))
2281
2282 pageSizer = wx.BoxSizer(wx.VERTICAL)
2283
2284 #
2285 # dbInfo
2286 #
2287 dbBox = StaticBox(parent=panel, id=wx.ID_ANY,
2288 label=" %s " % _("Database connection"))
2289 dbSizer = wx.StaticBoxSizer(dbBox, wx.VERTICAL)
2290 dbSizer.Add(
2291 CreateDbInfoDesc(
2292 panel,
2293 self.dbMgrData['mapDBInfo'],
2294 layer),
2295 proportion=1,
2296 flag=wx.EXPAND | wx.ALL,
2297 border=3)
2298
2299 #
2300 # table description
2301 #
2302 table = self.dbMgrData['mapDBInfo'].layers[layer]['table']
2303 tableBox = StaticBox(
2304 parent=panel,
2305 id=wx.ID_ANY,
2306 label=" %s " %
2307 _("Table <%s> - right-click to delete column(s)") %
2308 table)
2309
2310 tableSizer = wx.StaticBoxSizer(tableBox, wx.VERTICAL)
2311
2312 tlist = self._createTableDesc(panel, table)
2313 tlist.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnTableRightUp) # wxMSW
2314 tlist.Bind(wx.EVT_RIGHT_UP, self.OnTableRightUp) # wxGTK
2315 self.layerPage[layer]['tableData'] = tlist.GetId()
2316
2317 # manage columns (add)
2318 addBox = StaticBox(parent=panel, id=wx.ID_ANY,
2319 label=" %s " % _("Add column"))
2320 addSizer = wx.StaticBoxSizer(addBox, wx.HORIZONTAL)
2321
2322 column = TextCtrl(parent=panel, id=wx.ID_ANY, value='',
2323 size=(150, -1), style=wx.TE_PROCESS_ENTER)
2324 column.Bind(wx.EVT_TEXT, self.OnTableAddColumnName)
2325 column.Bind(wx.EVT_TEXT_ENTER, self.OnTableItemAdd)
2326 self.layerPage[layer]['addColName'] = column.GetId()
2327 addSizer.Add(
2328 StaticText(
2329 parent=panel,
2330 id=wx.ID_ANY,
2331 label=_("Column")),
2332 flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
2333 border=5)
2334 addSizer.Add(column, proportion=1,
2335 flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
2336 border=5)
2337
2338 ctype = wx.Choice(parent=panel, id=wx.ID_ANY,
2339 choices=["integer",
2340 "double",
2341 "varchar",
2342 "date"]) # FIXME
2343 ctype.SetSelection(0)
2344 ctype.Bind(wx.EVT_CHOICE, self.OnTableChangeType)
2345 self.layerPage[layer]['addColType'] = ctype.GetId()
2346 addSizer.Add(
2347 StaticText(
2348 parent=panel,
2349 id=wx.ID_ANY,
2350 label=_("Type")),
2351 flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
2352 border=5)
2353 addSizer.Add(ctype,
2354 flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
2355 border=5)
2356
2357 length = SpinCtrl(parent=panel, id=wx.ID_ANY, size=(65, -1),
2358 initial=250,
2359 min=1, max=1e6)
2360 length.Enable(False)
2361 self.layerPage[layer]['addColLength'] = length.GetId()
2362 addSizer.Add(
2363 StaticText(
2364 parent=panel,
2365 id=wx.ID_ANY,
2366 label=_("Length")),
2367 flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
2368 border=5)
2369 addSizer.Add(length,
2370 flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
2371 border=5)
2372
2373 btnAddCol = Button(parent=panel, id=wx.ID_ANY, label=_("Add"))
2374 btnAddCol.Bind(wx.EVT_BUTTON, self.OnTableItemAdd)
2375 btnAddCol.Enable(False)
2376 self.layerPage[layer]['addColButton'] = btnAddCol.GetId()
2377 addSizer.Add(btnAddCol, flag=wx.ALL | wx.ALIGN_RIGHT | wx.EXPAND,
2378 border=3)
2379
2380 # manage columns (rename)
2381 renameBox = StaticBox(parent=panel, id=wx.ID_ANY,
2382 label=" %s " % _("Rename column"))
2383 renameSizer = wx.StaticBoxSizer(renameBox, wx.HORIZONTAL)
2384
2385 columnFrom = wx.ComboBox(
2386 parent=panel, id=wx.ID_ANY, size=(150, -1),
2387 style=wx.CB_SIMPLE | wx.CB_READONLY,
2388 choices=self.dbMgrData['mapDBInfo'].GetColumns(table))
2389 columnFrom.SetSelection(0)
2390 self.layerPage[layer]['renameCol'] = columnFrom.GetId()
2391 renameSizer.Add(
2392 StaticText(
2393 parent=panel,
2394 id=wx.ID_ANY,
2395 label=_("Column")),
2396 flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
2397 border=5)
2398 renameSizer.Add(columnFrom, proportion=1,
2399 flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
2400 border=5)
2401
2402 columnTo = TextCtrl(parent=panel, id=wx.ID_ANY, value='',
2403 size=(150, -1), style=wx.TE_PROCESS_ENTER)
2404 columnTo.Bind(wx.EVT_TEXT, self.OnTableRenameColumnName)
2405 columnTo.Bind(wx.EVT_TEXT_ENTER, self.OnTableItemChange)
2406 self.layerPage[layer]['renameColTo'] = columnTo.GetId()
2407 renameSizer.Add(
2408 StaticText(
2409 parent=panel,
2410 id=wx.ID_ANY,
2411 label=_("To")),
2412 flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
2413 border=5)
2414 renameSizer.Add(columnTo, proportion=1,
2415 flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
2416 border=5)
2417
2418 btnRenameCol = Button(
2419 parent=panel,
2420 id=wx.ID_ANY,
2421 label=_("&Rename"))
2422 btnRenameCol.Bind(wx.EVT_BUTTON, self.OnTableItemChange)
2423 btnRenameCol.Enable(False)
2424 self.layerPage[layer]['renameColButton'] = btnRenameCol.GetId()
2425 renameSizer.Add(
2426 btnRenameCol,
2427 flag=wx.ALL | wx.ALIGN_RIGHT | wx.EXPAND,
2428 border=3)
2429
2430 tableSizer.Add(tlist,
2431 flag=wx.ALL | wx.EXPAND,
2432 proportion=1,
2433 border=3)
2434
2435 pageSizer.Add(dbSizer,
2436 flag=wx.ALL | wx.EXPAND,
2437 proportion=0,
2438 border=3)
2439
2440 pageSizer.Add(tableSizer,
2441 flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND,
2442 proportion=1,
2443 border=3)
2444
2445 pageSizer.Add(addSizer,
2446 flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND,
2447 proportion=0,
2448 border=3)
2449 pageSizer.Add(renameSizer,
2450 flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND,
2451 proportion=0,
2452 border=3)
2453
2454 panel.SetSizer(pageSizer)
2455
2456 if not self.dbMgrData['editable']:
2457 for widget in [columnTo, columnFrom, length, ctype,
2458 column, btnAddCol, btnRenameCol]:
2459 widget.Enable(False)
2460
2461 return True
2462
2463 def _createTableDesc(self, parent, table):
2464 """Create list with table description"""
2465 tlist = TableListCtrl(
2466 parent=parent,
2467 id=wx.ID_ANY,
2468 table=self.dbMgrData['mapDBInfo'].tables[table],
2469 columns=self.dbMgrData['mapDBInfo'].GetColumns(table))
2470 tlist.Populate()
2471 # sorter
2472 # itemDataMap = list.Populate()
2473 # listmix.ColumnSorterMixin.__init__(self, 2)
2474
2475 return tlist
2476
2477 def OnTableChangeType(self, event):
2478 """Data type for new column changed. Enable or disable
2479 data length widget"""
2480 win = self.FindWindowById(
2481 self.layerPage[
2482 self.selLayer]['addColLength'])
2483 if event.GetString() == "varchar":
2484 win.Enable(True)
2485 else:
2486 win.Enable(False)
2487
2488 def OnTableRenameColumnName(self, event):
2489 """Editing column name to be added to the table"""
2490 btn = self.FindWindowById(
2491 self.layerPage[
2492 self.selLayer]['renameColButton'])
2493 col = self.FindWindowById(self.layerPage[self.selLayer]['renameCol'])
2494 colTo = self.FindWindowById(
2495 self.layerPage[
2496 self.selLayer]['renameColTo'])
2497 if len(col.GetValue()) > 0 and len(colTo.GetValue()) > 0:
2498 btn.Enable(True)
2499 else:
2500 btn.Enable(False)
2501
2502 event.Skip()
2503
2504 def OnTableAddColumnName(self, event):
2505 """Editing column name to be added to the table"""
2506 btn = self.FindWindowById(
2507 self.layerPage[
2508 self.selLayer]['addColButton'])
2509 if len(event.GetString()) > 0:
2510 btn.Enable(True)
2511 else:
2512 btn.Enable(False)
2513
2514 event.Skip()
2515
2516 def OnTableItemChange(self, event):
2517 """Rename column in the table"""
2518 tlist = self.FindWindowById(self.layerPage[self.selLayer]['tableData'])
2519 name = self.FindWindowById(
2520 self.layerPage[
2521 self.selLayer]['renameCol']).GetValue()
2522 nameTo = self.FindWindowById(self.layerPage[self.selLayer][
2523 'renameColTo']).GetValue()
2524
2525 table = self.dbMgrData['mapDBInfo'].layers[self.selLayer]["table"]
2526
2527 if not name or not nameTo:
2528 GError(parent=self,
2529 message=_("Unable to rename column. "
2530 "No column name defined."))
2531 return
2532 else:
2533 item = tlist.FindItem(start=-1, str=name)
2534 if item > -1:
2535 if tlist.FindItem(start=-1, str=nameTo) > -1:
2536 GError(parent=self,
2537 message=_("Unable to rename column <%(column)s> to "
2538 "<%(columnTo)s>. Column already exists "
2539 "in the table <%(table)s>.") %
2540 {'column': name, 'columnTo': nameTo,
2541 'table': table})
2542 return
2543 else:
2544 tlist.SetItemText(item, nameTo)
2545
2546 self.listOfCommands.append(('v.db.renamecolumn',
2547 {'map': self.dbMgrData['vectName'],
2548 'layer': self.selLayer,
2549 'column': '%s,%s' % (name, nameTo)}
2550 ))
2551 else:
2552 GError(
2553 parent=self,
2554 message=_(
2555 "Unable to rename column. "
2556 "Column <%(column)s> doesn't exist in the table <%(table)s>.") % {
2557 'column': name,
2558 'table': table})
2559 return
2560
2561 # apply changes
2562 self.ApplyCommands(self.listOfCommands, self.listOfSQLStatements)
2563
2564 # update widgets
2565 self.FindWindowById(
2566 self.layerPage[
2567 self.selLayer]['renameCol']).SetItems(
2568 self.dbMgrData['mapDBInfo'].GetColumns(table))
2569 self.FindWindowById(self.layerPage[self.selLayer][
2570 'renameCol']).SetSelection(0)
2571 self.FindWindowById(self.layerPage[self.selLayer][
2572 'renameColTo']).SetValue('')
2573
2574 event.Skip()
2575
2576 def OnTableRightUp(self, event):
2577 """Table description area, context menu"""
2578 if not hasattr(self, "popupTableID"):
2579 self.popupTableID1 = wx.NewId()
2580 self.popupTableID2 = wx.NewId()
2581 self.popupTableID3 = wx.NewId()
2582 self.Bind(
2583 wx.EVT_MENU,
2584 self.OnTableItemDelete,
2585 id=self.popupTableID1)
2586 self.Bind(
2587 wx.EVT_MENU,
2588 self.OnTableItemDeleteAll,
2589 id=self.popupTableID2)
2590 self.Bind(wx.EVT_MENU, self.OnTableReload, id=self.popupTableID3)
2591
2592 # generate popup-menu
2593 menu = Menu()
2594 menu.Append(self.popupTableID1, _("Drop selected column"))
2595 if self.FindWindowById(self.layerPage[self.selLayer][
2596 'tableData']).GetFirstSelected() == -1:
2597 menu.Enable(self.popupTableID1, False)
2598 menu.Append(self.popupTableID2, _("Drop all columns"))
2599 menu.AppendSeparator()
2600 menu.Append(self.popupTableID3, _("Reload"))
2601
2602 if not self.dbMgrData['editable']:
2603 menu.Enable(self.popupTableID1, False)
2604 menu.Enable(self.popupTableID2, False)
2605
2606 self.PopupMenu(menu)
2607 menu.Destroy()
2608
2609 def OnTableItemDelete(self, event):
2610 """Delete selected item(s) from the list"""
2611 tlist = self.FindWindowById(self.layerPage[self.selLayer]['tableData'])
2612
2613 item = tlist.GetFirstSelected()
2614 countSelected = tlist.GetSelectedItemCount()
2615 if UserSettings.Get(
2616 group='atm', key='askOnDeleteRec', subkey='enabled'):
2617 # if the user select more columns to delete, all the columns name
2618 # will appear the the warning dialog
2619 if tlist.GetSelectedItemCount() > 1:
2620 deleteColumns = "columns '%s'" % tlist.GetItemText(item)
2621 while item != -1:
2622 item = tlist.GetNextSelected(item)
2623 if item != -1:
2624 deleteColumns += ", '%s'" % tlist.GetItemText(item)
2625 else:
2626 deleteColumns = "column '%s'" % tlist.GetItemText(item)
2627 deleteDialog = wx.MessageBox(
2628 parent=self,
2629 message=_(
2630 "Selected %s will PERMANENTLY removed "
2631 "from table. Do you want to drop the column?") %
2632 (deleteColumns),
2633 caption=_("Drop column(s)"),
2634 style=wx.YES_NO | wx.CENTRE)
2635 if deleteDialog != wx.YES:
2636 return False
2637 item = tlist.GetFirstSelected()
2638 while item != -1:
2639 self.listOfCommands.append(('v.db.dropcolumn',
2640 {'map': self.dbMgrData['vectName'],
2641 'layer': self.selLayer,
2642 'column': tlist.GetItemText(item)}
2643 ))
2644 tlist.DeleteItem(item)
2645 item = tlist.GetFirstSelected()
2646
2647 # apply changes
2648 self.ApplyCommands(self.listOfCommands, self.listOfSQLStatements)
2649
2650 # update widgets
2651 table = self.dbMgrData['mapDBInfo'].layers[self.selLayer]['table']
2652 self.FindWindowById(
2653 self.layerPage[
2654 self.selLayer]['renameCol']).SetItems(
2655 self.dbMgrData['mapDBInfo'].GetColumns(table))
2656 self.FindWindowById(self.layerPage[self.selLayer][
2657 'renameCol']).SetSelection(0)
2658
2659 event.Skip()
2660
2661 def OnTableItemDeleteAll(self, event):
2662 """Delete all items from the list"""
2663 table = self.dbMgrData['mapDBInfo'].layers[self.selLayer]['table']
2664 cols = self.dbMgrData['mapDBInfo'].GetColumns(table)
2665 keyColumn = self.dbMgrData['mapDBInfo'].layers[self.selLayer]['key']
2666 if keyColumn in cols:
2667 cols.remove(keyColumn)
2668
2669 if UserSettings.Get(
2670 group='atm', key='askOnDeleteRec', subkey='enabled'):
2671 deleteDialog = wx.MessageBox(
2672 parent=self,
2673 message=_(
2674 "Selected columns\n%s\nwill PERMANENTLY removed "
2675 "from table. Do you want to drop the columns?") %
2676 ('\n'.join(cols)),
2677 caption=_("Drop column(s)"),
2678 style=wx.YES_NO | wx.CENTRE)
2679 if deleteDialog != wx.YES:
2680 return False
2681
2682 for col in cols:
2683 self.listOfCommands.append(('v.db.dropcolumn',
2684 {'map': self.dbMgrData['vectName'],
2685 'layer': self.selLayer,
2686 'column': col}
2687 ))
2688 self.FindWindowById(self.layerPage[self.selLayer][
2689 'tableData']).DeleteAllItems()
2690
2691 # apply changes
2692 self.ApplyCommands(self.listOfCommands, self.listOfSQLStatements)
2693
2694 # update widgets
2695 table = self.dbMgrData['mapDBInfo'].layers[self.selLayer]['table']
2696 self.FindWindowById(
2697 self.layerPage[
2698 self.selLayer]['renameCol']).SetItems(
2699 self.dbMgrData['mapDBInfo'].GetColumns(table))
2700 self.FindWindowById(self.layerPage[self.selLayer][
2701 'renameCol']).SetSelection(0)
2702
2703 event.Skip()
2704
2705 def OnTableReload(self, event=None):
2706 """Reload table description"""
2707 self.FindWindowById(
2708 self.layerPage[
2709 self.selLayer]['tableData']).Populate(
2710 update=True)
2711 self.listOfCommands = []
2712
2713 def OnTableItemAdd(self, event):
2714 """Add new column to the table"""
2715 name = self.FindWindowById(
2716 self.layerPage[
2717 self.selLayer]['addColName']).GetValue()
2718
2719 ctype = self.FindWindowById(self.layerPage[self.selLayer][
2720 'addColType']). GetStringSelection()
2721
2722 length = int(
2723 self.FindWindowById(
2724 self.layerPage[
2725 self.selLayer]['addColLength']). GetValue())
2726
2727 # add item to the list of table columns
2728 tlist = self.FindWindowById(self.layerPage[self.selLayer]['tableData'])
2729
2730 index = tlist.InsertStringItem(tlist.GetItemCount(), str(name))
2731 tlist.SetStringItem(index, 0, str(name))
2732 tlist.SetStringItem(index, 1, str(ctype))
2733 tlist.SetStringItem(index, 2, str(length))
2734
2735 self.AddColumn(name, ctype, length)
2736
2737 # update widgets
2738 table = self.dbMgrData['mapDBInfo'].layers[self.selLayer]['table']
2739 self.FindWindowById(self.layerPage[self.selLayer][
2740 'addColName']).SetValue('')
2741 self.FindWindowById(
2742 self.layerPage[
2743 self.selLayer]['renameCol']).SetItems(
2744 self.dbMgrData['mapDBInfo'].GetColumns(table))
2745 self.FindWindowById(self.layerPage[self.selLayer][
2746 'renameCol']).SetSelection(0)
2747
2748 event.Skip()
2749
2750 def UpdatePage(self, layer):
2751
2752 if layer in self.layerPage.keys():
2753 table = self.dbMgrData['mapDBInfo'].layers[layer]['table']
2754
2755 # update table description
2756 tlist = self.FindWindowById(self.layerPage[layer]['tableData'])
2757 tlist.Update(table=self.dbMgrData['mapDBInfo'].tables[table],
2758 columns=self.dbMgrData['mapDBInfo'].GetColumns(table))
2759 self.OnTableReload(None)
2760
2761
2762class DbMgrLayersPage(wx.Panel):
2763
2764 def __init__(self, parent, parentDbMgrBase):
2765 """Create layer manage page"""
2766 self.parentDbMgrBase = parentDbMgrBase
2767 self.dbMgrData = self.parentDbMgrBase.dbMgrData
2768
2769 wx.Panel.__init__(self, parent=parent)
2770 splitterWin = wx.SplitterWindow(parent=self, id=wx.ID_ANY)
2771 splitterWin.SetMinimumPaneSize(100)
2772
2773 #
2774 # list of layers
2775 #
2776 panelList = wx.Panel(parent=splitterWin, id=wx.ID_ANY)
2777
2778 panelListSizer = wx.BoxSizer(wx.VERTICAL)
2779 layerBox = StaticBox(parent=panelList, id=wx.ID_ANY,
2780 label=" %s " % _("List of layers"))
2781 layerSizer = wx.StaticBoxSizer(layerBox, wx.VERTICAL)
2782
2783 self.layerList = self._createLayerDesc(panelList)
2784 self.layerList.Bind(
2785 wx.EVT_COMMAND_RIGHT_CLICK,
2786 self.OnLayerRightUp) # wxMSW
2787 self.layerList.Bind(wx.EVT_RIGHT_UP, self.OnLayerRightUp) # wxGTK
2788
2789 layerSizer.Add(self.layerList,
2790 flag=wx.ALL | wx.EXPAND,
2791 proportion=1,
2792 border=3)
2793
2794 panelListSizer.Add(layerSizer,
2795 flag=wx.ALL | wx.EXPAND,
2796 proportion=1,
2797 border=3)
2798
2799 panelList.SetSizer(panelListSizer)
2800
2801 #
2802 # manage part
2803 #
2804 panelManage = wx.Panel(parent=splitterWin, id=wx.ID_ANY)
2805
2806 manageSizer = wx.BoxSizer(wx.VERTICAL)
2807
2808 self.manageLayerBook = LayerBook(parent=panelManage, id=wx.ID_ANY,
2809 parentDialog=self)
2810 if not self.dbMgrData['editable']:
2811 self.manageLayerBook.Enable(False)
2812
2813 manageSizer.Add(self.manageLayerBook,
2814 proportion=1,
2815 flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND,
2816 border=5)
2817
2818 panelSizer = wx.BoxSizer(wx.VERTICAL)
2819 panelSizer.Add(splitterWin,
2820 proportion=1,
2821 flag=wx.EXPAND)
2822
2823 panelManage.SetSizer(manageSizer)
2824 splitterWin.SplitHorizontally(panelList, panelManage, 100)
2825 splitterWin.Fit()
2826 self.SetSizer(panelSizer)
2827
2828 def _createLayerDesc(self, parent):
2829 """Create list of linked layers"""
2830 tlist = LayerListCtrl(parent=parent, id=wx.ID_ANY,
2831 layers=self.dbMgrData['mapDBInfo'].layers)
2832
2833 tlist.Populate()
2834 # sorter
2835 # itemDataMap = list.Populate()
2836 # listmix.ColumnSorterMixin.__init__(self, 2)
2837
2838 return tlist
2839
2840 def UpdatePage(self):
2841 #
2842 # 'manage layers' page
2843 #
2844 # update list of layers
2845
2846 #self.dbMgrData['mapDBInfo'] = VectorDBInfo(self.dbMgrData['vectName'])
2847
2848 self.layerList.Update(self.dbMgrData['mapDBInfo'].layers)
2849 self.layerList.Populate(update=True)
2850 # update selected widgets
2851 listOfLayers = list(map(str, self.dbMgrData['mapDBInfo'].layers.keys()))
2852 # delete layer page
2853 self.manageLayerBook.deleteLayer.SetItems(listOfLayers)
2854 if len(listOfLayers) > 0:
2855 self.manageLayerBook.deleteLayer.SetStringSelection(
2856 listOfLayers
2857 [0])
2858 tableName = self.dbMgrData['mapDBInfo'].layers[int(listOfLayers[0])][
2859 'table']
2860 maxLayer = max(self.dbMgrData['mapDBInfo'].layers.keys())
2861 else:
2862 tableName = ''
2863 maxLayer = 0
2864 self.manageLayerBook.deleteTable.SetLabel(
2865 _('Drop also linked attribute table (%s)') %
2866 tableName)
2867 # add layer page
2868 self.manageLayerBook.addLayerWidgets['layer'][1].SetValue(
2869 maxLayer + 1)
2870 # modify layer
2871 self.manageLayerBook.modifyLayerWidgets[
2872 'layer'][1].SetItems(listOfLayers)
2873 self.manageLayerBook.OnChangeLayer(event=None)
2874
2875 def OnLayerRightUp(self, event):
2876 """Layer description area, context menu"""
2877 pass
2878
2879
2880class TableListCtrl(ListCtrl,
2881 listmix.ListCtrlAutoWidthMixin):
2882 # listmix.TextEditMixin):
2883 """Table description list"""
2884
2885 def __init__(self, parent, id, table, columns, pos=wx.DefaultPosition,
2886 size=wx.DefaultSize):
2887
2888 self.parent = parent
2889 self.table = table
2890 self.columns = columns
2891 ListCtrl.__init__(self, parent, id, pos, size,
2892 style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES |
2893 wx.BORDER_NONE)
2894
2895 listmix.ListCtrlAutoWidthMixin.__init__(self)
2896 # listmix.TextEditMixin.__init__(self)
2897
2898 def Update(self, table, columns):
2899 """Update column description"""
2900 self.table = table
2901 self.columns = columns
2902
2903 def Populate(self, update=False):
2904 """Populate the list"""
2905 itemData = {} # requested by sorter
2906
2907 if not update:
2908 headings = [_("Column name"), _("Data type"), _("Data length")]
2909 i = 0
2910 for h in headings:
2911 self.InsertColumn(col=i, heading=h)
2912 i += 1
2913 self.SetColumnWidth(col=0, width=350)
2914 self.SetColumnWidth(col=1, width=175)
2915 else:
2916 self.DeleteAllItems()
2917
2918 i = 0
2919 for column in self.columns:
2920 index = self.InsertStringItem(i, str(column))
2921 self.SetStringItem(index, 0, str(column))
2922 self.SetStringItem(index, 1, str(self.table[column]['type']))
2923 self.SetStringItem(index, 2, str(self.table[column]['length']))
2924 self.SetItemData(index, i)
2925 itemData[i] = (str(column),
2926 str(self.table[column]['type']),
2927 int(self.table[column]['length']))
2928 i = i + 1
2929
2930 self.SendSizeEvent()
2931
2932 return itemData
2933
2934
2935class LayerListCtrl(ListCtrl,
2936 listmix.ListCtrlAutoWidthMixin):
2937 # listmix.ColumnSorterMixin):
2938 # listmix.TextEditMixin):
2939 """Layer description list"""
2940
2941 def __init__(self, parent, id, layers,
2942 pos=wx.DefaultPosition,
2943 size=wx.DefaultSize):
2944
2945 self.parent = parent
2946 self.layers = layers
2947 ListCtrl.__init__(self, parent, id, pos, size,
2948 style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES |
2949 wx.BORDER_NONE)
2950
2951 listmix.ListCtrlAutoWidthMixin.__init__(self)
2952 # listmix.TextEditMixin.__init__(self)
2953
2954 def Update(self, layers):
2955 """Update description"""
2956 self.layers = layers
2957
2958 def Populate(self, update=False):
2959 """Populate the list"""
2960 itemData = {} # requested by sorter
2961
2962 if not update:
2963 headings = [
2964 _("Layer"),
2965 _("Driver"),
2966 _("Database"),
2967 _("Table"),
2968 _("Key")]
2969 i = 0
2970 for h in headings:
2971 self.InsertColumn(col=i, heading=h)
2972 i += 1
2973 else:
2974 self.DeleteAllItems()
2975
2976 i = 0
2977 for layer in self.layers.keys():
2978 index = self.InsertStringItem(i, str(layer))
2979 self.SetStringItem(index, 0, str(layer))
2980 database = str(self.layers[layer]['database'])
2981 driver = str(self.layers[layer]['driver'])
2982 table = str(self.layers[layer]['table'])
2983 key = str(self.layers[layer]['key'])
2984 self.SetStringItem(index, 1, driver)
2985 self.SetStringItem(index, 2, database)
2986 self.SetStringItem(index, 3, table)
2987 self.SetStringItem(index, 4, key)
2988 self.SetItemData(index, i)
2989 itemData[i] = (str(layer),
2990 driver,
2991 database,
2992 table,
2993 key)
2994 i += 1
2995
2996 for i in range(self.GetColumnCount()):
2997 self.SetColumnWidth(col=i, width=wx.LIST_AUTOSIZE)
2998 if self.GetColumnWidth(col=i) < 60:
2999 self.SetColumnWidth(col=i, width=60)
3000
3001 self.SendSizeEvent()
3002
3003 return itemData
3004
3005
3006class LayerBook(wx.Notebook):
3007 """Manage layers (add, delete, modify)"""
3008
3009 def __init__(self, parent, id,
3010 parentDialog,
3011 style=wx.BK_DEFAULT):
3012 wx.Notebook.__init__(self, parent, id, style=style)
3013
3014 self.parent = parent
3015 self.parentDialog = parentDialog
3016 self.mapDBInfo = self.parentDialog.dbMgrData['mapDBInfo']
3017
3018 #
3019 # drivers
3020 #
3021 drivers = RunCommand('db.drivers',
3022 quiet=True,
3023 read=True,
3024 flags='p')
3025
3026 self.listOfDrivers = []
3027 for drv in drivers.splitlines():
3028 self.listOfDrivers.append(drv.strip())
3029
3030 #
3031 # get default values
3032 #
3033 self.defaultConnect = {}
3034 connect = RunCommand('db.connect',
3035 flags='p',
3036 read=True,
3037 quiet=True)
3038
3039 for line in connect.splitlines():
3040 item, value = line.split(':', 1)
3041 self.defaultConnect[item.strip()] = value.strip()
3042
3043 # really needed?
3044 # if len(self.defaultConnect['driver']) == 0 or \
3045 # len(self.defaultConnect['database']) == 0:
3046 # GWarning(parent = self.parent,
3047 # message = _("Unknown default DB connection. "
3048 # "Please define DB connection using db.connect module."))
3049
3050 self.defaultTables = self._getTables(self.defaultConnect['driver'],
3051 self.defaultConnect['database'])
3052 try:
3053 self.defaultColumns = self._getColumns(
3054 self.defaultConnect['driver'],
3055 self.defaultConnect['database'],
3056 self.defaultTables[0])
3057 except IndexError:
3058 self.defaultColumns = []
3059
3060 self._createAddPage()
3061 self._createDeletePage()
3062 self._createModifyPage()
3063
3064 def _createAddPage(self):
3065 """Add new layer"""
3066 self.addPanel = wx.Panel(parent=self, id=wx.ID_ANY)
3067 self.AddPage(page=self.addPanel, text=_("Add layer"))
3068
3069 try:
3070 maxLayer = max(self.mapDBInfo.layers.keys())
3071 except ValueError:
3072 maxLayer = 0
3073
3074 # layer description
3075
3076 layerBox = StaticBox(parent=self.addPanel, id=wx.ID_ANY,
3077 label=" %s " % (_("Layer description")))
3078 layerSizer = wx.StaticBoxSizer(layerBox, wx.VERTICAL)
3079
3080 #
3081 # list of layer widgets (label, value)
3082 #
3083 self.addLayerWidgets = {'layer':
3084 (StaticText(parent=self.addPanel, id=wx.ID_ANY,
3085 label='%s:' % _("Layer")),
3086 SpinCtrl(parent=self.addPanel, id=wx.ID_ANY, size=(65, -1),
3087 initial=maxLayer + 1,
3088 min=1, max=1e6)),
3089 'driver':
3090 (StaticText(parent=self.addPanel, id=wx.ID_ANY,
3091 label='%s:' % _("Driver")),
3092 wx.Choice(parent=self.addPanel, id=wx.ID_ANY, size=(200, -1),
3093 choices=self.listOfDrivers)),
3094 'database':
3095 (StaticText(parent=self.addPanel, id=wx.ID_ANY,
3096 label='%s:' % _("Database")),
3097 TextCtrl(parent=self.addPanel, id=wx.ID_ANY,
3098 value='',
3099 style=wx.TE_PROCESS_ENTER)),
3100 'table':
3101 (StaticText(parent=self.addPanel, id=wx.ID_ANY,
3102 label='%s:' % _("Table")),
3103 wx.Choice(parent=self.addPanel, id=wx.ID_ANY, size=(200, -1),
3104 choices=self.defaultTables)),
3105 'key':
3106 (StaticText(parent=self.addPanel, id=wx.ID_ANY,
3107 label='%s:' % _("Key column")),
3108 wx.Choice(parent=self.addPanel, id=wx.ID_ANY, size=(200, -1),
3109 choices=self.defaultColumns)),
3110 'addCat':
3111 (CheckBox(parent=self.addPanel, id=wx.ID_ANY,
3112 label=_("Insert record for each category into table")),
3113 None),
3114 }
3115
3116 # set default values for widgets
3117 self.addLayerWidgets['driver'][1].SetStringSelection(
3118 self.defaultConnect['driver'])
3119 self.addLayerWidgets['database'][1].SetValue(
3120 self.defaultConnect['database'])
3121 self.addLayerWidgets['table'][1].SetSelection(0)
3122 self.addLayerWidgets['key'][1].SetSelection(0)
3123 self.addLayerWidgets['addCat'][0].SetValue(True)
3124 # events
3125 self.addLayerWidgets['driver'][1].Bind(
3126 wx.EVT_CHOICE, self.OnDriverChanged)
3127 self.addLayerWidgets['database'][1].Bind(
3128 wx.EVT_TEXT_ENTER, self.OnDatabaseChanged)
3129 self.addLayerWidgets['table'][1].Bind(
3130 wx.EVT_CHOICE, self.OnTableChanged)
3131
3132 # tooltips
3133 self.addLayerWidgets['addCat'][0].SetToolTip(
3134 _("You need to add categories " "by v.category module."))
3135
3136 # table description
3137 tableBox = StaticBox(parent=self.addPanel, id=wx.ID_ANY,
3138 label=" %s " % (_("Table description")))
3139 tableSizer = wx.StaticBoxSizer(tableBox, wx.VERTICAL)
3140
3141 #
3142 # list of table widgets
3143 #
3144 keyCol = UserSettings.Get(group='atm', key='keycolumn', subkey='value')
3145 self.tableWidgets = {'table': (StaticText(parent=self.addPanel, id=wx.ID_ANY,
3146 label='%s:' % _("Table name")),
3147 TextCtrl(parent=self.addPanel, id=wx.ID_ANY,
3148 value='',
3149 style=wx.TE_PROCESS_ENTER)),
3150 'key': (StaticText(parent=self.addPanel, id=wx.ID_ANY,
3151 label='%s:' % _("Key column")),
3152 TextCtrl(parent=self.addPanel, id=wx.ID_ANY,
3153 value=keyCol,
3154 style=wx.TE_PROCESS_ENTER))}
3155 # events
3156 self.tableWidgets['table'][1].Bind(
3157 wx.EVT_TEXT_ENTER, self.OnCreateTable)
3158 self.tableWidgets['key'][1].Bind(wx.EVT_TEXT_ENTER, self.OnCreateTable)
3159
3160 btnTable = Button(self.addPanel, wx.ID_ANY, _("&Create table"),
3161 size=(125, -1))
3162 btnTable.Bind(wx.EVT_BUTTON, self.OnCreateTable)
3163
3164 btnLayer = Button(self.addPanel, wx.ID_ANY, _("&Add layer"),
3165 size=(125, -1))
3166 btnLayer.Bind(wx.EVT_BUTTON, self.OnAddLayer)
3167
3168 btnDefault = Button(self.addPanel, wx.ID_ANY, _("&Set default"),
3169 size=(125, -1))
3170 btnDefault.Bind(wx.EVT_BUTTON, self.OnSetDefault)
3171
3172 # do layout
3173
3174 pageSizer = wx.BoxSizer(wx.HORIZONTAL)
3175
3176 # data area
3177 dataSizer = wx.GridBagSizer(hgap=5, vgap=5)
3178 row = 0
3179 for key in ('layer', 'driver', 'database', 'table', 'key', 'addCat'):
3180 label, value = self.addLayerWidgets[key]
3181 if not value:
3182 span = (1, 2)
3183 else:
3184 span = (1, 1)
3185 dataSizer.Add(label,
3186 flag=wx.ALIGN_CENTER_VERTICAL, pos=(row, 0),
3187 span=span)
3188
3189 if not value:
3190 row += 1
3191 continue
3192
3193 if key == 'layer':
3194 style = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT
3195 else:
3196 style = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND
3197
3198 dataSizer.Add(value,
3199 flag=style, pos=(row, 1))
3200
3201 row += 1
3202
3203 dataSizer.AddGrowableCol(1)
3204 layerSizer.Add(dataSizer,
3205 proportion=1,
3206 flag=wx.ALL | wx.EXPAND,
3207 border=5)
3208
3209 btnSizer = wx.BoxSizer(wx.HORIZONTAL)
3210 btnSizer.Add(btnDefault,
3211 proportion=0,
3212 flag=wx.ALL | wx.ALIGN_LEFT,
3213 border=5)
3214
3215 btnSizer.Add((5, 5),
3216 proportion=1,
3217 flag=wx.ALL | wx.EXPAND,
3218 border=5)
3219
3220 btnSizer.Add(btnLayer,
3221 proportion=0,
3222 flag=wx.ALL | wx.ALIGN_RIGHT,
3223 border=5)
3224
3225 layerSizer.Add(btnSizer,
3226 proportion=0,
3227 flag=wx.ALL | wx.EXPAND,
3228 border=0)
3229
3230 # data area
3231 dataSizer = wx.FlexGridSizer(cols=2, hgap=5, vgap=5)
3232 dataSizer.AddGrowableCol(1)
3233 for key in ['table', 'key']:
3234 label, value = self.tableWidgets[key]
3235 dataSizer.Add(label,
3236 flag=wx.ALIGN_CENTER_VERTICAL)
3237 dataSizer.Add(value,
3238 flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND)
3239
3240 tableSizer.Add(dataSizer,
3241 proportion=1,
3242 flag=wx.ALL | wx.EXPAND,
3243 border=5)
3244
3245 tableSizer.Add(btnTable,
3246 proportion=0,
3247 flag=wx.ALL | wx.ALIGN_BOTTOM | wx.ALIGN_RIGHT,
3248 border=5)
3249
3250 pageSizer.Add(layerSizer,
3251 proportion=3,
3252 flag=wx.ALL | wx.EXPAND,
3253 border=3)
3254
3255 pageSizer.Add(tableSizer,
3256 proportion=2,
3257 flag=wx.TOP | wx.BOTTOM | wx.RIGHT | wx.EXPAND,
3258 border=3)
3259
3260 # SetVirtualSizeHints is deprecated and is
3261 # exactly the same as FitInside() in wxWidgets 2.9 and later
3262 getattr(layerSizer, 'FitInside',
3263 layerSizer.SetVirtualSizeHints)(self.addPanel)
3264
3265 self.addPanel.SetAutoLayout(True)
3266 self.addPanel.SetSizer(pageSizer)
3267 pageSizer.Fit(self.addPanel)
3268
3269 def _createDeletePage(self):
3270 """Delete layer"""
3271 self.deletePanel = wx.Panel(parent=self, id=wx.ID_ANY)
3272 self.AddPage(page=self.deletePanel, text=_("Remove layer"))
3273
3274 label = StaticText(parent=self.deletePanel, id=wx.ID_ANY,
3275 label='%s:' % _("Layer to remove"))
3276
3277 self.deleteLayer = wx.ComboBox(
3278 parent=self.deletePanel, id=wx.ID_ANY, size=(100, -1),
3279 style=wx.CB_SIMPLE | wx.CB_READONLY,
3280 choices=list(map(str, self.mapDBInfo.layers.keys())))
3281 self.deleteLayer.SetSelection(0)
3282 self.deleteLayer.Bind(wx.EVT_COMBOBOX, self.OnChangeLayer)
3283
3284 try:
3285 tableName = self.mapDBInfo.layers[
3286 int(self.deleteLayer.GetStringSelection())]['table']
3287 except ValueError:
3288 tableName = ''
3289
3290 self.deleteTable = CheckBox(
3291 parent=self.deletePanel,
3292 id=wx.ID_ANY,
3293 label=_('Drop also linked attribute table (%s)') %
3294 tableName)
3295
3296 if tableName == '':
3297 self.deleteLayer.Enable(False)
3298 self.deleteTable.Enable(False)
3299
3300 btnDelete = Button(
3301 self.deletePanel, wx.ID_DELETE, _("&Remove layer"),
3302 size=(125, -1))
3303 btnDelete.Bind(wx.EVT_BUTTON, self.OnDeleteLayer)
3304
3305 #
3306 # do layout
3307 #
3308 pageSizer = wx.BoxSizer(wx.VERTICAL)
3309
3310 dataSizer = wx.BoxSizer(wx.VERTICAL)
3311
3312 flexSizer = wx.FlexGridSizer(cols=2, hgap=5, vgap=5)
3313
3314 flexSizer.Add(label,
3315 flag=wx.ALIGN_CENTER_VERTICAL)
3316 flexSizer.Add(self.deleteLayer,
3317 flag=wx.ALIGN_CENTER_VERTICAL)
3318
3319 dataSizer.Add(flexSizer,
3320 proportion=0,
3321 flag=wx.ALL | wx.EXPAND,
3322 border=1)
3323
3324 dataSizer.Add(self.deleteTable,
3325 proportion=0,
3326 flag=wx.ALL | wx.EXPAND,
3327 border=1)
3328
3329 pageSizer.Add(dataSizer,
3330 proportion=1,
3331 flag=wx.ALL | wx.EXPAND,
3332 border=5)
3333
3334 pageSizer.Add(btnDelete,
3335 proportion=0,
3336 flag=wx.ALL | wx.ALIGN_RIGHT,
3337 border=5)
3338
3339 self.deletePanel.SetSizer(pageSizer)
3340
3341 def _createModifyPage(self):
3342 """Modify layer"""
3343 self.modifyPanel = wx.Panel(parent=self, id=wx.ID_ANY)
3344 self.AddPage(page=self.modifyPanel, text=_("Modify layer"))
3345
3346 #
3347 # list of layer widgets (label, value)
3348 #
3349 self.modifyLayerWidgets = {'layer':
3350 (StaticText(parent=self.modifyPanel, id=wx.ID_ANY,
3351 label='%s:' % _("Layer")),
3352 wx.ComboBox(parent=self.modifyPanel, id=wx.ID_ANY,
3353 size=(100, -1),
3354 style=wx.CB_SIMPLE | wx.CB_READONLY,
3355 choices=list(map(str,
3356 self.mapDBInfo.layers.keys())))),
3357 'driver':
3358 (StaticText(parent=self.modifyPanel, id=wx.ID_ANY,
3359 label='%s:' % _("Driver")),
3360 wx.Choice(parent=self.modifyPanel, id=wx.ID_ANY,
3361 size=(200, -1),
3362 choices=self.listOfDrivers)),
3363 'database':
3364 (StaticText(parent=self.modifyPanel, id=wx.ID_ANY,
3365 label='%s:' % _("Database")),
3366 TextCtrl(parent=self.modifyPanel, id=wx.ID_ANY,
3367 value='', size=(350, -1),
3368 style=wx.TE_PROCESS_ENTER)),
3369 'table':
3370 (StaticText(parent=self.modifyPanel, id=wx.ID_ANY,
3371 label='%s:' % _("Table")),
3372 wx.Choice(parent=self.modifyPanel, id=wx.ID_ANY,
3373 size=(200, -1),
3374 choices=self.defaultTables)),
3375 'key':
3376 (StaticText(parent=self.modifyPanel, id=wx.ID_ANY,
3377 label='%s:' % _("Key column")),
3378 wx.Choice(parent=self.modifyPanel, id=wx.ID_ANY,
3379 size=(200, -1),
3380 choices=self.defaultColumns))}
3381
3382 # set default values for widgets
3383 self.modifyLayerWidgets['layer'][1].SetSelection(0)
3384 try:
3385 layer = int(self.modifyLayerWidgets[
3386 'layer'][1].GetStringSelection())
3387 except ValueError:
3388 layer = None
3389 for label in self.modifyLayerWidgets.keys():
3390 self.modifyLayerWidgets[label][1].Enable(False)
3391
3392 if layer:
3393 driver = self.mapDBInfo.layers[layer]['driver']
3394 database = self.mapDBInfo.layers[layer]['database']
3395 table = self.mapDBInfo.layers[layer]['table']
3396
3397 listOfColumns = self._getColumns(driver, database, table)
3398 self.modifyLayerWidgets['driver'][1].SetStringSelection(driver)
3399 self.modifyLayerWidgets['database'][1].SetValue(database)
3400 if table in self.modifyLayerWidgets['table'][1].GetItems():
3401 self.modifyLayerWidgets['table'][1].SetStringSelection(table)
3402 else:
3403 if self.defaultConnect['schema'] != '':
3404 # try with default schema
3405 table = self.defaultConnect['schema'] + table
3406 else:
3407 table = 'public.' + table # try with 'public' schema
3408 self.modifyLayerWidgets['table'][1].SetStringSelection(table)
3409 self.modifyLayerWidgets['key'][1].SetItems(listOfColumns)
3410 self.modifyLayerWidgets['key'][1].SetSelection(0)
3411
3412 # events
3413 self.modifyLayerWidgets['layer'][1].Bind(
3414 wx.EVT_COMBOBOX, self.OnChangeLayer)
3415 # self.modifyLayerWidgets['driver'][1].Bind(wx.EVT_CHOICE, self.OnDriverChanged)
3416 # self.modifyLayerWidgets['database'][1].Bind(wx.EVT_TEXT_ENTER, self.OnDatabaseChanged)
3417 # self.modifyLayerWidgets['table'][1].Bind(wx.EVT_CHOICE, self.OnTableChanged)
3418
3419 btnModify = Button(
3420 self.modifyPanel, wx.ID_DELETE, _("&Modify layer"),
3421 size=(125, -1))
3422 btnModify.Bind(wx.EVT_BUTTON, self.OnModifyLayer)
3423
3424 #
3425 # do layout
3426 #
3427 pageSizer = wx.BoxSizer(wx.VERTICAL)
3428
3429 # data area
3430 dataSizer = wx.FlexGridSizer(cols=2, hgap=5, vgap=5)
3431 dataSizer.AddGrowableCol(1)
3432 for key in ('layer', 'driver', 'database', 'table', 'key'):
3433 label, value = self.modifyLayerWidgets[key]
3434 dataSizer.Add(label,
3435 flag=wx.ALIGN_CENTER_VERTICAL)
3436 if key == 'layer':
3437 dataSizer.Add(value,
3438 flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
3439 else:
3440 dataSizer.Add(value,
3441 flag=wx.ALIGN_CENTER_VERTICAL)
3442
3443 pageSizer.Add(dataSizer,
3444 proportion=1,
3445 flag=wx.ALL | wx.EXPAND,
3446 border=5)
3447
3448 pageSizer.Add(btnModify,
3449 proportion=0,
3450 flag=wx.ALL | wx.ALIGN_RIGHT,
3451 border=5)
3452
3453 self.modifyPanel.SetSizer(pageSizer)
3454
3455 def _getTables(self, driver, database):
3456 """Get list of tables for given driver and database"""
3457 tables = []
3458
3459 ret = RunCommand('db.tables',
3460 parent=self,
3461 read=True,
3462 flags='p',
3463 driver=driver,
3464 database=database)
3465
3466 if ret is None:
3467 GError(
3468 parent=self, message=_(
3469 "Unable to get list of tables.\n"
3470 "Please use db.connect to set database parameters."))
3471
3472 return tables
3473
3474 for table in ret.splitlines():
3475 tables.append(table)
3476
3477 return tables
3478
3479 def _getColumns(self, driver, database, table):
3480 """Get list of column of given table"""
3481 columns = []
3482
3483 ret = RunCommand('db.columns',
3484 parent=self,
3485 quiet=True,
3486 read=True,
3487 driver=driver,
3488 database=database,
3489 table=table)
3490
3491 if ret is None:
3492 return columns
3493
3494 for column in ret.splitlines():
3495 columns.append(column)
3496
3497 return columns
3498
3499 def OnDriverChanged(self, event):
3500 """Driver selection changed, update list of tables"""
3501 driver = event.GetString()
3502 database = self.addLayerWidgets['database'][1].GetValue()
3503
3504 winTable = self.addLayerWidgets['table'][1]
3505 winKey = self.addLayerWidgets['key'][1]
3506 tables = self._getTables(driver, database)
3507
3508 winTable.SetItems(tables)
3509 winTable.SetSelection(0)
3510
3511 if len(tables) == 0:
3512 winKey.SetItems([])
3513
3514 event.Skip()
3515
3516 def OnDatabaseChanged(self, event):
3517 """Database selection changed, update list of tables"""
3518 event.Skip()
3519
3520 def OnTableChanged(self, event):
3521 """Table name changed, update list of columns"""
3522 driver = self.addLayerWidgets['driver'][1].GetStringSelection()
3523 database = self.addLayerWidgets['database'][1].GetValue()
3524 table = event.GetString()
3525
3526 win = self.addLayerWidgets['key'][1]
3527 cols = self._getColumns(driver, database, table)
3528 win.SetItems(cols)
3529 win.SetSelection(0)
3530
3531 event.Skip()
3532
3533 def OnSetDefault(self, event):
3534 """Set default values"""
3535 driver = self.addLayerWidgets['driver'][1]
3536 database = self.addLayerWidgets['database'][1]
3537 table = self.addLayerWidgets['table'][1]
3538 key = self.addLayerWidgets['key'][1]
3539
3540 driver.SetStringSelection(self.defaultConnect['driver'])
3541 database.SetValue(self.defaultConnect['database'])
3542 tables = self._getTables(self.defaultConnect['driver'],
3543 self.defaultConnect['database'])
3544 table.SetItems(tables)
3545 table.SetSelection(0)
3546 if len(tables) == 0:
3547 key.SetItems([])
3548 else:
3549 cols = self._getColumns(self.defaultConnect['driver'],
3550 self.defaultConnect['database'],
3551 tables[0])
3552 key.SetItems(cols)
3553 key.SetSelection(0)
3554
3555 event.Skip()
3556
3557 def OnCreateTable(self, event):
3558 """Create new table (name and key column given)"""
3559 driver = self.addLayerWidgets['driver'][1].GetStringSelection()
3560 database = self.addLayerWidgets['database'][1].GetValue()
3561 table = self.tableWidgets['table'][1].GetValue()
3562 key = self.tableWidgets['key'][1].GetValue()
3563
3564 if not table or not key:
3565 GError(parent=self,
3566 message=_("Unable to create new table. "
3567 "Table name or key column name is missing."))
3568 return
3569
3570 if table in self.addLayerWidgets['table'][1].GetItems():
3571 GError(
3572 parent=self, message=_(
3573 "Unable to create new table. "
3574 "Table <%s> already exists in the database.") %
3575 table)
3576 return
3577
3578 # create table
3579 sql = 'CREATE TABLE %s (%s INTEGER)' % (table, key)
3580
3581 RunCommand('db.execute',
3582 quiet=True,
3583 parent=self,
3584 stdin=sql,
3585 input='-',
3586 driver=driver,
3587 database=database)
3588
3589 # update list of tables
3590 tableList = self.addLayerWidgets['table'][1]
3591 tableList.SetItems(self._getTables(driver, database))
3592 tableList.SetStringSelection(table)
3593
3594 # update key column selection
3595 keyList = self.addLayerWidgets['key'][1]
3596 keyList.SetItems(self._getColumns(driver, database, table))
3597 keyList.SetStringSelection(key)
3598
3599 event.Skip()
3600
3601 def OnAddLayer(self, event):
3602 """Add new layer to vector map"""
3603 layer = int(self.addLayerWidgets['layer'][1].GetValue())
3604 layerWin = self.addLayerWidgets['layer'][1]
3605 driver = self.addLayerWidgets['driver'][1].GetStringSelection()
3606 database = self.addLayerWidgets['database'][1].GetValue()
3607 table = self.addLayerWidgets['table'][1].GetStringSelection()
3608 key = self.addLayerWidgets['key'][1].GetStringSelection()
3609
3610 if layer in self.mapDBInfo.layers.keys():
3611 GError(
3612 parent=self,
3613 message=_(
3614 "Unable to add new layer to vector map <%(vector)s>. "
3615 "Layer %(layer)d already exists.") %
3616 {'vector': self.mapDBInfo.map, 'layer': layer})
3617 return
3618
3619 # add new layer
3620 ret = RunCommand('v.db.connect',
3621 parent=self,
3622 quiet=True,
3623 map=self.mapDBInfo.map,
3624 driver=driver,
3625 database=database,
3626 table=table,
3627 key=key,
3628 layer=layer)
3629
3630 # insert records into table if required
3631 if self.addLayerWidgets['addCat'][0].IsChecked():
3632 RunCommand('v.to.db',
3633 parent=self,
3634 quiet=True,
3635 map=self.mapDBInfo.map,
3636 layer=layer,
3637 qlayer=layer,
3638 option='cat',
3639 columns=key)
3640
3641 if ret == 0:
3642 # update dialog (only for new layer)
3643 self.parentDialog.parentDbMgrBase.UpdateDialog(layer=layer)
3644 # update db info
3645 self.mapDBInfo = self.parentDialog.dbMgrData['mapDBInfo']
3646 # increase layer number
3647 layerWin.SetValue(layer + 1)
3648
3649 if len(self.mapDBInfo.layers.keys()) == 1:
3650 # first layer add --- enable previously disabled widgets
3651 self.deleteLayer.Enable()
3652 self.deleteTable.Enable()
3653 for label in self.modifyLayerWidgets.keys():
3654 self.modifyLayerWidgets[label][1].Enable()
3655
3656 def OnDeleteLayer(self, event):
3657 """Delete layer"""
3658 try:
3659 layer = int(self.deleteLayer.GetValue())
3660 except:
3661 return
3662
3663 RunCommand('v.db.connect',
3664 parent=self,
3665 flags='d',
3666 map=self.mapDBInfo.map,
3667 layer=layer)
3668
3669 # drop also table linked to layer which is deleted
3670 if self.deleteTable.IsChecked():
3671 driver = self.addLayerWidgets['driver'][1].GetStringSelection()
3672 database = self.addLayerWidgets['database'][1].GetValue()
3673 table = self.mapDBInfo.layers[layer]['table']
3674 sql = 'DROP TABLE %s' % (table)
3675
3676 RunCommand('db.execute',
3677 input='-',
3678 parent=self,
3679 stdin=sql,
3680 quiet=True,
3681 driver=driver,
3682 database=database)
3683
3684 # update list of tables
3685 tableList = self.addLayerWidgets['table'][1]
3686 tableList.SetItems(self._getTables(driver, database))
3687 tableList.SetStringSelection(table)
3688
3689 # update dialog
3690 self.parentDialog.parentDbMgrBase.UpdateDialog(layer=layer)
3691 # update db info
3692 self.mapDBInfo = self.parentDialog.dbMgrData['mapDBInfo']
3693
3694 if len(self.mapDBInfo.layers.keys()) == 0:
3695 # disable selected widgets
3696 self.deleteLayer.Enable(False)
3697 self.deleteTable.Enable(False)
3698 for label in self.modifyLayerWidgets.keys():
3699 self.modifyLayerWidgets[label][1].Enable(False)
3700
3701 event.Skip()
3702
3703 def OnChangeLayer(self, event):
3704 """Layer number of layer to be deleted is changed"""
3705 try:
3706 layer = int(event.GetString())
3707 except:
3708 try:
3709 layer = self.mapDBInfo.layers.keys()[0]
3710 except:
3711 return
3712
3713 if self.GetCurrentPage() == self.modifyPanel:
3714 driver = self.mapDBInfo.layers[layer]['driver']
3715 database = self.mapDBInfo.layers[layer]['database']
3716 table = self.mapDBInfo.layers[layer]['table']
3717 listOfColumns = self._getColumns(driver, database, table)
3718 self.modifyLayerWidgets['driver'][1].SetStringSelection(driver)
3719 self.modifyLayerWidgets['database'][1].SetValue(database)
3720 self.modifyLayerWidgets['table'][1].SetStringSelection(table)
3721 self.modifyLayerWidgets['key'][1].SetItems(listOfColumns)
3722 self.modifyLayerWidgets['key'][1].SetSelection(0)
3723 else:
3724 self.deleteTable.SetLabel(
3725 _('Drop also linked attribute table (%s)') %
3726 self.mapDBInfo.layers[layer]['table'])
3727 if event:
3728 event.Skip()
3729
3730 def OnModifyLayer(self, event):
3731 """Modify layer connection settings"""
3732
3733 layer = int(self.modifyLayerWidgets['layer'][1].GetStringSelection())
3734
3735 modify = False
3736 if self.modifyLayerWidgets['driver'][1].GetStringSelection() != \
3737 self.mapDBInfo.layers[layer]['driver'] or \
3738 self.modifyLayerWidgets['database'][1].GetStringSelection() != \
3739 self.mapDBInfo.layers[layer]['database'] or \
3740 self.modifyLayerWidgets['table'][1].GetStringSelection() != \
3741 self.mapDBInfo.layers[layer]['table'] or \
3742 self.modifyLayerWidgets['key'][1].GetStringSelection() != \
3743 self.mapDBInfo.layers[layer]['key']:
3744 modify = True
3745
3746 if modify:
3747 # delete layer
3748 RunCommand('v.db.connect',
3749 parent=self,
3750 quiet=True,
3751 flags='d',
3752 map=self.mapDBInfo.map,
3753 layer=layer)
3754
3755 # add modified layer
3756 RunCommand(
3757 'v.db.connect',
3758 quiet=True,
3759 map=self.mapDBInfo.map,
3760 driver=self.modifyLayerWidgets['driver'][1].GetStringSelection(),
3761 database=self.modifyLayerWidgets['database'][1].GetValue(),
3762 table=self.modifyLayerWidgets['table'][1].GetStringSelection(),
3763 key=self.modifyLayerWidgets['key'][1].GetStringSelection(),
3764 layer=int(layer))
3765
3766 # update dialog (only for new layer)
3767 self.parentDialog.parentDbMgrBase.UpdateDialog(layer=layer)
3768 # update db info
3769 self.mapDBInfo = self.parentDialog.dbMgrData['mapDBInfo']
3770
3771 event.Skip()
3772
3773
3774class FieldStatistics(wx.Frame):
3775
3776 def __init__(self, parent, id=wx.ID_ANY,
3777 style=wx.DEFAULT_FRAME_STYLE, **kwargs):
3778 """Dialog to display and save statistics of field stats
3779 """
3780 self.parent = parent
3781 wx.Frame.__init__(self, parent, id, style=style, **kwargs)
3782
3783 self.SetTitle(_("Field statistics"))
3784 self.SetIcon(
3785 wx.Icon(
3786 os.path.join(
3787 globalvar.ICONDIR,
3788 'grass_sql.ico'),
3789 wx.BITMAP_TYPE_ICO))
3790
3791 self.panel = wx.Panel(parent=self, id=wx.ID_ANY)
3792
3793 self.sp = scrolled.ScrolledPanel(parent=self.panel, id=wx.ID_ANY, size=(
3794 250, 150), style=wx.TAB_TRAVERSAL | wx.SUNKEN_BORDER, name="Statistics")
3795 self.text = TextCtrl(
3796 parent=self.sp,
3797 id=wx.ID_ANY,
3798 style=wx.TE_MULTILINE | wx.TE_READONLY)
3799 self.text.SetBackgroundColour("white")
3800
3801 # buttons
3802 self.btnClipboard = Button(parent=self.panel, id=wx.ID_COPY)
3803 self.btnClipboard.SetToolTip(
3804 _("Copy statistics the clipboard (Ctrl+C)"))
3805 self.btnCancel = Button(parent=self.panel, id=wx.ID_CLOSE)
3806 self.btnCancel.SetDefault()
3807
3808 # bindings
3809 self.btnCancel.Bind(wx.EVT_BUTTON, self.OnClose)
3810 self.btnClipboard.Bind(wx.EVT_BUTTON, self.OnCopy)
3811
3812 self._layout()
3813
3814 def _layout(self):
3815 sizer = wx.BoxSizer(wx.VERTICAL)
3816 txtSizer = wx.BoxSizer(wx.VERTICAL)
3817 btnSizer = wx.BoxSizer(wx.HORIZONTAL)
3818
3819 txtSizer.Add(self.text, proportion=1, flag=wx.EXPAND |
3820 wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)
3821
3822 self.sp.SetSizer(txtSizer)
3823 self.sp.SetAutoLayout(True)
3824 self.sp.SetupScrolling()
3825
3826 sizer.Add(self.sp, proportion=1, flag=wx.GROW |
3827 wx.LEFT | wx.RIGHT | wx.BOTTOM, border=3)
3828
3829 line = wx.StaticLine(parent=self.panel, id=wx.ID_ANY,
3830 size=(20, -1), style=wx.LI_HORIZONTAL)
3831 sizer.Add(line, proportion=0, flag=wx.GROW |
3832 wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT, border=3)
3833
3834 # buttons
3835 btnSizer.Add(self.btnClipboard, proportion=0,
3836 flag=wx.ALIGN_LEFT | wx.ALL, border=5)
3837 btnSizer.Add(self.btnCancel, proportion=0,
3838 flag=wx.ALIGN_RIGHT | wx.ALL, border=5)
3839 sizer.Add(
3840 btnSizer,
3841 proportion=0,
3842 flag=wx.ALIGN_RIGHT | wx.ALL,
3843 border=5)
3844
3845 self.panel.SetSizer(sizer)
3846 sizer.Fit(self.panel)
3847
3848 def OnCopy(self, event):
3849 """!Copy the statistics to the clipboard
3850 """
3851 stats = self.text.GetValue()
3852 rdata = wx.TextDataObject()
3853 rdata.SetText(stats)
3854
3855 if wx.TheClipboard.Open():
3856 wx.TheClipboard.SetData(rdata)
3857 wx.TheClipboard.Close()
3858
3859 def OnClose(self, event):
3860 """!Button 'Close' pressed
3861 """
3862 self.Close(True)
3863
3864 def Update(self, driver, database, table, column):
3865 """!Update statistics for given column
3866
3867 :param: column column name
3868 """
3869 if driver == 'dbf':
3870 GError(parent=self,
3871 message=_("Statistics is not support for DBF tables."))
3872 self.Close()
3873 return
3874
3875 fd, sqlFilePath = tempfile.mkstemp(text=True)
3876 sqlFile = open(sqlFilePath, 'w')
3877 stats = ['count', 'min', 'max', 'avg', 'sum', 'null']
3878 for fn in stats:
3879 if fn == 'null':
3880 sqlFile.write(
3881 'select count(*) from %s where %s is null;%s' %
3882 (table, column, os.linesep))
3883 else:
3884 sqlFile.write(
3885 'select %s(%s) from %s;%s' %
3886 (fn, column, table, os.linesep))
3887 sqlFile.close()
3888
3889 dataStr = RunCommand('db.select',
3890 parent=self.parent,
3891 read=True,
3892 flags='c',
3893 input=sqlFilePath,
3894 driver=driver,
3895 database=database)
3896 if not dataStr:
3897 GError(parent=self.parent,
3898 message=_("Unable to calculte statistics."))
3899 self.Close()
3900 return
3901
3902 dataLines = dataStr.splitlines()
3903 if len(dataLines) != len(stats):
3904 GError(
3905 parent=self.parent, message=_(
3906 "Unable to calculte statistics. "
3907 "Invalid number of lines %d (should be %d).") %
3908 (len(dataLines), len(stats)))
3909 self.Close()
3910 return
3911
3912 # calculate stddev
3913 avg = float(dataLines[stats.index('avg')])
3914 count = float(dataLines[stats.index('count')])
3915 sql = "select (%(column)s - %(avg)f)*(%(column)s - %(avg)f) from %(table)s" % {
3916 'column': column, 'avg': avg, 'table': table}
3917 dataVar = RunCommand('db.select',
3918 parent=self.parent,
3919 read=True,
3920 flags='c',
3921 sql=sql,
3922 driver=driver,
3923 database=database)
3924 if not dataVar:
3925 GWarning(parent=self.parent,
3926 message=_("Unable to calculte standard deviation."))
3927 varSum = 0
3928 for var in decode(dataVar).splitlines():
3929 varSum += float(var)
3930 stddev = math.sqrt(varSum / count)
3931
3932 self.SetTitle(_("Field statistics <%s>") % column)
3933 self.text.Clear()
3934 for idx in range(len(stats)):
3935 self.text.AppendText('%s: %s\n' % (stats[idx], dataLines[idx]))
3936 self.text.AppendText('stddev: %f\n' % stddev)
Note: See TracBrowser for help on using the repository browser.