Table Of Contents

Source code for kivy.input.providers.mouse

'''
Mouse provider implementation
=============================

On linux systems, the mouse provider can be annoying when used with another
multitouch provider (hidinput or mtdev). The Mouse can conflict with them: a
single touch can generate one event from the mouse provider and another
from the multitouch provider.

To avoid this behavior, you can activate the "disable_on_activity" token in
the mouse configuration. Then, if any touches are created by another
provider, the mouse event will be discarded. Add this to your configuration::

    [input]
    mouse = mouse,disable_on_activity

Using multitouch interaction with the mouse
-------------------------------------------

.. versionadded:: 1.3.0

By default, the middle and right mouse buttons, as well as a combination of
ctrl + left mouse button are used for multitouch emulation.

When multitouch simulation is enabled, pressing the right or middle button (or
ctrl + left button) places a red dot on screen representing a held touch. The
dot follows the cursor while the button is held, then stays fixed at its last
position until it is picked up again (by clicking near it) or removed (by
clicking on it with the left button). Pressing ctrl + left click behaves
identically to a right-click for multitouch emulation purposes.

If you want to use these mouse buttons for other purposes, you can disable
this behavior by activating the "disable_multitouch" token::

   [input]
   mouse = mouse,disable_multitouch

.. versionchanged:: 1.9.0

You can now selectively control whether a click initiated as described above
will emulate multi-touch. If the touch has been initiated in the above manner
(e.g. right mouse button), a `multitouch_sim` value will be added to the
touch's profile, and a `multitouch_sim` property will be added to the touch.
By default, `multitouch_sim` is True and multitouch will be emulated for that
touch. If, however, `multitouch_on_demand` is added to the config::

   [input]
   mouse = mouse,multitouch_on_demand

then `multitouch_sim` defaults to `False`. In that case, if `multitouch_sim`
is set to True before the mouse is released (e.g. in on_touch_down/move), the
touch will simulate a multi-touch event. For example::

    if 'multitouch_sim' in touch.profile:
        touch.multitouch_sim = True

.. versionchanged:: 2.1.0

Provider dispatches hover events by listening to properties/events in
:class:`~kivy.core.window.Window`. Dispatching can be disabled by setting
:attr:`MouseMotionEventProvider.disable_hover` to ``True`` or by adding
`disable_hover` in the config::

    [input]
    mouse = mouse,disable_hover

It's also possible to enable/disable hover events at runtime with
:attr:`MouseMotionEventProvider.disable_hover` property.

Following is a list of the supported values for the
:attr:`~kivy.input.motionevent.MotionEvent.profile` property list.

================ ==========================================================
Profile value    Description
---------------- ----------------------------------------------------------
button           Mouse button (one of `left`, `right`, `middle`, `scrollup`
                 or `scrolldown`). Accessed via the 'button' property.
pos              2D position. Also reflected in the
                 :attr:`~kivy.input.motionevent.MotionEvent.x`,
                 :attr:`~kivy.input.motionevent.MotionEvent.y`
                 and :attr:`~kivy.input.motionevent.MotionEvent.pos`
                 properties.
multitouch_sim   Specifies whether multitouch is simulated or not. Accessed
                 via the 'multitouch_sim' property.

modifiers        Active keyboard modifiers (list of strings: 'ctrl', 'alt',
                 'shift', etc.). Accessed via the 'modifiers' property.
================ ==========================================================

'''

__all__ = ('MouseMotionEventProvider', )

from kivy.base import EventLoop
from collections import deque
from kivy.logger import Logger
from kivy.input.provider import MotionEventProvider
from kivy.input.factory import MotionEventFactory
from kivy.input.motionevent import MotionEvent

# late binding
Color = Ellipse = None


