Pārlūkot izejas kodu

wxGUI: Fix key binding issues on mac (#818)

Fixes #785

The main problem with stock id buttons is that they all come with a key binding. E.g. Close, Clear and Copy have the same key binding ctrl+c and causes problems at least for mac. To remedy this issue we are using subclassed versions of Button: ClearButton, CancelButton, ApplyButton).

I also made some effort to implement ESC to close dialog (attribute table manager, mapcalc)

Note: for module dialogs (forms.py) and Map calculator I have replaced ctrl+c to copy selected text, not to copy the whole command.
nilason 4 gadi atpakaļ
vecāks
revīzija
5fd8c298d2

+ 9 - 3
gui/wxpython/dbmgr/manager.py

@@ -42,7 +42,7 @@ from core.gcmd import GMessage
 from core.debug import Debug
 from core.debug import Debug
 from dbmgr.base import DbMgrBase
 from dbmgr.base import DbMgrBase
 from gui_core.widgets import GNotebook
 from gui_core.widgets import GNotebook
-from gui_core.wrap import Button
+from gui_core.wrap import Button, ClearButton, CloseButton
 
 
 
 
 class AttributeManager(wx.Frame, DbMgrBase):
 class AttributeManager(wx.Frame, DbMgrBase):
@@ -136,15 +136,21 @@ class AttributeManager(wx.Frame, DbMgrBase):
             wx.CallAfter(self.notebook.SetSelection, 0)  # select browse tab
             wx.CallAfter(self.notebook.SetSelection, 0)  # select browse tab
 
 
         # buttons
         # buttons
-        self.btnClose = Button(parent=self.panel, id=wx.ID_CLOSE)
+        self.btnClose = CloseButton(parent=self.panel)
         self.btnClose.SetToolTip(_("Close Attribute Table Manager"))
         self.btnClose.SetToolTip(_("Close Attribute Table Manager"))
         self.btnReload = Button(parent=self.panel, id=wx.ID_REFRESH)
         self.btnReload = Button(parent=self.panel, id=wx.ID_REFRESH)
         self.btnReload.SetToolTip(
         self.btnReload.SetToolTip(
             _("Reload currently selected attribute data"))
             _("Reload currently selected attribute data"))
-        self.btnReset = Button(parent=self.panel, id=wx.ID_CLEAR)
+        self.btnReset = ClearButton(parent=self.panel)
         self.btnReset.SetToolTip(
         self.btnReset.SetToolTip(
             _("Reload all attribute data (drop current selection)"))
             _("Reload all attribute data (drop current selection)"))
 
 
+        # bind closing to ESC
+        self.Bind(wx.EVT_MENU, self.OnCloseWindow, id=wx.ID_CANCEL)
+        accelTableList = [(wx.ACCEL_NORMAL, wx.WXK_ESCAPE, wx.ID_CANCEL)]
+        accelTable = wx.AcceleratorTable(accelTableList)
+        self.SetAcceleratorTable(accelTable)
+
         # events
         # events
         self.btnClose.Bind(wx.EVT_BUTTON, self.OnCloseWindow)
         self.btnClose.Bind(wx.EVT_BUTTON, self.OnCloseWindow)
         self.btnReload.Bind(wx.EVT_BUTTON, self.OnReloadData)
         self.btnReload.Bind(wx.EVT_BUTTON, self.OnReloadData)

+ 5 - 4
gui/wxpython/dbmgr/sqlbuilder.py

@@ -37,7 +37,8 @@ from grass.pydispatch.signal import Signal
 
 
 from core.gcmd import RunCommand, GError, GMessage
 from core.gcmd import RunCommand, GError, GMessage
 from dbmgr.vinfo import CreateDbInfoDesc, VectorDBInfo, GetUnicodeValue
 from dbmgr.vinfo import CreateDbInfoDesc, VectorDBInfo, GetUnicodeValue
-from gui_core.wrap import Button, TextCtrl, StaticText, StaticBox
+from gui_core.wrap import ApplyButton, Button, ClearButton, CloseButton, \
+    TextCtrl, StaticText, StaticBox
 
 
 import grass.script as grass
 import grass.script as grass
 
 
