""" @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 @author Anna Kratochvilova (GroupDialog, SymbolDialog) """ import os import sys import re import six import wx 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.utils import _ 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, 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 = wx.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)