forked from napari/napari
-
Notifications
You must be signed in to change notification settings - Fork 0
/
action_manager.py
116 lines (85 loc) · 3.6 KB
/
action_manager.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
"""
Action manager
==============
.. tags:: gui, experimental
"""
from random import shuffle
import numpy as np
from skimage import data
import napari
from napari._qt.widgets.qt_viewer_buttons import QtViewerPushButton
from napari.components import ViewerModel
from napari.utils.action_manager import action_manager
def rotate45(viewer: napari.Viewer):
"""
Rotate layer 0 of the viewer by 45º
Parameters
----------
viewer : napari.Viewer
active (unique) instance of the napari viewer
Notes
-----
The `viewer` parameter needs to be named `viewer`, the action manager will
infer that we need an instance of viewer.
"""
angle = np.pi / 4
from numpy import cos, sin
r = np.array([[cos(angle), -sin(angle)], [sin(angle), cos(angle)]])
layer = viewer.layers[0]
layer.rotate = layer.rotate @ r
# create the viewer with an image
viewer = napari.view_image(data.astronaut(), rgb=True)
layer_buttons = viewer.window.qt_viewer.layerButtons
# Button do not need to do anything, just need to be pretty; all the action
# binding and (un) binding will be done with the action manager, idem for
# setting the tooltip.
rot_button = QtViewerPushButton('warning')
layer_buttons.layout().insertWidget(3, rot_button)
def register_action():
# Here we pass ViewerModel as the KeymapProvider as we want it to handle the shortcuts.
# we could also pass none and bind the shortcuts at the window level – though we
# are trying to not change the KeymapProvider API too much for now.
# we give an action name to the action for configuration purposes as we need
# it to be storable in json.
# By convention (may be enforce later), we do give an action name which is iprefixed
# by the name of the package it is defined in, here napari,
action_manager.register_action(
name='napari:rotate45',
command=rotate45,
description='Rotate layer 0 by 45deg',
keymapprovider=ViewerModel,
)
def bind_shortcut():
# note that the tooltip of the corresponding button will be updated to
# remove the shortcut.
action_manager.unbind_shortcut('napari:reset_view') # Control-R
action_manager.bind_shortcut('napari:rotate45', 'Control-R')
def bind_button():
action_manager.bind_button('napari:rotate45', rot_button)
# we can all bind_shortcut or register_action or bind_button in any order;
# this let us configure shortcuts even if plugins are loaded / unloaded.
callbacks = [register_action, bind_shortcut, bind_button]
shuffle(callbacks)
for c in callbacks:
print('calling', c)
c()
# We can set the action manager in debug mode, to help us figure out which
# button is triggering which action. This will update the tooltips of the buttons
# to include the name of the action in between square brackets.
action_manager._debug(True)
# Let's also modify some existing shortcuts, by unbinding a few existing actions,
# and rebinding them with new shortcuts; below we change the add and select mode
# to be the = (same as + key on US Keyboards but without modifiers) and - keys.
# unbinding returns the old key if it exists; but we don't use it.
# in practice you likely don't need to modify the shortcuts this way as it will
# be implemented in settings, though you could imagine a plugin that would
# allow toggling between many keymaps.
settings = {
'napari:activate_points_add_mode' : '=',
'napari:activate_points_select_mode': '-',
}
for action, key in settings.items():
_old_shortcut = action_manager.unbind_shortcut(action)
action_manager.bind_shortcut(action, key)
if __name__ == '__main__':
napari.run()