class MouseMotionEvent(MotionEvent):

    def __init__(self, *args, **kwargs):
        self.multitouch_sim = False
        self.modifiers = []
        super().__init__(*args, **kwargs)

    def depack(self, args):
        self.sx, self.sy = args[:2]
        profile = self.profile
        if self.is_touch:
            # don't overwrite previous profile
            if not profile:
                profile.extend(('pos', 'button', 'modifiers'))
            if len(args) >= 3:
                self.button = args[2]
            if len(args) >= 4:
                self.multitouch_sim = args[3]
                profile.append('multitouch_sim')
        else:
            if not profile:
                profile.append('pos')
        super().depack(args)

    #
    # Create automatically touch on the surface.
    #

    def update_graphics(self, win, create=False):
        global Color, Ellipse
        de = self.ud.get('_drawelement', None)
        if de is None and create:
            if Color is None:
                from kivy.graphics import Color, Ellipse
            with win.canvas.after:
                de = (
                    Color(.8, .2, .2, .7),
                    Ellipse(size=(20, 20), segments=15))
            self.ud._drawelement = de
        if de is not None:
            self.push()

            # use same logic as WindowBase.on_motion() so we get correct
            # coordinates when _density != 1
            w, h = win.unrotated_size

            self.scale_for_screen(w, h, rotation=win.rotation)

            de[1].pos = self.x - 10, self.y - 10
            self.pop()

    def clear_graphics(self, win):
        de = self.ud.pop('_drawelement', None)
        if de is not None:
            win.canvas.after.remove(de[0])
            win.canvas.after.remove(de[1])


