"""
@package gui_core.dialogs

@brief Various dialogs used in wxGUI.

List of classes:
 - :class:`SimpleDialog`
 - :class:`LocationDialog`
 - :class:`MapsetDialog`
 - :class:`VectorDialog`
 - :class:`NewVectorDialog`
 - :class:`SavedRegion`
 - :class:`GroupDialog`
 - :class:`MapLayersDialog`
 - :class:`SetOpacityDialog`
 - :class:`ImageSizeDialog`
 - :class:`SqlQueryFrame`
 - :class:`SymbolDialog`
 - :class:`QuitDialog`
 - :class:`DefaultFontDialog`

(C) 2008-2016 by the GRASS Development Team

This program is free software under the GNU General Public License
(>=v2). Read the file COPYING that comes with GRASS for details.

@author Martin Landa <landa.martin gmail.com>
@author Anna Kratochvilova <kratochanna gmail.com> (GroupDialog, SymbolDialog)
"""

import os
import sys
import re
import six

import wx

try:
    from wx.adv import HyperlinkCtrl
except ImportError:
    from wx import HyperlinkCtrl

from grass.script import core as grass
from grass.script.utils import natural_sort, try_remove

from grass.pydispatch.signal import Signal

from core import globalvar
from core.gcmd import GError, RunCommand, GMessage
from gui_core.gselect import LocationSelect, MapsetSelect, Select, \
    OgrTypeSelect, SubGroupSelect
from gui_core.widgets import SingleSymbolPanel, GListCtrl, SimpleValidator, MapValidator
from core.settings import UserSettings
from core.debug import Debug
from gui_core.wrap import SpinCtrl, TextCtrl, Button, CheckListBox, \
    StaticText, StaticBox, Menu


class SimpleDialog(wx.Dialog):

    def __init__(self, parent, title, id=wx.ID_ANY,
                 style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER,
                 **kwargs):
        """General dialog to choose given element (location, mapset, vector map, etc.)

        :param parent: window
        :param title: window title
        """
        wx.Dialog.__init__(self, parent, id, title, style=style, **kwargs)
        self.SetExtraStyle(wx.WS_EX_VALIDATE_RECURSIVELY)
        self.panel = wx.Panel(parent=self, id=wx.ID_ANY)

        self.btnCancel = Button(parent=self.panel, id=wx.ID_CANCEL)
        self.btnOK = Button(parent=self.panel, id=wx.ID_OK)
        self.btnOK.SetDefault()

        self.__layout()
        self.warning = _("Required item is not set.")

    def __layout(self):
        """Do layout"""
        self.sizer = wx.BoxSizer(wx.VERTICAL)

        self.dataSizer = wx.BoxSizer(wx.VERTICAL)

        # self.informLabel = wx.StaticText(self.panel, id = wx.ID_ANY)
        # buttons
        btnSizer = wx.StdDialogButtonSizer()
        btnSizer.AddButton(self.btnCancel)
        btnSizer.AddButton(self.btnOK)
        btnSizer.Realize()

        self.sizer.Add(self.dataSizer, proportion=1,
                       flag=wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border=5)

        # self.sizer.Add(item = self.informLabel, proportion = 0, flag = wx.ALL, border = 5)
        self.sizer.Add(btnSizer, proportion=0,
                       flag=wx.EXPAND | wx.ALL, border=5)

    def ValidatorCallback(self, win):
        GMessage(parent=self, message=self.warning)
        # self.informLabel.SetForegroundColour(wx.Colour(255, 0, 0))
        # self.informLabel.SetLabel(self.warning)


class LocationDialog(SimpleDialog):
    """Dialog used to select location"""

    def __init__(self, parent, title=_("Select GRASS location and mapset")):
        SimpleDialog.__init__(self, parent, title)

        self.element1 = LocationSelect(
            parent=self.panel,
            id=wx.ID_ANY,
            size=globalvar.DIALOG_GSELECT_SIZE,
            validator=SimpleValidator(
                callback=self.ValidatorCallback))
        self.element1.Bind(wx.EVT_TEXT, self.OnLocation)
        self.element1.Bind(wx.EVT_COMBOBOX, self.OnLocation)
        self.element2 = MapsetSelect(
            parent=self.panel,
            id=wx.ID_ANY,
            size=globalvar.DIALOG_GSELECT_SIZE,
            setItems=False,
            skipCurrent=True,
            validator=SimpleValidator(
                callback=self.ValidatorCallback))
        self.element1.SetFocus()
        self.warning = _("Location or mapset is not defined.")
        self._layout()
        self.SetMinSize(self.GetSize())

    def _layout(self):
        """Do layout"""
        self.dataSizer.Add(
            StaticText(
                parent=self.panel,
                id=wx.ID_ANY,
                label=_("Name of GRASS location:")),
            proportion=0,
            flag=wx.ALL,
            border=1)
        self.dataSizer.Add(self.element1, proportion=0,
                           flag=wx.EXPAND | wx.ALL, border=1)

        self.dataSizer.Add(
            StaticText(
                parent=self.panel,
                id=wx.ID_ANY,
                label=_("Name of mapset:")),
            proportion=0,
            flag=wx.EXPAND | wx.ALL,
            border=1)

        self.dataSizer.Add(self.element2, proportion=0,
                           flag=wx.EXPAND | wx.ALL, border=1)

        self.panel.SetSizer(self.sizer)
        self.sizer.Fit(self)

    def OnLocation(self, event):
        """Select mapset given location name"""
        location = event.GetString()

        if location:
            dbase = grass.gisenv()['GISDBASE']
            self.element2.UpdateItems(dbase=dbase, location=location)
            self.element2.SetSelection(0)
            mapset = self.element2.GetStringSelection()

    def GetValues(self):
        """Get location, mapset"""
        return (self.element1.GetValue(), self.element2.GetValue())


class MapsetDialog(SimpleDialog):
    """Dialog used to select mapset"""

    def __init__(self, parent, title=_("Select mapset in GRASS location"),
                 location=None):
        SimpleDialog.__init__(self, parent, title)

        if location:
            self.SetTitle(self.GetTitle() + ' <%s>' % location)
        else:
            self.SetTitle(
                self.GetTitle() + ' <%s>' %
                grass.gisenv()['LOCATION_NAME'])

        self.element = MapsetSelect(
            parent=self.panel,
            id=wx.ID_ANY,
            skipCurrent=True,
            size=globalvar.DIALOG_GSELECT_SIZE,
            validator=SimpleValidator(
                callback=self.ValidatorCallback))

        self.element.SetFocus()
        self.warning = _("Name of mapset is missing.")

        self._layout()
        self.SetMinSize(self.GetSize())

    def _layout(self):
        """Do layout"""
        self.dataSizer.Add(StaticText(parent=self.panel, id=wx.ID_ANY,
                                      label=_("Name of mapset:")),
                           proportion=0, flag=wx.ALL, border=1)
        self.dataSizer.Add(self.element, proportion=0,
                           flag=wx.EXPAND | wx.ALL, border=1)
        self.panel.SetSizer(self.sizer)
        self.sizer.Fit(self)

    def GetMapset(self):
        return self.element.GetValue()


class VectorDialog(SimpleDialog):

    def __init__(self, parent, title=_("Select vector map"), layerTree=None):
        """Dialog for selecting existing vector map

        :param parent: parent window
        :param title: window title
        :param layerTree: show only vector maps in given layer tree if not None

        :return: dialog instance
        """
        SimpleDialog.__init__(self, parent, title)

        self.element = Select(
            parent=self.panel,
            id=wx.ID_ANY,
            size=globalvar.DIALOG_GSELECT_SIZE,
            type='vector',
            layerTree=layerTree,
            validator=MapValidator())
        self.element.SetFocus()

        self.warning = _("Name of vector map is missing.")
        wx.CallAfter(self._layout)

    def _layout(self):
        """Do layout"""
        self.dataSizer.Add(StaticText(parent=self.panel, id=wx.ID_ANY,
                                      label=_("Name of vector map:")),
                           proportion=0, flag=wx.ALL, border=1)
        self.dataSizer.Add(self.element, proportion=0,
                           flag=wx.EXPAND | wx.ALL, border=1)

        self.panel.SetSizer(self.sizer)
        self.sizer.Fit(self)

    def GetName(self, full=False):
        """Get name of vector map to be created

        :param full: True to get fully qualified name
        """
        name = self.element.GetValue()
        if full:
            if '@' in name:
                return name
            else:
                return name + '@' + grass.gisenv()['MAPSET']

        return name.split('@', 1)[0]


