1"""
2@package gui_core.gselect
3
4@brief Custom control that selects elements
5
6Classes:
7 - :class:`Select`
8 - :class:`VectorSelect`
9 - :class:`ListCtrlComboPopup`
10 - :class:`TreeCrtlComboPopup`
11 - :class:`VectorDBInfo`
12 - :class:`LayerSelect`
13 - :class:`DriverSelect`
14 - :class:`DatabaseSelect`
15 - :class:`TableSelect`
16 - :class:`ColumnSelect`
17 - :class:`DbaseSelect`
18 - :class:`LocationSelect`
19 - :class:`MapsetSelect`
20 - :class:`SubGroupSelect`
21 - :class:`FormatSelect`
22 - :class:`GdalSelect`
23 - :class:`ProjSelect`
24 - :class:`ElementSelect`
25 - :class:`OgrTypeSelect`
26 - :class:`CoordinatesSelect`
27 - :class:`VectorCategorySelect`
28 - :class:`SignatureSelect`
29 - :class:`SeparatorSelect`
30 - :class:`SqlWhereSelect`
31
32(C) 2007-2018 by the GRASS Development Team
33
34This program is free software under the GNU General Public License
35(>=v2). Read the file COPYING that comes with GRASS for details.
36
37@author Michael Barton
38@author Martin Landa <landa.martin gmail.com>
39@author Vaclav Petras <wenzeslaus gmail.com> (menu customization)
40@author Stepan Turek <stepan.turek seznam.cz> (CoordinatesSelect, ListCtrlComboPopup)
41@author Matej Krejci <matejkrejci gmail.com> (VectorCategorySelect)
42"""
43
44from __future__ import print_function
45
46import os
47import sys
48import glob
49import copy
50import six
51
52import wx
53
54from core import globalvar
55import wx.lib.buttons as buttons
56import wx.lib.filebrowsebutton as filebrowse
57
58
59
60import grass.script as grass
61from grass.script import task as gtask
62from grass.exceptions import CalledModuleError
63
64from gui_core.widgets import ManageSettingsWidget, CoordinatesValidator
65
66from core.gcmd import RunCommand, GError, GMessage, GWarning, GException
67from core.utils    import GetListOfLocations, GetListOfMapsets, \
68    GetFormats, rasterFormatExtension, vectorFormatExtension
69from core.utils import GetSettingsPath, GetValidLayerName, ListSortLower
70from core.utils import GetVectorNumberOfLayers
71from core.settings import UserSettings
72from core.debug import Debug
73from gui_core.vselect import VectorSelectBase
74from gui_core.wrap import TreeCtrl, Button, StaticText, StaticBox, \
75    TextCtrl, Panel, ComboPopup, ComboCtrl
76
77from grass.pydispatch.signal import Signal
78
79
80class Select(ComboCtrl):
81
82    def __init__(
83            self, parent, id=wx.ID_ANY, size=globalvar.DIALOG_GSELECT_SIZE,
84            type=None, multiple=False, nmaps=1, mapsets=None,
85            updateOnPopup=True, onPopup=None, fullyQualified=True,
86            extraItems={},
87            layerTree=None, validator=wx.DefaultValidator):
88        """Custom control to create a ComboBox with a tree control to
89        display and select GIS elements within acessible mapsets.
90        Elements can be selected with mouse. Can allow multiple
91        selections, when argument <em>multiple</em> is True. Multiple
92        selections are separated by commas.
93
94        :param type: type of GIS elements ('raster, 'vector', ...)
95        :param multiple: True for multiple input
96        :param nmaps: number of maps to be entered
97        :param mapsets: force list of mapsets (otherwise search path)
98        :param updateOnPopup: True for updating list of elements on popup
99        :param onPopup: function to be called on Popup
100        :param fullyQualified: True to provide fully qualified names (map@mapset)
101        :param extraItems: extra items to add (given as dictionary) - see gmodeler for usage
102        :param layerTree: show only elements from given layer tree if not None
103        :param validator: validator for TextCtrl
104        """
105        ComboCtrl.__init__(
106            self,
107            parent=parent,
108            id=id,
109            size=size,
110            validator=validator)
111        if globalvar.CheckWxVersion([3]):
112            self.SetName("Select")
113        else:
114            self.GetChildren()[0].SetName("Select")
115
116        self.GetChildren()[0].type = type
117
118        self.tcp = TreeCtrlComboPopup()
119        self.SetPopupControl(self.tcp)
120        self.SetPopupExtents(0, 100)
121        if type:
122            self.tcp.SetData(
123                type=type,
124                mapsets=mapsets,
125                multiple=multiple,
126                nmaps=nmaps,
127                updateOnPopup=updateOnPopup,
128                onPopup=onPopup,
129                fullyQualified=fullyQualified,
130                extraItems=extraItems,
131                layerTree=layerTree)
132        self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
133
134    def OnKeyDown(self, event):
135        """Open popup and send key events to the tree."""
136        if self.IsPopupShown():
137            # on some configurations we get key down, with some only key up
138            # so we are trying to catch either key up or down
139            # this mess shouldn't be necessary with wxPython 3
140            self.tcp.OnKeyUp(event)
141        else:
142            if event.GetKeyCode() == wx.WXK_DOWN:
143                self.ShowPopup()
144            event.Skip()
145
146    def SetElementList(self, type, mapsets=None):
147        """Set element list
148
149        :param type: GIS element type
150        :param mapsets: list of acceptable mapsets (None for all in search path)
151        """
152        self.tcp.SetData(type=type, mapsets=mapsets)
153
154    def GetElementList(self):
155        """Load elements"""
156        self.tcp.GetElementList()
157
158    def SetType(self, etype, multiple=False, nmaps=1,
159                mapsets=None, updateOnPopup=True, onPopup=None):
160        """Param set element type for widget
161
162        :param etype: element type, see gselect.ElementSelect
163        """
164        self.tcp.SetData(type=etype, mapsets=mapsets,
165                         multiple=multiple, nmaps=nmaps,
166                         updateOnPopup=updateOnPopup, onPopup=onPopup)
167
168
169class VectorSelect(Select):
170
171    def __init__(self, parent, ftype, **kwargs):
172        """Custom to create a ComboBox with a tree control to display and
173        select vector maps. You can filter the vector maps. If you
174        don't need this feature use Select class instead
175
176        :param ftype: filter vector maps based on feature type
177        """
178        Select.__init__(self, parent=parent, id=wx.ID_ANY,
179                        type='vector', **kwargs)
180
181        self.ftype = ftype
182
183        # remove vector maps which do not contain given feature type
184        self.tcp.SetFilter(self._isElement)
185
186    def _isElement(self, vectorName):
187        """Check if element should be filtered out"""
188        try:
189            if int(grass.vector_info_topo(vectorName)[self.ftype]) < 1:
190                return False
191        except KeyError:
192            return False
193
194        return True
195
196
197class ListCtrlComboPopup(ComboPopup):
198    """Create a list ComboBox using TreeCtrl with hidden root.
199
200    .. todo::
201
202        use event.EventObject instead of hardcoding (see forms.py)
203        https://groups.google.com/forum/#!topic/wxpython-users/pRz6bi0k0XY
204
205    """
206    # overridden ComboPopup methods
207
208    def Init(self):
209        self.value = []            # for multiple is False ->
210        # len(self.value) in [0,1]
211        self.curitem = None
212        self.multiple = False
213        self.updateOnPopup = True
214        self.filterItems = []      # limit items based on this list,
215        # see layerTree parameter
216
217    def Create(self, parent):
218        self.seltree = TreeCtrl(parent, style=wx.TR_HIDE_ROOT |
219                                wx.TR_HAS_BUTTONS |
220                                wx.TR_SINGLE |
221                                wx.TR_LINES_AT_ROOT |
222                                wx.SIMPLE_BORDER |
223                                wx.TR_FULL_ROW_HIGHLIGHT)
224        self.seltree.Bind(wx.EVT_MOTION, self.OnMotion)
225        self.seltree.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
226        # the following dummy handler are needed to keep tree events
227        # from propagating up to the parent GIS Manager layer tree
228        self.seltree.Bind(wx.EVT_TREE_ITEM_EXPANDING, lambda x: None)
229        self.seltree.Bind(wx.EVT_TREE_ITEM_COLLAPSED, lambda x: None)
230        self.seltree.Bind(wx.EVT_TREE_SEL_CHANGED, lambda x: None)
231        self.seltree.Bind(wx.EVT_TREE_DELETE_ITEM, lambda x: None)
232        self.seltree.Bind(wx.EVT_TREE_BEGIN_DRAG, lambda x: None)
233        self.seltree.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, lambda x: None)
234        # navigation in list/tree is handled automatically since wxPython 3
235        # for older versions, we have to workaround it and write our own
236        # navigation
237        if globalvar.CheckWxVersion(version=[3]):
238            self.seltree.Bind(
239                wx.EVT_TREE_ITEM_ACTIVATED,
240                self._onItemConfirmed)
241            self.seltree.Bind(wx.EVT_TREE_KEY_DOWN, self._onDismissPopup)
242        else:
243            self.seltree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, lambda x: None)
244            self.seltree.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
245
246        return True
247
248    def GetControl(self):
249        return self.seltree
250
251    def GetComboCtrl(self):
252        if globalvar.wxPythonPhoenix:
253            return super(ListCtrlComboPopup, self).GetComboCtrl()
254        else:
255            return self.GetCombo()
256
257    def GetStringValue(self):
258        """Get value as a string separated by commas
259        """
260        return ','.join(self.value)
261
262    def SetStringValue(self, value):
263        # this assumes that item strings are unique...
264        root = self.seltree.GetRootItem()
265        if not root:
266            return
267        winValue = self.GetComboCtrl().GetValue().strip(',')
268        self.value = []
269        if winValue:
270            self.value = winValue.split(',')
271
272    def OnPopup(self, force=False):
273        """Limited only for first selected
274        """
275        if not force and not self.updateOnPopup:
276            return
277
278        # selects map starting according to written text
279        inputText = self.GetComboCtrl().GetValue().strip()
280        if inputText:
281            root = self.seltree.GetRootItem()
282            match = self.FindItem(root, inputText, startLetters=True)
283            if match.IsOk():
284                self.seltree.EnsureVisible(match)
285                self.seltree.SelectItem(match)
286
287    def GetAdjustedSize(self, minWidth, prefHeight, maxHeight):
288        """Reads UserSettings to get height (which was 200 in old implementation).
289        """
290        height = UserSettings.Get(
291            group='appearance',
292            key='gSelectPopupHeight',
293            subkey='value')
294        return wx.Size(minWidth, min(height, maxHeight))
295
296    def FindItem(self, parentItem, text, startLetters=False):
297        """Finds item with given name or starting with given text
298        """
299        startletters = startLetters
300        item, cookie = self.seltree.GetFirstChild(parentItem)
301        while wx.TreeItemId.IsOk(item):
302            if self.seltree.GetItemText(item) == text:
303                return item
304            if self.seltree.ItemHasChildren(item):
305                item = self.FindItem(item, text, startLetters=startletters)
306                if wx.TreeItemId.IsOk(item):
307                    return item
308            elif startletters and self.seltree.GetItemText(item).startswith(text.split('@', 1)[0]):
309                return item
310            item, cookie = self.seltree.GetNextChild(parentItem, cookie)
311        return wx.TreeItemId()
312
313    def AddItem(self, value):
314        root = self.seltree.GetRootItem()
315        if not root:
316            root = self.seltree.AddRoot("<hidden root>")
317        self.seltree.AppendItem(root, text=value)
318
319    def SetItems(self, items):
320        root = self.seltree.GetRootItem()
321        if not root:
322            root = self.seltree.AddRoot("<hidden root>")
323        for item in items:
324            self.seltree.AppendItem(root, text=item)
325
326    def OnKeyUp(self, event):
327        """Enable to select items using keyboard.
328
329        Unused with wxPython 3, can be removed in the future.
330        """
331        item = self.seltree.GetSelection()
332        if event.GetKeyCode() == wx.WXK_DOWN:
333            self.seltree.SelectItem(self.seltree.GetNextVisible(item))
334
335        elif event.GetKeyCode() == wx.WXK_UP:
336            itemPrev = self.seltree.GetPrevSibling(item)
337            self.seltree.SelectItem(itemPrev)
338
339        elif event.GetKeyCode() == wx.WXK_ESCAPE:
340            self.Dismiss()
341
342        elif event.GetKeyCode() == wx.WXK_RETURN:
343            self.seltree.SelectItem(item)
344            self.curitem = item
345            self._selectTreeItem(item)
346            self.Dismiss()
347
348    def _onDismissPopup(self, event):
349        """Hide popup without selecting item on Esc"""
350        if event.GetKeyCode() == wx.WXK_ESCAPE:
351            self.Dismiss()
352        else:
353            event.Skip()
354
355    def _selectTreeItem(self, item):
356        item_str = self.seltree.GetItemText(item)
357        if self.multiple:
358            if item_str not in self.value:
359                self.value.append(item_str)
360        else:
361            self.value = [item_str]
362
363    def _onItemConfirmed(self, event):
364        item = event.GetItem()
365        self._selectTreeItem(item)
366        self.Dismiss()
367
368    def OnMotion(self, evt):
369        """Have the selection follow the mouse, like in a real combobox
370        """
371        item, flags = self.seltree.HitTest(evt.GetPosition())
372        if item and flags & wx.TREE_HITTEST_ONITEMLABEL:
373            self.seltree.SelectItem(item)
374            self.curitem = item
375        evt.Skip()
376
377    def OnLeftDown(self, evt):
378        """Do the combobox selection
379        """
380        if self.curitem is None:
381            return
382
383        self._selectTreeItem(self.curitem)
384        self.Dismiss()
385
386        evt.Skip()
387
388    def SetData(self, **kargs):
389        """Set object properties"""
390        if 'multiple' in kargs:
391            self.multiple = kargs['multiple']
392        if 'onPopup' in kargs:
393            self.onPopup = kargs['onPopup']
394        if kargs.get('layerTree', None):
395            self.filterItems = []  # reset
396            ltype = kargs['type']
397            for layer in kargs['layerTree'].GetVisibleLayers(
398                    skipDigitized=True):
399                if layer.GetType() != ltype:
400                    continue
401                self.filterItems.append(layer.GetName())
402
403    def DeleteAllItems(self):
404        """Delete all items in popup"""
405        self.seltree.DeleteAllItems()
406
407
408class TreeCtrlComboPopup(ListCtrlComboPopup):
409    """Create a tree ComboBox for selecting maps and other GIS elements
410    in accessible mapsets within the current location
411    """
412    # overridden ComboPopup methods
413
414    def Init(self):
415
416        ListCtrlComboPopup.Init(self)
417        self.nmaps = 1
418        self.type = None
419        self.mapsets = None
420        self.onPopup = None
421        self.fullyQualified = True
422        self.extraItems = dict()
423
424        self.SetFilter(None)
425        self.tgis_error = False
426
427    def SetFilter(self, filter):
428        """Set filter for GIS elements, see e.g. VectorSelect"""
429        self.filterElements = filter
430
431    def OnPopup(self, force=False):
432        """Limited only for first selected"""
433        if not force and not self.updateOnPopup:
434            return
435        if self.onPopup:
436            selected, exclude = self.onPopup(self.type)
437        else:
438            selected = None
439            exclude = False
440
441        self.GetElementList(selected, exclude)
442
443        ListCtrlComboPopup.OnPopup(self, force)
444
445    def GetElementList(self, elements=None, exclude=False):
446        """Get filtered list of GIS elements in accessible mapsets
447        and display as tree with all relevant elements displayed
448        beneath each mapset branch
449        """
450        # update list
451        self.seltree.DeleteAllItems()
452        if self.type:
453            self._getElementList(self.type, self.mapsets, elements, exclude)
454
455        if len(self.value) > 0:
456            root = self.seltree.GetRootItem()
457            if not root:
458                return
459            item = self.FindItem(root, self.value[0])
460            try:
461                self.seltree.EnsureVisible(item)
462                self.seltree.SelectItem(item)
463            except:
464                pass
465
466    def _getElementList(self, element, mapsets=None,
467                        elements=None, exclude=False):
468        """Get list of GIS elements in accessible mapsets and display as tree
469        with all relevant elements displayed beneath each mapset branch
470
471        :param element: GIS element
472        :param mapsets: list of acceptable mapsets (None for all mapsets in search path)
473        :param elements: list of forced GIS elements
474        :param exclude: True to exclude, False for forcing the list (elements)
475        """
476        # get current mapset
477        curr_mapset = grass.gisenv()['MAPSET']
478
479        # map element types to g.list types
480        elementdict = {'cell': 'raster',
481                       'raster': 'raster',
482                       'grid3': 'raster_3d',
483                       'raster_3d': 'raster_3d',
484                       'vector': 'vector',
485                       'paint/labels': 'label',
486                       'label': 'label',
487                       'windows': 'region',
488                       'region': 'region',
489                       'group': 'group',
490                       'stds': 'stds',
491                       'strds': 'strds',
492                       'str3ds': 'str3ds',
493                       'stvds': 'stvds'}
494
495        # to support multiple elements
496        element_list = element.split(',')
497        renamed_elements = []
498        for elem in element_list:
499            if elem not in elementdict:
500                self.AddItem(_('Not selectable element'), node=False)
501                return
502            else:
503                renamed_elements.append(elementdict[elem])
504
505        if element in ('stds', 'strds', 'str3ds', 'stvds'):
506            if not self.tgis_error:
507                import grass.temporal as tgis
508                filesdict = tgis.tlist_grouped(
509                    elementdict[element], element == 'stds')
510            else:
511                filesdict = None
512        else:
513            filesdict = grass.list_grouped(renamed_elements,
514                                           check_search_path=False)
515
516        # add extra items first
517        if self.extraItems:
518            for group, items in six.iteritems(self.extraItems):
519                node = self.AddItem(group, node=True)
520                self.seltree.SetItemTextColour(node, wx.Colour(50, 50, 200))
521                for item in items:
522                    self.AddItem(item, node=False, parent=node)
523                self.seltree.ExpandAllChildren(node)
524
525        # list of mapsets in current location
526        if mapsets is None:
527            mapsets = grass.mapsets(search_path=True)
528
529        # current mapset first
530        if curr_mapset in mapsets and mapsets[0] != curr_mapset:
531            mapsets.remove(curr_mapset)
532            mapsets.insert(0, curr_mapset)
533
534        first_mapset = None
535        for mapset in mapsets:
536            mapset_node = self.AddItem(
537                _('Mapset') + ': ' + mapset, node=True, mapset=mapset)
538            node = mapset_node
539            if not first_mapset:
540                first_mapset = mapset_node
541
542            self.seltree.SetItemTextColour(mapset_node, wx.Colour(50, 50, 200))
543            if mapset not in filesdict:
544                continue
545            try:
546                if isinstance(filesdict[mapset], dict):
547                    for elementType in filesdict[mapset].keys():
548                        node = self.AddItem(
549                            _('Type: ') + elementType,
550                            mapset=mapset,
551                            node=True,
552                            parent=mapset_node)
553                        self.seltree.SetItemTextColour(
554                            node, wx.Colour(50, 50, 200))
555                        elem_list = filesdict[mapset][elementType]
556                        self._addItems(
557                            elist=elem_list,
558                            elements=elements,
559                            mapset=mapset,
560                            exclude=exclude,
561                            node=node)
562                else:
563                    elem_list = filesdict[mapset]
564                    self._addItems(elist=elem_list, elements=elements,
565                                   mapset=mapset, exclude=exclude, node=node)
566            except Exception as e:
567                sys.stderr.write(_("GSelect: invalid item: %s") % e)
568                continue
569
570            if self.seltree.ItemHasChildren(mapset_node):
571                sel = UserSettings.Get(
572                    group='appearance',
573                    key='elementListExpand',
574                    subkey='selection')
575                collapse = True
576
577                if sel == 0:  # collapse all except PERMANENT and current
578                    if mapset in ('PERMANENT', curr_mapset):
579                        collapse = False
580                elif sel == 1:  # collapse all except PERMANENT
581                    if mapset == 'PERMANENT':
582                        collapse = False
583                elif sel == 2:  # collapse all except current
584                    if mapset == curr_mapset:
585                        collapse = False
586                elif sel == 3:  # collapse all
587                    pass
588                elif sel == 4:  # expand all
589                    collapse = False
590
591                if collapse:
592                    self.seltree.CollapseAllChildren(mapset_node)
593                else:
594                    self.seltree.ExpandAllChildren(mapset_node)
595
596        if first_mapset:
597            # select first mapset (MSW hack)
598            self.seltree.SelectItem(first_mapset)
599
600    # helpers
601    def _addItems(self, elist, elements, mapset, exclude, node):
602        """Helper function for adding multiple items (maps, stds).
603
604        :param list elist: list of map/stds names
605        :param list elements: list of forced elements
606        :param str mapset:  mapset name
607        :param exclude: True to exclude, False for forcing the list
608        :param node: parent node
609        """
610        elist = grass.natural_sort(elist)
611        for elem in elist:
612            if elem != '':
613                fullqElem = elem + '@' + mapset
614                if self.filterItems and fullqElem not in self.filterItems:
615                    continue  # skip items missed in self.filterItems
616
617                if elements is not None:
618                    if (exclude and fullqElem in elements) or \
619                            (not exclude and fullqElem not in elements):
620                        continue
621
622                if self.filterElements:
623                    if self.filterElements(fullqElem):
624                        self.AddItem(
625                            elem, mapset=mapset, node=False, parent=node)
626                else:
627                    self.AddItem(elem, mapset=mapset, node=False, parent=node)
628
629    def AddItem(self, value, mapset=None, node=True, parent=None):
630        if not parent:
631            root = self.seltree.GetRootItem()
632            if not root:
633                root = self.seltree.AddRoot("<hidden root>")
634            parent = root
635
636        data = {'node': node, 'mapset': mapset}
637
638        item = self.seltree.AppendItem(
639            parent, text=value, data=data)
640        return item
641
642    def OnKeyUp(self, event):
643        """Enables to select items using keyboard
644
645        Unused with wxPython 3, can be removed in the future.
646        """
647
648        item = self.seltree.GetSelection()
649        if event.GetKeyCode() == wx.WXK_DOWN:
650            self.seltree.SelectItem(self.seltree.GetNextVisible(item))
651
652        # problem with GetPrevVisible
653        elif event.GetKeyCode() == wx.WXK_UP:
654            if self.seltree.ItemHasChildren(item) and self.seltree.IsExpanded(
655                    self.seltree.GetPrevSibling(item)):
656                itemPrev = self.seltree.GetLastChild(
657                    self.seltree.GetPrevSibling(item))
658            else:
659                itemPrev = self.seltree.GetPrevSibling(item)
660                if not wx.TreeItemId.IsOk(itemPrev):
661                    itemPrev = self.seltree.GetItemParent(item)
662                    if item == self.seltree.GetFirstChild(
663                            self.seltree.GetRootItem())[0]:
664                        itemPrev = item
665            self.seltree.SelectItem(itemPrev)
666
667        # selects first item starting with the written text in next mapset
668        elif event.GetKeyCode() == wx.WXK_TAB:
669            selected = self.seltree.GetSelection()
670            if self.seltree.ItemHasChildren(selected):
671                parent = selected
672            else:
673                parent = self.seltree.GetItemParent(selected)
674            nextSibling = self.seltree.GetNextSibling(parent)
675            if wx.TreeItemId.IsOk(nextSibling):
676                match = self.FindItem(
677                    nextSibling, self.GetCombo().GetValue().strip(), True)
678            else:
679                match = self.FindItem(
680                    self.seltree.GetFirstChild(
681                        self.seltree.GetItemParent(parent))[0],
682                    self.GetCombo().GetValue().strip(),
683                    True)
684            self.seltree.SelectItem(match)
685
686        elif event.GetKeyCode() == wx.WXK_RIGHT:
687            if self.seltree.ItemHasChildren(item):
688                self.seltree.Expand(item)
689
690        elif event.GetKeyCode() == wx.WXK_LEFT:
691            if self.seltree.ItemHasChildren(item):
692                self.seltree.Collapse(item)
693
694        elif event.GetKeyCode() == wx.WXK_ESCAPE:
695            self.Dismiss()
696
697        elif event.GetKeyCode() == wx.WXK_RETURN:
698            if self.seltree.GetItemData(item)['node']:
699                self.value = []
700            else:
701                self._selectTreeItem(item)
702
703            self.Dismiss()
704
705    def OnLeftDown(self, evt):
706        """Do the combobox selection
707        """
708        item, flags = self.seltree.HitTest(evt.GetPosition())
709        if item and flags & wx.TREE_HITTEST_ONITEMLABEL:
710            self.curitem = item
711
712            if self.seltree.GetItemData(item)['node']:
713                evt.Skip()
714                return
715
716            self._selectTreeItem(item)
717            self.Dismiss()
718
719        evt.Skip()
720
721    def _selectTreeItem(self, item):
722        fullName = self.seltree.GetItemText(item)
723        if self.fullyQualified and self.seltree.GetItemData(item)['mapset']:
724            fullName += '@' + self.seltree.GetItemData(item)['mapset']
725
726        if self.multiple:
727            self.value.append(fullName)
728        else:
729            if self.nmaps > 1:  # see key_desc
730                if len(self.value) >= self.nmaps:
731                    self.value = [fullName]
732                else:
733                    self.value.append(fullName)
734            else:
735                self.value = [fullName]
736
737    def _onItemConfirmed(self, event):
738        item = event.GetItem()
739        if self.seltree.GetItemData(item)['node']:
740            self.value = []
741        else:
742            self._selectTreeItem(item)
743        self.Dismiss()
744
745    def SetData(self, **kargs):
746        """Set object properties"""
747        ListCtrlComboPopup.SetData(self, **kargs)
748        if 'type' in kargs:
749            self.type = kargs['type']
750            if self.type in ('stds', 'strds', 'str3ds', 'stvds'):
751                # Initiate the temporal framework. Catch database error
752                # and set the error flag for the stds listing.
753                try:
754                    import grass.temporal as tgis
755                    from grass.pygrass import messages
756                    try:
757                        tgis.init(True)
758                    except messages.FatalError as e:
759                        sys.stderr.write(_("Temporal GIS error:\n%s") % e)
760                        self.tgis_error = True
761                except ImportError as e:
762                    # PyGRASS (ctypes) is the likely cause
763                    sys.stderr.write(_(
764                        "Unable to import pyGRASS: %s\n"
765                        "Some functionality will be not accessible") % e)
766                    self.tgis_error = True
767        if 'mapsets' in kargs:
768            self.mapsets = kargs['mapsets']
769        if 'nmaps' in kargs:
770            self.nmaps = kargs['nmaps']
771        if 'updateOnPopup' in kargs:
772            self.updateOnPopup = kargs['updateOnPopup']
773        if 'fullyQualified' in kargs:
774            self.fullyQualified = kargs['fullyQualified']
775        if 'extraItems' in kargs:
776            self.extraItems = kargs['extraItems']
777
778    def GetType(self):
779        """Get element type
780        """
781        return self.type
782
783
784class VectorDBInfo:
785    """Class providing information about attribute tables
786    linked to a vector map"""
787
788    def __init__(self, map):
789        self.map = map
790
791        # dictionary of layer number and associated (driver, database, table)
792        self.layers = {}
793        # dictionary of table and associated columns (type, length, values,
794        # ids)
795        self.tables = {}
796
797        if not self._CheckDBConnection():  # -> self.layers
798            return
799
800        self._DescribeTables()  # -> self.tables
801
802    def _CheckDBConnection(self):
803        """Check DB connection"""
804        nuldev = open(os.devnull, 'w+')
805        # if map is not defined (happens with vnet initialization) or it
806        # doesn't exist
807        try:
808            self.layers = grass.vector_db(map=self.map, stderr=nuldev)
809        except CalledModuleError:
810            return False
811        finally:  # always close nuldev
812            nuldev.close()
813
814        return bool(len(self.layers.keys()) > 0)
815
816    def _DescribeTables(self):
817        """Describe linked tables"""
818        for layer in self.layers.keys():
819            # determine column names and types
820            table = self.layers[layer]["table"]
821            columns = {}  # {name: {type, length, [values], [ids]}}
822            i = 0
823            Debug.msg(
824                1,
825                "gselect.VectorDBInfo._DescribeTables(): table=%s driver=%s database=%s" %
826                (self.layers[layer]["table"],
827                 self.layers[layer]["driver"],
828                 self.layers[layer]["database"]))
829            for item in grass.db_describe(
830                    table=self.layers[layer]["table"],
831                    driver=self.layers[layer]["driver"],
832                    database=self.layers[layer]["database"])['cols']:
833                name, type, length = item
834                # FIXME: support more datatypes
835                if type.lower() == "integer":
836                    ctype = int
837                elif type.lower() == "double precision":
838                    ctype = float
839                else:
840                    ctype = str
841
842                columns[name.strip()] = {'index': i,
843                                         'type': type.lower(),
844                                         'ctype': ctype,
845                                         'length': int(length),
846                                         'values': [],
847                                         'ids': []}
848                i += 1
849
850            # check for key column
851            # v.db.connect -g/p returns always key column name lowercase
852            if self.layers[layer]["key"] not in columns.keys():
853                for col in columns.keys():
854                    if col.lower() == self.layers[layer]["key"]:
855                        self.layers[layer]["key"] = col.upper()
856                        break
857
858            self.tables[table] = columns
859
860        return True
861
862    def Reset(self):
863        """Reset"""
864        for layer in self.layers:
865            table = self.layers[layer]["table"]  # get table desc
866            for name in self.tables[table].keys():
867                self.tables[table][name]['values'] = []
868                self.tables[table][name]['ids'] = []
869
870    def GetName(self):
871        """Get vector name"""
872        return self.map
873
874    def GetKeyColumn(self, layer):
875        """Get key column of given layer
876
877        :param layer: vector layer number
878        """
879        return str(self.layers[layer]['key'])
880
881    def GetTable(self, layer):
882        """Get table name of given layer
883
884        :param layer: vector layer number
885        """
886        if layer not in self.layers:
887            raise GException(_("No table linked to layer <{}>.".format(layer)))
888        return self.layers[layer]['table']
889
890    def GetDbSettings(self, layer):
891        """Get database settins
892
893        :param layer: layer number
894
895        :return: (driver, database)
896        """
897        return self.layers[layer]['driver'], self.layers[layer]['database']
898
899    def GetTableDesc(self, table):
900        """Get table columns
901
902        :param table: table name
903        """
904        return self.tables[table]
905
906
907class LayerSelect(wx.ComboBox):
908
909    def __init__(self, parent, id=wx.ID_ANY,
910                 size=globalvar.DIALOG_COMBOBOX_SIZE,
911                 vector=None, dsn=None, choices=[], all=False, default=None):
912        """Creates combo box for selecting vector map layer names
913
914        :param str vector: vector map name (native or connected via v.external)
915        :param str dsn: OGR data source name
916        """
917        super(
918            LayerSelect,
919            self).__init__(
920            parent,
921            id,
922            size=size,
923            choices=choices)
924
925        self.all = all
926
927        self.SetName("LayerSelect")
928
929        # default value
930        self.default = default
931
932        self.InsertLayers(vector=vector, dsn=dsn)
933
934    def InsertLayers(self, vector=None, dsn=None):
935        """Insert layers for a vector into the layer combobox
936
937        :param str vector: vector map name (native or connected via v.external)
938        :param str dsn: OGR data source name
939        """
940        layers = list()
941
942        if vector:
943            layers = GetVectorNumberOfLayers(vector)
944        elif dsn:
945            ret = RunCommand('v.in.ogr',
946                             read=True,
947                             quiet=True,
948                             flags='l',
949                             input=dsn)
950            if ret:
951                layers = ret.splitlines()
952
953        if self.default:
954            if len(layers) == 0:
955                layers.insert(0, str(self.default))
956            elif self.default not in layers:
957                layers.append(self.default)
958
959        if self.all:
960            layers.insert(0, '-1')
961
962        if len(layers) > 0:
963            self.SetItems(layers)
964        else:
965            self.Clear()
966
967        self.SetValue("")
968
969        if self.default and self.default in layers:
970            self.SetValue(self.default)
971
972
973class DriverSelect(wx.ComboBox):
974    """Creates combo box for selecting database driver.
975    """
976
977    def __init__(self, parent, choices, value,
978                 id=wx.ID_ANY, pos=wx.DefaultPosition,
979                 size=globalvar.DIALOG_LAYER_SIZE, **kargs):
980
981        super(DriverSelect, self).__init__(parent, id, value, pos, size,
982                                           choices, style=wx.CB_READONLY)
983
984        self.SetName("DriverSelect")
985
986        self.SetStringSelection(value)
987
988
989class DatabaseSelect(TextCtrl):
990    """Creates combo box for selecting database driver.
991    """
992
993    def __init__(self, parent, value='', id=wx.ID_ANY,
994                 size=globalvar.DIALOG_TEXTCTRL_SIZE, **kargs):
995        super(
996            DatabaseSelect,
997            self).__init__(
998            parent,
999            id,
1000            value,
1001            size=size,
1002            **kargs)
1003        self.SetName("DatabaseSelect")
1004
1005
1006class TableSelect(wx.ComboBox):
1007    """Creates combo box for selecting attribute tables from the database
1008    """
1009
1010    def __init__(self, parent,
1011                 id=wx.ID_ANY, value='',
1012                 size=globalvar.DIALOG_COMBOBOX_SIZE, choices=[], **kargs):
1013        super(
1014            TableSelect,
1015            self).__init__(
1016            parent,
1017            id,
1018            value,
1019            size=size,
1020            choices=choices,
1021            style=wx.CB_READONLY,
1022            **kargs)
1023        self.SetName("TableSelect")
1024
1025        if not choices:
1026            self.InsertTables()
1027
1028    def InsertTables(self, driver=None, database=None):
1029        """Insert attribute tables into combobox"""
1030        items = []
1031
1032        if not driver or not database:
1033            connect = grass.db_connection()
1034
1035            driver = connect['driver']
1036            database = connect['database']
1037
1038        ret = RunCommand('db.tables',
1039                         flags='p',
1040                         read=True,
1041                         driver=driver,
1042                         database=database)
1043
1044        if ret:
1045            for table in ret.splitlines():
1046                items.append(table)
1047
1048        self.SetItems(items)
1049        self.SetValue('')
1050
1051
1052class ColumnSelect(ComboCtrl):
1053    """Creates combo box for selecting columns in the attribute table
1054    for a vector map.
1055
1056    :param parent: window parent
1057    :param id: window id
1058    :param value: default value
1059    :param size: window size
1060    :param str vector: vector map name
1061    :param layer: layer number
1062    :param multiple: True if it is possible to add multiple columns
1063    :param param: parameters list (see menuform.py)
1064    :param kwags: wx.ComboBox parameters
1065    """
1066
1067    def __init__(self, parent, id=wx.ID_ANY, value='',
1068                 size=globalvar.DIALOG_COMBOBOX_SIZE,
1069                 vector=None, layer=1, multiple=False,
1070                 param=None, **kwargs):
1071        self.defaultValue = value
1072        self.param = param
1073        self.columns = []
1074
1075        ComboCtrl.__init__(self, parent, id, size=size, **kwargs)
1076        self.GetChildren()[0].SetName("ColumnSelect")
1077        self.GetChildren()[0].type = type
1078
1079        self.tcp = ListCtrlComboPopup()
1080        self.SetPopupControl(self.tcp)
1081        self.tcp.SetData(multiple=multiple)
1082
1083        if vector:
1084            self.InsertColumns(vector, layer)
1085        self.GetChildren()[0].Bind(wx.EVT_KEY_UP, self.OnKeyUp)
1086
1087    def GetColumns(self):
1088        return self.columns
1089
1090    def OnKeyUp(self, event):
1091        """Shows popupwindow if down arrow key is released"""
1092        if event.GetKeyCode() == wx.WXK_DOWN and not self.IsPopupShown():
1093            self.ShowPopup()
1094        else:
1095            event.Skip()
1096
1097    def Clear(self):
1098        self.tcp.DeleteAllItems()
1099        self.SetValue('')
1100
1101    def InsertColumns(self, vector, layer, excludeKey=False,
1102                      excludeCols=None, type=None, dbInfo=None):
1103        """Insert columns for a vector attribute table into the columns combobox
1104
1105        :param str vector: vector name
1106        :param int layer: vector layer number
1107        :param excludeKey: exclude key column from the list?
1108        :param excludeCols: list of columns to be removed from the list
1109        :param type: only columns of given type (given as list)
1110        """
1111        if not dbInfo:
1112            dbInfo = VectorDBInfo(vector)
1113
1114        try:
1115            try:
1116                layer = int(layer)
1117            except TypeError:
1118                # assuming layer 1
1119                layer = 1
1120            table = dbInfo.GetTable(layer)
1121            columnchoices = dbInfo.GetTableDesc(table)
1122            keyColumn = dbInfo.GetKeyColumn(layer)
1123            self.columns = len(columnchoices.keys()) * ['']
1124            for key, val in six.iteritems(columnchoices):
1125                self.columns[val['index']] = key
1126            if excludeKey:  # exclude key column
1127                self.columns.remove(keyColumn)
1128            if excludeCols:  # exclude key column
1129                for key in six.iterkeys(columnchoices):
1130                    if key in excludeCols:
1131                        self.columns.remove(key)
1132            if type:  # only selected column types
1133                for key, value in six.iteritems(columnchoices):
1134                    if value['type'] not in type:
1135                        try:
1136                            self.columns.remove(key)
1137                        except ValueError:
1138                            pass
1139        except (KeyError, ValueError, GException):
1140            self.columns[:] = []
1141
1142        # update list
1143        self.tcp.DeleteAllItems()
1144        for col in self.columns:
1145            self.tcp.AddItem(col)
1146
1147        self.SetValue(self.defaultValue)
1148
1149        if self.param:
1150            value = self.param.get('value', '')
1151            if value != '' and value in self.columns:
1152                self.SetValue(value)
1153
1154    def InsertTableColumns(self, table, driver=None, database=None):
1155        """Insert table columns
1156
1157        :param str table: table name
1158        :param str driver: driver name
1159        :param str database: database name
1160        """
1161        self.columns[:] = []
1162
1163        ret = RunCommand('db.columns',
1164                         read=True,
1165                         driver=driver,
1166                         database=database,
1167                         table=table)
1168
1169        if ret:
1170            self.columns = ret.splitlines()
1171
1172        # update list
1173        self.tcp.DeleteAllItems()
1174        self.SetValue(self.defaultValue)
1175
1176        for col in self.columns:
1177            self.tcp.AddItem(col)
1178        if self.param:
1179            value = self.param.get('value', '')
1180            if value != '' and value in self.columns:
1181                self.SetValue(value)
1182
1183
1184class DbaseSelect(wx.lib.filebrowsebutton.DirBrowseButton):
1185    """Widget for selecting GRASS Database"""
1186
1187    def __init__(self, parent, **kwargs):
1188        super(
1189            DbaseSelect,
1190            self).__init__(
1191            parent,
1192            id=wx.ID_ANY,
1193            size=globalvar.DIALOG_GSELECT_SIZE,
1194            labelText='',
1195            dialogTitle=_('Choose GIS Data Directory'),
1196            buttonText=_('Browse'),
1197            startDirectory=grass.gisenv()['GISDBASE'],
1198            **kwargs)
1199
1200
1201class LocationSelect(wx.ComboBox):
1202    """Widget for selecting GRASS location"""
1203
1204    def __init__(
1205            self, parent, id=wx.ID_ANY, size=globalvar.DIALOG_COMBOBOX_SIZE,
1206            gisdbase=None, **kwargs):
1207        super(LocationSelect, self).__init__(parent, id, size=size, **kwargs)
1208        self.SetName("LocationSelect")
1209
1210        if not gisdbase:
1211            self.gisdbase = grass.gisenv()['GISDBASE']
1212        else:
1213            self.gisdbase = gisdbase
1214
1215        self.SetItems(GetListOfLocations(self.gisdbase))
1216
1217    def UpdateItems(self, dbase):
1218        """Update list of locations
1219
1220        :param str dbase: path to GIS database
1221        """
1222        self.gisdbase = dbase
1223        if dbase:
1224            self.SetItems(GetListOfLocations(self.gisdbase))
1225        else:
1226            self.SetItems([])
1227
1228
1229class MapsetSelect(wx.ComboBox):
1230    """Widget for selecting GRASS mapset"""
1231
1232    def __init__(self, parent, id=wx.ID_ANY,
1233                 size=globalvar.DIALOG_COMBOBOX_SIZE, gisdbase=None,
1234                 location=None, setItems=True, searchPath=False, new=False,
1235                 skipCurrent=False, multiple=False, **kwargs):
1236        style = 0
1237        # disabled, read-only widget has no TextCtrl children (TODO: rewrite)
1238        # if not new and not multiple:
1239        ###     style = wx.CB_READONLY
1240
1241        wx.ComboBox.__init__(self, parent, id, size=size,
1242                             style=style, **kwargs)
1243        self.searchPath = searchPath
1244        self.skipCurrent = skipCurrent
1245        self.SetName("MapsetSelect")
1246        self.value = ''
1247        self.multiple = multiple
1248        if not gisdbase:
1249            self.gisdbase = grass.gisenv()['GISDBASE']
1250        else:
1251            self.gisdbase = gisdbase
1252
1253        if not location:
1254            self.location = grass.gisenv()['LOCATION_NAME']
1255        else:
1256            self.location = location
1257
1258        if setItems:
1259            self.SetItems(self._getMapsets())
1260
1261        if self.multiple:
1262            self.Bind(wx.EVT_COMBOBOX, self._onSelection)
1263            self.Bind(wx.EVT_TEXT, self._onSelection)
1264
1265    def _onSelection(self, event):
1266        value = self.GetValue()
1267        if value:
1268            if self.value:
1269                self.value += ','
1270            self.value += value
1271            self.SetValue(self.value)
1272        else:
1273            self.value = value
1274        event.Skip()
1275
1276    def UpdateItems(self, location, dbase=None):
1277        """Update list of mapsets for given location
1278
1279        :param str dbase: path to GIS database (None to use currently
1280                          selected)
1281        :param str location: name of location
1282        """
1283        if dbase:
1284            self.gisdbase = dbase
1285        self.location = location
1286
1287        if location:
1288            self.SetItems(self._getMapsets())
1289        else:
1290            self.SetItems([])
1291
1292    def _getMapsets(self):
1293        if self.searchPath:
1294            mlist = RunCommand('g.mapsets',
1295                               read=True, flags='p',
1296                               sep='newline').splitlines()
1297        else:
1298            mlist = GetListOfMapsets(self.gisdbase, self.location,
1299                                     selectable=False)
1300
1301        gisenv = grass.gisenv()
1302        if self.skipCurrent and \
1303                gisenv['LOCATION_NAME'] == self.location and \
1304                gisenv['MAPSET'] in mlist:
1305            mlist.remove(gisenv['MAPSET'])
1306
1307        return mlist
1308
1309
1310class SubGroupSelect(wx.ComboBox):
1311    """Widget for selecting subgroups"""
1312
1313    def __init__(self, parent, id=wx.ID_ANY,
1314                 size=globalvar.DIALOG_GSELECT_SIZE, **kwargs):
1315        super(SubGroupSelect, self).__init__(parent, id, size=size,
1316                                             **kwargs)
1317        self.SetName("SubGroupSelect")
1318
1319    def Insert(self, group):
1320        """Insert subgroups for defined group"""
1321        if not group:
1322            return
1323        gisenv = grass.gisenv()
1324        try:
1325            name, mapset = group.split('@', 1)
1326        except ValueError:
1327            name = group
1328            mapset = gisenv['MAPSET']
1329
1330        mlist = RunCommand('i.group', group=group,
1331                           read=True, flags='sg').splitlines()
1332        try:
1333            self.SetItems(mlist)
1334        except OSError:
1335            self.SetItems([])
1336
1337
1338class FormatSelect(wx.Choice):
1339
1340    def __init__(self, parent, srcType, ogr=False,
1341                 size=globalvar.DIALOG_SPIN_SIZE,
1342                 **kwargs):
1343        """Widget for selecting external (GDAL/OGR) format
1344
1345        :param parent: parent window
1346        :param srcType: source type ('file', 'database', 'protocol')
1347        :param ogr: True for OGR otherwise GDAL
1348        """
1349        super(FormatSelect, self).__init__(parent, id=wx.ID_ANY, size=size,
1350                                           **kwargs)
1351        self.SetName("FormatSelect")
1352
1353        if ogr:
1354            ftype = 'ogr'
1355        else:
1356            ftype = 'gdal'
1357
1358        formats = list()
1359        for f in GetFormats()[ftype][srcType].values():
1360            formats += f
1361        self.SetItems(formats)
1362
1363    def GetExtension(self, name):
1364        """Get file extension by format name"""
1365        formatToExt = dict()
1366        formatToExt.update(rasterFormatExtension)
1367        formatToExt.update(vectorFormatExtension)
1368
1369        return formatToExt.get(name, '')
1370
1371
1372class GdalSelect(wx.Panel):
1373
1374    def __init__(self, parent, panel, ogr=False, link=False, dest=False,
1375                 exclude=None, settings=True):
1376        """Widget for selecting GDAL/OGR datasource, format
1377
1378        .. todo::
1379             Split into GdalSelect and OgrSelect and optionally to
1380             GdalSelectOutput, OgrSelectOutput
1381
1382        :param parent: parent window
1383        :param bool ogr: use OGR selector instead of GDAL
1384        :param bool dest: True for output (destination)
1385        :param default: default type (ignored when dest == True)
1386        :param exclude: list of types to be excluded
1387        """
1388        self.parent = parent
1389        self.ogr = ogr
1390        self.link = link
1391        self.dest = dest
1392        self._sourceType = None
1393
1394        wx.Panel.__init__(self, parent=panel, name='GdalSelect')
1395
1396        self.reloadDataRequired = Signal('GdalSelect.reloadDataRequired')
1397
1398        self.inputBox = StaticBox(parent=self)
1399        if dest:
1400            self.inputBox.SetLabel(" %s " % _("Output settings"))
1401        else:
1402            self.inputBox.SetLabel(" %s " % _("Source input"))
1403
1404        # source type
1405        sources = list()
1406        self.sourceMap = {'file': -1,
1407                          'dir': -1,
1408                          'db': -1,
1409                          'pro': -1,
1410                          'native': -1}
1411        idx = 0
1412        if dest:
1413            sources.append(_("Native"))
1414            self.sourceMap['native'] = idx
1415            idx += 1
1416        if exclude is None:
1417            exclude = []
1418        if 'file' not in exclude:
1419            sources.append(_("File"))
1420            self.sourceMap['file'] = idx
1421            idx += 1
1422        if 'directory' not in exclude:
1423            sources.append(_("Directory"))
1424            self.sourceMap['dir'] = idx
1425            idx += 1
1426        if 'database' not in exclude:
1427            sources.append(_("Database"))
1428            self.sourceMap['db'] = idx
1429            idx += 1
1430        if 'protocol' not in exclude:
1431            sources.append(_("Protocol"))
1432            self.sourceMap['pro'] = idx
1433            idx += 1
1434        self.sourceMapByIdx = {}
1435        for name, idx in self.sourceMap.items():
1436            self.sourceMapByIdx[idx] = name
1437
1438        self.source = wx.RadioBox(parent=self, id=wx.ID_ANY,
1439                                  style=wx.RA_SPECIFY_COLS,
1440                                  choices=sources)
1441        if dest:
1442            self.source.SetLabel(" %s " % _('Output type'))
1443        else:
1444            self.source.SetLabel(" %s " % _('Source type'))
1445
1446        self.source.SetSelection(0)
1447        self.source.Bind(
1448            wx.EVT_RADIOBOX,
1449            lambda evt: self.SetSourceType(
1450                self.sourceMapByIdx[
1451                    evt.GetInt()]))
1452
1453        self.nativeWidgets = {}
1454        self.fileWidgets = {}
1455        self.dirWidgets = {}
1456        self.dbWidgets = {}
1457        self.protocolWidgets = {}
1458        self.pgWidgets = {}
1459
1460        if ogr:
1461            fType = 'ogr'
1462        else:
1463            fType = 'gdal'
1464
1465        # file
1466        fileMask = '%(all)s (*)|*|' % {'all': _('All files')}
1467        if not ogr:
1468            extList = rasterFormatExtension
1469            fileMask += ('%(name)s (*.%(low1)s;*.%(low2)s;*.%(up1)s;*.%(up2)s)|'
1470                         '*.%(low1)s;*.%(low2)s;*.%(up1)s;*.%(up2)s|' %
1471                         {'name': 'GeoTIFF', 'low1': 'tif', 'low2': 'tiff', 'up1': 'TIF', 'up2': 'TIFF'})
1472        else:
1473            extList = vectorFormatExtension
1474            fileMask += '%(name)s (*.%(low)s;*.%(up)s)|*.%(low)s;*.%(up)s|' % {
1475                'name': 'ESRI Shapefile', 'low': 'shp', 'up': 'SHP'}
1476
1477        for name, ext in sorted(extList.items()):
1478            if name in ('ESRI Shapefile', 'GeoTIFF'):
1479                continue
1480            fileMask += '%(name)s (*.%(low)s;*.%(up)s)|*.%(low)s;*.%(up)s|' % {
1481                'name': name, 'low': ext.lower(), 'up': ext.upper()}
1482        fileMask += '%s (*.zip;*.ZIP)|*.zip;*.ZIP|' % _('ZIP files')
1483        fileMask += '%s (*.gz;*.GZ)|*.gz;*.GZ|' % _('GZIP files')
1484        fileMask += '%s (*.tar;*.TAR)|*.tar;*.TAR|' % _('TAR files')
1485        # don't include last '|' - windows and mac throw error
1486        fileMask += '%s (*.tar.gz;*.TAR.GZ;*.tgz;*.TGZ)|*.tar.gz;*.TAR.GZ;*.tgz;*.TGZ' % _('TARGZ files')
1487        # only contains formats with extensions hardcoded
1488
1489        self.filePanel = wx.Panel(parent=self)
1490        browse = filebrowse.FileBrowseButton(
1491            parent=self.filePanel,
1492            id=wx.ID_ANY,
1493            size=globalvar.DIALOG_GSELECT_SIZE,
1494            labelText=_('File:'),
1495            dialogTitle=_('Choose file to import'),
1496            buttonText=_('Browse'),
1497            startDirectory=os.getcwd(),
1498            changeCallback=self.OnUpdate,
1499            fileMask=fileMask)
1500        browse.GetChildren()[1].SetName('GdalSelectDataSource')
1501        self.fileWidgets['browse'] = browse
1502        self.fileWidgets['options'] = TextCtrl(parent=self.filePanel)
1503
1504        # directory
1505        self.dirPanel = wx.Panel(parent=self)
1506        browse = filebrowse.DirBrowseButton(
1507            parent=self.dirPanel,
1508            id=wx.ID_ANY,
1509            size=globalvar.DIALOG_GSELECT_SIZE,
1510            labelText=_('Directory:'),
1511            dialogTitle=_('Choose input directory'),
1512            buttonText=_('Browse'),
1513            startDirectory=os.getcwd(),
1514            changeCallback=self.OnUpdate)
1515        browse.GetChildren()[1].SetName('GdalSelectDataSource')
1516
1517        self.dirWidgets['browse'] = browse
1518        formatSelect = wx.Choice(parent=self.dirPanel)
1519        self.dirWidgets['format'] = formatSelect
1520        fileFormats = GetFormats(writableOnly=dest)[fType]['file']
1521        formatSelect.SetItems(sorted(list(fileFormats)))
1522        formatSelect.Bind(
1523            wx.EVT_CHOICE, lambda evt: self.SetExtension(
1524                self.dirWidgets['format'].GetStringSelection()))
1525        formatSelect.Bind(wx.EVT_CHOICE, self.OnUpdate)
1526
1527        self.dirWidgets['extensionLabel'] = StaticText(
1528            parent=self.dirPanel, label=_("Extension:"))
1529        self.dirWidgets['extension'] = TextCtrl(parent=self.dirPanel)
1530        self.dirWidgets['extension'].Bind(wx.EVT_TEXT, self.ExtensionChanged)
1531        self.dirWidgets['options'] = TextCtrl(parent=self.dirPanel)
1532        if self.ogr:
1533            shapefile = 'ESRI Shapefile'
1534            if shapefile in fileFormats:
1535                formatSelect.SetStringSelection(shapefile)
1536                self.SetExtension(shapefile)
1537        else:
1538            tiff = 'GeoTIFF'
1539            if tiff in fileFormats:
1540                formatSelect.SetStringSelection(tiff)
1541                self.SetExtension(tiff)
1542
1543        # database
1544        self.dbPanel = wx.Panel(parent=self)
1545        self.dbFormats = GetFormats(writableOnly=dest)[fType]['database']
1546        dbChoice = wx.Choice(parent=self.dbPanel, choices=self.dbFormats)
1547        dbChoice.Bind(
1548            wx.EVT_CHOICE,
1549            lambda evt: self.SetDatabase(
1550                db=dbChoice.GetStringSelection()))
1551        self.dbWidgets['format'] = dbChoice
1552
1553        browse = filebrowse.FileBrowseButton(
1554            parent=self.dbPanel,
1555            id=wx.ID_ANY,
1556            size=globalvar.DIALOG_GSELECT_SIZE,
1557            labelText=_("Name:"),
1558            dialogTitle=_('Choose file'),
1559            buttonText=_('Browse'),
1560            startDirectory=os.getcwd(),
1561            changeCallback=self.OnUpdate)
1562        browse.GetChildren()[1].SetName('GdalSelectDataSource')
1563
1564        self.dbWidgets['browse'] = browse
1565        self.dbWidgets['choice'] = wx.Choice(
1566            parent=self.dbPanel, name='GdalSelectDataSource')
1567        self.dbWidgets['choice'].Bind(wx.EVT_CHOICE, self.OnUpdate)
1568        self.dbWidgets['text'] = TextCtrl(
1569            parent=self.dbPanel, name='GdalSelectDataSource')
1570        self.dbWidgets['text'].Bind(wx.EVT_TEXT, self.OnUpdate)
1571        self.dbWidgets['textLabel1'] = StaticText(
1572            parent=self.dbPanel, label=_("Name:"))
1573        self.dbWidgets['textLabel2'] = StaticText(
1574            parent=self.dbPanel, label=_("Name:"))
1575        self.dbWidgets['featType'] = wx.RadioBox(
1576            parent=self.dbPanel,
1577            id=wx.ID_ANY,
1578            label=" %s " %
1579            _("Feature type:"),
1580            choices=[
1581                _("simple features"),
1582                _("topological")],
1583            majorDimension=2,
1584            style=wx.RA_SPECIFY_COLS)
1585        if dest:
1586            self.dbWidgets['featType'].Disable()
1587        else:
1588            self.dbWidgets['featType'].Hide()
1589        browse = filebrowse.DirBrowseButton(
1590            parent=self.dbPanel,
1591            id=wx.ID_ANY,
1592            size=globalvar.DIALOG_GSELECT_SIZE,
1593            labelText=_('Directory:'),
1594            dialogTitle=_('Choose input directory'),
1595            buttonText=_('Browse'),
1596            startDirectory=os.getcwd(),
1597            changeCallback=self.OnUpdate)
1598        self.dbWidgets['dirbrowse'] = browse
1599        self.dbWidgets['options'] = TextCtrl(parent=self.dbPanel)
1600
1601        # protocol
1602        self.protocolPanel = wx.Panel(parent=self)
1603        protocolFormats = GetFormats(writableOnly=self.dest)[fType]['protocol']
1604        protocolChoice = wx.Choice(
1605            parent=self.protocolPanel,
1606            choices=protocolFormats)
1607        self.protocolWidgets['format'] = protocolChoice
1608
1609        self.protocolWidgets['text'] = TextCtrl(parent=self.protocolPanel)
1610        self.protocolWidgets['text'].Bind(wx.EVT_TEXT, self.OnUpdate)
1611        self.protocolWidgets['options'] = TextCtrl(
1612            parent=self.protocolPanel)
1613
1614        # native
1615        self.nativePanel = wx.Panel(parent=self)
1616
1617        self._layout()
1618        sourceType = 'file'
1619        self.SetSourceType(sourceType)  # needed always to fit dialog size
1620        if self.dest:
1621            current = RunCommand('v.external.out',
1622                                 parent=self,
1623                                 read=True, parse=grass.parse_key_val,
1624                                 flags='g')
1625            if current['format'] == 'native':
1626                sourceType = 'native'
1627            elif current['format'] in GetFormats()['ogr']['database']:
1628                sourceType = 'db'
1629            else:
1630                sourceType = 'dir'
1631
1632        if self.dest:
1633            wx.CallAfter(self._postInit, sourceType, current)
1634
1635    def _postInit(self, sourceType, data):
1636        """Fill in default values."""
1637        format = data.get('format', '')
1638        pg = 'conninfo' in data.keys()
1639        if pg:
1640            dsn = ''
1641            for item in data.get('conninfo').split(' '):
1642                k, v = item.split('=')
1643                if k == 'dbname':
1644                    dsn = v
1645                    break
1646            optList = list()
1647            for k, v in six.iteritems(data):
1648                if k in ('format', 'conninfo', 'topology'):
1649                    continue
1650                optList.append('%s=%s' % (k, v))
1651            options = ','.join(optList)
1652        else:
1653            dsn = data.get('dsn')
1654            options = data.get('options', '')
1655
1656        self.SetSourceType(sourceType)
1657        self.source.SetSelection(self.sourceMap[sourceType])
1658
1659        # v.external.out does not return dsn for the native format
1660        if dsn:
1661            dsn = os.path.expandvars(dsn)  # v.external.out uses $HOME
1662            # fill in default values
1663            if sourceType == 'dir':
1664                self.dirWidgets['format'].SetStringSelection(format)
1665                self.dirWidgets['browse'].SetValue(dsn)
1666                self.dirWidgets['options'].SetValue(options)
1667            elif sourceType == 'db':
1668                self.dbWidgets['format'].SetStringSelection(format)
1669                self.dbWidgets['options'].SetValue(options)
1670                name = self._getCurrentDbWidgetName()
1671                if name == 'choice':
1672                    if dsn in self.dbWidgets[name].GetItems():
1673                        self.dbWidgets[name].SetStringSelection(dsn)
1674                    if 'topology' in data.keys():
1675                        self.dbWidgets['featType'].SetSelection(1)
1676                else:
1677                    self.dbWidgets[name].SetValue(dsn)
1678
1679    def _layout(self):
1680        """Layout"""
1681        self.mainSizer = wx.BoxSizer(wx.VERTICAL)
1682
1683        self.changingSizer = wx.StaticBoxSizer(self.inputBox, wx.VERTICAL)
1684
1685        # file
1686        paddingSizer = wx.BoxSizer(wx.VERTICAL)
1687        sizer = wx.GridBagSizer(vgap=5, hgap=10)
1688        paddingSizer.Add(self.fileWidgets['browse'],
1689                         flag=wx.BOTTOM | wx.EXPAND,
1690                         border=35)
1691        sizer.Add(paddingSizer, flag=wx.EXPAND, pos=(0, 0), span=(1, 2))
1692        sizer.AddGrowableCol(0)
1693        if self.dest:
1694            sizer.Add(StaticText(parent=self.filePanel,
1695                                 label=_("Creation options:")),
1696                      flag=wx.ALIGN_CENTER_VERTICAL,
1697                      pos=(1, 0))
1698            sizer.Add(self.fileWidgets['options'],
1699                      flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
1700                      pos=(1, 1))
1701
1702        else:
1703            self.fileWidgets['options'].Hide()
1704        self.filePanel.SetSizer(sizer)
1705
1706        # directory
1707        sizer = wx.GridBagSizer(vgap=3, hgap=10)
1708        sizer.Add(StaticText(parent=self.dirPanel,
1709                             label=_("Format:")),
1710                  flag=wx.ALIGN_CENTER_VERTICAL,
1711                  pos=(0, 0))
1712        sizer.Add(self.dirWidgets['format'],
1713                  flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
1714                  pos=(0, 1))
1715        sizer.Add(self.dirWidgets['extensionLabel'],
1716                  flag=wx.ALIGN_CENTER_VERTICAL,
1717                  pos=(0, 2))
1718        sizer.Add(self.dirWidgets['extension'],
1719                  flag=wx.ALIGN_CENTER_VERTICAL,
1720                  pos=(0, 3))
1721        sizer.Add(self.dirWidgets['browse'],
1722                  flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
1723                  pos=(1, 0), span=(1, 4))
1724        if self.dest:
1725            sizer.Add(StaticText(parent=self.dirPanel,
1726                                 label=_("Creation options:")),
1727                      flag=wx.ALIGN_CENTER_VERTICAL,
1728                      pos=(2, 0))
1729            sizer.Add(self.dirWidgets['options'],
1730                      flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
1731                      pos=(2, 1))
1732            helpBtn = Button(parent=self.dirPanel, id=wx.ID_HELP)
1733            helpBtn.Bind(wx.EVT_BUTTON, self.OnHelp)
1734            sizer.Add(helpBtn,
1735                      flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
1736                      pos=(2, 2))
1737
1738            self.dirWidgets['extensionLabel'].Hide()
1739            self.dirWidgets['extension'].Hide()
1740        else:
1741            self.dirWidgets['options'].Hide()
1742        sizer.AddGrowableCol(1)
1743        self.dirPanel.SetSizer(sizer)
1744
1745        # database
1746        sizer = wx.GridBagSizer(vgap=1, hgap=5)
1747        sizer.Add(StaticText(parent=self.dbPanel,
1748                             label=_("Format:")),
1749                  flag=wx.ALIGN_CENTER_VERTICAL,
1750                  pos=(0, 0))
1751        sizer.Add(self.dbWidgets['format'],
1752                  flag=wx.ALIGN_CENTER_VERTICAL,
1753                  pos=(0, 1))
1754        sizer.Add(self.dbWidgets['textLabel1'],
1755                  flag=wx.ALIGN_CENTER_VERTICAL,
1756                  pos=(1, 0))
1757        sizer.Add(self.dbWidgets['text'],
1758                  flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
1759                  pos=(1, 1), span=(1, 2))
1760        sizer.Add(self.dbWidgets['browse'],
1761                  flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
1762                  pos=(2, 0), span=(1, 3))
1763        sizer.Add(self.dbWidgets['dirbrowse'],
1764                  flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
1765                  pos=(3, 0), span=(1, 2))
1766        sizer.Add(self.dbWidgets['textLabel2'],
1767                  flag=wx.ALIGN_CENTER_VERTICAL,
1768                  pos=(4, 0))
1769        sizer.Add(self.dbWidgets['choice'],
1770                  flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
1771                  pos=(4, 1), span=(1, 2))
1772        if self.dest:
1773            sizer.Add(self.dbWidgets['featType'],
1774                      pos=(0, 2), flag=wx.EXPAND)
1775
1776            sizer.Add(StaticText(parent=self.dbPanel,
1777                                 label=_("Creation options:")),
1778                      flag=wx.ALIGN_CENTER_VERTICAL,
1779                      pos=(5, 0))
1780            sizer.Add(self.dbWidgets['options'],
1781                      flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
1782                      pos=(5, 1), span=(1, 2))
1783
1784            # help button
1785            helpBtn = Button(parent=self.dbPanel, id=wx.ID_HELP)
1786            helpBtn.Bind(wx.EVT_BUTTON, self.OnHelp)
1787            sizer.Add(helpBtn,
1788                      pos=(5, 3))
1789
1790        else:
1791            self.dbWidgets['options'].Hide()
1792
1793        self.dbPanel.SetSizer(sizer)
1794        sizer.SetEmptyCellSize((0, 0))
1795        sizer.AddGrowableCol(1)
1796
1797        # protocol
1798        sizer = wx.GridBagSizer(vgap=3, hgap=3)
1799        sizer.Add(StaticText(parent=self.protocolPanel,
1800                             label=_("Format:")),
1801                  flag=wx.ALIGN_CENTER_VERTICAL,
1802                  pos=(0, 0))
1803        sizer.Add(self.protocolWidgets['format'],
1804                  flag=wx.ALIGN_CENTER_VERTICAL,
1805                  pos=(0, 1))
1806        sizer.Add(StaticText(parent=self.protocolPanel,
1807                             label=_("Protocol:")),
1808                  flag=wx.ALIGN_CENTER_VERTICAL,
1809                  pos=(1, 0))
1810        sizer.Add(self.protocolWidgets['text'],
1811                  flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
1812                  pos=(1, 1))
1813        if self.dest:
1814            sizer.Add(StaticText(parent=self.protocolPanel,
1815                                 label=_("Creation options:")),
1816                      flag=wx.ALIGN_CENTER_VERTICAL,
1817                      pos=(2, 0))
1818            sizer.Add(self.protocolWidgets['options'],
1819                      flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
1820                      pos=(2, 1))
1821
1822        else:
1823            self.protocolWidgets['options'].Hide()
1824        sizer.AddGrowableCol(1)
1825        self.protocolPanel.SetSizer(sizer)
1826
1827        # native
1828        sizer = wx.BoxSizer(wx.VERTICAL)
1829        sizer.Add(StaticText(parent=self.nativePanel,
1830                             label=_("No settings available")),
1831                  flag=wx.ALL | wx.EXPAND, border=5)
1832        self.nativePanel.SetSizer(sizer)
1833
1834        for panel in (self.nativePanel, self.filePanel,
1835                      self.dirPanel, self.dbPanel,
1836                      self.protocolPanel):
1837
1838            self.changingSizer.Add(panel, proportion=1,
1839                                   flag=wx.EXPAND)
1840
1841        self.mainSizer.Add(self.source, proportion=0,
1842                           flag=wx.LEFT | wx.RIGHT | wx.EXPAND, border=5)
1843        self.mainSizer.Add(self.changingSizer, proportion=1,
1844                           flag=wx.ALL | wx.EXPAND, border=5)
1845        self.SetSizer(self.mainSizer)
1846        self.mainSizer.Fit(self)
1847
1848    def _getExtension(self, name):
1849        """Get file extension by format name"""
1850        formatToExt = dict()
1851        formatToExt.update(rasterFormatExtension)
1852        formatToExt.update(vectorFormatExtension)
1853
1854        return formatToExt.get(name, '')
1855
1856    def SetSourceType(self, sourceType):
1857        """Set source type (db, file, dir, ...).
1858        Does not switch radioboxes."""
1859        self._sourceType = sourceType
1860        self.changingSizer.Show(
1861            self.filePanel, show=(
1862            sourceType == 'file'))
1863        self.changingSizer.Show(
1864            self.nativePanel, show=(
1865            sourceType == 'native'))
1866        self.changingSizer.Show(self.dirPanel, show=(sourceType == 'dir'))
1867        self.changingSizer.Show(
1868            self.protocolPanel, show=(
1869            sourceType == 'pro'))
1870        self.changingSizer.Show(self.dbPanel, show=(sourceType == 'db'))
1871
1872        self.changingSizer.Layout()
1873
1874        if sourceType == 'db':
1875            self.dbWidgets['format'].SetItems(self.dbFormats)
1876            if self.dbFormats:
1877                if 'PostgreSQL' in self.dbFormats:
1878                    self.dbWidgets['format'].SetStringSelection('PostgreSQL')
1879                else:
1880                    self.dbWidgets['format'].SetSelection(0)
1881            self.dbWidgets['format'].Enable()
1882
1883        if sourceType == 'db':
1884            db = self.dbWidgets['format'].GetStringSelection()
1885            self.SetDatabase(db)
1886
1887        if not self.dest:
1888            self.reloadDataRequired.emit(listData=None, data=None)
1889            self._reloadLayers()
1890
1891    def OnSettingsChanged(self, data):
1892        """User changed setting"""
1893        # data list: [type, dsn, format, options]
1894        if len(data) == 3:
1895            data.append('')
1896        elif len(data) < 3:
1897            return
1898
1899        self.source.SetSelection(self.sourceMap[data[0]])
1900        self.SetSourceType(data[0])
1901        if data[0] == 'file':
1902            self.fileWidgets['browse'].SetValue(data[1])
1903            self.fileWidgets['options'].SetValue(data[3])
1904        elif data[0] == 'dir':
1905            self.dirWidgets['browse'].SetValue(data[1])
1906            self.dirWidgets['format'].SetStringSelection(data[2])
1907            self.dirWidgets['options'].SetValue(data[3])
1908            self.SetExtension(data[2])
1909        elif data[0] == 'pro':
1910            self.protocolWidgets['text'].SetValue(data[2])
1911            self.protocolWidgets['options'].SetValue(data[3])
1912        elif data[0] == 'db':
1913            name = self._getCurrentDbWidgetName()
1914            if name == 'choice':
1915                if len(data[1].split(':', 1)) > 1:
1916                    for item in data[1].split(':', 1)[1].split(','):
1917                        key, value = item.split('=', 1)
1918                        if key == 'dbname':
1919                            self.dbWidgets[name].SetStringSelection(value)
1920                            break
1921                else:
1922                    self.dbWidgets[name].SetStringSelection(data[1])
1923            else:
1924                self.dbWidgets[name].SetValue(data[1])
1925            self.dbWidgets['options'].SetValue(data[3])
1926
1927        if not self.dest:
1928            self.reloadDataRequired.emit(listData=None, data=None)
1929            self._reloadLayers()
1930
1931    def AttachSettings(self):
1932        if self.ogr:
1933            settingsFile = os.path.join(GetSettingsPath(), 'wxOGR')
1934        else:
1935            settingsFile = os.path.join(GetSettingsPath(), 'wxGDAL')
1936
1937        self.settsManager = ManageSettingsWidget(parent=self,
1938                                                 settingsFile=settingsFile)
1939        self.settsManager.settingsChanged.connect(self.OnSettingsChanged)
1940        self.settsManager.settingsSaving.connect(self.OnSettingsSaving)
1941
1942        # do layout
1943        self.mainSizer.Insert(0, self.settsManager,
1944                              flag=wx.ALL | wx.EXPAND, border=5)
1945
1946    def OnSettingsSaving(self, name):
1947        """Saving data"""
1948        if not self.GetDsn():
1949            GMessage(parent=self, message=_(
1950                "No data source defined, settings are not saved."))
1951            return
1952
1953        self.settsManager.SetDataToSave((self._sourceType, self.GetDsn(),
1954                                         self.GetFormat(), self.GetOptions()))
1955        self.settsManager.SaveSettings(name)
1956
1957    def _getExtPatternGlob(self, ext):
1958        """Get pattern for case-insensitive globing"""
1959        pattern = '*.'
1960        for c in ext:
1961            pattern += '[%s%s]' % (c.lower(), c.upper())
1962        return pattern
1963
1964    def _getCurrentDbWidgetName(self):
1965        """Returns active dns database widget name."""
1966        for widget in ('browse', 'dirbrowse', 'text', 'choice'):
1967            if self.dbWidgets[widget].IsShown():
1968                return widget
1969
1970    def GetDsn(self):
1971        """Get datasource name
1972        """
1973        if self._sourceType == 'db':
1974            if self.dbWidgets['format'].GetStringSelection() in(
1975                    'PostgreSQL', 'PostGIS Raster driver'):
1976
1977                dsn = 'PG:dbname=%s' % self.dbWidgets[
1978                    'choice'].GetStringSelection()
1979            else:
1980                name = self._getCurrentDbWidgetName()
1981                if name == 'choice':
1982                    dsn = self.dbWidgets[name].GetStringSelection()
1983                else:
1984                    dsn = self.dbWidgets[name].GetValue()
1985
1986        else:
1987            if self._sourceType == 'file':
1988                dsn = self.fileWidgets['browse'].GetValue()
1989            elif self._sourceType == 'dir':
1990                dsn = self.dirWidgets['browse'].GetValue()
1991            elif self._sourceType == 'pro':
1992                dsn = self.protocolWidgets['text'].GetValue()
1993            else:
1994                dsn = ''
1995            # check compressed files
1996            try:
1997                ext = os.path.splitext(dsn)[1].lower()
1998            except KeyError:
1999                ext = None
2000
2001            if ext == '.zip':
2002                dsn = '/vsizip/' + dsn
2003            elif ext == '.gzip':
2004                dsn = '/vsigzip/' + dsn
2005            elif ext in ('.tar', '.tar.gz', '.tgz'):
2006                dsn = '/vsitar/' + dsn
2007
2008        return dsn
2009
2010    def SetDatabase(self, db):
2011        """Update database panel."""
2012        sizer = self.dbPanel.GetSizer()
2013        showBrowse = db in ('SQLite', 'Rasterlite')
2014        showDirbrowse = db in ('FileGDB')
2015        showChoice = db in ('PostgreSQL', 'PostGIS WKT Raster driver',
2016                            'PostGIS Raster driver')
2017        enableFeatType = self.dest and self.ogr and db in ('PostgreSQL')
2018        showText = not(showBrowse or showChoice or showDirbrowse)
2019
2020        sizer.Show(self.dbWidgets['browse'], show=showBrowse)
2021        sizer.Show(self.dbWidgets['dirbrowse'], show=showDirbrowse)
2022        sizer.Show(self.dbWidgets['choice'], show=showChoice)
2023        sizer.Show(self.dbWidgets['textLabel2'], show=showChoice)
2024        sizer.Show(self.dbWidgets['text'], show=showText)
2025        sizer.Show(self.dbWidgets['textLabel1'], show=showText)
2026        self.dbWidgets['featType'].Enable(enableFeatType)
2027        if showChoice:
2028            # try to get list of PG databases
2029            dbNames = RunCommand(
2030                'db.databases',
2031                parent=self,
2032                quiet=True,
2033                read=True,
2034                driver='pg').splitlines()
2035            if dbNames is not None:
2036                self.dbWidgets['choice'].SetItems(sorted(dbNames))
2037                self.dbWidgets['choice'].SetSelection(0)
2038            elif grass.find_program('psql', '--help'):
2039                if not self.dbWidgets['choice'].GetItems():
2040                    p = grass.Popen(['psql', '-ltA'], stdout=grass.PIPE)
2041                    ret = p.communicate()[0]
2042                    if ret:
2043                        dbNames = list()
2044                        for line in ret.splitlines():
2045                            sline = line.split('|')
2046                            if len(sline) < 2:
2047                                continue
2048                            dbname = sline[0]
2049                            if dbname:
2050                                dbNames.append(dbname)
2051                        self.dbWidgets['choice'].SetItems(db)
2052                        self.dbWidgets['choice'].SetSelection(0)
2053            else:
2054                sizer.Show(self.dbWidgets['text'])
2055                sizer.Show(self.dbWidgets['choice'], False)
2056
2057        sizer.Layout()
2058
2059    def OnUpdate(self, event):
2060        """Update required - load layers."""
2061        if not self.dest:
2062            self._reloadLayers()
2063
2064        event.Skip()
2065
2066    def _reloadLayers(self):
2067        """Reload list of layers"""
2068
2069        def hasRastSameProjAsLocation(dsn):
2070
2071            ret = RunCommand('r.external',
2072                             quiet=True,
2073                             read=True,
2074                             flags='t',
2075                             input=dsn)
2076
2077            # v.external returns info for individual bands, however projection is shared by all bands ->
2078            # (it is possible to take first line)
2079
2080            lines = ret.splitlines()
2081            projectionMatch = '0'
2082            if lines:
2083                bandNumber, bandType, projectionMatch = map(
2084                    lambda x: x.strip(), lines[0].split(','))
2085
2086            return projectionMatch
2087
2088        def getProjMatchCaption(projectionMatch):
2089
2090            if projectionMatch == '0':
2091                projectionMatchCaption = _("No")
2092            else:
2093                projectionMatchCaption = _("Yes")
2094
2095            return projectionMatchCaption
2096
2097        dsn = self.GetDsn()
2098        if not dsn:
2099            return
2100
2101        data = list()
2102        listData = list()
2103        layerId = 1
2104
2105        if self.ogr:
2106            ret = RunCommand('v.external',
2107                             quiet=True,
2108                             read=True,
2109                             flags='t',
2110                             input=dsn)
2111            if not ret:
2112                self.reloadDataRequired.emit(listData=None, data=None)
2113                return
2114
2115            layerId = 1
2116            for line in ret.splitlines():
2117                layerName, featureType, projectionMatch, geometryColumn = map(
2118                    lambda x: x.strip(), line.split(','))
2119                projectionMatchCaption = getProjMatchCaption(projectionMatch)
2120                grassName = GetValidLayerName(layerName)
2121                if geometryColumn:
2122                    featureType = geometryColumn + '/' + featureType
2123                listData.append(
2124                    (layerId,
2125                     layerName,
2126                     featureType,
2127                     projectionMatchCaption,
2128                     grassName))
2129                data.append(
2130                    (layerId,
2131                     layerName,
2132                     featureType,
2133                     int(projectionMatch),
2134                        grassName))
2135                layerId += 1
2136        else:
2137            if self._sourceType == 'file':
2138                baseName = os.path.basename(dsn)
2139                grassName = GetValidLayerName(baseName.split('.', -1)[0])
2140                projectionMatch = hasRastSameProjAsLocation(dsn)
2141                projectionMatchCaption = getProjMatchCaption(projectionMatch)
2142                listData.append(
2143                    (layerId, baseName, projectionMatchCaption, grassName))
2144                data.append(
2145                    (layerId, baseName, int(projectionMatch), grassName))
2146            elif self._sourceType == 'dir':
2147                ext = self.dirWidgets['extension'].GetValue()
2148                for filename in glob.glob(os.path.join(
2149                        dsn, "%s") % self._getExtPatternGlob(ext)):
2150                    baseName = os.path.basename(filename)
2151                    grassName = GetValidLayerName(baseName.split('.', -1)[0])
2152                    projectionMatch = hasRastSameProjAsLocation(filename)
2153                    projectionMatchCaption = getProjMatchCaption(
2154                        projectionMatch)
2155                    listData.append(
2156                        (layerId, baseName, projectionMatchCaption, grassName))
2157                    data.append(
2158                        (layerId, baseName, int(projectionMatch), grassName))
2159                    layerId += 1
2160
2161        # emit signal
2162        self.reloadDataRequired.emit(listData=listData, data=data)
2163
2164    def ExtensionChanged(self, event):
2165        if not self.dest:
2166            # reload layers
2167            self._reloadLayers()
2168
2169    def SetExtension(self, name):
2170        """Extension changed"""
2171        ext = self._getExtension(name)
2172        self.dirWidgets['extension'].SetValue(ext)
2173
2174    def GetType(self):
2175        """Get source type"""
2176        return self._sourceType
2177
2178    def GetFormat(self):
2179        """Get format as string"""
2180        if self._sourceType == 'dir':
2181            format = self.dirWidgets['format'].GetStringSelection()
2182        elif self._sourceType == 'pro':
2183            format = self.protocolWidgets['format'].GetStringSelection()
2184        elif self._sourceType == 'db':
2185            format = self.dbWidgets['format'].GetStringSelection()
2186        else:
2187            format = ''
2188
2189        return format.replace(' ', '_')
2190
2191    def GetFormatExt(self):
2192        """Get format extension"""
2193        return self._getExtension(self.GetFormat())
2194
2195    def GetOptions(self):
2196        """Get creation options"""
2197        if self._sourceType == 'file':
2198            options = self.fileWidgets['options'].GetValue()
2199        elif self._sourceType == 'dir':
2200            options = self.dirWidgets['options'].GetValue()
2201        elif self._sourceType == 'pro':
2202            options = self.protocolWidgets['options'].GetValue()
2203        elif self._sourceType == 'db':
2204            if self.dbWidgets['featType'].GetSelection() == 1:
2205                options = 'topology=yes '
2206            else:
2207                options = ''
2208            options += self.dbWidgets['options'].GetValue()
2209
2210        return options.strip()
2211
2212    def OnHelp(self, event):
2213        """Show related manual page"""
2214        cmd = ''
2215        if self.dest:
2216            if self.ogr:
2217                cmd = 'v.external.out'
2218            else:
2219                cmd = 'r.external.out'
2220        else:
2221            if self.link:
2222                if self.ogr:
2223                    cmd = 'v.external'
2224                else:
2225                    cmd = 'r.external'
2226            else:
2227                if self.ogr:
2228                    cmd = 'v.in.ogr'
2229                else:
2230                    cmd = 'r.in.gdal'
2231
2232        RunCommand('g.manual', entry=cmd)
2233
2234
2235class ProjSelect(wx.ComboBox):
2236    """Widget for selecting input raster/vector map used by
2237    r.proj/v.proj modules."""
2238
2239    def __init__(self, parent, isRaster, id=wx.ID_ANY,
2240                 size=globalvar.DIALOG_COMBOBOX_SIZE, **kwargs):
2241        super(ProjSelect, self).__init__(parent, id, size=size, **kwargs)
2242        self.SetName("ProjSelect")
2243        self.isRaster = isRaster
2244
2245    def UpdateItems(self, dbase, location, mapset):
2246        """Update list of maps
2247
2248        """
2249        if not dbase:
2250            dbase = grass.gisenv()['GISDBASE']
2251        if not mapset:
2252            mapset = grass.gisenv()['MAPSET']
2253        if self.isRaster:
2254            ret = RunCommand('r.proj',
2255                             quiet=True,
2256                             read=True,
2257                             flags='l',
2258                             dbase=dbase,
2259                             location=location,
2260                             mapset=mapset)
2261        else:
2262            ret = RunCommand('v.proj',
2263                             quiet=True,
2264                             read=True,
2265                             flags='l',
2266                             dbase=dbase,
2267                             location=location,
2268                             mapset=mapset)
2269        listMaps = list()
2270        if ret:
2271            for line in ret.splitlines():
2272                listMaps.append(line.strip())
2273        ListSortLower(listMaps)
2274
2275        self.SetItems(listMaps)
2276        self.SetValue('')
2277
2278
2279class ElementSelect(wx.Choice):
2280
2281    def __init__(self, parent, id=wx.ID_ANY, elements=None,
2282                 size=globalvar.DIALOG_COMBOBOX_SIZE,
2283                 **kwargs):
2284        """Widget for selecting GIS element
2285
2286        :param parent: parent window
2287        :param elements: filter elements
2288        """
2289        super(ElementSelect, self).__init__(parent, id, size=size,
2290                                            **kwargs)
2291        self.SetName("ElementSelect")
2292
2293        task = gtask.parse_interface('g.list')
2294        p = task.get_param(value='type')
2295        self.values = p.get('values', [])
2296        self.valuesDesc = p.get('values_desc', [])
2297
2298        if elements:
2299            values = []
2300            valuesDesc = []
2301            for idx in range(0, len(self.values)):
2302                value = self.values[idx]
2303                if value in elements:
2304                    values.append(value)
2305                    valuesDesc.append(self.valuesDesc[idx])
2306            self.values = values
2307            self.valuesDesc = valuesDesc
2308
2309        self.SetItems(self.valuesDesc)
2310
2311    def GetValue(self, name):
2312        """Translate value
2313
2314        :param name: element name
2315        """
2316        idx = self.valuesDesc.index(name)
2317        if idx > -1:
2318            return self.values[idx]
2319        return ''
2320
2321
2322class OgrTypeSelect(wx.Panel):
2323
2324    def __init__(self, parent, panel, **kwargs):
2325        """Widget to choose OGR feature type
2326
2327        :param parent: parent window
2328        :param panel: wx.Panel instance used as parent window
2329        """
2330        wx.Panel.__init__(self, parent=panel, id=wx.ID_ANY)
2331
2332        self.ftype = wx.Choice(parent=self, id=wx.ID_ANY, size=(
2333            200, -1), choices=(_("Point"), _("LineString"), _("Polygon")))
2334        self._layout()
2335
2336    def _layout(self):
2337        """Do layout"""
2338        sizer = wx.BoxSizer(wx.HORIZONTAL)
2339        sizer.Add(StaticText(parent=self,
2340                             id=wx.ID_ANY,
2341                             label=_("Feature type:")),
2342                  proportion=1,
2343                  flag=wx.ALIGN_CENTER_VERTICAL,
2344                  border=5)
2345        sizer.Add(self.ftype,
2346                  proportion=0,
2347                  flag=wx.EXPAND | wx.ALIGN_RIGHT)
2348
2349        self.SetSizer(sizer)
2350        sizer.Fit(self)
2351
2352    def GetType(self):
2353        """Get selected type as string
2354
2355        :return: feature type as string
2356        """
2357        sel = self.ftype.GetSelection()
2358        if sel == 0:
2359            return 'point'
2360        elif sel == 1:
2361            return 'line'
2362        elif sel == 2:
2363            return 'boundary'
2364
2365
2366class CoordinatesSelect(Panel):
2367
2368    def __init__(self, parent, giface, multiple=False, **kwargs):
2369        """Widget to get coordinates from map window  by mouse click
2370
2371        :param parent: parent window
2372        :param giface: GRASS interface
2373        :param multiple: - True if it is possible to insert more coordinates
2374        """
2375        self._giface = giface
2376        self.multiple = multiple
2377        self.mapWin = None
2378        self.drawMapWin = None
2379
2380        super(CoordinatesSelect, self).__init__(parent=parent, id=wx.ID_ANY)
2381
2382        self.coordsField = TextCtrl(parent=self, id=wx.ID_ANY,
2383                                    size=globalvar.DIALOG_TEXTCTRL_SIZE,
2384                                    validator=CoordinatesValidator())
2385
2386        icon = wx.Bitmap(
2387            os.path.join(
2388                globalvar.ICONDIR,
2389                "grass",
2390                "pointer.png"))
2391        self.buttonInsCoords = buttons.ThemedGenBitmapToggleButton(
2392            parent=self, id=wx.ID_ANY, bitmap=icon, size=globalvar.DIALOG_COLOR_SIZE)
2393        self.registered = False
2394        self.buttonInsCoords.Bind(wx.EVT_BUTTON, self._onClick)
2395
2396        mapdisp = self._giface.GetMapDisplay()
2397        if mapdisp:
2398            switcher = mapdisp.GetToolSwitcher()
2399            switcher.AddCustomToolToGroup(
2400                group='mouseUse',
2401                btnId=self.buttonInsCoords.GetId(),
2402                toggleHandler=self.buttonInsCoords.SetValue)
2403        self._doLayout()
2404        self.coordsField.Bind(wx.EVT_TEXT, lambda event: self._draw(delay=1))
2405
2406    def _doLayout(self):
2407        self.dialogSizer = wx.BoxSizer(wx.HORIZONTAL)
2408        self.dialogSizer.Add(self.coordsField,
2409                             proportion=1,
2410                             flag=wx.EXPAND)
2411        self.dialogSizer.Add(self.buttonInsCoords)
2412        self.SetSizer(self.dialogSizer)
2413
2414    def _onClick(self, event):
2415        """Button for interacitve inserting of coordinates clicked"""
2416
2417        self.mapWin = self._giface.GetMapWindow()
2418        if self.buttonInsCoords.GetToggle() and self.mapWin:
2419            switcher = self._giface.GetMapDisplay().GetToolSwitcher()
2420            switcher.ToolChanged(self.buttonInsCoords.GetId())
2421            if self.mapWin.RegisterMouseEventHandler(wx.EVT_LEFT_DOWN,
2422                                                     self._onMapClickHandler,
2423                                                     'cross') == False:
2424                return
2425
2426            self.registered = True
2427            self._giface.GetMapDisplay().Raise()
2428        else:
2429            if self.mapWin and self.mapWin.UnregisterMouseEventHandler(
2430                    wx.EVT_LEFT_DOWN, self._onMapClickHandler):
2431                self.registered = False
2432                return
2433
2434    def drawCleanUp(self):
2435        if self.drawMapWin:
2436            self.drawMapWin.UnregisterGraphicsToDraw(self.pointsToDraw)
2437
2438    def _draw(self, delay):
2439        """Draws points representing inserted coordinates in mapwindow."""
2440        if self.drawMapWin != self.mapWin:
2441            self.drawCleanUp()
2442            if self.mapWin:
2443                self.drawMapWin = self.mapWin
2444                self.pointsToDraw = self.drawMapWin.RegisterGraphicsToDraw(
2445                    graphicsType="point")
2446
2447        if self.drawMapWin:
2448            items = self.pointsToDraw.GetAllItems()
2449            for i in items:
2450                self.pointsToDraw.DeleteItem(i)
2451
2452            coords = self._getCoords()
2453            if coords is not None:
2454                for i in range(len(coords) // 2):
2455                    i = i * 2
2456                    self.pointsToDraw.AddItem(
2457                        coords=(coords[i], coords[i + 1]))
2458
2459            self._giface.updateMap.emit(
2460                render=False, renderVector=False, delay=delay)
2461
2462    def _getCoords(self):
2463        """Get list of coordinates.
2464
2465        :return: None if values are not valid
2466        """
2467        if self.coordsField.GetValidator().Validate(self):
2468            return self.coordsField.GetValue().split(',')
2469
2470        return None
2471
2472    def _onMapClickHandler(self, event):
2473        """Gets coordinates from mapwindow"""
2474        if event == "unregistered":
2475            return
2476
2477        e, n = self.mapWin.GetLastEN()
2478        prevCoords = ""
2479
2480        if self.multiple:
2481            prevCoords = self.coordsField.GetValue().strip()
2482            if prevCoords != "":
2483                prevCoords += ","
2484
2485        value = prevCoords + str(e) + "," + str(n)
2486        self.coordsField.SetValue(value)
2487
2488        self._draw(delay=0)
2489
2490    def OnClose(self):
2491        """Unregistrates _onMapClickHandler from mapWin"""
2492        self.drawCleanUp()
2493        self._giface.updateMap.emit(render=False, renderVector=False)
2494
2495        mapdisp = self._giface.GetMapDisplay()
2496        if mapdisp:
2497            switcher = mapdisp.GetToolSwitcher()
2498            switcher.RemoveCustomToolFromGroup(self.buttonInsCoords.GetId())
2499
2500        if self.mapWin and self.registered:
2501            self.mapWin.UnregisterMouseEventHandler(wx.EVT_LEFT_DOWN,
2502                                                    self._onMapClickHandler)
2503
2504    def GetTextWin(self):
2505        """Get TextCtrl widget"""
2506        return self.coordsField
2507
2508
2509class VectorCategorySelect(wx.Panel):
2510    """Widget that allows interactive selection of vector features"""
2511
2512    def __init__(self, parent, giface, task=None):
2513        super(VectorCategorySelect, self).__init__(parent=parent, id=wx.ID_ANY)
2514        self.task = task
2515        self.parent = parent
2516        self.giface = giface
2517
2518        self.selectedFeatures = None
2519        self.registered = False
2520        self._vectorSelect = None
2521
2522        self.mapdisp = self.giface.GetMapDisplay()
2523
2524        self.catsField = TextCtrl(parent=self, id=wx.ID_ANY,
2525                                  size=globalvar.DIALOG_TEXTCTRL_SIZE)
2526
2527        icon = wx.Bitmap(
2528            os.path.join(
2529                globalvar.ICONDIR,
2530                "grass",
2531                "select.png"))
2532        self.buttonVecSelect = buttons.ThemedGenBitmapToggleButton(
2533            parent=self, id=wx.ID_ANY, bitmap=icon, size=globalvar.DIALOG_COLOR_SIZE)
2534        self.buttonVecSelect.Bind(wx.EVT_BUTTON, self._onClick)
2535
2536        if self.mapdisp:
2537            switcher = self.mapdisp.GetToolSwitcher()
2538            switcher.AddCustomToolToGroup(
2539                group='mouseUse',
2540                btnId=self.buttonVecSelect.GetId(),
2541                toggleHandler=self.buttonVecSelect.SetValue)
2542
2543        self._layout()
2544
2545    def _isMapSelected(self):
2546        """Check if layer list contains at least one selected map
2547        """
2548        layerList = self.giface.GetLayerList()
2549        layerSelected = layerList.GetSelectedLayer()
2550        if layerSelected is None:
2551            GWarning(
2552                _("No vector map selected in layer manager. Operation canceled."))
2553            return False
2554
2555        return True
2556
2557    def _chckMap(self):
2558        """Check if selected map in 'input' widget is the same as selected map in lmgr """
2559        if self._isMapSelected():
2560            layerList = self.giface.GetLayerList()
2561            layerSelected = layerList.GetSelectedLayer()
2562            # d.vect module
2563            inputName = self.task.get_param(value='map', raiseError=False)
2564            if not inputName:
2565                inputName = self.task.get_param('input')
2566            if inputName['value'] != str(layerSelected):
2567                if inputName['value'] == '' or inputName['value'] is None:
2568                    GWarning(_("Input vector map is not selected"))
2569                    return False
2570                GWarning(
2571                    _(
2572                        "Input vector map <%s> and selected map <%s> in layer manager are different. "
2573                        "Operation canceled.") %
2574                    (inputName['value'], str(layerSelected)))
2575                return False
2576            return True
2577        return False
2578
2579    def _onClick(self, evt=None):
2580        if self.task is not None:
2581            if not self._chckMap():
2582                self.buttonVecSelect.SetValue(False)
2583                return
2584        else:
2585            if not self._isMapSelected():
2586                self.buttonVecSelect.SetValue(False)
2587                return
2588        if self._vectorSelect is None:
2589
2590            if self.mapdisp:
2591                if self.buttonVecSelect.IsEnabled():
2592                    switcher = self.mapdisp.GetToolSwitcher()
2593                    switcher.ToolChanged(self.buttonVecSelect.GetId())
2594
2595                self._vectorSelect = VectorSelectBase(
2596                    self.mapdisp, self.giface)
2597                if self.mapdisp.GetWindow().RegisterMouseEventHandler(
2598                        wx.EVT_LEFT_DOWN, self._onMapClickHandler, 'cross') == False:
2599                    return
2600                self.registered = True
2601                self.mapdisp.Raise()
2602        else:
2603            self.OnClose()
2604
2605    def OnClose(self, event=None):
2606        if not self.mapdisp:
2607            return
2608
2609        switcher = self.mapdisp.GetToolSwitcher()
2610        switcher.RemoveCustomToolFromGroup(self.buttonVecSelect.GetId())
2611        if self._vectorSelect is not None:
2612            tmp = self._vectorSelect.GetLineStringSelectedCats()
2613            self._vectorSelect.OnClose()
2614            self.catsField.SetValue(tmp)
2615        self._vectorSelect = None
2616
2617    def _onMapClickHandler(self, event):
2618        """Update category text input widget"""
2619        if event == "unregistered":
2620            return
2621
2622        if self.task is None:
2623            if not self._isMapSelected():
2624                self.OnClose()
2625            else:
2626                self.catsField.SetValue(
2627                    self._vectorSelect.GetLineStringSelectedCats())
2628        else:
2629            if not self._chckMap():
2630                self.OnClose()
2631            else:
2632                self.catsField.SetValue(
2633                    self._vectorSelect.GetLineStringSelectedCats())
2634
2635    def GetTextWin(self):
2636        return self.catsField
2637
2638    def GetValue(self):
2639        return self.catsField.GetValue()
2640
2641    def SetValue(self, value):
2642        self.catsField.SetValue(value)
2643
2644    def _layout(self):
2645        self.dialogSizer = wx.BoxSizer(wx.HORIZONTAL)
2646        self.dialogSizer.Add(self.catsField,
2647                             proportion=1,
2648                             flag=wx.EXPAND)
2649
2650        self.dialogSizer.Add(self.buttonVecSelect)
2651        self.SetSizer(self.dialogSizer)
2652
2653
2654class SignatureSelect(wx.ComboBox):
2655    """Widget for selecting signatures"""
2656
2657    def __init__(self, parent, element, id=wx.ID_ANY,
2658                 size=globalvar.DIALOG_GSELECT_SIZE, **kwargs):
2659        super(SignatureSelect, self).__init__(parent, id, size=size,
2660                                              **kwargs)
2661        self.element = element
2662        self.SetName("SignatureSelect")
2663
2664    def Insert(self, group, subgroup=None):
2665        """Insert signatures for defined group/subgroup
2666
2667        :param group: group name (can be fully-qualified)
2668        :param subgroup: non fully-qualified name of subgroup
2669        """
2670        if not group:
2671            return
2672        gisenv = grass.gisenv()
2673        try:
2674            name, mapset = group.split('@', 1)
2675        except ValueError:
2676            name = group
2677            mapset = gisenv['MAPSET']
2678
2679        path = os.path.join(
2680            gisenv['GISDBASE'],
2681            gisenv['LOCATION_NAME'],
2682            mapset, 'group', name)
2683
2684        if subgroup:
2685            path = os.path.join(path, 'subgroup', subgroup)
2686        try:
2687            items = list()
2688            for element in os.listdir(os.path.join(path, self.element)):
2689                items.append(element)
2690            self.SetItems(items)
2691        except OSError:
2692            self.SetItems([])
2693        self.SetValue('')
2694
2695
2696class SeparatorSelect(wx.ComboBox):
2697    """Widget for selecting seperator"""
2698
2699    def __init__(self, parent, id=wx.ID_ANY,
2700                 size=globalvar.DIALOG_GSELECT_SIZE, **kwargs):
2701        super(SeparatorSelect, self).__init__(parent, id, size=size,
2702                                              **kwargs)
2703        self.SetName("SeparatorSelect")
2704        self.SetItems(['pipe', 'comma', 'space', 'tab', 'newline'])
2705
2706
2707class SqlWhereSelect(wx.Panel):
2708
2709    def __init__(self, parent, **kwargs):
2710        """Widget to define SQL WHERE condition.
2711
2712        :param parent: parent window
2713        """
2714        super(SqlWhereSelect, self).__init__(parent=parent, id=wx.ID_ANY)
2715        self.parent = parent
2716        self.vector_map = None
2717
2718        self.sqlField = TextCtrl(parent=self, id=wx.ID_ANY,
2719                                 size=globalvar.DIALOG_TEXTCTRL_SIZE)
2720        self.GetChildren()[0].SetName("SqlWhereSelect")
2721        icon = wx.Bitmap(
2722            os.path.join(
2723                globalvar.ICONDIR,
2724                "grass",
2725                "table.png"))
2726        self.buttonInsSql = buttons.ThemedGenBitmapButton(
2727            parent=self, id=wx.ID_ANY, bitmap=icon, size=globalvar.DIALOG_COLOR_SIZE)
2728        self.buttonInsSql.Bind(wx.EVT_BUTTON, self._onClick)
2729
2730        self._doLayout()
2731
2732
2733    def _doLayout(self):
2734        self.dialogSizer = wx.BoxSizer(wx.HORIZONTAL)
2735        self.dialogSizer.Add(self.sqlField,
2736                             proportion=1,
2737                             flag=wx.EXPAND)
2738        self.dialogSizer.Add(self.buttonInsSql)
2739        self.SetSizer(self.dialogSizer)
2740
2741    def GetTextWin(self):
2742        return self.sqlField
2743
2744    def _onClick(self, event):
2745        from dbmgr.sqlbuilder import SQLBuilderWhere
2746        try:
2747            if not self.vector_map:
2748                raise GException(_('No vector map selected'))
2749            win = SQLBuilderWhere(parent=self,
2750                                  vectmap=self.vector_map,
2751                                  layer=self.vector_layer)
2752            win.Show()
2753        except GException as e:
2754            GMessage(parent=self.parent, message='{}'.format(e))
2755
2756    def SetData(self, vector, layer):
2757        self.vector_map = vector
2758        self.vector_layer = int(layer) # TODO: support layer names
2759
2760    def SetValue(self, value):
2761        self.sqlField.SetValue(value)
2762