Repository: napari/napari

Issue:
Default key bindings of standard layers not working when inherited
## 🐛 Bug

The child object of a layer class does not inherit its key bindings.

This bug happens because of the changes in PR #1528.

## To Reproduce

Steps to reproduce the behavior:

1. Install the latest version of napari.
2. Execute the code provided below.
3. Try to use some key bindings such as hold SPACE to pan or zoom while adding points.

```python3
import napari
from skimage import data


class MyPoints(napari.layers.Points):
    def random_function():
        return 42


with napari.gui_qt():
    viewer = napari.view_image(data.astronaut())

    points = MyPoints()
    viewer.add_layer(points)
```

## Expected behavior

I expected the default key bindings to work normally.

## Environment

```text
napari: 0.3.7.dev12+ge468550.d20200806
Platform: Darwin-18.7.0-x86_64-i386-64bit
Python: 3.7.8 | packaged by conda-forge | (default, Jul 31 2020, 02:37:09) [Clang 10.0.1 ]
Qt: 5.14.2
PySide2: 5.14.2.3
NumPy: 1.19.1
SciPy: 1.5.2
Dask: 2.22.0
VisPy: 0.6.4

GL version: 2.1 ATI-2.11.22
MAX_TEXTURE_SIZE: 16384

Plugins:
- napari_plugin_engine: 0.1.7
- svg: 0.1.3
```



Discussion:
I'm not entirely sure if this is expected or not.

However, if the key binding inheritance is not expected, it should not show "hold <space> to pan/zoom" in the bottom right corner when in add/select mode.
Hi @JoOkuma, so sorry for taking so long to get back to you on this one. I'm running into this problem again myself actually too. Different context, but same issue. I'm not sure if @jni or @kne42 have any ideas on this one
Looking back at the code, this was intended functionality https://github.com/napari/napari/blob/bc3298bb047b00d65133d5e4cf969b62aa65def1/napari/utils/key_bindings.py#L313-L315

IIRC, I originally had it toss out inherited keybindings I think maybe because some of the overwriting might get confusing.

I realize now that this is probably undesirable functionality. To fix this, you would need to traverse down the MRO for `parent` here, adding in each `class_keybindings` where present: https://github.com/napari/napari/blob/bc3298bb047b00d65133d5e4cf969b62aa65def1/napari/utils/key_bindings.py#L361-L363

You would do this instead of simply copying over the inherited keybindings since it wouldn't be updated properly when the superclass has its keybindings updated.

Resolving patch:
diff --git a/napari/utils/key_bindings.py b/napari/utils/key_bindings.py
--- a/napari/utils/key_bindings.py
+++ b/napari/utils/key_bindings.py
@@ -360,7 +360,10 @@ def keymap_chain(self):
 
         for parent in self.keymap_providers:
             maps.append(_bind_keymap(parent.keymap, parent))
-            maps.append(_bind_keymap(parent.class_keymap, parent))
+            # For parent and superclasses add inherited keybindings
+            for cls in parent.__class__.__mro__:
+                if hasattr(cls, 'class_keymap'):
+                    maps.append(_bind_keymap(cls.class_keymap, parent))
 
         return ChainMap(*maps)
 


Test patch:
diff --git a/napari/utils/_tests/test_key_bindings.py b/napari/utils/_tests/test_key_bindings.py
--- a/napari/utils/_tests/test_key_bindings.py
+++ b/napari/utils/_tests/test_key_bindings.py
@@ -168,6 +168,10 @@ class Bar(KeymapProvider):
     class_keymap = {'E': lambda x: setattr(x, 'E', 42)}
 
 
+class Baz(Bar):
+    class_keymap = {'F': lambda x: setattr(x, 'F', 16)}
+
+
 def test_handle_single_keymap_provider():
     foo = Foo()
 
@@ -266,6 +270,22 @@ def catch_all(x):
     assert not hasattr(foo, 'B')
 
 
+def test_inherited_keymap():
+    baz = Baz()
+    handler = KeymapHandler()
+    handler.keymap_providers = [baz]
+
+    assert handler.keymap_chain.maps == [
+        _bind_keymap(baz.keymap, baz),
+        _bind_keymap(baz.class_keymap, baz),
+        _bind_keymap(Bar.class_keymap, baz),
+    ]
+    assert handler.active_keymap == {
+        'F': types.MethodType(baz.class_keymap['F'], baz),
+        'E': types.MethodType(Bar.class_keymap['E'], baz),
+    }
+
+
 def test_handle_on_release_bindings():
     def make_42(x):
         # on press