Ticket #3269: mousmodes.diff

File mousmodes.diff, 15.3 KB (added by Tristan Croll, 6 years ago)

Added by email2trac

  • src/bundles/mouse_modes/src/mousemodes.py

    diff --git a/src/bundles/mouse_modes/src/mousemodes.py b/src/bundles/mouse_modes/src/mousemodes.py
    old mode 100644
    new mode 100755
    index eea9b8496..fdd99387d
    a b class MouseMode:  
    6464
    6565    def enable(self):
    6666        '''
    67         Supported API. 
     67        Supported API.
    6868        Called when mouse mode is enabled.
    6969        Override if mode wants to know that it has been bound to a mouse button.
    7070        '''
    class MouseMode:  
    139139    def uses_wheel(self):
    140140        '''Return True if derived class implements the wheel() method.'''
    141141        return getattr(self, 'wheel') != MouseMode.wheel
    142    
     142
    143143    def pause(self, position):
    144144        '''
    145145        Supported API.
    class MouseMode:  
    201201        cfile = inspect.getfile(cls)
    202202        p = path.join(path.dirname(cfile), file)
    203203        return p
    204    
     204
    205205class MouseBinding:
    206206    '''
    207207    Associates a mouse button ('left', 'middle', 'right', 'wheel', 'pause') and
    class MouseBinding:  
    227227        '''
    228228        return button == self.button and set(modifiers) == set(self.modifiers)
    229229
     230
     231
    230232class MouseModes:
    231233    '''
    232234    Keep the list of available mouse modes and also which mode is bound
    class MouseModes:  
    246248
    247249        from PyQt5.QtCore import Qt
    248250        # Qt maps control to meta on Mac...
    249         self._modifier_bits = []
    250         for keyfunc in ["alt", "control", "command", "shift"]:
    251             self._modifier_bits.append((mod_key_info(keyfunc)[0], keyfunc))
    252251
    253252        # Mouse pause parameters
    254253        self._last_mouse_time = None
    class MouseModes:  
    261260        self._last_mode = None                  # Remember mode at mouse down and stay with it until mouse up
    262261
    263262        from .trackpad import MultitouchTrackpad
    264         self.trackpad = MultitouchTrackpad(session)
     263        self.trackpad = MultitouchTrackpad(session, self)
    265264
    266265    def bind_mouse_mode(self, button, modifiers, mode):
    267266        '''
    class MouseModes:  
    331330            if m.name == name:
    332331                return m
    333332        return None
    334    
     333
    335334    def mouse_pause_tracking(self):
    336335        '''
    337336        Called periodically to check for mouse pause and invoke pause mode.
    class MouseModes:  
    382381    def _mouse_buttons_down(self):
    383382        from PyQt5.QtCore import Qt
    384383        return self.session.ui.mouseButtons() != Qt.NoButton
    385        
     384
    386385    def _dispatch_mouse_event(self, event, action):
    387386        button, modifiers = self._event_type(event)
    388387        if button is None:
    class MouseModes:  
    404403            self._last_mode = None
    405404
    406405    def _event_type(self, event):
    407         modifiers = self._key_modifiers(event)
     406        modifiers = key_modifiers(event)
    408407
    409408        # button() gives press/release buttons; buttons() gives move buttons
    410409        from PyQt5.QtCore import Qt
    class MouseModes:  
    449448
    450449        return button, modifiers
    451450
     451    def _dispatch_touch_event(self, touch_event):
     452        te = touch_event
     453        t_string = ('Registered touch event: \n'
     454            'wheel_value: {}\n'
     455            'two_finger_trans: {}\n'
     456            'two_finger_scale: {}\n'
     457            'two_finger_twist: {}\n'
     458            'three_finger_trans: {}\n'
     459            'four_finger_trans: {}').format(
     460                te.wheel_value,
     461                te.two_finger_trans,
     462                te.two_finger_scale,
     463                te.two_finger_twist,
     464                te.three_finger_trans,
     465                te.four_finger_trans
     466            )
     467        print(t_string)
     468
     469
    452470    def _have_mode(self, button, modifier):
    453471        for b in self.bindings:
    454472            if b.exact_match(button, [modifier]):
    455473                return True
    456474        return False
    457475
    458     def _key_modifiers(self, event):
    459         mod = event.modifiers()
    460         modifiers = [mod_name for bit, mod_name in self._modifier_bits if bit & mod]
    461         return modifiers
    462 
    463476    def _mouse_pause(self):
    464477        m = self.mode('pause')
    465478        if m:
    class MouseModes:  
    482495    def _wheel_event(self, event):
    483496        if self.trackpad.discard_trackpad_wheel_event(event):
    484497            return      # Trackpad processing handled this event
    485         f = self.mode('wheel', self._key_modifiers(event))
     498        f = self.mode('wheel', key_modifiers(event))
    486499        if f:
    487500            f.wheel(MouseEvent(event))
    488501
    class MouseEvent:  
    498511                                        # for mouse button emulation.
    499512        self._position = position       # x,y in pixels, can be None
    500513        self._wheel_value = wheel_value # wheel clicks (usually 1 click equals 15 degrees rotation).
    501        
     514
    502515    def shift_down(self):
    503516        '''
    504517        Supported API.
    class MouseEvent:  
    556569                delta = min(deltas.x(), deltas.y())
    557570            return delta/120.0   # Usually one wheel click is delta of 120
    558571        return 0
    559        
     572
    560573def mod_key_info(key_function):
    561574    """Qt swaps control/meta on Mac, so centralize that knowledge here.
    562575    The possible "key_functions" are: alt, control, command, and shift
    def mod_key_info(key_function):  
    584597            return Qt.ControlModifier, "control"
    585598        return Qt.MetaModifier, command_name
    586599
     600_function_keys = ["alt", "control", "command", "shift"]
     601_modifier_bits = [(mod_key_info(fkey)[0], fkey) for fkey in _function_keys]
     602
     603def key_modifiers(event):
     604    mod = event.modifiers()
     605    modifiers = [mod_name for bit, mod_name in _modifier_bits if bit & mod]
     606    return modifiers
     607
     608
    587609def keyboard_modifier_names(qt_keyboard_modifiers):
    588610    from PyQt5.QtCore import Qt
    589611    import sys
    def keyboard_modifier_names(qt_keyboard_modifiers):  
    601623    mnames = [mname for mflag, mname in modifiers if mflag & qt_keyboard_modifiers]
    602624    return mnames
    603625
     626
     627
     628
     629
    604630def unpickable(drawing):
    605631    return not getattr(drawing, 'pickable', True)
    606    
     632
    607633def picked_object(window_x, window_y, view, max_transparent_layers = 3, exclude = unpickable):
    608634    xyz1, xyz2 = view.clip_plane_points(window_x, window_y)
    609635    if xyz1 is None or xyz2 is None:
    def picked_object_on_segment(xyz1, xyz2, view, max_transparent_layers = 3, exclu  
    621647        else:
    622648            break
    623649    return p2 if p2 else p
    624 
  • src/bundles/mouse_modes/src/trackpad.py

    diff --git a/src/bundles/mouse_modes/src/trackpad.py b/src/bundles/mouse_modes/src/trackpad.py
    old mode 100644
    new mode 100755
    index d0e2109d3..f18fe8dea
    a b class MultitouchTrackpad:  
    1717    and three finger drag translate scene,
    1818    and two finger pinch zoom scene.
    1919    '''
    20     def __init__(self, session):
     20    def __init__(self, session, mouse_mode_mgr):
    2121        self._session = session
     22        self._mouse_mode_mgr = mouse_mode_mgr
    2223        self._view = session.main_view
    2324        self._recent_touches = []       # List of Touch instances
     25        self._modifier_keys = []
    2426        self._last_touch_locations = {} # Map touch id -> (x,y)
    2527        from .settings import settings
    2628        self.trackpad_speed = settings.trackpad_sensitivity     # Trackpad position sensitivity
    class MultitouchTrackpad:  
    5052            t.remove_handler(h)
    5153            h = None
    5254        self._touch_handler = h
    53    
     55
    5456    def _enable_touch_events(self, graphics_window):
    5557        from sys import platform
    5658        if platform == 'darwin':
    class MultitouchTrackpad:  
    6971        w.setAttribute(Qt.WA_AcceptTouchEvents)
    7072        print('graphics widget touch enabled', w.testAttribute(Qt.WA_AcceptTouchEvents))
    7173        '''
    72        
     74
    7375    # Appears that Qt has disabled touch events on Mac due to unresolved scrolling lag problems.
    7476    # Searching for qt setAcceptsTouchEvents shows they were disabled Oct 17, 2012.
    7577    # A patch that allows an environment variable QT_MAC_ENABLE_TOUCH_EVENTS to allow touch
    class MultitouchTrackpad:  
    8486
    8587        from PyQt5.QtCore import QEvent
    8688        t = event.type()
     89        from .mousemodes import key_modifiers
     90        self._modifier_keys = key_modifiers(event)
    8791        if t == QEvent.TouchUpdate:
    88             # On Mac touch events get backlogged in queue when the events cause 
     92            # On Mac touch events get backlogged in queue when the events cause
    8993            # time consuming computatation.  It appears Qt does not collapse the events.
    9094            # So event processing can get tens of seconds behind.  To reduce this problem
    9195            # we only handle the most recent touch update per redraw.
    class MultitouchTrackpad:  
    100104    def _collapse_touch_events(self):
    101105        touches = self._recent_touches
    102106        if touches:
    103             self._process_touches(touches)
     107            event = self._process_touches(touches)
    104108            self._recent_touches = []
     109            self._mouse_mode_mgr._dispatch_touch_event(event)
    105110
    106111    def _process_touches(self, touches):
     112        pinch = twist = scroll = None
     113        two_swipe = None
     114        three_swipe = None
     115        four_swipe = None
    107116        n = len(touches)
    108117        speed = self.trackpad_speed
    109118        moves = [t.move(self._last_touch_locations) for t in touches]
     119        dx = sum(x for x,y in moves)/n
     120        dy = sum(y for x,y in moves)/n
     121
    110122        if n == 2:
    111123            (dx0,dy0),(dx1,dy1) = moves[0], moves[1]
    112124            from math import sqrt, exp, atan2, pi
    113125            l0,l1 = sqrt(dx0*dx0 + dy0*dy0),sqrt(dx1*dx1 + dy1*dy1)
    114126            d12 = dx0*dx1+dy0*dy1
    115127            if d12 < 0:
    116                 # Finger moving in opposite directions: pinch or twist
     128                # Finger moving in opposite directions: pinch/twist
    117129                (x0,y0),(x1,y1) = [(t.x,t.y) for t in touches[:2]]
    118130                sx,sy = x1-x0,y1-y0
    119131                sn = sqrt(sx*sx + sy*sy)
    120132                sd0,sd1 = sx*dx0 + sy*dy0, sx*dx1 + sy*dy1
    121                 if abs(sd0) > 0.5*sn*l0 and abs(sd1) > 0.5*sn*l1:
    122                     # Fingers move along line between them: pinch to zoom
    123                     zf = 1 + speed * self._zoom_scaling * (l0+l1) / self._full_width_translation_distance
    124                     if sd1 < 0:
    125                         zf = 1/zf
    126                     self._zoom(zf)
    127                 else:
    128                     # Fingers move perpendicular to line between them: twist
    129                     rot = atan2(-sy*dx1+sx*dy1,sn*sn) + atan2(sy*dx0-sx*dy0,sn*sn)
    130                     a = -speed * self._twist_scaling * rot * 180 / pi
    131                     zaxis = (0,0,1)
    132                     self._rotate(zaxis, a)
    133                 return
    134             # Fingers moving in same direction: rotation
    135             dx = sum(x for x,y in moves)/n
    136             dy = sum(y for x,y in moves)/n
    137             from math import sqrt
    138             turns = sqrt(dx*dx + dy*dy)/self._full_rotation_distance
    139             angle = speed*360*turns
    140             self._rotate((dy, dx, 0), angle)
     133                zf = 1 + speed * self._zoom_scaling * (l0+l1) / self._full_width_translation_distance
     134                if sd1 < 0:
     135                    zf = 1/zf
     136                pinch = zf
     137                rot = atan2(-sy*dx1+sx*dy1,sn*sn) + atan2(sy*dx0-sx*dy0,sn*sn)
     138                a = -speed * self._twist_scaling * rot * 180 / pi
     139                twist = a
     140            else:
     141                two_swipe = (dx, dy)
     142                scroll = speed * dy / self._wheel_click_pixels
    141143        elif n == 3:
    142             dx = sum(x for x,y in moves)/n
    143             dy = sum(y for x,y in moves)/n
    144             ww = self._view.window_size[0]      # Window width in pixels
    145             s = speed * ww / self._full_width_translation_distance
    146             self._translate((s*dx, -s*dy, 0))
     144            three_swipe = (dx, dy)
    147145        elif n == 4:
    148             # Use scrollwheel mouse mode
    149             ses = self._session
    150             from .mousemodes import keyboard_modifier_names, MouseEvent
    151             modifiers = keyboard_modifier_names(ses.ui.queryKeyboardModifiers())
    152             scrollwheel_mode = ses.ui.mouse_modes.mode(button = 'wheel', modifiers = modifiers)
    153             if scrollwheel_mode:
    154                 xy = (sum(t.x for t in touches)/n, sum(t.y for t in touches)/n)
    155                 dy = sum(y for x,y in moves)/n                  # pixels
    156                 delta = speed * dy / self._wheel_click_pixels   # wheel clicks
    157                 scrollwheel_mode.wheel(MouseEvent(position = xy, wheel_value = delta, modifiers = modifiers))
     146            four_swipe = (dx, dy)
     147
     148        return MultitouchEvent(modifiers=self._modifier_keys,
     149            wheel_value=scroll, two_finger_trans=two_swipe, two_finger_scale=pinch,
     150            two_finger_twist=twist, three_finger_trans=three_swipe,
     151            four_finger_trans=four_swipe)
     152
     153        return pinch, twist, scroll, two_swipe, three_swipe, four_swipe
    158154
    159155    def _rotate(self, screen_axis, angle):
    160156        if angle == 0:
    class Touch:  
    230226        x,y = self.x, self.y
    231227        last_touch_locations[id] = (x,y)
    232228        return (x-lx, y-ly)
     229
     230
     231from .mousemodes import MouseEvent
     232class MultitouchEvent(MouseEvent):
     233    '''
     234    Provides an interface to events fired by multi-touch trackpads and modifier
     235    keys so that mouse modes do not directly depend on details of the window
     236    toolkit or trackpad implementation.
     237    '''
     238    def __init__(self, modifiers = None,  wheel_value = None,
     239            two_finger_trans=None, two_finger_scale=None, two_finger_twist=None,
     240            three_finger_trans=None, four_finger_trans=None):
     241        super().__init__(event=None, modifiers=modifiers, position=None, wheel_value=wheel_value)
     242        self._two_finger_trans = two_finger_trans
     243        self._two_finger_scale = two_finger_scale
     244        self._two_finger_twist = two_finger_twist
     245        self._three_finger_trans = three_finger_trans
     246        self._four_finger_trans = four_finger_trans
     247
     248    @property
     249    def event(self):
     250        '''
     251        The core QTouchEvent object
     252        '''
     253        return self._event
     254
     255    @property
     256    def wheel_value(self):
     257        '''
     258        Supported API.
     259        Effective mouse wheel value if two-finger vertical swipe is to be
     260        interpreted as a scrolling action.
     261        '''
     262        return self._wheel_value
     263
     264    @property
     265    def two_finger_trans(self):
     266        '''
     267        Supported API.
     268        Returns a tuple (delta_x, delta_y) in screen coordinates representing
     269        the movement when a two-finger swipe is interpreted as a translation
     270        action.
     271        '''
     272        return self._two_finger_trans
     273
     274    @property
     275    def two_finger_scale(self):
     276        '''
     277        Supported API
     278        Returns a float representing the change in a two-finger pinching action.
     279        '''
     280        return self._two_finger_scale
     281
     282    @property
     283    def two_finger_twist(self):
     284        '''
     285        Supported API
     286        Returns the rotation in degrees defined by a two-finger twisting action.
     287        '''
     288        return self._two_finger_twist
     289
     290    @property
     291    def three_finger_trans(self):
     292        '''
     293        Supported API
     294        Returns a tuple (delta_x, delta_y) in screen coordinates representing
     295        the translation in a 3-fingered swipe.
     296        '''
     297        return self._three_finger_trans
     298
     299    @property
     300    def four_finger_trans(self):
     301        '''
     302        Supported API
     303        Returns a tuple (delta_x, delta_y) in screen coordinates representing
     304        the translation in a 3-fingered swipe.
     305        '''
     306        return self._four_finger_trans