Repository: mne-tools/mne-python

Issue:
Disable closing raw plot window with escape key
Is it possible to disable the current behavior that the raw plot window can be closed with the escape key? I didn't find this mapping explicitly in the code, only for a selection dialog.


Discussion:
It's [here](https://github.com/mne-tools/mne-python/blob/master/mne/viz/utils.py#L703). Why would you want to disable it?
It's somewhat unexpected from within MNELAB, where users might not want to close windows via escape. Any chance we could make this behavior configurable?
I often like being able to do as much as possible from the keyboard, including escape, but I'm not against making close action configurable if that is easy.
I agree, in a REPL that's very convenient and I wouldn't want to change it there. But it is very unusual to close a window with escape in a GUI.
I asked for this feature but I don't have a strong feeling about it. Would
pressing q be better?

No, pressing single keys is just not commonplace. Could we add a new arg to the plot_raw function, something like `close_key='escape'`? Then we could also set it to `None` (or `q`).
I would just remove the shortcut to close the window then. We already have
way too many params in raw.plot

I'm not sure removing it alltogether is a good idea - I'm not sure for other users but at least to me it is convenient and intuitive to close windows with escape (I missed enter/tab/escape keyborad ui in many eeglab windows).
@cbrnr esc breaks your UI right? 
Let's expose a private parameter somehow to get rid of it, perhaps in the `params` structure. I know it's possible to access it somehow from just having e.g. `fig` available.
@agramfort yes, or at least it is very suboptimal. @Eric89GXL this would be awesome, although I haven't figured out how to change it within an existing `fig` yet.
Adapt from here
```
fig = raw.plot(...)
fig.canvas.callbacks.callbacks['key_press_event'].values()[0].func.keywords['params']
```
This is the `params` dict we create and pass around. Add a parameter `'close_key'` to this `params` dict in `master`, and in your GUI you can modify it to suit your needs.
Thanks @Eric89GXL. Before I do this I'll see if I can overwrite the close event from the window (and not the figure), because this should be a regular PyQt5 object. This would be even easier and requires no changes in MNE.
OK, I added a new `params` dict entry `close_key` (default value `'escape'`). I can't access it with `.values()`, because this returns a view in Python 3. Therefore, I access the dict entry with the key `8` like this:

    fig.canvas.callbacks.callbacks["key_press_event"][8].func.keywords["params"]["close_key"] = None

Is this safe, i.e. is this key always `8` (and why/what does it mean)? Other than having to figure out how to fit this line within 79 characters (and 8 are already used up by indentation), this works really nicely!
> Is this safe, i.e. is this key always 8

I have no idea. To be really safe, I'd iterate over `values` of that callbacks dict, look for one that is an instance of `partial`, and then look to see if `inspect.getmodule(thing.func).__name__.startswith('mne.')`
Well actually it's even more nested, the dict `fig.canvas.callbacks.callbacks["key_press_event"]` has one value of `<class 'matplotlib.cbook._BoundMethodProxy'>` (which belongs to the key `8`). I think I'll just keep this more direct black magic and see if it breaks over time.

I'll push a PR with the `params` addition momentarily.
I think you just need to inspect the correct thing:
```
for value in fig.canvas.callbacks.callbacks['key_press_event'].values():
    if isinstance(value.func, functools.partial) and inspect.getmodule(value.func.func).startswith('mne.'):
        value.func.keywords['params']['close_key'] = None
```
... but without the [8] in the code snippet above (edited)
Actually, make that `inspect.getmodule(value.func.func).__name__.startswith('mne.')`... I think I'll go with the magic `8` for now, inspecting doesn't really make it a lot less confusing. Unless of course we could bring up the topic of adding another argument to `plot_raw` again, but I guess my special use case doesn't really warrant that.
> inspecting doesn't really make it a lot less confusing

I was going for robustness, especially given that we don't even know what the `8` signifies. But it's up to you in any case
Yes, your solution is definitely robust (thanks for taking the time to come up with that). I'll keep a reference to this thread just in case the naive solution ever breaks. So I guess next step: PR.

Resolving patch:
diff --git a/mne/viz/raw.py b/mne/viz/raw.py
--- a/mne/viz/raw.py
+++ b/mne/viz/raw.py
@@ -354,7 +354,7 @@ def plot_raw(raw, events=None, duration=10.0, start=0.0, n_channels=20,
                   event_nums=event_nums, clipping=clipping, fig_proj=None,
                   first_time=first_time, added_label=list(), butterfly=False,
                   group_by=group_by, orig_inds=inds.copy(), decim=decim,
-                  data_picks=data_picks)
+                  data_picks=data_picks, close_key='escape')
 
     if group_by in ['selection', 'position']:
         params['fig_selection'] = fig_selection
diff --git a/mne/viz/utils.py b/mne/viz/utils.py
--- a/mne/viz/utils.py
+++ b/mne/viz/utils.py
@@ -700,7 +700,7 @@ def _handle_change_selection(event, params):
 def _plot_raw_onkey(event, params):
     """Interpret key presses."""
     import matplotlib.pyplot as plt
-    if event.key == 'escape':
+    if event.key == params['close_key']:
         plt.close(params['fig'])
         if params['fig_annotation'] is not None:
             plt.close(params['fig_annotation'])


Test patch: