Skip to content

Commit

Permalink
wxGUI: Fix key binding issues on mac (#818)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
nilason committed Dec 1, 2020
1 parent 31113f1 commit 5fd8c29
Show file tree
Hide file tree
Showing 9 changed files with 96 additions and 72 deletions.
12 changes: 9 additions & 3 deletions gui/wxpython/dbmgr/manager.py
Expand Up @@ -42,7 +42,7 @@
from core.debug import Debug
from dbmgr.base import DbMgrBase
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):
Expand Down Expand Up @@ -136,15 +136,21 @@ def __init__(self, parent, id=wx.ID_ANY,
wx.CallAfter(self.notebook.SetSelection, 0) # select browse tab

# 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.btnReload = Button(parent=self.panel, id=wx.ID_REFRESH)
self.btnReload.SetToolTip(
_("Reload currently selected attribute data"))
self.btnReset = Button(parent=self.panel, id=wx.ID_CLEAR)
self.btnReset = ClearButton(parent=self.panel)
self.btnReset.SetToolTip(
_("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
self.btnClose.Bind(wx.EVT_BUTTON, self.OnCloseWindow)
self.btnReload.Bind(wx.EVT_BUTTON, self.OnReloadData)
Expand Down
9 changes: 5 additions & 4 deletions gui/wxpython/dbmgr/sqlbuilder.py
Expand Up @@ -37,7 +37,8 @@

from core.gcmd import RunCommand, GError, GMessage
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

Expand Down Expand Up @@ -128,11 +129,11 @@ def _doLayout(self, modeChoices, showDbInfo=False):
#
# 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_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_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_logic = {'is': ['=', ],
Expand Down
27 changes: 10 additions & 17 deletions gui/wxpython/gui_core/forms.py
Expand Up @@ -105,8 +105,8 @@
from gui_core.widgets import FloatValidator, GNotebook, FormNotebook, FormListbook
from core.giface import Notification, StandaloneGrassInterface
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

wxUpdateDialog, EVT_DIALOG_UPDATE = NewEvent()
Expand Down Expand Up @@ -572,11 +572,7 @@ def __init__(self, parent, giface, task_description, id=wx.ID_ANY,
# buttons
btnsizer = wx.BoxSizer(orient=wx.HORIZONTAL)
# 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(
_("Close this window without executing the command (Ctrl+Q)"))
btnsizer.Add(
Expand All @@ -586,9 +582,9 @@ def __init__(self, parent, giface, task_description, id=wx.ID_ANY,
border=10)
self.btn_cancel.Bind(wx.EVT_BUTTON, self.OnCancel)
# 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)

if self.get_dcmd is not None: # A callback has been set up
Expand Down Expand Up @@ -621,17 +617,14 @@ def __init__(self, parent, giface, task_description, id=wx.ID_ANY,
accelTableList.append((wx.ACCEL_CTRL, ord('R'), wx.ID_OK))

# 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(
_("Copy the current command string to the clipboard"))
btnsizer.Add(self.btn_clipboard, proportion=0,
flag=wx.ALL | wx.ALIGN_CENTER,
border=10)
self.btn_clipboard.Bind(wx.EVT_BUTTON, self.OnCopy)
self.btn_clipboard.Bind(wx.EVT_BUTTON, self.OnCopyCommand)

# help
self.btn_help = Button(parent=self.panel, id=wx.ID_HELP)
Expand Down Expand Up @@ -874,7 +867,7 @@ def OnAbort(self, event):
event = wxCmdAbort(aborted=True)
wx.PostEvent(self._gconsole, event)

def OnCopy(self, event):
def OnCopyCommand(self, event):
"""Copy the command"""
cmddata = wx.TextDataObject()
# list -> string
Expand Down
7 changes: 3 additions & 4 deletions gui/wxpython/gui_core/goutput.py
Expand Up @@ -38,7 +38,7 @@
Notification
from core.globalvar import CheckWxVersion, wxPythonPhoenix
from gui_core.prompt import GPromptSTC
from gui_core.wrap import Button, ToggleButton, StaticText, \
from gui_core.wrap import Button, ClearButton, ToggleButton, StaticText, \
StaticBox
from core.settings import UserSettings
from gui_core.widgets import SearchModuleWidget
Expand Down Expand Up @@ -158,10 +158,9 @@ def __init__(self, parent, giface, gconsole, menuModel=None, margin=False,
label=" %s " % cmdLabel)

# 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.btnCmdClear = Button(parent=self.panelOutput, id=wx.ID_CLEAR)
self.btnCmdClear = ClearButton(parent=self.panelOutput)
self.btnCmdClear.SetToolTip(_("Clear command prompt content"))
self.btnOutputSave = Button(parent=self.panelOutput, id=wx.ID_SAVE)
self.btnOutputSave.SetToolTip(
Expand Down
47 changes: 47 additions & 0 deletions gui/wxpython/gui_core/wrap.py
Expand Up @@ -15,6 +15,7 @@
@author Anna Petrasova <kratochanna gmail.com>
"""

import sys
import wx
import wx.lib.agw.floatspin as fs
import wx.lib.colourselect as csel
Expand Down Expand Up @@ -178,6 +179,52 @@ def SetToolTip(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):
"""Wrapper around wx.RadioButton to have more control
over the widget on different platforms/wxpython versions"""
Expand Down
14 changes: 0 additions & 14 deletions gui/wxpython/lmgr/frame.py
Expand Up @@ -255,13 +255,6 @@ def show_menu_errors(messages):

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
if self.currentPage:
self.GetMapDisplay().Raise()
Expand Down Expand Up @@ -683,13 +676,6 @@ def CanClosePage(self, caption):
return False
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):
self._switchPage(notification=notification)
event.Skip()
Expand Down
8 changes: 5 additions & 3 deletions gui/wxpython/lmgr/pyshell.py
Expand Up @@ -29,7 +29,7 @@
import grass.script as grass
from grass.script.utils import try_remove

from gui_core.wrap import Button
from gui_core.wrap import Button, ClearButton


class PyShellWindow(wx.Panel):
Expand All @@ -44,14 +44,16 @@ def __init__(self, parent, giface, id=wx.ID_ANY, simpleEditorHandler=None, **kwa
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 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,
introText=self.intro,
locals={'gs': grass,
'AddLayer': self.AddLayer})
'AddLayer': self.AddLayer},
useStockId=(sys.platform != "darwin"))

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.SetToolTip(_("Delete all text from the shell"))

Expand Down
22 changes: 3 additions & 19 deletions gui/wxpython/modules/import_export.py
Expand Up @@ -42,7 +42,7 @@
from gui_core.gselect import OgrTypeSelect, GdalSelect, SubGroupSelect
from gui_core.widgets import GListCtrl, GNotebook, LayersList, \
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.settings import UserSettings, GetDisplayVectSettings

Expand Down Expand Up @@ -116,14 +116,12 @@ def __init__(self, parent, giface, itype,
# buttons
#
# 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.Bind(wx.EVT_BUTTON, self.OnClose)
# run
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.SetDefault()
self.btn_run.Bind(wx.EVT_BUTTON, self.OnRun)
Expand All @@ -139,13 +137,6 @@ def __init__(self, parent, giface, itype,

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):

self._blackList = {
Expand Down Expand Up @@ -356,13 +347,6 @@ def OnCmdDone(self, event):
"""Do what has to be done after importing"""
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):
"""If there are layers with different projection from loation projection,
show dialog to user to explicitly select layers which will be reprojected..."""
Expand Down
22 changes: 14 additions & 8 deletions gui/wxpython/modules/mcalc_builder.py
Expand Up @@ -28,8 +28,8 @@
from gui_core.gselect import Select
from gui_core.forms import GUI
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


Expand Down Expand Up @@ -150,20 +150,21 @@ def __init__(self, parent, giface, cmd, id=wx.ID_ANY,
#
# 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_run = Button(
parent=self.panel,
id=wx.ID_ANY,
label=_("&Run"))
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.SetToolTip(_('Save expression to file'))
self.btn_load = Button(parent=self.panel, id=wx.ID_ANY,
label=_("&Load"))
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(
_("Copy the current command string to the clipboard"))

Expand Down Expand Up @@ -323,9 +324,8 @@ def __init__(self, parent, giface, cmd, id=wx.ID_ANY,
self.btn_help.Bind(wx.EVT_BUTTON, self.OnHelp)
self.btn_save.Bind(wx.EVT_BUTTON, self.OnSaveExpression)
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.function.Bind(wx.EVT_COMBOBOX, self._return_funct)
self.function.Bind(wx.EVT_TEXT_ENTER, self.OnSelect)
Expand All @@ -336,6 +336,12 @@ def __init__(self, parent, giface, cmd, id=wx.ID_ANY,
self.randomSeed.Bind(wx.EVT_CHECKBOX, self.OnSeedFlag)
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.SetMinSize(self.panel.GetBestSize())
Expand Down Expand Up @@ -782,7 +788,7 @@ def OnLoadExpression(self, event):

dlg.Destroy()

def OnCopy(self, event):
def OnCopyCommand(self, event):
command = self._getCommand()
cmddata = wx.TextDataObject()
cmddata.SetText(command)
Expand Down

0 comments on commit 5fd8c29

Please sign in to comment.