@@ -128,11 +129,11 @@ class SQLBuilder(wx.Frame):
         #
         #
         # buttons
         # buttons
         #
         #
-        self.btn_clear = Button(parent=self.panel, id=wx.ID_CLEAR)
+        self.btn_clear = ClearButton(parent=self.panel)
         self.btn_clear.SetToolTip(_("Set SQL statement to default"))
         self.btn_clear.SetToolTip(_("Set SQL statement to default"))
-        self.btn_apply = Button(parent=self.panel, id=wx.ID_APPLY)
+        self.btn_apply = ApplyButton(parent=self.panel)
         self.btn_apply.SetToolTip(_("Apply SQL statement"))
         self.btn_apply.SetToolTip(_("Apply SQL statement"))
-        self.btn_close = Button(parent=self.panel, id=wx.ID_CLOSE)
+        self.btn_close = CloseButton(parent=self.panel)
         self.btn_close.SetToolTip(_("Close the dialog"))
         self.btn_close.SetToolTip(_("Close the dialog"))
 
 
         self.btn_logic = {'is': ['=', ],
         self.btn_logic = {'is': ['=', ],

+ 10 - 17
gui/wxpython/gui_core/forms.py

@@ -105,8 +105,8 @@ from core.settings import UserSettings
 from gui_core.widgets import FloatValidator, GNotebook, FormNotebook, FormListbook
 from gui_core.widgets import FloatValidator, GNotebook, FormNotebook, FormListbook
 from core.giface import Notification, StandaloneGrassInterface
 from core.giface import Notification, StandaloneGrassInterface
 from gui_core.widgets import LayersList
 from gui_core.widgets import LayersList
-from gui_core.wrap import BitmapFromImage, Button, StaticText, StaticBox, SpinCtrl, \
-    CheckBox, BitmapButton, TextCtrl, NewId
+from gui_core.wrap import BitmapFromImage, Button, CloseButton, StaticText, \
+    StaticBox, SpinCtrl, CheckBox, BitmapButton, TextCtrl, NewId
 from core.debug import Debug
 from core.debug import Debug
 
 
 wxUpdateDialog, EVT_DIALOG_UPDATE = NewEvent()
 wxUpdateDialog, EVT_DIALOG_UPDATE = NewEvent()
@@ -572,11 +572,7 @@ class TaskFrame(wx.Frame):
         # buttons
         # buttons
         btnsizer = wx.BoxSizer(orient=wx.HORIZONTAL)
         btnsizer = wx.BoxSizer(orient=wx.HORIZONTAL)
         # cancel
         # cancel
-        if sys.platform == 'darwin':
-            # stock id automatically adds ctrl-c shortcut to close dialog
-            self.btn_cancel = Button(parent=self.panel, label=_("Close"))
-        else:
-            self.btn_cancel = Button(parent=self.panel, id=wx.ID_CLOSE)
+        self.btn_cancel = CloseButton(parent=self.panel)
         self.btn_cancel.SetToolTip(
         self.btn_cancel.SetToolTip(
             _("Close this window without executing the command (Ctrl+Q)"))
             _("Close this window without executing the command (Ctrl+Q)"))
         btnsizer.Add(
         btnsizer.Add(
@@ -586,9 +582,9 @@ class TaskFrame(wx.Frame):
             border=10)
             border=10)
         self.btn_cancel.Bind(wx.EVT_BUTTON, self.OnCancel)
         self.btn_cancel.Bind(wx.EVT_BUTTON, self.OnCancel)
         # bind closing to ESC and CTRL+Q
         # bind closing to ESC and CTRL+Q
-        self.Bind(wx.EVT_MENU, self.OnCancel, id=wx.ID_CLOSE)
-        accelTableList = [(wx.ACCEL_NORMAL, wx.WXK_ESCAPE, wx.ID_CLOSE)]
-        accelTableList.append((wx.ACCEL_CTRL, ord('Q'), wx.ID_CLOSE))
+        self.Bind(wx.EVT_MENU, self.OnCancel, id=wx.ID_CANCEL)
+        accelTableList = [(wx.ACCEL_NORMAL, wx.WXK_ESCAPE, wx.ID_CANCEL)]
+        accelTableList.append((wx.ACCEL_CTRL, ord('Q'), wx.ID_CANCEL))
         # TODO: bind Ctrl-t for tile windows here (trac #2004)
         # TODO: bind Ctrl-t for tile windows here (trac #2004)
 
 
         if self.get_dcmd is not None:  # A callback has been set up
         if self.get_dcmd is not None:  # A callback has been set up
@@ -621,17 +617,14 @@ class TaskFrame(wx.Frame):
             accelTableList.append((wx.ACCEL_CTRL, ord('R'), wx.ID_OK))
             accelTableList.append((wx.ACCEL_CTRL, ord('R'), wx.ID_OK))
 
 
         # copy
         # copy
-        if sys.platform == 'darwin':
-            # stock id automatically adds ctrl-c shortcut to copy command
-            self.btn_clipboard = Button(parent=self.panel, label=_("Copy"))
-        else:
-            self.btn_clipboard = Button(parent=self.panel, id=wx.ID_COPY)
+        self.btn_clipboard = Button(
+            parent=self.panel, id=wx.ID_ANY, label=_("Copy"))
         self.btn_clipboard.SetToolTip(
         self.btn_clipboard.SetToolTip(
             _("Copy the current command string to the clipboard"))
             _("Copy the current command string to the clipboard"))
         btnsizer.Add(self.btn_clipboard, proportion=0,
         btnsizer.Add(self.btn_clipboard, proportion=0,
                      flag=wx.ALL | wx.ALIGN_CENTER,
                      flag=wx.ALL | wx.ALIGN_CENTER,
                      border=10)
                      border=10)
-        self.btn_clipboard.Bind(wx.EVT_BUTTON, self.OnCopy)
+        self.btn_clipboard.Bind(wx.EVT_BUTTON, self.OnCopyCommand)
 
 
         # help
         # help
         self.btn_help = Button(parent=self.panel, id=wx.ID_HELP)
         self.btn_help = Button(parent=self.panel, id=wx.ID_HELP)
@@ -874,7 +867,7 @@ class TaskFrame(wx.Frame):
         event = wxCmdAbort(aborted=True)
         event = wxCmdAbort(aborted=True)
         wx.PostEvent(self._gconsole, event)
         wx.PostEvent(self._gconsole, event)
 
 
-    def OnCopy(self, event):
+    def OnCopyCommand(self, event):
         """Copy the command"""
         """Copy the command"""
         cmddata = wx.TextDataObject()
         cmddata = wx.TextDataObject()
         # list -> string
         # list -> string

+ 3 - 4
gui/wxpython/gui_core/goutput.py

@@ -38,7 +38,7 @@ from core.gconsole   import GConsole, \
     Notification
     Notification
 from core.globalvar import CheckWxVersion, wxPythonPhoenix
 from core.globalvar import CheckWxVersion, wxPythonPhoenix
 from gui_core.prompt import GPromptSTC
 from gui_core.prompt import GPromptSTC
-from gui_core.wrap import Button, ToggleButton, StaticText, \
+from gui_core.wrap import Button, ClearButton, ToggleButton, StaticText, \
     StaticBox
     StaticBox
 from core.settings import UserSettings
 from core.settings import UserSettings
 from gui_core.widgets import SearchModuleWidget
 from gui_core.widgets import SearchModuleWidget
@@ -158,10 +158,9 @@ class GConsoleWindow(wx.SplitterWindow):
                                     label=" %s " % cmdLabel)
                                     label=" %s " % cmdLabel)
 
 
         # buttons
         # buttons