class NewVectorDialog(VectorDialog):

    def __init__(self, parent, title=_("Create new vector map"),
                 disableAdd=False, disableTable=False, showType=False):
        """Dialog for creating new vector map

        :param parent: parent window
        :param title: window title
        :param disableAdd: disable 'add layer' checkbox
        :param disableTable: disable 'create table' checkbox
        :param showType: True to show feature type selector (used for creating new empty OGR layers)

        :return: dialog instance
        """
        VectorDialog.__init__(self, parent, title)

        # determine output format
        if showType:
            self.ftype = OgrTypeSelect(parent=self, panel=self.panel)
        else:
            self.ftype = None

        # create attribute table
        self.table = wx.CheckBox(parent=self.panel, id=wx.ID_ANY,
                                 label=_("Create attribute table"))
        self.table.SetValue(True)
        if disableTable:
            self.table.Enable(False)

        if showType:
            self.keycol = None
        else:
            self.keycol = TextCtrl(parent=self.panel, id=wx.ID_ANY,
                                   size=globalvar.DIALOG_SPIN_SIZE)
            self.keycol.SetValue(
                UserSettings.Get(
                    group='atm',
                    key='keycolumn',
                    subkey='value'))
            if disableTable:
                self.keycol.Enable(False)

        self.addbox = wx.CheckBox(
            parent=self.panel,
            label=_('Add created map into layer tree'),
            style=wx.NO_BORDER)
        if disableAdd:
            self.addbox.SetValue(True)
            self.addbox.Enable(False)
        else:
            self.addbox.SetValue(
                UserSettings.Get(
                    group='cmd',
                    key='addNewLayer',
                    subkey='enabled'))

        self.table.Bind(wx.EVT_CHECKBOX, self.OnTable)

        self.warning = _("Name of new vector map is missing.")

    def OnTable(self, event):
        if self.keycol:
            self.keycol.Enable(event.IsChecked())

    def _layout(self):
        """Do layout"""
        self.dataSizer.Add(
            StaticText(
                parent=self.panel,
                id=wx.ID_ANY,
                label=_("Name for new vector map:")),
            proportion=0,
            flag=wx.ALL,
            border=1)
        self.dataSizer.Add(self.element, proportion=0,
                           flag=wx.EXPAND | wx.ALL, border=1)
        if self.ftype:
            self.dataSizer.AddSpacer(1)
            self.dataSizer.Add(self.ftype, proportion=0,
                               flag=wx.EXPAND | wx.ALL, border=1)

        self.dataSizer.Add(self.table, proportion=0,
                           flag=wx.EXPAND | wx.ALL, border=1)

        if self.keycol:
            keySizer = wx.BoxSizer(wx.HORIZONTAL)
            keySizer.Add(
                StaticText(
                    parent=self.panel,
                    label=_("Key column:")),
                proportion=0,
                flag=wx.ALIGN_CENTER_VERTICAL)
            keySizer.AddSpacer(10)
            keySizer.Add(self.keycol, proportion=0,
                         flag=wx.ALIGN_RIGHT)
            self.dataSizer.Add(keySizer, proportion=1,
                               flag=wx.EXPAND | wx.ALL, border=1)

        self.dataSizer.AddSpacer(5)

        self.dataSizer.Add(self.addbox, proportion=0,
                           flag=wx.EXPAND | wx.ALL, border=1)

        self.panel.SetSizer(self.sizer)
        self.sizer.Fit(self)
        self.SetMinSize(self.GetSize())

    def GetKey(self):
        """Get key column name"""
        if self.keycol:
            return self.keycol.GetValue()
        return UserSettings.Get(group='atm', key='keycolumn', subkey='value')

    def IsChecked(self, key):
        """Get dialog properties

        :param key: window key ('add', 'table')

        :return: True/False
        :return: None on error
        """
        if key == 'add':
            return self.addbox.IsChecked()
        elif key == 'table':
            return self.table.IsChecked()

        return None

    def GetFeatureType(self):
        """Get feature type for OGR

        :return: feature type as string
        :return: None for native format
        """
        if self.ftype:
            return self.ftype.GetType()

        return None