[docs] class MouseMotionEventProvider(MotionEventProvider): def __init__(self, device, args): super(MouseMotionEventProvider, self).__init__(device, args) self.waiting_event = deque() self.touches = {} self.counter = 0 self.current_drag = None self.disable_on_activity = False self.disable_multitouch = False self.multitouch_on_demand = False self.hover_event = None self._disable_hover = False self._running = False # split arguments args = args.split(',') for arg in args: arg = arg.strip() if arg == '': continue elif arg == 'disable_on_activity': self.disable_on_activity = True elif arg == 'disable_multitouch': self.disable_multitouch = True elif arg == 'disable_hover': self.disable_hover = True elif arg == 'multitouch_on_demand': self.multitouch_on_demand = True else: Logger.error('Mouse: unknown parameter <%s>' % arg) def _get_disable_hover(self): return self._disable_hover def _set_disable_hover(self, value): if self._disable_hover != value: if self._running: if value: self._stop_hover_events() else: self._start_hover_events() self._disable_hover = value disable_hover = property(_get_disable_hover, _set_disable_hover) '''Disables dispatching of hover events if set to ``True``. Hover events are enabled by default (`disable_hover` is ``False``). See module documentation if you want to enable/disable hover events through config file. .. versionadded:: 2.1.0 '''
[docs] def start(self): '''Start the mouse provider''' if not EventLoop.window: return fbind = EventLoop.window.fbind fbind('on_mouse_down', self.on_mouse_press) fbind('on_mouse_move', self.on_mouse_motion) fbind('on_mouse_up', self.on_mouse_release) fbind('on_rotate', self.update_touch_graphics) fbind('system_size', self.update_touch_graphics) if not self.disable_hover: self._start_hover_events() self._running = True
def _start_hover_events(self): fbind = EventLoop.window.fbind fbind('mouse_pos', self.begin_or_update_hover_event) fbind('system_size', self.update_hover_event) fbind('on_cursor_enter', self.begin_hover_event) fbind('on_cursor_leave', self.end_hover_event) fbind('on_close', self.end_hover_event) fbind('on_rotate', self.update_hover_event)
[docs] def stop(self): '''Stop the mouse provider''' if not EventLoop.window: return funbind = EventLoop.window.funbind funbind('on_mouse_down', self.on_mouse_press) funbind('on_mouse_move', self.on_mouse_motion) funbind('on_mouse_up', self.on_mouse_release) funbind('on_rotate', self.update_touch_graphics) funbind('system_size', self.update_touch_graphics) if not self.disable_hover: self._stop_hover_events() self._running = False
def _stop_hover_events(self): funbind = EventLoop.window.funbind funbind('mouse_pos', self.begin_or_update_hover_event) funbind('system_size', self.update_hover_event) funbind('on_cursor_enter', self.begin_hover_event) funbind('on_cursor_leave', self.end_hover_event) funbind('on_close', self.end_hover_event) funbind('on_rotate', self.update_hover_event) def test_activity(self): if not self.disable_on_activity: return False # trying to get if we currently have other touch than us # discard touches generated from kinetic for touch in EventLoop.touches: # discard all kinetic touch if touch.__class__.__name__ == 'KineticMotionEvent': continue # not our instance, stop mouse if touch.__class__ != MouseMotionEvent: return True return False def find_touch(self, win, x, y): factor = 10. / win.system_size[0] for touch in self.touches.values(): if abs(x - touch.sx) < factor and abs(y - touch.sy) < factor: return touch return None def create_event_id(self): self.counter += 1 return self.device + str(self.counter) def create_touch( self, win, nx, ny, is_double_tap, do_graphics, button, modifiers=None ): event_id = self.create_event_id() args = [nx, ny, button] if do_graphics: args += [not self.multitouch_on_demand] self.current_drag = touch = MouseMotionEvent( self.device, event_id, args, is_touch=True, type_id='touch' ) touch.is_double_tap = is_double_tap touch.modifiers = [] if modifiers is None else modifiers self.touches[event_id] = touch if do_graphics: # only draw red circle if multitouch is not disabled, and # if the multitouch_on_demand feature is not enable # (because in that case, we wait to see if multitouch_sim # is True or not before doing the multitouch) create_flag = ( not self.disable_multitouch and not self.multitouch_on_demand ) touch.update_graphics(win, create_flag) self.waiting_event.append(('begin', touch)) return touch def remove_touch(self, win, touch): if touch.id in self.touches: del self.touches[touch.id] touch.update_time_end() self.waiting_event.append(('end', touch)) touch.clear_graphics(win) def create_hover(self, win, etype): nx, ny = win.to_normalized_pos(*win.mouse_pos) # Divide by density because it's used by mouse_pos nx /= win._density ny /= win._density args = (nx, ny) hover = self.hover_event if hover: hover.move(args) else: self.hover_event = hover = MouseMotionEvent( self.device, self.create_event_id(), args, type_id='hover' ) if etype == 'end': hover.update_time_end() self.hover_event = None self.waiting_event.append((etype, hover)) def on_mouse_motion(self, win, x, y, modifiers): nx, ny = win.to_normalized_pos(x, y) ny = 1.0 - ny if self.current_drag: touch = self.current_drag touch.move([nx, ny]) touch.update_graphics(win) touch.modifiers = modifiers self.waiting_event.append(('update', touch)) def on_mouse_press(self, win, x, y, button, modifiers): if self.test_activity(): return nx, ny = win.to_normalized_pos(x, y) ny = 1.0 - ny found_touch = self.find_touch(win, nx, ny) if found_touch: self.current_drag = found_touch else: is_double_tap = 'shift' in modifiers do_graphics = ( not self.disable_multitouch and (button != 'left' or 'ctrl' in modifiers) ) self.create_touch( win, nx, ny, is_double_tap, do_graphics, button, modifiers ) def on_mouse_release(self, win, x, y, button, modifiers): if button == 'all': # Special case, if button is all, # then remove all the current touches. for touch in list(self.touches.values()): self.remove_touch(win, touch) self.current_drag = None touch = self.current_drag if touch: touch.modifiers = modifiers not_right = button in ( 'left', 'scrollup', 'scrolldown', 'scrollleft', 'scrollright' ) not_ctrl = 'ctrl' not in modifiers not_multi = ( self.disable_multitouch or 'multitouch_sim' not in touch.profile or not touch.multitouch_sim ) if (not_right and not_ctrl) or not_multi: self.remove_touch(win, touch) self.current_drag = None else: touch.update_graphics(win, True) def update_touch_graphics(self, win, *args): for touch in self.touches.values(): touch.update_graphics(win) def begin_or_update_hover_event(self, win, *args): etype = 'update' if self.hover_event else 'begin' self.create_hover(win, etype) def begin_hover_event(self, win, *args): if not self.hover_event: self.create_hover(win, 'begin') def update_hover_event(self, win, *args): if self.hover_event: self.create_hover(win, 'update') def end_hover_event(self, win, *args): if self.hover_event: self.create_hover(win, 'end')
[docs] def update(self, dispatch_fn): '''Update the mouse provider (pop event from the queue)''' try: while True: event = self.waiting_event.popleft() dispatch_fn(*event) except IndexError: pass
# registers MotionEventFactory.register('mouse', MouseMotionEventProvider)