-        self.btnOutputClear = Button(
-            parent=self.panelOutput, id=wx.ID_CLEAR)
+        self.btnOutputClear = ClearButton(parent=self.panelOutput)
         self.btnOutputClear.SetToolTip(_("Clear output window content"))
         self.btnOutputClear.SetToolTip(_("Clear output window content"))
-        self.btnCmdClear = Button(parent=self.panelOutput, id=wx.ID_CLEAR)
+        self.btnCmdClear = ClearButton(parent=self.panelOutput)
         self.btnCmdClear.SetToolTip(_("Clear command prompt content"))
         self.btnCmdClear.SetToolTip(_("Clear command prompt content"))
         self.btnOutputSave = Button(parent=self.panelOutput, id=wx.ID_SAVE)
         self.btnOutputSave = Button(parent=self.panelOutput, id=wx.ID_SAVE)
         self.btnOutputSave.SetToolTip(
         self.btnOutputSave.SetToolTip(

+ 47 - 0
gui/wxpython/gui_core/wrap.py

@@ -15,6 +15,7 @@ This program is free software under the GNU General Public License
 @author Anna Petrasova <kratochanna gmail.com>
 @author Anna Petrasova <kratochanna gmail.com>
 """
 """
 
 
+import sys
 import wx
 import wx
 import wx.lib.agw.floatspin as fs
 import wx.lib.agw.floatspin as fs
 import wx.lib.colourselect as csel
 import wx.lib.colourselect as csel
@@ -178,6 +179,52 @@ class Button(wx.Button):
             wx.Button.SetToolTipString(self, tip)
             wx.Button.SetToolTipString(self, tip)
 
 
 
 
+class ClearButton(Button):
+    """Wrapper around a Button with stock id wx.ID_CLEAR,
+    to disable default key binding on certain platforms"""
+    def __init__(self, *args, **kwargs):
+        Button.__init__(self, *args, **kwargs)
+        self.SetId(wx.ID_CLEAR)
+        if sys.platform == "darwin":
+            self.SetLabel(_("Clear"))
+        else:
+            self.SetLabel(_("&Clear"))
+
+
+class CancelButton(Button):
+    """Wrapper around a Button with stock id wx.ID_CANCEL, to disable
+    default key binding on certain platforms/wxpython versions"""
+    def __init__(self, *args, **kwargs):
+        Button.__init__(self, *args, **kwargs)
+        self.SetId(wx.ID_CANCEL)
+        if sys.platform == "darwin" and not CheckWxVersion([4, 1, 0]):
+            self.SetLabel(_("Cancel"))
+        else:
+            self.SetLabel(_("&Cancel"))
+
+class CloseButton(Button):
+    """Wrapper around a Close labeled Button with stock id wx.ID_CANCEL
+    to disable default key binding on certain platforms/wxpython versions"""
+    def __init__(self, *args, **kwargs):
+        Button.__init__(self, *args, **kwargs)
+        self.SetId(wx.ID_CANCEL)
+        if sys.platform == "darwin" and not CheckWxVersion([4, 1, 0]):
+            self.SetLabel(_("Close"))
+        else:
+            self.SetLabel(_("&Close"))
+
+class ApplyButton(Button):
+    """Wrapper around a Button with stock id wx.ID_APPLY,
+    to disable default key binding on certain platforms"""
+    def __init__(self, *args, **kwargs):
+        Button.__init__(self, *args, **kwargs)
+        self.SetId(wx.ID_APPLY)
+        if sys.platform == "darwin":
+            self.SetLabel(_("Apply"))
+        else:
+            self.SetLabel(_("&Apply"))
+
+
 class RadioButton(wx.RadioButton):
 class RadioButton(wx.RadioButton):
     """Wrapper around wx.RadioButton to have more control
     """Wrapper around wx.RadioButton to have more control
     over the widget on different platforms/wxpython versions"""
     over the widget on different platforms/wxpython versions"""

+ 0 - 14
gui/wxpython/lmgr/frame.py

@@ -255,13 +255,6 @@ class GMFrame(wx.Frame):
 
 
         show_menu_errors(menu_errors)
         show_menu_errors(menu_errors)
 
 
-        # Enable copying to clipboard with cmd+c from console and python shell on macOS
-        # (default key binding will clear the console), trac #3008
-        if sys.platform == "darwin":
-            self.Bind(wx.EVT_MENU, self.OnCopyToClipboard, id=wx.ID_COPY)
-            self.accel_tbl = wx.AcceleratorTable([(wx.ACCEL_CTRL, ord("C"), wx.ID_COPY)])
-            self.SetAcceleratorTable(self.accel_tbl)
-
         # start with layer manager on top
         # start with layer manager on top
         if self.currentPage:
         if self.currentPage:
             self.GetMapDisplay().Raise()
             self.GetMapDisplay().Raise()
@@ -683,13 +676,6 @@ class GMFrame(wx.Frame):
                     return False
                     return False
         return True
         return True
 
 
-    def OnCopyToClipboard(self, event):
-        """Copy selected text in shell to the clipboard"""
-        try:
-            wx.Window.FindFocus().Copy()
-        except:
-            pass
-
     def _switchPageHandler(self, event, notification):
     def _switchPageHandler(self, event, notification):
         self._switchPage(notification=notification)
         self._switchPage(notification=notification)
         event.Skip()
         event.Skip()

+ 5 - 3
gui/wxpython/lmgr/pyshell.py

@@ -29,7 +29,7 @@ from wx.py.version import VERSION
 import grass.script as grass
 import grass.script as grass
 from grass.script.utils import try_remove
 from grass.script.utils import try_remove
 
 
-from gui_core.wrap import Button
+from gui_core.wrap import Button, ClearButton
 
 
 
 
 class PyShellWindow(wx.Panel):
 class PyShellWindow(wx.Panel):
@@ -44,14 +44,16 @@ class PyShellWindow(wx.Panel):
         self.intro = _("Welcome to wxGUI Interactive Python Shell %s") % VERSION + "\n\n" + \
         self.intro = _("Welcome to wxGUI Interactive Python Shell %s") % VERSION + "\n\n" + \
             _("Type %s for more GRASS scripting related information.") % "\"help(gs)\"" + "\n" + \
             _("Type %s for more GRASS scripting related information.") % "\"help(gs)\"" + "\n" + \
             _("Type %s to add raster or vector to the layer tree.") % "\"AddLayer()\"" + "\n\n"
             _("Type %s to add raster or vector to the layer tree.") % "\"AddLayer()\"" + "\n\n"
+        # useStockId should be False on macOS
         self.shell = PyShell(parent=self, id=wx.ID_ANY,
         self.shell = PyShell(parent=self, id=wx.ID_ANY,
                              introText=self.intro,
                              introText=self.intro,
                              locals={'gs': grass,
                              locals={'gs': grass,
-                                     'AddLayer': self.AddLayer})
+                                     'AddLayer': self.AddLayer},
+                             useStockId=(sys.platform != "darwin"))
 
 
         sys.displayhook = self._displayhook
         sys.displayhook = self._displayhook
 
 
-        self.btnClear = Button(self, wx.ID_CLEAR)
+        self.btnClear = ClearButton(self)
         self.btnClear.Bind(wx.EVT_BUTTON, self.OnClear)
         self.btnClear.Bind(wx.EVT_BUTTON, self.OnClear)
         self.btnClear.SetToolTip(_("Delete all text from the shell"))
         self.btnClear.SetToolTip(_("Delete all text from the shell"))
 
 

+ 3 - 19
gui/wxpython/modules/import_export.py

@@ -42,7 +42,7 @@ from gui_core.forms import CmdPanel
 from gui_core.gselect import OgrTypeSelect, GdalSelect, SubGroupSelect
 from gui_core.gselect import OgrTypeSelect, GdalSelect, SubGroupSelect
 from gui_core.widgets import GListCtrl, GNotebook, LayersList, \
 from gui_core.widgets import GListCtrl, GNotebook, LayersList, \
     LayersListValidator
     LayersListValidator
-from gui_core.wrap import Button, StaticText, StaticBox
+from gui_core.wrap import Button, CloseButton, StaticText, StaticBox
 from core.utils import GetValidLayerName
 from core.utils import GetValidLayerName
 from core.settings import UserSettings, GetDisplayVectSettings
 from core.settings import UserSettings, GetDisplayVectSettings
 
 
@@ -116,14 +116,12 @@ class ImportDialog(wx.Dialog):
         # buttons
         # buttons
         #
         #
         # cancel
         # cancel
-        self.btn_close = Button(parent=self.panel, id=wx.ID_CLOSE)
+        self.btn_close = CloseButton(parent=self.panel)
         self.btn_close.SetToolTip(_("Close dialog"))
         self.btn_close.SetToolTip(_("Close dialog"))
         self.btn_close.Bind(wx.EVT_BUTTON, self.OnClose)
         self.btn_close.Bind(wx.EVT_BUTTON, self.OnClose)
         # run
         # run
         self.btn_run = Button(
         self.btn_run = Button(
-            parent=self.panel,
-            id=wx.ID_OK,
-            label=_("&Import"))
+            parent=self.panel, id=wx.ID_OK, label=_("&Import"))
         self.btn_run.SetToolTip(_("Import selected layers"))
         self.btn_run.SetToolTip(_("Import selected layers"))
         self.btn_run.SetDefault()
         self.btn_run.SetDefault()
         self.btn_run.Bind(wx.EVT_BUTTON, self.OnRun)
         self.btn_run.Bind(wx.EVT_BUTTON, self.OnRun)
@@ -139,13 +137,6 @@ class ImportDialog(wx.Dialog):
 
 
         self.createSettingsPage()
         self.createSettingsPage()
 
 
-        # Enable copying to clipboard with cmd+c from dialog on macOS
-        # (default key binding will close the dialog), trac #3592
-        if sys.platform == "darwin":
-            self.Bind(wx.EVT_MENU, self.OnCopyToClipboard, id=wx.ID_COPY)
-            self.accel_tbl = wx.AcceleratorTable([(wx.ACCEL_CTRL, ord("C"), wx.ID_COPY)])
-            self.SetAcceleratorTable(self.accel_tbl)
-
     def createSettingsPage(self):
     def createSettingsPage(self):
 
 
         self._blackList = {
         self._blackList = {
@@ -356,13 +347,6 @@ class ImportDialog(wx.Dialog):
         """Do what has to be done after importing"""
         """Do what has to be done after importing"""
         pass
         pass
 
 
-    def OnCopyToClipboard(self, event):
-        """Copy selected text in dialog to the clipboard"""
-        try:
-            wx.Window.FindFocus().Copy()
-        except:
-            pass
-
     def _getLayersToReprojetion(self, projMatch_idx, grassName_idx):
     def _getLayersToReprojetion(self, projMatch_idx, grassName_idx):
         """If there are layers with different projection from loation projection,
         """If there are layers with different projection from loation projection,
            show dialog to user to explicitly select layers which will be reprojected..."""
            show dialog to user to explicitly select layers which will be reprojected..."""

+ 14 - 8
gui/wxpython/modules/mcalc_builder.py

@@ -28,8 +28,8 @@ from core.giface import StandaloneGrassInterface
 from gui_core.gselect import Select
 from gui_core.gselect import Select
 from gui_core.forms import GUI
 from gui_core.forms import GUI
 from gui_core.widgets import IntegerValidator
 from gui_core.widgets import IntegerValidator
-from gui_core.wrap import Button, TextCtrl, StaticText, \
-    StaticBox
+from gui_core.wrap import Button, ClearButton, CloseButton, TextCtrl, \
+    StaticText, StaticBox
 from core.settings import UserSettings
 from core.settings import UserSettings
 
 
 
 
@@ -150,20 +150,21 @@ class MapCalcFrame(wx.Frame):
         #
         #
         # Buttons
         # Buttons
         #
         #
-        self.btn_clear = Button(parent=self.panel, id=wx.ID_CLEAR)
+        self.btn_clear = ClearButton(parent=self.panel)
         self.btn_help = Button(parent=self.panel, id=wx.ID_HELP)
         self.btn_help = Button(parent=self.panel, id=wx.ID_HELP)
         self.btn_run = Button(
         self.btn_run = Button(
             parent=self.panel,
             parent=self.panel,
             id=wx.ID_ANY,
             id=wx.ID_ANY,
             label=_("&Run"))
             label=_("&Run"))
         self.btn_run.SetDefault()
         self.btn_run.SetDefault()
-        self.btn_close = Button(parent=self.panel, id=wx.ID_CLOSE)
+        self.btn_close = CloseButton(parent=self.panel)
         self.btn_save = Button(parent=self.panel, id=wx.ID_SAVE)
         self.btn_save = Button(parent=self.panel, id=wx.ID_SAVE)
         self.btn_save.SetToolTip(_('Save expression to file'))
         self.btn_save.SetToolTip(_('Save expression to file'))
         self.btn_load = Button(parent=self.panel, id=wx.ID_ANY,
         self.btn_load = Button(parent=self.panel, id=wx.ID_ANY,
                                label=_("&Load"))
                                label=_("&Load"))
         self.btn_load.SetToolTip(_('Load expression from file'))
         self.btn_load.SetToolTip(_('Load expression from file'))
-        self.btn_copy = Button(parent=self.panel, id=wx.ID_COPY)
+        self.btn_copy = Button(
+            parent=self.panel, id=wx.ID_ANY, label=_("Copy"))
         self.btn_copy.SetToolTip(
         self.btn_copy.SetToolTip(
             _("Copy the current command string to the clipboard"))
             _("Copy the current command string to the clipboard"))
 
 
@@ -323,9 +324,8 @@ class MapCalcFrame(wx.Frame):
         self.btn_help.Bind(wx.EVT_BUTTON, self.OnHelp)
         self.btn_help.Bind(wx.EVT_BUTTON, self.OnHelp)
         self.btn_save.Bind(wx.EVT_BUTTON, self.OnSaveExpression)
         self.btn_save.Bind(wx.EVT_BUTTON, self.OnSaveExpression)
         self.btn_load.Bind(wx.EVT_BUTTON, self.OnLoadExpression)
         self.btn_load.Bind(wx.EVT_BUTTON, self.OnLoadExpression)
-        self.btn_copy.Bind(wx.EVT_BUTTON, self.OnCopy)
+        self.btn_copy.Bind(wx.EVT_BUTTON, self.OnCopyCommand)
 
 
-        # self.mapselect.Bind(wx.EVT_TEXT, self.OnSelectTextEvt)
         self.mapselect.Bind(wx.EVT_TEXT, self.OnSelect)
         self.mapselect.Bind(wx.EVT_TEXT, self.OnSelect)
         self.function.Bind(wx.EVT_COMBOBOX, self._return_funct)
         self.function.Bind(wx.EVT_COMBOBOX, self._return_funct)
         self.function.Bind(wx.EVT_TEXT_ENTER, self.OnSelect)
         self.function.Bind(wx.EVT_TEXT_ENTER, self.OnSelect)
@@ -336,6 +336,12 @@ class MapCalcFrame(wx.Frame):
         self.randomSeed.Bind(wx.EVT_CHECKBOX, self.OnSeedFlag)
         self.randomSeed.Bind(wx.EVT_CHECKBOX, self.OnSeedFlag)
         self.randomSeedText.Bind(wx.EVT_TEXT, self.OnUpdateStatusBar)
         self.randomSeedText.Bind(wx.EVT_TEXT, self.OnUpdateStatusBar)
 
 
+        # bind closing to ESC
+        self.Bind(wx.EVT_MENU, self.OnClose, id=wx.ID_CANCEL)
+        accelTableList = [(wx.ACCEL_NORMAL, wx.WXK_ESCAPE, wx.ID_CANCEL)]
+        accelTable = wx.AcceleratorTable(accelTableList)
+        self.SetAcceleratorTable(accelTable)
+
         self._layout()
         self._layout()
 
 
         self.SetMinSize(self.panel.GetBestSize())
         self.SetMinSize(self.panel.GetBestSize())
@@ -782,7 +788,7 @@ class MapCalcFrame(wx.Frame):
 
 
         dlg.Destroy()
         dlg.Destroy()
 
 
-    def OnCopy(self, event):
+    def OnCopyCommand(self, event):
         command = self._getCommand()
         command = self._getCommand()
         cmddata = wx.TextDataObject()
         cmddata = wx.TextDataObject()
         cmddata.SetText(command)
         cmddata.SetText(command)