def CreateNewVector(parent, cmd, title=_('Create new vector map'),
                    exceptMap=None, giface=None,
                    disableAdd=False, disableTable=False):
    """Create new vector map layer

    :param cmd: (prog, \*\*kwargs)
    :param title: window title
    :param exceptMap: list of maps to be excepted
    :param log:
    :param disableAdd: disable 'add layer' checkbox
    :param disableTable: disable 'create table' checkbox

    :return: dialog instance
    :return: None on error
    """
    vExternalOut = grass.parse_command('v.external.out', flags='g')
    isNative = vExternalOut['format'] == 'native'
    if cmd[0] == 'v.edit' and not isNative:
        showType = True
    else:
        showType = False
    dlg = NewVectorDialog(parent, title=title,
                          disableAdd=disableAdd, disableTable=disableTable,
                          showType=showType)

    if dlg.ShowModal() != wx.ID_OK:
        dlg.Destroy()
        return None

    outmap = dlg.GetName()
    key = dlg.GetKey()
    if outmap == exceptMap:
        GError(parent=parent,
               message=_("Unable to create vector map <%s>.") % outmap)
        dlg.Destroy()
        return None
    if dlg.table.IsEnabled() and not key:
        GError(parent=parent,
               message=_("Invalid or empty key column.\n"
                         "Unable to create vector map <%s>.") % outmap)
        dlg.Destroy()
        return

    if outmap == '':  # should not happen
        dlg.Destroy()
        return None

    # update cmd -> output name defined
    cmd[1][cmd[2]] = outmap
    if showType:
        cmd[1]['type'] = dlg.GetFeatureType()

    curMapset = grass.gisenv()['MAPSET']
    if isNative:
        listOfVectors = grass.list_grouped('vector')[curMapset]
    else:
        listOfVectors = RunCommand('v.external',
                                   quiet=True,
                                   parent=parent,
                                   read=True,
                                   flags='l',
                                   input=vExternalOut['dsn']).splitlines()

    overwrite = False
    if not UserSettings.Get(group='cmd', key='overwrite',
                            subkey='enabled') and outmap in listOfVectors:
        dlgOw = wx.MessageDialog(
            parent,
            message=_(
                "Vector map <%s> already exists "
                "in the current mapset. "
                "Do you want to overwrite it?") %
            outmap,
            caption=_("Overwrite?"),
            style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
        if dlgOw.ShowModal() == wx.ID_YES:
            overwrite = True
        else:
            dlgOw.Destroy()
            dlg.Destroy()
            return None

    if UserSettings.Get(group='cmd', key='overwrite', subkey='enabled'):
        overwrite = True

    ret = RunCommand(prog=cmd[0],
                     parent=parent,
                     overwrite=overwrite,
                     **cmd[1])
    if ret != 0:
        dlg.Destroy()
        return None

    if not isNative and not grass.find_file(
            outmap, element='vector', mapset=curMapset)['fullname']:
        # create link for OGR layers
        RunCommand('v.external',
                   overwrite=overwrite,
                   parent=parent,
                   input=vExternalOut['dsn'],
                   layer=outmap)

    # create attribute table
    if dlg.table.IsEnabled() and dlg.table.IsChecked():
        if isNative:
            sql = 'CREATE TABLE %s (%s INTEGER)' % (outmap, key)

            RunCommand('db.connect',
                       flags='c')

            Debug.msg(1, "SQL: %s" % sql)
            RunCommand('db.execute',
                       quiet=True,
                       parent=parent,
                       input='-',
                       stdin=sql)

            RunCommand('v.db.connect',
                       quiet=True,
                       parent=parent,
                       map=outmap,
                       table=outmap,
                       key=key,
                       layer='1')
        # TODO: how to deal with attribute tables for OGR layers?

    # return fully qualified map name
    if '@' not in outmap:
        outmap += '@' + grass.gisenv()['MAPSET']

    # if giface:
    #     giface.WriteLog(_("New vector map <%s> created") % outmap)

    return dlg


class SavedRegion(wx.Dialog):

    def __init__(self, parent, title, id=wx.ID_ANY, loadsave='load',
                 **kwargs):
        """Loading or saving of display extents to saved region file

        :param loadsave: load or save region?
        """
        wx.Dialog.__init__(self, parent, id, title, **kwargs)

        self.loadsave = loadsave
        self.wind = ''

        sizer = wx.BoxSizer(wx.VERTICAL)

        box = wx.BoxSizer(wx.HORIZONTAL)
        label = StaticText(parent=self, id=wx.ID_ANY)
        box.Add(
            label,
            proportion=0,
            flag=wx.ALIGN_CENTRE | wx.ALL,
            border=5)
        if loadsave == 'load':
            label.SetLabel(_("Load region:"))
            self._selection = Select(
                parent=self,
                size=globalvar.DIALOG_GSELECT_SIZE,
                type='windows')
        elif loadsave == 'save':
            label.SetLabel(_("Save region:"))
            self._selection = Select(
                parent=self,
                size=globalvar.DIALOG_GSELECT_SIZE,
                type='windows',
                mapsets=[
                    grass.gisenv()['MAPSET']],
                fullyQualified=False)

        box.Add(
            self._selection,
            proportion=0,
            flag=wx.ALIGN_CENTRE | wx.ALL,
            border=5)
        self._selection.SetFocus()
        self._selection.Bind(wx.EVT_TEXT, self.OnRegion)

        sizer.Add(box, proportion=0, flag=wx.GROW |
                  wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)

        line = wx.StaticLine(
            parent=self, id=wx.ID_ANY, size=(
                20, -1), style=wx.LI_HORIZONTAL)
        sizer.Add(line, proportion=0, flag=wx.GROW |
                  wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT, border=5)

        btnsizer = wx.StdDialogButtonSizer()

        btn = Button(parent=self, id=wx.ID_OK)
        btn.SetDefault()
        btnsizer.AddButton(btn)

        btn = Button(parent=self, id=wx.ID_CANCEL)
        btnsizer.AddButton(btn)
        btnsizer.Realize()

        sizer.Add(
            btnsizer,
            proportion=0,
            flag=wx.ALIGN_RIGHT | wx.ALL,
            border=5)

        self.SetSizer(sizer)
        sizer.Fit(self)
        self.Layout()

    def OnRegion(self, event):
        value = self._selection.GetValue()
        if '@' in value:
            value = value.rsplit('@', 1)[0]
        if not grass.legal_name(value):
            GMessage(parent=self,
                     message=_("Name cannot begin with '.' "
                               "and must not contain space, quotes, "
                               "'/', '\'', '@', ',', '=', '*', "
                               "and all other non-alphanumeric characters."))
        else:
            self.wind = value

    def GetName(self):
        """Return region name"""
        return self.wind


class GroupDialog(wx.Dialog):
    """Dialog for creating/editing groups"""

    def __init__(self, parent=None, defaultGroup=None, defaultSubgroup=None,
                 title=_("Create or edit imagery groups"),
                 style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):

        wx.Dialog.__init__(self, parent=parent, id=wx.ID_ANY, title=title,
                           style=style, **kwargs)

        self.parent = parent
        self.defaultGroup = defaultGroup
        self.defaultSubgroup = defaultSubgroup
        self.currentGroup = self.defaultGroup
        self.currentSubgroup = self.defaultGroup

        self.dataChanged = False

        # signaling edit subgroup / group mode
        self.edit_subg = False

        # sungroup maps dict value - ischecked
        self.subgmaps = {}

        # list of group maps
        self.gmaps = []

        # pattern chosen for filtering
        self.flt_pattern = ''

        self.bodySizer = self._createDialogBody()

        # buttons
        btnOk = Button(parent=self, id=wx.ID_OK)
        btnApply = Button(parent=self, id=wx.ID_APPLY)
        btnClose = Button(parent=self, id=wx.ID_CANCEL)

        btnOk.SetToolTip(
            _("Apply changes to selected group and close dialog"))
        btnApply.SetToolTip(_("Apply changes to selected group"))
        btnClose.SetToolTip(_("Close dialog, changes are not applied"))

        # btnOk.SetDefault()

        # sizers & do layout
        # btnSizer = wx.BoxSizer(wx.HORIZONTAL)
        # btnSizer.Add(item = btnClose, proportion = 0,
        #              flag = wx.RIGHT | wx.ALIGN_RIGHT | wx.EXPAND, border = 5)
        # btnSizer.Add(item = btnApply, proportion = 0,
        #              flag = wx.LEFT, border = 5)
        btnSizer = wx.StdDialogButtonSizer()
        btnSizer.AddButton(btnOk)
        btnSizer.AddButton(btnApply)
        btnSizer.AddButton(btnClose)
        btnSizer.Realize()

        mainSizer = wx.BoxSizer(wx.VERTICAL)
        mainSizer.Add(self.bodySizer, proportion=1,
                      flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=10)
        mainSizer.Add(wx.StaticLine(parent=self, id=wx.ID_ANY,
                                    style=wx.LI_HORIZONTAL), proportion=0,
                      flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=10)

        mainSizer.Add(btnSizer, proportion=0, flag=wx.LEFT |
                      wx.RIGHT | wx.BOTTOM | wx.ALIGN_RIGHT, border=10)

        self.SetSizer(mainSizer)
        mainSizer.Fit(self)

        btnOk.Bind(wx.EVT_BUTTON, self.OnOk)
        btnApply.Bind(wx.EVT_BUTTON, self.OnApply)
        btnClose.Bind(wx.EVT_BUTTON, self.OnClose)

        # set dialog min size
        self.SetMinSize(self.GetSize())
        self.SetSize((-1, 400))

    def _createDialogBody(self):
        bodySizer = wx.BoxSizer(wx.VERTICAL)
        # TODO same text in MapLayersDialogBase

        filter_tooltip = _("Put here a regular expression."
                           " Characters '.*' stand for anything,"
                           " character '^' stands for the beginning"
                           " and '$' for the end.")

        # group selection
        bodySizer.Add(StaticText(parent=self, id=wx.ID_ANY,
                                 label=_("Select existing group or "
                                            "enter name of new group:")),
                      flag=wx.ALIGN_CENTER_VERTICAL | wx.TOP, border=10)
        self.groupSelect = Select(parent=self, type='group',
                                  mapsets=[grass.gisenv()['MAPSET']],
                                  size=globalvar.DIALOG_GSELECT_SIZE,
                                  fullyQualified=False)  # searchpath?

        bodySizer.Add(self.groupSelect, flag=wx.TOP | wx.EXPAND, border=5)

        self.subg_chbox = wx.CheckBox(parent=self, id=wx.ID_ANY,
                                      label=_("Edit/create subgroup"))

        bodySizer.Add(self.subg_chbox,
                      flag=wx.ALIGN_CENTER_VERTICAL | wx.TOP, border=10)

        self.subg_panel = wx.Panel(self)
        subg_sizer = wx.BoxSizer(wx.VERTICAL)

        subg_sizer.Add(
            StaticText(
                parent=self.subg_panel,
                id=wx.ID_ANY,
                label=_(
                    "Select existing subgroup or "
                    "enter name of new subgroup:")),
            flag=wx.ALIGN_CENTER_VERTICAL)

        self.subGroupSelect = SubGroupSelect(parent=self.subg_panel)

        subg_sizer.Add(
            self.subGroupSelect,
            flag=wx.EXPAND | wx.TOP,
            border=5)

        self.subg_panel.SetSizer(subg_sizer)

        bodySizer.Add(self.subg_panel, flag=wx.TOP | wx.EXPAND, border=5)

        bodySizer.AddSpacer(10)

        buttonSizer = wx.BoxSizer(wx.VERTICAL)

        # layers in group
        self.gListPanel = wx.Panel(self)

        gListSizer = wx.GridBagSizer(vgap=3, hgap=2)

        self.g_sel_all = wx.CheckBox(parent=self.gListPanel, id=wx.ID_ANY,
                                     label=_("Select all"))

        gListSizer.Add(self.g_sel_all,
                       flag=wx.ALIGN_CENTER_VERTICAL,
                       pos=(0, 1))

        gListSizer.Add(
            StaticText(
                parent=self.gListPanel,
                label=_("Pattern:")),
            flag=wx.ALIGN_CENTER_VERTICAL,
            pos=(
                1,
                0))

        self.gfilter = TextCtrl(parent=self.gListPanel, id=wx.ID_ANY,
                                   value="",
                                   size=(250, -1))
        self.gfilter.SetToolTip(filter_tooltip)

        gListSizer.Add(self.gfilter,
                       flag=wx.EXPAND,
                       pos=(1, 1))

        gListSizer.Add(
            StaticText(
                parent=self.gListPanel,
                label=_("List of maps:")),
            flag=wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM,
            border=5,
            pos=(
                2,
                0))

        sizer = wx.BoxSizer(wx.HORIZONTAL)

        self.gLayerBox = wx.ListBox(
            parent=self.gListPanel, id=wx.ID_ANY, size=(-1, 150),
            style=wx.LB_MULTIPLE | wx.LB_NEEDED_SB)
        sizer.Add(self.gLayerBox, proportion=1, flag=wx.EXPAND)

        self.addLayer = Button(self.gListPanel, id=wx.ID_ADD)
        self.addLayer.SetToolTip(
            _("Select map layers and add them to the list."))
        buttonSizer.Add(self.addLayer, flag=wx.BOTTOM, border=10)

        self.removeLayer = Button(self.gListPanel, id=wx.ID_REMOVE)
        self.removeLayer.SetToolTip(
            _("Remove selected layer(s) from list."))
        buttonSizer.Add(self.removeLayer)
        sizer.Add(buttonSizer, flag=wx.LEFT, border=5)

        gListSizer.Add(sizer, flag=wx.EXPAND, pos=(2, 1))
        gListSizer.AddGrowableCol(1)
        gListSizer.AddGrowableRow(2)

        self.gListPanel.SetSizer(gListSizer)
        bodySizer.Add(self.gListPanel, proportion=1, flag=wx.EXPAND)

        # layers in subgroup
        self.subgListPanel = wx.Panel(self)

        subgListSizer = wx.GridBagSizer(vgap=3, hgap=2)

        # select toggle
        self.subg_sel_all = wx.CheckBox(
            parent=self.subgListPanel,
            id=wx.ID_ANY,
            label=_("Select all"))

        subgListSizer.Add(self.subg_sel_all,
                          flag=wx.ALIGN_CENTER_VERTICAL,
                          pos=(0, 1))

        subgListSizer.Add(
            StaticText(
                parent=self.subgListPanel,
                label=_("Pattern:")),
            flag=wx.ALIGN_CENTER_VERTICAL,
            pos=(
                1,
                0))

        self.subgfilter = TextCtrl(parent=self.subgListPanel, id=wx.ID_ANY,
                                      value="",
                                      size=(250, -1))
        self.subgfilter.SetToolTip(filter_tooltip)

        subgListSizer.Add(self.subgfilter,
                          flag=wx.EXPAND,
                          pos=(1, 1))

        subgListSizer.Add(
            StaticText(
                parent=self.subgListPanel,
                label=_("List of maps:")),
            flag=wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM,
            border=5,
            pos=(
                2,
                0))

        self.subgListBox = CheckListBox(
            parent=self.subgListPanel, id=wx.ID_ANY, size=(250, 100))
        self.subgListBox.SetToolTip(
            _("Check maps from group to be included into subgroup."))

        subgListSizer.Add(self.subgListBox, flag=wx.EXPAND, pos=(2, 1))
        subgListSizer.AddGrowableCol(1)
        subgListSizer.AddGrowableRow(2)

        self.subgListPanel.SetSizer(subgListSizer)
        bodySizer.Add(self.subgListPanel, proportion=1, flag=wx.EXPAND)

        self.infoLabel = StaticText(parent=self, id=wx.ID_ANY)
        bodySizer.Add(
            self.infoLabel,
            flag=wx.ALIGN_CENTER_VERTICAL | wx.TOP | wx.BOTTOM,
            border=5)

        # bindings
        self.gfilter.Bind(wx.EVT_TEXT, self.OnGroupFilter)
        self.subgfilter.Bind(wx.EVT_TEXT, self.OnSubgroupFilter)
        self.gLayerBox.Bind(wx.EVT_LISTBOX, self.OnGLayerCheck)
        self.subgListBox.Bind(wx.EVT_CHECKLISTBOX, self.OnSubgLayerCheck)
        self.groupSelect.GetTextCtrl().Bind(wx.EVT_TEXT, self.OnGroupSelected)
        self.addLayer.Bind(wx.EVT_BUTTON, self.OnAddLayer)
        self.removeLayer.Bind(wx.EVT_BUTTON, self.OnRemoveLayer)
        self.subg_chbox.Bind(wx.EVT_CHECKBOX, self.OnSubgChbox)
        self.subGroupSelect.Bind(
            wx.EVT_TEXT, lambda event: self.SubGroupSelected())
        self.subg_sel_all.Bind(wx.EVT_CHECKBOX, self.OnSubgSelAll)
        self.g_sel_all.Bind(wx.EVT_CHECKBOX, self.OnGSelAll)

        if self.defaultGroup:
            self.groupSelect.SetValue(self.defaultGroup)

        if self.defaultSubgroup is not None:
            self.subGroupSelect.SetValue(self.defaultSubgroup)
            self.subg_chbox.SetValue(1)
            self.SubgChbox(True)
        else:
            self.subg_chbox.SetValue(0)
            self.SubgChbox(False)

        return bodySizer

    def OnGLayerCheck(self, event):
        self._checkGSellAll()

    def OnSubgSelAll(self, event):
        check = event.IsChecked()
        for item in range(self.subgListBox.GetCount()):
            self.CheckSubgItem(item, check)
            self.dataChanged = True

        event.Skip()

    def OnGSelAll(self, event):
        check = event.IsChecked()
        if not check:
            self.gLayerBox.DeselectAll()
        else:
            for item in range(self.subgListBox.GetCount()):
                self.gLayerBox.Select(item)

        event.Skip()

    def _checkGSellAll(self):
        check = False

        nsel = len(self.gLayerBox.GetSelections())
        if self.gLayerBox.GetCount() == nsel and \
           self.gLayerBox.GetCount() != 0:
            check = True

        self.g_sel_all.SetValue(check)

    def _checkSubGSellAll(self):
        not_all_checked = False
        if self.subgListBox.GetCount() == 0:
            not_all_checked = True
        else:
            for item in range(self.subgListBox.GetCount()):
                if not self.subgListBox.IsChecked(item):
                    not_all_checked = True

        self.subg_sel_all.SetValue(not not_all_checked)

    def OnSubgroupFilter(self, event):
        text = event.GetString()
        self.gfilter.ChangeValue(text)
        self.flt_pattern = text

        self.FilterGroup()
        self.FilterSubgroup()

        event.Skip()

    def OnGroupFilter(self, event):
        text = event.GetString()
        self.subgfilter.ChangeValue(text)
        self.flt_pattern = text

        self.FilterGroup()
        self.FilterSubgroup()

        event.Skip()

    def OnSubgLayerCheck(self, event):
        idx = event.GetInt()
        m = self.subgListBox.GetString(idx)
        self.subgmaps[m] = self.subgListBox.IsChecked(idx)
        self.dataChanged = True
        self._checkSubGSellAll()

    def CheckSubgItem(self, idx, val):
        m = self.subgListBox.GetString(idx)
        self.subgListBox.Check(idx, val)
        self.subgmaps[m] = val
        self.dataChanged = val

    def DisableSubgroupEdit(self):
        """Disable editation of subgroups in the dialog

        .. todo::
            used by gcp manager, maybe the gcp m should also support subgroups
        """
        self.edit_subg = False
        self.subg_panel.Hide()
        self.subg_chbox.Hide()
        self.subgListBox.Hide()

        self.Layout()

    def OnSubgChbox(self, event):
        edit_subg = self.subg_chbox.GetValue()
        self.SubgChbox(edit_subg)

    def SubgChbox(self, edit_subg):
        self._checkChange()
        if edit_subg:
            self.edit_subg = edit_subg

            self.SubGroupSelected()
            self._subgroupLayout()
        else:
            self.edit_subg = edit_subg

            self.GroupSelected()
            self._groupLayout()

        self.SetMinSize(self.GetBestSize())

    def _groupLayout(self):
        self.subg_panel.Hide()
        self.subgListPanel.Hide()
        self.gListPanel.Show()
        self.Layout()

    def _subgroupLayout(self):
        self.subg_panel.Show()
        self.subgListPanel.Show()
        self.gListPanel.Hide()
        self.Layout()

    def OnAddLayer(self, event):
        """Add new layer to listbox"""
        dlg = MapLayersDialogForGroups(
            parent=self, title=_("Add selected map layers into group"))

        if dlg.ShowModal() != wx.ID_OK:
            dlg.Destroy()
            return

        layers = dlg.GetMapLayers()
        for layer in layers:
            if layer not in self.gmaps:
                self.gLayerBox.Append(layer)
                self.gmaps.append(layer)
                self.dataChanged = True

    def OnRemoveLayer(self, event):
        """Remove layer from listbox"""
        while self.gLayerBox.GetSelections():
            sel = self.gLayerBox.GetSelections()[0]
            m = self.gLayerBox.GetString(sel)
            self.gLayerBox.Delete(sel)
            self.gmaps.remove(m)
            self.dataChanged = True

    def GetLayers(self):
        """Get layers"""
        if self.edit_subg:
            layers = []
            for maps, sel in six.iteritems(self.subgmaps):
                if sel:
                    layers.append(maps)
        else:
            layers = self.gmaps[:]

        return layers

    def OnGroupSelected(self, event):
        """Text changed in group selector"""
        # callAfter must be called to close popup before other actions
        wx.CallAfter(self.GroupSelected)

    def GroupSelected(self):
        """Group was selected, check if changes were apllied"""
        self._checkChange()
        group, s = self.GetSelectedGroup()
        maps = list()
        groups = self.GetExistGroups()
        if group in groups:
            maps = self.GetGroupLayers(group)

        self.subGroupSelect.Insert(group)

        self.gmaps = maps
        maps = self._filter(maps)

        self.ShowGroupLayers(maps)
        self.currentGroup = group

        self.SubGroupSelected()
        self.ClearNotification()

        self._checkGSellAll()

    def FilterGroup(self):
        maps = self._filter(self.gmaps)
        self.ShowGroupLayers(maps)
        self._checkGSellAll()

    def FilterSubgroup(self):
        maps = self._filter(self.gmaps)
        self.subgListBox.Set(maps)

        for i, m in enumerate(maps):
            if m in six.iterkeys(self.subgmaps) and self.subgmaps[m]:
                self.subgListBox.Check(i)

        self._checkSubGSellAll()

    def SubGroupSelected(self):
        """Subgroup was selected, check if changes were apllied"""
        self._checkChange()

        subgroup = self.subGroupSelect.GetValue().strip()
        group = self.currentGroup

        gmaps = list()
        groups = self.GetExistGroups()

        self.subgmaps = {}
        if group in groups:
            gmaps = self.GetGroupLayers(group)
            if subgroup:
                maps = self.GetGroupLayers(group, subgroup)
                for m in maps:
                    if m in gmaps:
                        self.subgmaps[m] = True
                    else:
                        self.subgmaps[m] = False

        gmaps = self._filter(gmaps)
        self.subgListBox.Set(gmaps)

        for i, m in enumerate(gmaps):
            if m in self.subgmaps:
                self.subgListBox.Check(i)
            else:
                self.subgListBox.Check(i, False)

        self._checkSubGSellAll()
        self.currentSubgroup = subgroup
        self.ClearNotification()

    def _filter(self, data):
        """Apply filter for strings in data list"""
        flt_data = []
        if len(self.flt_pattern) == 0:
            flt_data = data[:]
            return flt_data

        for dt in data:
            try:
                if re.compile(self.flt_pattern).search(dt):
                    flt_data.append(dt)
            except:
                pass

        return flt_data

    def _checkChange(self):
        if self.edit_subg:
            self._checkSubgroupChange()
        else:
            self._checkGroupChange()

    def _checkGroupChange(self):
        if self.currentGroup and self.dataChanged:
            dlg = wx.MessageDialog(
                self,
                message=_(
                    "Group <%s> was changed, "
                    "do you want to apply changes?") %
                self.currentGroup,
                caption=_("Unapplied changes"),
                style=wx.YES_NO | wx.ICON_QUESTION | wx.YES_DEFAULT)
            if dlg.ShowModal() == wx.ID_YES:
                self.ApplyChanges()

            dlg.Destroy()
        self.dataChanged = False

    def _checkSubgroupChange(self):
        if self.currentSubgroup and self.dataChanged:
            dlg = wx.MessageDialog(
                self,
                message=_(
                    "Subgroup <%s> was changed, "
                    "do you want to apply changes?") %
                self.currentSubgroup,
                caption=_("Unapplied changes"),
                style=wx.YES_NO | wx.ICON_QUESTION | wx.YES_DEFAULT)
            if dlg.ShowModal() == wx.ID_YES:
                self.ApplyChanges()

            dlg.Destroy()
        self.dataChanged = False

    def ShowGroupLayers(self, mapList):
        """Show map layers in currently selected group"""
        self.gLayerBox.Set(mapList)

    def EditGroup(self, group, subgroup=None):
        """Edit selected group"""
        layersNew = self.GetLayers()
        layersOld = self.GetGroupLayers(group, subgroup)

        add = []
        remove = []
        for layerNew in layersNew:
            if layerNew not in layersOld:
                add.append(layerNew)

        for layerOld in layersOld:
            if layerOld not in layersNew:
                remove.append(layerOld)

        kwargs = {}
        if subgroup:
            kwargs["subgroup"] = subgroup

        ret = None
        if remove:
            ret = RunCommand('i.group',
                             parent=self,
                             group=group,
                             flags='r',
                             input=','.join(remove),
                             **kwargs)

        if add:
            ret = RunCommand('i.group',
                             parent=self,
                             group=group,
                             input=','.join(add),
                             **kwargs)

        return ret

    def CreateNewGroup(self, group, subgroup):
        """Create new group"""
        layers = self.GetLayers()
        if not layers:
            GMessage(parent=self,
                     message=_("No raster maps selected."))
            return 1

        kwargs = {}
        if subgroup:
            kwargs["subgroup"] = subgroup

        ret = RunCommand('i.group',
                         parent=self,
                         group=group,
                         input=layers,
                         **kwargs)
        # update subgroup select
        self.SubGroupSelected()
        return ret

    def GetExistGroups(self):
        """Returns existing groups in current mapset"""
        return grass.list_grouped('group')[grass.gisenv()['MAPSET']]

    def GetExistSubgroups(self, group):
        """Returns existing subgroups in a group"""
        return RunCommand('i.group', group=group,
                          read=True, flags='sg').splitlines()

    def ShowResult(self, group, returnCode, create):
        """Show if operation was successfull."""
        group += '@' + grass.gisenv()['MAPSET']
        if returnCode is None:
            label = _("No changes to apply in group <%s>.") % group
        elif returnCode == 0:
            if create:
                label = _("Group <%s> was successfully created.") % group
            else:
                label = _("Group <%s> was successfully changed.") % group
        else:
            if create:
                label = _("Creating of new group <%s> failed.") % group
            else:
                label = _("Changing of group <%s> failed.") % group

        self.infoLabel.SetLabel(label)
        wx.FutureCall(4000, self.ClearNotification)

    def GetSelectedGroup(self):
        """Return currently selected group (without mapset)"""
        g = self.groupSelect.GetValue().split('@')[0]
        if self.edit_subg:
            s = self.subGroupSelect.GetValue()
        else:
            s = None
        return g, s

    def GetGroupLayers(self, group, subgroup=None):
        """Get layers in group"""
        kwargs = dict()
        kwargs['group'] = group
        if subgroup:
            kwargs['subgroup'] = subgroup

        res = RunCommand('i.group',
                         parent=self,
                         flags='g',
                         read=True, **kwargs)
        if not res:
            return []
        return res.splitlines()

    def ClearNotification(self):
        """Clear notification string"""
        self.infoLabel.SetLabel("")

    def ApplyChanges(self):
        """Create or edit group"""
        group = self.currentGroup
        if not group:
            GMessage(parent=self,
                     message=_("No group selected."))
            return False

        if self.edit_subg and not self.currentSubgroup:
            GMessage(parent=self,
                     message=_("No subgroup selected."))
            return 0

        if self.edit_subg:
            subgroup = self.currentSubgroup
        else:
            subgroup = None

        groups = self.GetExistGroups()
        if group in groups:
            ret = self.EditGroup(group, subgroup)
            self.ShowResult(group=group, returnCode=ret, create=False)

        else:
            ret = self.CreateNewGroup(group, subgroup)
            self.ShowResult(group=group, returnCode=ret, create=True)

        self.dataChanged = False

        return True

    def OnApply(self, event):
        """Apply changes"""
        self.ApplyChanges()

    def OnOk(self, event):
        """Apply changes and close dialog"""
        if self.ApplyChanges():
            self.OnClose(event)

    def OnClose(self, event):
        """Close dialog"""
        if not self.IsModal():
            self.Destroy()
        event.Skip()


class MapLayersDialogBase(wx.Dialog):
    """Base dialog for selecting map layers (raster, vector).

    There are 3 subclasses: MapLayersDialogForGroups, MapLayersDialogForModeler,
    MapLayersDialog. Base class contains core functionality.
    """

    def __init__(self, parent, title,
                 style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
        wx.Dialog.__init__(self, parent=parent, id=wx.ID_ANY, title=title,
                           style=style, **kwargs)

        self.parent = parent  # GMFrame or ?

        self.applyAddingMapLayers = Signal(
            'MapLayersDialogBase.applyAddingMapLayers')

        self.mainSizer = wx.BoxSizer(wx.VERTICAL)

        # dialog body
        self.bodySizer = self._createDialogBody()
        self.mainSizer.Add(self.bodySizer, proportion=1,
                           flag=wx.EXPAND | wx.ALL, border=5)

        # update list of layer to be loaded
        self.map_layers = []  # list of map layers (full list type/mapset)
        self.LoadMapLayers(self.GetLayerType(cmd=True),
                           self.mapset.GetStringSelection())

        self._fullyQualifiedNames()
        self._modelerDSeries()

        # buttons
        btnCancel = Button(parent=self, id=wx.ID_CANCEL)
        btnOk = Button(parent=self, id=wx.ID_OK)
        btnOk.SetDefault()

        # sizers & do layout
        self.btnSizer = wx.StdDialogButtonSizer()
        self.btnSizer.AddButton(btnCancel)
        self.btnSizer.AddButton(btnOk)
        self._addApplyButton()
        self.btnSizer.Realize()

        self.mainSizer.Add(self.btnSizer, proportion=0,
                           flag=wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border=5)

        self.SetSizer(self.mainSizer)
        self.mainSizer.Fit(self)

        # set dialog min size
        self.SetMinSize(self.GetSize())

    def _modelerDSeries(self):
        """Method used only by MapLayersDialogForModeler,
        for other subclasses does nothing.
        """
        pass

    def _addApplyButton(self):
        """Method used only by MapLayersDialog,
        for other subclasses does nothing.
        """
        pass

    def _fullyQualifiedNames(self):
        """Adds CheckBox which determines is fully qualified names are retuned.
        """
        self.fullyQualified = wx.CheckBox(
            parent=self, id=wx.ID_ANY,
            label=_("Use fully-qualified map names"))
        self.fullyQualified.SetValue(True)
        self.mainSizer.Add(self.fullyQualified, proportion=0,
                           flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=5)

    def _useFullyQualifiedNames(self):
        return self.fullyQualified.IsChecked()

    def _layerTypes(self):
        """Determines which layer types can be chosen.

         Valid values:
         - raster
         - raster3d
         - vector
         """
        return [_('raster'), _('3D raster'), _('vector')]

    def _selectAll(self):
        """Check all layers by default"""
        return True

    def _createDialogBody(self):
        bodySizer = wx.GridBagSizer(vgap=3, hgap=3)

        # layer type
        bodySizer.Add(StaticText(parent=self, label=_("Map type:")),
                      flag=wx.ALIGN_CENTER_VERTICAL,
                      pos=(0, 0))

        self.layerType = wx.Choice(parent=self, id=wx.ID_ANY,
                                   choices=self._layerTypes(), size=(100, -1))

        self.layerType.SetSelection(0)

        bodySizer.Add(self.layerType,
                      pos=(0, 1))
        self.layerType.Bind(wx.EVT_CHOICE, self.OnChangeParams)

        # select toggle
        self.toggle = wx.CheckBox(parent=self, id=wx.ID_ANY,
                                  label=_("Select toggle"))
        self.toggle.SetValue(self._selectAll())
        bodySizer.Add(self.toggle,
                      flag=wx.ALIGN_CENTER_VERTICAL,
                      pos=(0, 2))

        # mapset filter
        bodySizer.Add(StaticText(parent=self, label=_("Mapset:")),
                      flag=wx.ALIGN_CENTER_VERTICAL,
                      pos=(1, 0))

        self.mapset = MapsetSelect(parent=self, searchPath=True)
        self.mapset.SetStringSelection(grass.gisenv()['MAPSET'])
        bodySizer.Add(self.mapset,
                      pos=(1, 1), span=(1, 2))

        # map name filter
        bodySizer.Add(StaticText(parent=self, label=_("Pattern:")),
                      flag=wx.ALIGN_CENTER_VERTICAL,
                      pos=(2, 0))

        self.filter = TextCtrl(parent=self, id=wx.ID_ANY,
                               value="",
                               size=(250, -1))
        bodySizer.Add(self.filter,
                      flag=wx.EXPAND,
                      pos=(2, 1), span=(1, 2))

        self.filter.SetFocus()
        # TODO same text in GroupDialog
        self.filter.SetToolTip(
            _(
                "Put here a regular expression."
                " Characters '.*' stand for anything,"
                " character '^' stands for the beginning"
                " and '$' for the end."))

        # layer list
        bodySizer.Add(
            StaticText(
                parent=self,
                label=_("List of maps:")),
            flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_TOP,
            pos=(
                3,
                0))
        self.layers = CheckListBox(parent=self, id=wx.ID_ANY,
                                   size=(250, 100),
                                   choices=[])
        bodySizer.Add(self.layers,
                      flag=wx.EXPAND,
                      pos=(3, 1), span=(1, 2))

        bodySizer.AddGrowableCol(1)
        bodySizer.AddGrowableRow(3)

        # bindings
        self.mapset.Bind(wx.EVT_TEXT, self.OnChangeParams)
        self.mapset.Bind(wx.EVT_COMBOBOX, self.OnChangeParams)
        self.layers.Bind(wx.EVT_RIGHT_DOWN, self.OnMenu)
        self.filter.Bind(wx.EVT_TEXT, self.OnFilter)
        self.toggle.Bind(wx.EVT_CHECKBOX, self.OnToggle)

        return bodySizer

    def LoadMapLayers(self, type, mapset):
        """Load list of map layers

        :param str type: layer type ('raster' or 'vector')
        :param str mapset: mapset name
        """
        self.map_layers = grass.list_grouped(type=type)[mapset]
        self.layers.Set(natural_sort(self.map_layers))

        # check all items by default
        for item in range(self.layers.GetCount()):

            self.layers.Check(item, check=self._selectAll())

    def OnChangeParams(self, event):
        """Filter parameters changed by user"""
        # update list of layer to be loaded
        self.LoadMapLayers(self.GetLayerType(cmd=True),
                           self.mapset.GetStringSelection())

        event.Skip()

    def OnMenu(self, event):
        """Table description area, context menu"""
        if not hasattr(self, "popupID1"):
            self.popupDataID1 = wx.NewId()
            self.popupDataID2 = wx.NewId()
            self.popupDataID3 = wx.NewId()

            self.Bind(wx.EVT_MENU, self.OnSelectAll, id=self.popupDataID1)
            self.Bind(wx.EVT_MENU, self.OnSelectInvert, id=self.popupDataID2)
            self.Bind(wx.EVT_MENU, self.OnDeselectAll, id=self.popupDataID3)

        # generate popup-menu
        menu = Menu()
        menu.Append(self.popupDataID1, _("Select all"))
        menu.Append(self.popupDataID2, _("Invert selection"))
        menu.Append(self.popupDataID3, _("Deselect all"))

        self.PopupMenu(menu)
        menu.Destroy()

    def OnSelectAll(self, event):
        """Select all map layer from list"""
        for item in range(self.layers.GetCount()):
            self.layers.Check(item, True)

    def OnSelectInvert(self, event):
        """Invert current selection"""
        for item in range(self.layers.GetCount()):
            if self.layers.IsChecked(item):
                self.layers.Check(item, False)
            else:
                self.layers.Check(item, True)

    def OnDeselectAll(self, event):
        """Select all map layer from list"""
        for item in range(self.layers.GetCount()):
            self.layers.Check(item, False)

    def OnFilter(self, event):
        """Apply filter for map names"""
        if len(event.GetString()) == 0:
            self.layers.Set(self.map_layers)
            return

        list = []
        for layer in self.map_layers:
            try:
                if re.compile(event.GetString()).search(layer):
                    list.append(layer)
            except:
                pass
        list = natural_sort(list)

        self.layers.Set(list)
        self.OnSelectAll(None)

        event.Skip()

    def OnToggle(self, event):
        """Select toggle (check or uncheck all layers)"""
        check = event.IsChecked()
        for item in range(self.layers.GetCount()):
            self.layers.Check(item, check)

        event.Skip()

    def GetMapLayers(self):
        """Return list of checked map layers"""
        layerNames = []
        for indx in self.layers.GetSelections():
            # layers.append(self.layers.GetStringSelec(indx))
            pass

        mapset = self.mapset.GetStringSelection()
        for item in range(self.layers.GetCount()):
            if not self.layers.IsChecked(item):
                continue
            if self._useFullyQualifiedNames():
                layerNames.append(self.layers.GetString(item) + '@' + mapset)
            else:
                layerNames.append(self.layers.GetString(item))

        return layerNames

    def GetLayerType(self, cmd=False):
        """Get selected layer type

        :param bool cmd: True for g.list
        """
        if not cmd:
            return self.layerType.GetStringSelection()

        sel = self.layerType.GetSelection()
        if sel == 0:
            ltype = 'raster'
        elif sel == 1:
            ltype = 'raster_3d'
        else:
            ltype = 'vector'

        return ltype


class MapLayersDialog(MapLayersDialogBase):
    """Subclass of MapLayersDialogBase used in Layer Manager.

    Contains apply button, which sends wxApplyMapLayers event.
    """

    def __init__(self, parent, title, **kwargs):
        MapLayersDialogBase.__init__(
            self, parent=parent, title=title, **kwargs)

    def _addApplyButton(self):
        btnApply = Button(parent=self, id=wx.ID_APPLY)
        self.btnSizer.AddButton(btnApply)
        btnApply.Bind(wx.EVT_BUTTON, self.OnApply)

    def OnApply(self, event):
        self.applyAddingMapLayers.emit(mapLayers=self.GetMapLayers(),
                                       ltype=self.GetLayerType(cmd=True))


class MapLayersDialogForGroups(MapLayersDialogBase):
    """Subclass of MapLayersDialogBase used for specyfying maps in an imagery group.

    Shows only raster maps.
    """

    def __init__(self, parent, title, **kwargs):
        MapLayersDialogBase.__init__(
            self, parent=parent, title=title, **kwargs)

    def _layerTypes(self):
        return [_('raster'), ]

    def _selectAll(self):
        """Could be overriden"""
        return False

    def _fullyQualifiedNames(self):
        pass

    def _useFullyQualifiedNames(self):
        return True


class MapLayersDialogForModeler(MapLayersDialogBase):
    """Subclass of MapLayersDialogBase used in Modeler.
    """

    def __init__(self, parent, title, **kwargs):
        MapLayersDialogBase.__init__(
            self, parent=parent, title=title, **kwargs)

    def _modelerDSeries(self):
        self.dseries = wx.CheckBox(parent=self, id=wx.ID_ANY,
                                   label=_("Dynamic series (%s)") % 'g.list')
        self.dseries.SetValue(False)
        self.mainSizer.Add(self.dseries, proportion=0,
                           flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=5)

    def GetDSeries(self):
        """Used by modeler only

        :return: g.list command
        """
        if not self.dseries or not self.dseries.IsChecked():
            return ''

        cond = 'map in `g.list type=%s ' % self.GetLayerType(cmd=True)
        patt = self.filter.GetValue()
        if patt:
            cond += 'pattern=%s ' % patt
        cond += 'mapset=%s`' % self.mapset.GetStringSelection()

        return cond


class SetOpacityDialog(wx.Dialog):
    """Set opacity of map layers.
    Dialog expects opacity between 0 and 1 and returns this range, too.
    """

    def __init__(self, parent, id=wx.ID_ANY, title=_("Set Map Layer Opacity"),
                 size=wx.DefaultSize, pos=wx.DefaultPosition,
                 style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, opacity=1):

        self.parent = parent    # GMFrame
        self.opacity = opacity  # current opacity

        super(
            SetOpacityDialog,
            self).__init__(
            parent,
            id=id,
            pos=pos,
            size=size,
            style=style,
            title=title)

        self.applyOpacity = Signal('SetOpacityDialog.applyOpacity')
        panel = wx.Panel(parent=self, id=wx.ID_ANY)

        sizer = wx.BoxSizer(wx.VERTICAL)

        box = wx.GridBagSizer(vgap=5, hgap=5)
        self.value = wx.Slider(
            panel, id=wx.ID_ANY, value=int(self.opacity * 100),
            style=wx.SL_HORIZONTAL | wx.SL_AUTOTICKS | wx.SL_TOP | wx.SL_LABELS,
            minValue=0, maxValue=100, size=(350, -1))

        box.Add(self.value,
                flag=wx.ALIGN_CENTRE, pos=(0, 0), span=(1, 2))
        box.Add(StaticText(parent=panel, id=wx.ID_ANY,
                           label=_("transparent")),
                pos=(1, 0))
        box.Add(StaticText(parent=panel, id=wx.ID_ANY,
                           label=_("opaque")),
                flag=wx.ALIGN_RIGHT,
                pos=(1, 1))

        sizer.Add(box, proportion=0,
                  flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)

        line = wx.StaticLine(parent=panel, id=wx.ID_ANY,
                             style=wx.LI_HORIZONTAL)
        sizer.Add(line, proportion=0,
                  flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)

        # buttons
        btnsizer = wx.StdDialogButtonSizer()

        btnOK = Button(parent=panel, id=wx.ID_OK)
        btnOK.SetDefault()
        btnsizer.AddButton(btnOK)

        btnCancel = Button(parent=panel, id=wx.ID_CANCEL)
        btnsizer.AddButton(btnCancel)

        btnApply = Button(parent=panel, id=wx.ID_APPLY)
        btnApply.Bind(wx.EVT_BUTTON, self.OnApply)
        btnsizer.AddButton(btnApply)
        btnsizer.Realize()

        sizer.Add(btnsizer, proportion=0,
                  flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)

        panel.SetSizer(sizer)
        sizer.Fit(panel)

        self.SetSize(self.GetBestSize())

        self.Layout()

    def GetOpacity(self):
        """Button 'OK' pressed"""
        # return opacity value
        opacity = float(self.value.GetValue()) / 100
        return opacity

    def OnApply(self, event):
        self.applyOpacity.emit(value=self.GetOpacity())


def GetImageHandlers(image):
    """Get list of supported image handlers"""
    lext = list()
    ltype = list()
    try:
        for h in image.GetHandlers():
            lext.append(h.GetExtension())
    except AttributeError:
        lext = {'png', 'gif', 'jpg', 'pcx', 'pnm', 'tif', 'xpm'}

    filetype = ''
    if 'png' in lext:
        filetype += "PNG file (*.png)|*.png|"
        ltype.append({'type': wx.BITMAP_TYPE_PNG,
                      'ext': 'png'})
    filetype += "BMP file (*.bmp)|*.bmp|"
    ltype.append({'type': wx.BITMAP_TYPE_BMP,
                  'ext': 'bmp'})
    if 'gif' in lext:
        filetype += "GIF file (*.gif)|*.gif|"
        ltype.append({'type': wx.BITMAP_TYPE_GIF,
                      'ext': 'gif'})

    if 'jpg' in lext:
        filetype += "JPG file (*.jpg)|*.jpg|"
        ltype.append({'type': wx.BITMAP_TYPE_JPEG,
                      'ext': 'jpg'})

    if 'pcx' in lext:
        filetype += "PCX file (*.pcx)|*.pcx|"
        ltype.append({'type': wx.BITMAP_TYPE_PCX,
                      'ext': 'pcx'})

    if 'pnm' in lext:
        filetype += "PNM file (*.pnm)|*.pnm|"
        ltype.append({'type': wx.BITMAP_TYPE_PNM,
                      'ext': 'pnm'})

    if 'tif' in lext:
        filetype += "TIF file (*.tif)|*.tif|"
        ltype.append({'type': wx.BITMAP_TYPE_TIF,
                      'ext': 'tif'})

    if 'xpm' in lext:
        filetype += "XPM file (*.xpm)|*.xpm"
        ltype.append({'type': wx.BITMAP_TYPE_XPM,
                      'ext': 'xpm'})

    return filetype, ltype


class ImageSizeDialog(wx.Dialog):
    """Set size for saved graphic file"""

    def __init__(self, parent, id=wx.ID_ANY, title=_("Set image size"),
                 style=wx.DEFAULT_DIALOG_STYLE, **kwargs):
        self.parent = parent

        wx.Dialog.__init__(
            self,
            parent,
            id=id,
            style=style,
            title=title,
            **kwargs)

        self.panel = wx.Panel(parent=self, id=wx.ID_ANY)

        self.box = StaticBox(parent=self.panel, id=wx.ID_ANY,
                             label=' % s' % _("Image size"))

        size = self.parent.GetWindow().GetClientSize()
        self.width = SpinCtrl(parent=self.panel, id=wx.ID_ANY,
                              style=wx.SP_ARROW_KEYS)
        self.width.SetRange(20, 1e6)
        self.width.SetValue(size.width)
        wx.CallAfter(self.width.SetFocus)
        self.height = SpinCtrl(parent=self.panel, id=wx.ID_ANY,
                               style=wx.SP_ARROW_KEYS)
        self.height.SetRange(20, 1e6)
        self.height.SetValue(size.height)
        self.template = wx.Choice(parent=self.panel, id=wx.ID_ANY,
                                  size=(125, -1),
                                  choices=["",
                                           "640x480",
                                           "800x600",
                                           "1024x768",
                                           "1280x960",
                                           "1600x1200",
                                           "1920x1440"])

        self.btnOK = Button(parent=self.panel, id=wx.ID_OK)
        self.btnOK.SetDefault()
        self.btnCancel = Button(parent=self.panel, id=wx.ID_CANCEL)

        self.template.Bind(wx.EVT_CHOICE, self.OnTemplate)

        self._layout()
        self.SetSize(self.GetBestSize())

    def _layout(self):
        """Do layout"""
        sizer = wx.BoxSizer(wx.VERTICAL)

        # body
        box = wx.StaticBoxSizer(self.box, wx.HORIZONTAL)
        fbox = wx.FlexGridSizer(cols=2, vgap=5, hgap=5)
        fbox.Add(StaticText(parent=self.panel, id=wx.ID_ANY,
                            label=_("Width:")),
                 flag=wx.ALIGN_CENTER_VERTICAL)
        fbox.Add(self.width)
        fbox.Add(StaticText(parent=self.panel, id=wx.ID_ANY,
                            label=_("Height:")),
                 flag=wx.ALIGN_CENTER_VERTICAL)
        fbox.Add(self.height)
        fbox.Add(StaticText(parent=self.panel, id=wx.ID_ANY,
                            label=_("Template:")),
                 flag=wx.ALIGN_CENTER_VERTICAL)
        fbox.Add(self.template)

        box.Add(fbox, proportion=1,
                flag=wx.EXPAND | wx.ALL, border=5)
        sizer.Add(box, proportion=1,
                  flag=wx.EXPAND | wx.ALL, border=3)

        # buttons
        btnsizer = wx.StdDialogButtonSizer()
        btnsizer.AddButton(self.btnOK)
        btnsizer.AddButton(self.btnCancel)
        btnsizer.Realize()

        sizer.Add(btnsizer, proportion=0,
                  flag=wx.EXPAND | wx.ALIGN_RIGHT | wx.ALL, border=5)

        self.panel.SetSizer(sizer)
        sizer.Fit(self.panel)
        self.Layout()

    def GetValues(self):
        """Get width/height values"""
        return self.width.GetValue(), self.height.GetValue()

    def OnTemplate(self, event):
        """Template selected"""
        sel = event.GetString()
        if not sel:
            width, height = self.parent.GetWindow().GetClientSize()
        else:
            width, height = map(int, sel.split('x'))
        self.width.SetValue(width)
        self.height.SetValue(height)


class SqlQueryFrame(wx.Frame):

    def __init__(self, parent, id=wx.ID_ANY,
                 title=_("GRASS GIS SQL Query Utility"),
                 *kwargs):
        """SQL Query Utility window
        """
        self.parent = parent

        wx.Frame.__init__(self, parent=parent, id=id, title=title, *kwargs)
        self.SetIcon(
            wx.Icon(
                os.path.join(
                    globalvar.ICONDIR,
                    'grass_sql.ico'),
                wx.BITMAP_TYPE_ICO))
        self.panel = wx.Panel(parent=self, id=wx.ID_ANY)

        self.sqlBox = StaticBox(parent=self.panel, id=wx.ID_ANY,
                                label=_(" SQL statement "))
        self.sql = TextCtrl(parent=self.panel, id=wx.ID_ANY,
                            style=wx.TE_MULTILINE)

        self.btnApply = Button(parent=self.panel, id=wx.ID_APPLY)
        self.btnCancel = Button(parent=self.panel, id=wx.ID_CANCEL)
        self.Bind(wx.EVT_BUTTON, self.OnCloseWindow, self.btnCancel)

        self._layout()

        self.SetMinSize(wx.Size(300, 150))
        self.SetSize(wx.Size(500, 200))

    def _layout(self):
        """Do layout"""
        sizer = wx.BoxSizer(wx.VERTICAL)

        sqlSizer = wx.StaticBoxSizer(self.sqlBox, wx.HORIZONTAL)
        sqlSizer.Add(self.sql, proportion=1,
                     flag=wx.EXPAND)

        btnSizer = wx.StdDialogButtonSizer()
        btnSizer.AddButton(self.btnApply)
        btnSizer.AddButton(self.btnCancel)
        btnSizer.Realize()

        sizer.Add(sqlSizer, proportion=1,
                  flag=wx.EXPAND | wx.ALL, border=5)
        sizer.Add(btnSizer, proportion=0,
                  flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border=5)

        self.panel.SetSizer(sizer)

        self.Layout()

    def OnCloseWindow(self, event):
        """Close window
        """
        self.Close()


class SymbolDialog(wx.Dialog):
    """Dialog for GRASS symbols selection.

    Dialog is called in gui_core::forms module.
    """

    def __init__(self, parent, symbolPath,
                 currentSymbol=None, title=_("Symbols")):
        """Dialog constructor.

        It is assumed that symbolPath contains folders with symbols.

        :param parent: dialog parent
        :param symbolPath: absolute path to symbols
        :param currentSymbol: currently selected symbol (e.g. 'basic/x')
        :param title: dialog title
        """
        wx.Dialog.__init__(self, parent=parent, title=title, id=wx.ID_ANY)

        self.symbolPath = symbolPath
        self.currentSymbol = currentSymbol  # default basic/x
        self.selected = None
        self.selectedDir = None

        self._layout()

    def _layout(self):
        mainPanel = wx.Panel(self, id=wx.ID_ANY)
        mainSizer = wx.BoxSizer(wx.VERTICAL)
        vSizer = wx.BoxSizer(wx.VERTICAL)
        fgSizer = wx.FlexGridSizer(rows=2, cols=2, vgap=5, hgap=5)
        self.folderChoice = wx.Choice(
            mainPanel,
            id=wx.ID_ANY,
            choices=os.listdir(
                self.symbolPath))
        self.folderChoice.Bind(wx.EVT_CHOICE, self.OnFolderSelect)

        fgSizer.Add(
            StaticText(
                mainPanel,
                id=wx.ID_ANY,
                label=_("Symbol directory:")),
            proportion=0,
            flag=wx.ALIGN_CENTER_VERTICAL)

        fgSizer.Add(self.folderChoice, proportion=0,
                    flag=wx.ALIGN_CENTER, border=0)

        self.infoLabel = StaticText(mainPanel, id=wx.ID_ANY)
        fgSizer.Add(
            StaticText(
                mainPanel,
                id=wx.ID_ANY,
                label=_("Symbol name:")),
            flag=wx.ALIGN_CENTRE_VERTICAL)
        fgSizer.Add(self.infoLabel, proportion=0,
                    flag=wx.ALIGN_CENTRE_VERTICAL)
        vSizer.Add(fgSizer, proportion=0, flag=wx.ALL, border=5)

        self.panels = self._createSymbolPanels(mainPanel)
        for panel in self.panels:
            vSizer.Add(panel, proportion=0, flag=wx.ALL | wx.EXPAND, border=5)

        mainSizer.Add(vSizer, proportion=1, flag=wx.ALL | wx.EXPAND, border=5)
        self.btnCancel = Button(parent=mainPanel, id=wx.ID_CANCEL)
        self.btnOK = Button(parent=mainPanel, id=wx.ID_OK)
        self.btnOK.SetDefault()
        self.btnOK.Enable(False)

        # buttons
        btnSizer = wx.StdDialogButtonSizer()
        btnSizer.AddButton(self.btnCancel)
        btnSizer.AddButton(self.btnOK)
        btnSizer.Realize()
        mainSizer.Add(btnSizer, proportion=0,
                      flag=wx.EXPAND | wx.ALL, border=5)

        # show panel with the largest number of images and fit size
        count = []
        for folder in os.listdir(self.symbolPath):
            count.append(
                len(os.listdir(os.path.join(self.symbolPath, folder))))

        index = count.index(max(count))
        self.folderChoice.SetSelection(index)
        self.OnFolderSelect(None)
        self.infoLabel.Show()

        mainPanel.SetSizerAndFit(mainSizer)
        self.SetSize(self.GetBestSize())

        # show currently selected symbol
        if self.currentSymbol:
            # set directory
            self.selectedDir, self.selected = os.path.split(self.currentSymbol)
            self.folderChoice.SetStringSelection(self.selectedDir)
            # select symbol
            panelIdx = self.folderChoice.GetSelection()
            for panel in self.symbolPanels[panelIdx]:
                if panel.GetName() == self.selected:
                    panel.Select()
        else:
            self.folderChoice.SetSelection(0)

        self.OnFolderSelect(None)

    def _createSymbolPanels(self, parent):
        """Creates multiple panels with symbols.

        Panels are shown/hidden according to selected folder."""
        folders = os.listdir(self.symbolPath)

        panels = []
        self.symbolPanels = []

        for folder in folders:
            panel = wx.Panel(parent, style=wx.BORDER_RAISED)
            sizer = wx.GridSizer(cols=6, vgap=3, hgap=3)
            images = self._getSymbols(
                path=os.path.join(
                    self.symbolPath, folder))

            symbolPanels = []
            for img in images:
                iP = SingleSymbolPanel(parent=panel, symbolPath=img)
                iP.symbolSelectionChanged.connect(self.SelectionChanged)
                sizer.Add(iP, proportion=0, flag=wx.ALIGN_CENTER)
                symbolPanels.append(iP)

            panel.SetSizerAndFit(sizer)
            panel.Hide()
            panels.append(panel)
            self.symbolPanels.append(symbolPanels)

        return panels

    def _getSymbols(self, path):
        # we assume that images are in subfolders (1 level only)
        imageList = []
        for image in os.listdir(path):
            imageList.append(os.path.join(path, image))

        return sorted(imageList)

    def OnFolderSelect(self, event):
        """Selected folder with symbols changed."""
        idx = self.folderChoice.GetSelection()
        for i in range(len(self.panels)):
            sizer = self.panels[i].GetContainingSizer()
            sizer.Show(self.panels[i], i == idx, recursive=True)
            sizer.Layout()

        if self.selectedDir == self.folderChoice.GetStringSelection():
            self.btnOK.Enable()
            self.infoLabel.SetLabel(self.selected)
        else:
            self.btnOK.Disable()
            self.infoLabel.SetLabel('')

    def SelectionChanged(self, name, doubleClick):
        """Selected symbol changed."""
        if doubleClick:
            self.EndModal(wx.ID_OK)
        # deselect all
        for i in range(len(self.panels)):
            for panel in self.symbolPanels[i]:
                if panel.GetName() != name:
                    panel.Deselect()

        self.btnOK.Enable()

        self.selected = name
        self.selectedDir = self.folderChoice.GetStringSelection()

        self.infoLabel.SetLabel(name)

    def GetSelectedSymbolName(self):
        """Returns currently selected symbol name (e.g. 'basic/x').
        """
        # separator must be '/' and not dependent on OS
        return self.selectedDir + '/' + self.selected

    def GetSelectedSymbolPath(self):
        """Returns currently selected symbol full path.
        """
        return os.path.join(self.symbolPath, self.selectedDir, self.selected)


class TextEntryDialog(wx.Dialog):
    """Simple dialog with text field.

    It differs from wx.TextEntryDialog because it allows adding validator.
    """

    def __init__(
            self, parent, message, caption='', defaultValue='',
            validator=wx.DefaultValidator, style=wx.OK | wx.CANCEL | wx.CENTRE,
            textStyle=0, textSize=(300, -1),
            **kwargs):
        wx.Dialog.__init__(
            self,
            parent=parent,
            id=wx.ID_ANY,
            title=caption,
            **kwargs)

        vbox = wx.BoxSizer(wx.VERTICAL)

        stline = StaticText(self, id=wx.ID_ANY, label=message)
        vbox.Add(stline, proportion=0, flag=wx.EXPAND | wx.ALL, border=10)

        self._textCtrl = TextCtrl(
            self,
            id=wx.ID_ANY,
            value=defaultValue,
            validator=validator,
            style=textStyle)
        self._textCtrl.SetInitialSize(textSize)
        wx.CallAfter(self._textCtrl.SetFocus)

        vbox.Add(
            self._textCtrl,
            proportion=0,
            flag=wx.EXPAND | wx.LEFT | wx.RIGHT,
            border=10)
        self._textCtrl.SetFocus()

        sizer = self.CreateSeparatedButtonSizer(style)
        vbox.Add(sizer, proportion=1, flag=wx.EXPAND | wx.ALL, border=10)

        self.SetSizerAndFit(vbox)

    def GetValue(self):
        return self._textCtrl.GetValue()

    def SetValue(self, value):
        self._textCtrl.SetValue(value)


class HyperlinkDialog(wx.Dialog):
    """Dialog for displaying message with hyperlink."""

    def __init__(self, parent, title, message, hyperlink,
                 hyperlinkLabel=None, style=wx.OK):
        """Constructor

        :param parent: gui parent
        :param title: dialog title
        :param message: message
        :param hyperlink: url
        :param hyperlinkLabel: label shown instead of url
        :param style: button style
        """
        wx.Dialog.__init__(self, parent=parent, id=wx.ID_ANY, title=title,
                           style=wx.DEFAULT_DIALOG_STYLE)

        sizer = wx.BoxSizer(wx.VERTICAL)

        label = StaticText(self, label=message)
        sizer.Add(
            label,
            proportion=0,
            flag=wx.ALIGN_CENTRE | wx.ALL,
            border=10)
        hyperlinkLabel = hyperlinkLabel if hyperlinkLabel else hyperlink
        hyperlinkCtrl = HyperlinkCtrl(
            self, id=wx.ID_ANY, label=hyperlinkLabel, url=hyperlink,
            style=wx.HL_ALIGN_LEFT | wx.HL_CONTEXTMENU)
        sizer.Add(
            hyperlinkCtrl,
            proportion=0,
            flag=wx.EXPAND | wx.ALL,
            border=10)

        btnsizer = self.CreateSeparatedButtonSizer(style)
        sizer.Add(
            btnsizer,
            proportion=1,
            flag=wx.EXPAND | wx.ALL,
            border=10)

        self.SetSizer(sizer)
        sizer.Fit(self)


class QuitDialog(wx.Dialog):

    def __init__(self, parent, title=_("Quit GRASS GIS"), id=wx.ID_ANY,
                 style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
        """Dialog to quit GRASS

        :param parent: window
        """
        wx.Dialog.__init__(self, parent, id, title, style=style, **kwargs)
        self.panel = wx.Panel(parent=self, id=wx.ID_ANY)

        self._icon = wx.StaticBitmap(
            self.panel, wx.ID_ANY,
            wx.ArtProvider().GetBitmap(
                wx.ART_QUESTION,
                client=wx.ART_MESSAGE_BOX))

        self.informLabel = StaticText(
            parent=self.panel, id=wx.ID_ANY, label=_(
                "Do you want to quit GRASS including shell "
                "prompt or just close the GUI?"))
        self.btnCancel = Button(parent=self.panel, id=wx.ID_CANCEL)
        self.btnClose = Button(parent=self.panel, id=wx.ID_NO,
                                  label=_("Close GUI"))
        self.btnClose.SetFocus()
        self.btnQuit = Button(parent=self.panel, id=wx.ID_YES,
                                 label=_("Quit GRASS GIS"))
        self.btnQuit.SetForegroundColour(wx.Colour(35, 142, 35))

        self.btnClose.Bind(wx.EVT_BUTTON, self.OnClose)
        self.btnQuit.Bind(wx.EVT_BUTTON, self.OnQuit)

        self.__layout()

    def __layout(self):
        """Do layout"""
        sizer = wx.BoxSizer(wx.VERTICAL)

        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
        btnSizer.Add(self.btnCancel, flag=wx.RIGHT, border=5)
        btnSizer.Add(self.btnClose, flag=wx.RIGHT, border=5)
        btnSizer.Add(self.btnQuit, flag=wx.RIGHT, border=5)

        bodySizer = wx.BoxSizer(wx.HORIZONTAL)
        bodySizer.Add(self._icon, flag=wx.RIGHT, border=10)
        bodySizer.Add(self.informLabel, proportion=1, flag=wx.EXPAND)

        sizer.Add(bodySizer, proportion=1,
                  flag=wx.EXPAND | wx.ALL, border=15)
        sizer.Add(btnSizer, proportion=0,
                  flag=wx.ALL | wx.ALIGN_RIGHT, border=5)

        self.panel.SetSizer(sizer)
        sizer.Fit(self)
        self.Layout()

    def OnClose(self, event):
        self.EndModal(wx.ID_NO)

    def OnQuit(self, event):
        self.EndModal(wx.ID_YES)


class DefaultFontDialog(wx.Dialog):
    """
    Opens a file selection dialog to select default font
    to use in all GRASS displays
    """

    def __init__(self, parent, title, id=wx.ID_ANY,
                 style=wx.DEFAULT_DIALOG_STYLE |
                 wx.RESIZE_BORDER,
                 settings=UserSettings,
                 type='font'):

        self.settings = settings
        self.type = type

        wx.Dialog.__init__(self, parent, id, title, style=style)

        panel = wx.Panel(parent=self, id=wx.ID_ANY)
        self.tmp_file = grass.tempfile(False) + '.png'

        self.fontdict, fontdict_reverse, self.fontlist = self.GetFonts()

        border = wx.BoxSizer(wx.VERTICAL)
        box = StaticBox(
            parent=panel,
            id=wx.ID_ANY,
            label=" %s " %
            _("Font settings"))
        sizer = wx.StaticBoxSizer(box, wx.VERTICAL)

        gridSizer = wx.GridBagSizer(hgap=5, vgap=5)

        label = StaticText(parent=panel, id=wx.ID_ANY,
                           label=_("Select font:"))
        gridSizer.Add(label,
                      flag=wx.ALIGN_TOP,
                      pos=(0, 0))

        self.fontlb = wx.ListBox(
            parent=panel,
            id=wx.ID_ANY,
            pos=wx.DefaultPosition,
            choices=self.fontlist,
            style=wx.LB_SINGLE)
        self.Bind(wx.EVT_LISTBOX, self.EvtListBox, self.fontlb)
        self.Bind(wx.EVT_LISTBOX_DCLICK, self.EvtListBoxDClick, self.fontlb)

        gridSizer.Add(self.fontlb,
                      flag=wx.EXPAND, pos=(1, 0))

        self.renderfont = wx.StaticBitmap(panel, -1, wx.EmptyBitmapRGBA(100, 50, 255, 255, 255))
        gridSizer.Add(self.renderfont,
                      flag=wx.EXPAND, pos=(2, 0))

        if self.type == 'font':
            if "GRASS_FONT" in os.environ:
                self.font = os.environ["GRASS_FONT"]
            else:
                self.font = self.settings.Get(group='display',
                                              key='font', subkey='type')
            self.encoding = self.settings.Get(group='display',
                                              key='font', subkey='encoding')

            label = StaticText(parent=panel, id=wx.ID_ANY,
                               label=_("Character encoding:"))
            gridSizer.Add(label,
                          flag=wx.ALIGN_CENTER_VERTICAL,
                          pos=(3, 0))

            self.textentry = TextCtrl(parent=panel, id=wx.ID_ANY,
                                      value=self.encoding)
            gridSizer.Add(self.textentry,
                          flag=wx.EXPAND, pos=(4, 0))

            self.textentry.Bind(wx.EVT_TEXT, self.OnEncoding)

        elif self.type == 'outputfont':
            self.font = self.settings.Get(group='appearance',
                                          key='outputfont', subkey='type')
            self.fontsize = self.settings.Get(group='appearance',
                                              key='outputfont', subkey='size')
            label = StaticText(parent=panel, id=wx.ID_ANY,
                               label=_("Font size:"))
            gridSizer.Add(label,
                          flag=wx.ALIGN_CENTER_VERTICAL,
                          pos=(3, 0))

            self.spin = SpinCtrl(parent=panel, id=wx.ID_ANY)
            if self.fontsize:
                self.spin.SetValue(int(self.fontsize))
            self.spin.Bind(wx.EVT_SPINCTRL, self.OnSizeSpin)
            self.spin.Bind(wx.EVT_TEXT, self.OnSizeSpin)
            gridSizer.Add(self.spin,
                          flag=wx.ALIGN_CENTER_VERTICAL,
                          pos=(4, 0))

        else:
            return

        if self.font:
            long_name = fontdict_reverse.get(self.font, None)
            if long_name:
                self.fontlb.SetStringSelection(long_name, True)
            else:
                # font is not in the list of GRASS recognized fonts
                self.font = None

        gridSizer.AddGrowableCol(0)
        sizer.Add(gridSizer, proportion=1,
                  flag=wx.EXPAND | wx.ALL,
                  border=5)

        border.Add(sizer, proportion=1,
                   flag=wx.ALL | wx.EXPAND, border=3)

        btnsizer = wx.StdDialogButtonSizer()

        btn = Button(parent=panel, id=wx.ID_OK)
        btn.SetDefault()
        btnsizer.AddButton(btn)

        btn = Button(parent=panel, id=wx.ID_CANCEL)
        btnsizer.AddButton(btn)
        btnsizer.Realize()

        border.Add(btnsizer, proportion=0,
                   flag=wx.EXPAND | wx.ALIGN_RIGHT | wx.ALL, border=5)

        panel.SetAutoLayout(True)
        panel.SetSizer(border)
        border.Fit(self)
        row, col = gridSizer.GetItemPosition(self.renderfont)
        self.renderfont.SetSize(gridSizer.GetCellSize(row, col))
        if self.font:
            self.RenderText(self.font, _("Example"), size=self.renderfont.GetSize())

        self.Layout()

    def OnEncoding(self, event):
        self.encoding = event.GetString()

    def EvtListBox(self, event):
        self.font = self.fontdict[event.GetString()]
        self.RenderText(self.font, "Example", size=self.renderfont.GetSize())
        event.Skip()

    def EvtListBoxDClick(self, event):
        self.font = self.fontdict[event.GetString()]
        event.Skip()

    def OnSizeSpin(self, event):
        self.fontsize = self.spin.GetValue()
        event.Skip()

    def GetFonts(self):
        """
        parses fonts directory or fretypecap file to get a list of fonts
        for the listbox
        """
        fontlist = []
        fontdict = {}
        fontdict_reverse = {}
        env = os.environ.copy()
        driver = UserSettings.Get(group='display', key='driver', subkey='type')
        if driver == 'png':
            env['GRASS_RENDER_IMMEDIATE'] = 'png'
        else:
            env['GRASS_RENDER_IMMEDIATE'] = 'cairo'
        ret = RunCommand('d.fontlist', flags='v',
                         read=True,
                         env=env)
        if not ret:
            return fontlist

        dfonts = ret.splitlines()
        for line in dfonts:
            shortname = line.split('|')[0]
            longname = line.split('|')[1]
            # not sure when this happens?
            if shortname.startswith('#'):
                continue
            fontlist.append(longname)
            fontdict[longname] = shortname
            fontdict_reverse[shortname] = longname
        fontlist = natural_sort(list(set(fontlist)))

        return fontdict, fontdict_reverse, fontlist

    def RenderText(self, font, text, size):
        """Renders an example text with the selected font and resets the bitmap widget"""
        env = os.environ.copy()
        driver = UserSettings.Get(group='display', key='driver', subkey='type')
        if driver == 'png':
            env['GRASS_RENDER_IMMEDIATE'] = 'png'
        else:
            env['GRASS_RENDER_IMMEDIATE'] = 'cairo'
        env['GRASS_RENDER_WIDTH'] = str(size[0])
        env['GRASS_RENDER_HEIGHT'] = str(size[1])
        env['GRASS_RENDER_FILE'] = self.tmp_file
        ret = RunCommand('d.text', text=text, font=font, align='cc', at='50,50',
                         size=80, color='black', env=env)
        if ret == 0:
            self.renderfont.SetBitmap(wx.Bitmap(self.tmp_file))
        else:
            self.renderfont.SetBitmap(wx.EmptyBitmapRGBA(size[0], size[1]))
        try_remove(self.tmp_file)
