#
# Bindings.py -- Bindings classes for Ginga viewer.
#
# This is open-source software licensed under a BSD license.
# Please see the file LICENSE.txt for details.
#
import os.path
import itertools
from ginga.misc import Bunch, Settings, Callback
from ginga.util.paths import icondir
from ginga import events
[docs]
class ImageViewBindings(object):
"""Configurable event handling (bindings) for UI events.
This module handles the mapping of user interface events (keyboard,
mouse, trackpad, gestures) to operations on a Ginga viewer.
"""
action_prefixes = ['kp_', 'ms_', 'sc_', 'pi_', 'pa_']
def __init__(self, logger, settings=None):
super(ImageViewBindings, self).__init__()
self.logger = logger
if settings is None:
# No settings passed. Set up defaults.
settings = Settings.SettingGroup(name='bindings',
logger=self.logger)
#self.initialize_settings(settings)
self.settings = settings
self.features = dict(pan=False, zoom=False, cut=False, cmap=False,
flip=False, rotate=False)
self.cursor_map = {}
self._modes = {}
self._cur_mode = None
[docs]
def initialize_settings(self, settings):
settings.add_settings(
# You should rarely have to change these.
btn_nobtn=0x0,
btn_left=0x1,
btn_middle=0x2,
btn_right=0x4,
btn_back=0x8,
btn_forward=0x10,
# Set up our standard modifiers
mod_shift=['shift_l', 'shift_r'],
mod_ctrl=['control_l', 'control_r'],
mod_win=['meta_right'],
default_mode_type='locked',
default_lock_mode_type='locked',
# MOUSE/BUTTON
ms_none=['nobtn'],
ms_cursor=['left'],
ms_wheel=[],
)
[docs]
def get_settings(self):
return self.settings
[docs]
def add_mode_obj(self, mode_obj):
#self.settings.add_defaults(**mode_obj.actions)
self._modes[str(mode_obj)] = mode_obj
[docs]
def get_mode_obj(self, mode_name):
return self._modes[mode_name]
[docs]
def window_map(self, viewer):
#self.to_default_mode(viewer)
pass
[docs]
def set_bindings(self, viewer):
viewer.add_callback('map', self.window_map)
bindmap = viewer.get_bindmap()
bindmap.clear_button_map()
bindmap.clear_event_map()
bindmap.add_callback('mode-set', self.mode_set_cb, viewer)
# Set up bindings
self.setup_settings_events(viewer, bindmap)
[docs]
def set_mode(self, viewer, name, mode_type='oneshot'):
bindmap = viewer.get_bindmap()
bindmap.set_mode(name, mode_type=mode_type)
[docs]
def mode_set_cb(self, bm, mode, mode_type, viewer):
self.logger.debug(f'mode change to {mode}')
if mode != self._cur_mode:
if self._cur_mode not in ('meta', None):
try:
self._modes[self._cur_mode].stop()
except Exception as e:
self.logger.error("Error stopping mode '{}': {}".format(self._cur_mode, e),
exc_info=True)
self._cur_mode = mode
if self._cur_mode not in ('meta', None):
try:
self._modes[self._cur_mode].start()
except Exception as e:
self.logger.error("Error starting mode '{}': {}".format(self._cur_mode, e),
exc_info=True)
[docs]
def parse_combo(self, combo, modes_set, modifiers_set, pfx):
"""
Parse a string into a mode, a set of modifiers and a trigger.
"""
mode, mods, trigger = None, set([]), combo
if '+' in combo:
if combo.endswith('+'):
# special case: probably contains the keystroke '+'
trigger, combo = '+', combo[:-1]
if '+' in combo:
items = set(combo.split('+'))
else:
items = set(combo)
else:
# trigger is always specified last
items = combo.split('+')
trigger, items = items[-1], set(items[:-1])
if '*' in items:
items.remove('*')
# modifier wildcard
mods = '*'
else:
mods = items.intersection(modifiers_set)
mode = items.intersection(modes_set)
if len(mode) == 0:
mode = None
else:
mode = mode.pop()
if pfx is not None:
trigger = pfx + trigger
return (mode, mods, trigger)
[docs]
def setup_settings_events(self, viewer, bindmap):
self.initialize_settings(self.settings)
# merge in all default settings from various modes, being
# careful not to override any user preferences
act_d = dict()
from ginga.modes.modeinfo import available_modes
for mode_class in available_modes:
mode_obj = mode_class(viewer, settings=self.settings)
for name, value in mode_obj.actions.items():
pfx = name[:3]
if pfx not in self.action_prefixes:
act_d.setdefault(name, value)
continue
# a binding
act_l = act_d.setdefault(name, [])
for trigger in value:
if trigger not in act_l:
act_l.append(trigger)
self.add_mode_obj(mode_obj)
self.settings.add_defaults(**act_d)
# Now settings should have all available modes defined
d = self.settings.get_dict()
# First scan settings for buttons and modes
bindmap.clear_button_map()
bindmap.clear_modifier_map()
bindmap.clear_mode_map()
bindmap.clear_event_map()
mode_type = self.settings.get('default_mode_type', 'oneshot')
bindmap.set_default_mode_type(mode_type)
for name, value in d.items():
if name.startswith('mod_'):
modname = name[4:]
for combo in value:
# NOTE: for now no chorded combinations to make modifiers
keyname = combo
bindmap.add_modifier(keyname, modname)
elif name.startswith('cur_'):
curname = name[4:]
self.add_cursor(viewer, curname, value)
elif name.startswith('btn_'):
btnname = name[4:]
bindmap.map_button(value, btnname)
elif name.startswith('dmod_'):
mode_name = name[5:]
keyname, mode_type, curname = value
bindmap.add_mode(keyname, mode_name, mode_type=mode_type,
msg=None)
if curname is not None:
self.cursor_map[mode_name] = curname
self.merge_actions(viewer, bindmap, self, d.items())
# merge in specific actions for each mode
for mode_obj in self._modes.values():
actions = mode_obj.actions
d = {key: self.settings[key]
for key in actions.keys() if key in self.settings}
self.merge_actions(viewer, bindmap, mode_obj, d.items())
[docs]
def merge_actions(self, viewer, bindmap, obj, tups):
modes_set = bindmap.get_modes()
modifiers_set = bindmap.get_modifiers()
# Add events
for name, value in tups:
if len(name) <= 3:
continue
pfx = name[:3]
if pfx not in self.action_prefixes:
continue
evname = name[3:]
for combo in value:
mode, modifiers, trigger = self.parse_combo(combo, modes_set,
modifiers_set, pfx)
if modifiers == '*':
# wildcard; register for all modifier combinations
modifiers_poss = set([])
for i in range(len(modifiers_set) + 1):
modifiers_poss = modifiers_poss.union(
itertools.combinations(modifiers_set, i))
for modifiers in modifiers_poss:
bindmap.map_event(mode, modifiers, trigger, evname)
else:
bindmap.map_event(mode, modifiers, trigger, evname)
# Register for this symbolic event if we have a handler for it
try:
cb_method = getattr(obj, name)
except AttributeError:
# Do we need a warning here?
#self.logger.warning("No method found matching '%s'" % (name))
cb_method = None
if pfx == 'kp_':
# keyboard event
event = 'keydown-%s' % (evname)
viewer.enable_callback(event)
if cb_method is not None and viewer.num_callbacks(event) == 0:
viewer.add_callback(event, cb_method)
elif pfx == 'ms_':
# mouse/button event
for action in ('down', 'move', 'up'):
event = '%s-%s' % (evname, action)
viewer.enable_callback(event)
if cb_method is not None and viewer.num_callbacks(event) == 0:
viewer.add_callback(event, cb_method)
elif pfx == 'sc_':
# scrolling event
event = '%s-scroll' % evname
viewer.enable_callback(event)
if cb_method is not None and viewer.num_callbacks(event) == 0:
viewer.add_callback(event, cb_method)
elif pfx == 'pi_':
# pinch event
event = '%s-pinch' % evname
viewer.enable_callback(event)
if cb_method is not None and viewer.num_callbacks(event) == 0:
viewer.add_callback(event, cb_method)
elif pfx == 'pa_':
# pan event
event = '%s-pan' % evname
viewer.enable_callback(event)
if cb_method is not None and viewer.num_callbacks(event) == 0:
viewer.add_callback(event, cb_method)
[docs]
def reset(self, viewer):
bindmap = viewer.get_bindmap()
bindmap.reset_mode(viewer)
viewer.onscreen_message(None)
[docs]
def add_cursor(self, viewer, curname, curpath):
if not curpath.startswith('/'):
curpath = os.path.join(icondir, curpath)
cursor = viewer.make_cursor(curpath, 8, 8, size=(16, 16))
viewer.define_cursor(curname, cursor)
##### ENABLERS #####
# These methods are a quick way to enable or disable certain user
# interface features in a ImageView window
[docs]
def enable_pan(self, tf):
"""Enable the image to be panned interactively (True/False)."""
self.enable(pan=tf)
[docs]
def enable_zoom(self, tf):
"""Enable the image to be zoomed interactively (True/False)."""
self.enable(zoom=tf)
[docs]
def enable_cuts(self, tf):
"""Enable the cuts levels to be set interactively (True/False)."""
self.enable(cut=tf)
[docs]
def enable_cmap(self, tf):
"""Enable the color map to be warped interactively (True/False)."""
self.enable(cmap=tf)
[docs]
def enable_flip(self, tf):
"""Enable the image to be flipped interactively (True/False)."""
self.enable(flip=tf)
[docs]
def enable_rotate(self, tf):
"""Enable the image to be rotated interactively (True/False)."""
self.enable(rotate=tf)
[docs]
def enable(self, **kwargs):
"""General enable function encompassing all user interface features.
Usage (e.g.): viewer.enable(rotate=False, flip=True)
"""
for feat, value in kwargs.items():
feat = feat.lower()
if feat not in self.features:
raise ValueError("'%s' is not a feature. Must be one of %s" % (
feat, str(list(self.features.keys()))))
self.features[feat] = bool(value)
[docs]
def enable_all(self, tf):
for feat in list(self.features.keys()):
self.features[feat] = tf
[docs]
def get_feature_allow(self, feat_name):
return self.features[feat_name]
[docs]
def get_direction(self, direction, rev=False):
"""Translate a direction in compass degrees into 'up' or 'down'."""
# TODO: merge with the one in mode_base
if (direction < 90.0) or (direction >= 270.0):
if not rev:
return 'up'
else:
return 'down'
elif (90.0 <= direction < 270.0):
if not rev:
return 'down'
else:
return 'up'
else:
return 'none'
[docs]
class BindingMapError(Exception):
pass
[docs]
class BindingMapper(Callback.Callbacks):
"""Map physical events to logical events.
The BindingMapper class maps physical events (key presses, button
clicks, mouse movement, etc) into logical events. By registering for
logical events, plugins and other event handling code doesn't need to
care about the physical controls bindings. The bindings can be changed
and everything continues to work.
"""
def __init__(self, logger, btnmap=None, mode_map=None, modifier_map=None):
Callback.Callbacks.__init__(self)
self.logger = logger
# For event mapping
self.event_names = ['keydown', 'keyup',
'btn-down', 'btn-move', 'btn-up',
'scroll', 'pinch', 'pan']
self.eventmap = {}
self._kbdmode = None
self._kbdmode_types = ('held', 'oneshot', 'locked')
self._kbdmode_type = 'held'
self._kbdmode_type_default = 'locked'
self._delayed_reset = False
self._modifiers = frozenset([])
# Set up button mapping
if btnmap is None:
btnmap = {0x1: 'cursor', 0x2: 'wheel', 0x4: 'draw'}
self.btnmap = btnmap
self._button = 0
# Set up modifier mapping
if modifier_map is None:
self.modifier_map = {}
for keyname in ('shift_l', 'shift_r'):
self.add_modifier(keyname, 'shift')
for keyname in ('control_l', 'control_r'):
self.add_modifier(keyname, 'ctrl')
for keyname in ('meta_right',):
self.add_modifier(keyname, 'win')
else:
self.modifier_map = modifier_map
# Set up mode mapping
if mode_map is None:
self.mode_map = {}
else:
self.mode_map = mode_map
self._empty_set = frozenset([])
self.mode_tbl = dict()
# For callbacks
for name in ('mode-set', ):
self.enable_callback(name)
[docs]
def add_modifier(self, keyname, modname):
bnch = Bunch.Bunch(name=modname)
self.modifier_map[keyname] = bnch
self.modifier_map['mod_%s' % modname] = bnch
[docs]
def get_modifiers(self):
return set([bnch.name for keyname, bnch
in self.modifier_map.items()])
[docs]
def clear_modifier_map(self):
self.modifier_map = {}
[docs]
def set_mode_map(self, mode_map):
self.mode_map = mode_map
[docs]
def clear_mode_map(self):
self.mode_map = {}
[docs]
def has_mode(self, mode_name):
key = 'mode_%s' % mode_name
return key in self.mode_map
[docs]
def remove_mode(self, mode_name):
key = 'mode_%s' % mode_name
del self.mode_map[key]
[docs]
def current_mode(self):
return (self._kbdmode, self._kbdmode_type)
[docs]
def get_modes(self):
return set([bnch.name for keyname, bnch in self.mode_map.items()])
[docs]
def add_mode(self, keyname, mode_name, mode_type='held', msg=None):
if mode_type is not None:
if mode_type not in self._kbdmode_types:
raise ValueError("Bad mode type '%s': must be one of %s" % (
mode_type, self._kbdmode_types))
bnch = Bunch.Bunch(name=mode_name, type=mode_type, msg=msg)
if keyname is not None:
# Key to launch this mode
if keyname[0:2] == '__':
keyname = keyname[2]
self.mode_tbl[keyname] = bnch
else:
self.mode_map[keyname] = bnch
self.mode_map['mode_%s' % mode_name] = bnch
[docs]
def set_mode(self, name, mode_type=None):
if mode_type is None:
mode_type = self._kbdmode_type_default
if mode_type not in self._kbdmode_types:
raise ValueError("Bad mode type '%s': must be one of %s" % (
mode_type, self._kbdmode_types))
self._kbdmode = name
if name is None:
# like a reset_mode()
mode_type = 'held'
self._delayed_reset = False
self._kbdmode_type = mode_type
self.logger.info("set keyboard mode to '%s' type=%s" % (name, mode_type))
self.make_callback('mode-set', self._kbdmode, self._kbdmode_type)
[docs]
def set_default_mode_type(self, mode_type):
if mode_type not in self._kbdmode_types:
raise ValueError("Bad mode type '%s': must be one of %s" % (
mode_type, self._kbdmode_types))
self._kbdmode_type_default = mode_type
[docs]
def get_default_mode_type(self):
return self._kbdmode_type_default
[docs]
def reset_mode(self, viewer):
try:
bnch = self.mode_map['mode_%s' % self._kbdmode]
except Exception:
bnch = None
self._kbdmode = None
self._kbdmode_type = 'held'
self._delayed_reset = False
self.logger.debug("set keyboard mode reset")
# clear onscreen message, if any
if (bnch is not None) and (bnch.msg is not None):
viewer.onscreen_message(None)
self.make_callback('mode-set', self._kbdmode, self._kbdmode_type)
[docs]
def clear_event_map(self):
self.eventmap = {}
[docs]
def map_event(self, mode, modifiers, trigger, eventname):
self.eventmap[(mode, frozenset(tuple(modifiers)),
trigger)] = Bunch.Bunch(name=eventname)
[docs]
def register_for_events(self, viewer):
# Add callbacks for interesting events
viewer.add_callback('motion', self.window_motion)
viewer.add_callback('button-press', self.window_button_press)
viewer.add_callback('button-release', self.window_button_release)
viewer.add_callback('key-press', self.window_key_press)
viewer.add_callback('key-release', self.window_key_release)
## viewer.add_callback('drag-drop', self.window_drag_drop)
viewer.add_callback('scroll', self.window_scroll)
viewer.add_callback('map', self.window_map)
viewer.add_callback('focus', self.window_focus)
viewer.add_callback('enter', self.window_enter)
viewer.add_callback('leave', self.window_leave)
if viewer.has_callback('pinch'):
viewer.add_callback('pinch', self.window_pinch)
if viewer.has_callback('pan'):
viewer.add_callback('pan', self.window_pan)
for pfx in self.event_names:
# TODO: add moded versions of callbacks?
viewer.enable_callback('%s-none' % (pfx))
[docs]
def mode_key_down(self, viewer, keyname):
"""This method is called when a key is pressed and was not handled
by some other handler with precedence, such as a subcanvas.
"""
# Is this a mode key?
if keyname not in self.mode_map:
if (self._kbdmode != 'meta') or (keyname not in self.mode_tbl):
# No
return False
bnch = self.mode_tbl[keyname]
else:
bnch = self.mode_map[keyname]
mode_name = bnch.name
self.logger.debug("cur mode='%s' mode pressed='%s'" % (
self._kbdmode, mode_name))
if self._delayed_reset:
# <== this shouldn't happen, but put here to reset handling
# of delayed_reset just in case (see cursor up handling)
self._delayed_reset = False
return True
if ((self._kbdmode in (None, 'meta')) or
(self._kbdmode_type != 'locked') or (mode_name == 'meta')):
if self._kbdmode is not None:
self.reset_mode(viewer)
# activate this mode
if self._kbdmode in (None, 'meta'):
mode_type = bnch.type
if mode_type is None:
mode_type = self._kbdmode_type_default
self.set_mode(mode_name, mode_type)
if bnch.msg is not None:
viewer.onscreen_message(bnch.msg)
return True
return False
[docs]
def mode_key_up(self, viewer, keyname):
"""This method is called when a key is pressed in a mode and was
not handled by some other handler with precedence, such as a
subcanvas.
"""
# Is this a mode key?
if keyname not in self.mode_map:
# <== no
return False
bnch = self.mode_map[keyname]
if self._kbdmode == bnch.name:
# <-- the current mode key is being released
if bnch.type == 'held':
if self._button == 0:
# if no button is being held, then reset mode
self.reset_mode(viewer)
else:
self._delayed_reset = True
return True
return False
[docs]
def window_map(self, viewer):
return True
[docs]
def window_focus(self, viewer, has_focus):
if not has_focus:
# fixes a problem with not receiving key release events when the
# window loses focus
self._modifiers = frozenset([])
return False
[docs]
def window_enter(self, viewer):
return False
[docs]
def window_leave(self, viewer):
return False
[docs]
def window_key_press(self, viewer, keyname):
self.logger.debug("keyname=%s" % (keyname))
# Is this a modifer key?
if keyname in self.modifier_map:
bnch = self.modifier_map[keyname]
self._modifiers = self._modifiers.union(set([bnch.name]))
return True
if self.mode_key_down(viewer, keyname):
return True
trigger = 'kp_' + keyname
last_x, last_y = viewer.get_last_data_xy()
event = events.KeyEvent(key=keyname, state='down', mode=self._kbdmode,
modifiers=self._modifiers, viewer=viewer,
data_x=last_x, data_y=last_y)
if self._kbdmode is None:
cbname = 'key-down-none'
else:
if keyname == 'escape':
idx = (None, self._empty_set, trigger)
else:
idx = (self._kbdmode, self._modifiers, trigger)
if idx in self.eventmap:
emap = self.eventmap[idx]
cbname = 'keydown-%s' % (emap.name)
else:
# TEMP: hack to get around the issue of how keynames
# are generated--shifted characters with no modifiers
idx = (self._kbdmode, self._empty_set, trigger)
if idx in self.eventmap:
emap = self.eventmap[idx]
cbname = 'keydown-%s' % (emap.name)
else:
cbname = 'key-down-%s' % str(self._kbdmode).lower()
res = viewer.make_ui_callback_viewer(viewer, cbname, event,
last_x, last_y)
if not event.was_handled() and not res:
# no response for this canvas or mode, try non-mode entry
idx = (None, self._modifiers, trigger)
if idx in self.eventmap:
emap = self.eventmap[idx]
cbname = 'keydown-%s' % (emap.name)
res = viewer.make_ui_callback_viewer(viewer, cbname, event,
last_x, last_y)
else:
# TEMP: hack to get around the issue of how keynames
# are generated--shifted characters with no modifiers
idx = (None, self._empty_set, trigger)
if idx in self.eventmap:
emap = self.eventmap[idx]
cbname = 'keydown-%s' % (emap.name)
res = viewer.make_ui_callback_viewer(viewer, cbname, event,
last_x, last_y)
return res
[docs]
def window_key_release(self, viewer, keyname):
self.logger.debug("keyname=%s" % (keyname))
# Is this a modifer key?
if keyname in self.modifier_map:
bnch = self.modifier_map[keyname]
self._modifiers = self._modifiers.difference(set([bnch.name]))
return True
if self.mode_key_up(viewer, keyname):
return True
trigger = 'kp_' + keyname
last_x, last_y = viewer.get_last_data_xy()
event = events.KeyEvent(key=keyname, state='up', mode=self._kbdmode,
modifiers=self._modifiers, viewer=viewer,
data_x=last_x, data_y=last_y)
if self._kbdmode is None:
cbname = 'key-up-none'
else:
idx = (self._kbdmode, self._modifiers, trigger)
if idx in self.eventmap:
emap = self.eventmap[idx]
cbname = 'keyup-%s' % (emap.name)
else:
# TEMP: hack to get around the issue of how keynames
# are generated--shifted characters with no modifiers
idx = (self._kbdmode, self._empty_set, trigger)
if idx in self.eventmap:
emap = self.eventmap[idx]
cbname = 'keyup-%s' % (emap.name)
else:
cbname = 'key-up-%s' % str(self._kbdmode).lower()
res = viewer.make_ui_callback_viewer(viewer, cbname, event,
last_x, last_y)
if not event.was_handled() and not res:
# no response for this canvas or mode, try non-mode entry
idx = (None, self._modifiers, trigger)
if idx in self.eventmap:
emap = self.eventmap[idx]
cbname = 'keyup-%s' % (emap.name)
res = viewer.make_ui_callback_viewer(viewer, cbname, event,
last_x, last_y)
else:
# TEMP: hack to get around the issue of how keynames
# are generated--shifted characters with no modifiers
idx = (None, self._empty_set, trigger)
if idx in self.eventmap:
emap = self.eventmap[idx]
cbname = 'keyup-%s' % (emap.name)
res = viewer.make_ui_callback_viewer(viewer, cbname, event,
last_x, last_y)
return res
[docs]
def window_motion(self, viewer, btncode, data_x, data_y):
button = self.get_button(btncode)
if button is None:
self.logger.error("unrecognized button code (%x)" % (btncode))
return False
trigger = 'ms_' + button
event = events.PointEvent(button=button, state='move',
mode=self._kbdmode,
modifiers=self._modifiers, viewer=viewer,
data_x=data_x, data_y=data_y)
if self._kbdmode is None:
cbname = 'btn-move-none'
else:
idx = (self._kbdmode, self._modifiers, trigger)
if idx in self.eventmap:
emap = self.eventmap[idx]
cbname = '%s-move' % (emap.name)
else:
cbname = 'btn-move-%s' % str(self._kbdmode).lower()
res = viewer.make_ui_callback_viewer(viewer, cbname, event,
data_x, data_y)
if not event.was_handled() and not res:
# no entry for this mode, try non-mode entry
idx = (None, self._modifiers, trigger)
if idx in self.eventmap:
emap = self.eventmap[idx]
cbname = '%s-move' % (emap.name)
res = viewer.make_ui_callback_viewer(viewer, cbname, event,
data_x, data_y)
return res
[docs]
def window_pinch(self, viewer, state, rot_deg, scale):
btncode = 0
button = self.get_button(btncode)
if button is None:
self.logger.error("unrecognized button code (%x)" % (btncode))
return False
trigger = 'pi_pinch'
last_x, last_y = viewer.get_last_data_xy()
event = events.PinchEvent(button=button, state=state,
mode=self._kbdmode,
modifiers=self._modifiers, viewer=viewer,
rot_deg=rot_deg, scale=scale,
data_x=last_x, data_y=last_y)
if self._kbdmode is None:
cbname = 'pinch-none'
else:
idx = (self._kbdmode, self._modifiers, trigger)
if idx in self.eventmap:
emap = self.eventmap[idx]
cbname = '%s-pinch' % (emap.name)
else:
cbname = 'pinch-%s' % (str(self._kbdmode).lower())
self.logger.debug("making callback for %s (mode=%s)" % (
cbname, self._kbdmode))
res = viewer.make_ui_callback_viewer(viewer, cbname, event)
if not event.was_handled() and not res:
# no entry for this mode, try non-mode entry
idx = (None, self._modifiers, trigger)
if idx in self.eventmap:
emap = self.eventmap[idx]
cbname = '%s-pinch' % (emap.name)
res = viewer.make_ui_callback_viewer(viewer, cbname, event)
return res
[docs]
def window_pan(self, viewer, state, delta_x, delta_y):
btncode = 0
button = self.get_button(btncode)
if button is None:
self.logger.error("unrecognized button code (%x)" % (btncode))
return False
trigger = 'pa_pan'
last_x, last_y = viewer.get_last_data_xy()
event = events.PanEvent(button=button, state=state, mode=self._kbdmode,
modifiers=self._modifiers, viewer=viewer,
delta_x=delta_x, delta_y=delta_y,
data_x=last_x, data_y=last_y)
if self._kbdmode is None:
cbname = 'pan-none'
else:
idx = (self._kbdmode, self._modifiers, trigger)
if idx in self.eventmap:
emap = self.eventmap[idx]
cbname = '%s-pan' % (emap.name)
else:
cbname = 'pan-%s' % (str(self._kbdmode).lower())
self.logger.debug("making callback for %s (mode=%s)" % (
cbname, self._kbdmode))
res = viewer.make_ui_callback_viewer(viewer, cbname, event)
if not event.was_handled() and not res:
# no entry for this mode, try non-mode entry
idx = (None, self._modifiers, trigger)
if idx in self.eventmap:
emap = self.eventmap[idx]
cbname = '%s-pan' % (emap.name)
res = viewer.make_ui_callback_viewer(viewer, cbname, event)
return res