Ticket #3269: mousmodes.diff
| File mousmodes.diff, 15.3 KB (added by , 6 years ago) |
|---|
-
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: 64 64 65 65 def enable(self): 66 66 ''' 67 Supported API. 67 Supported API. 68 68 Called when mouse mode is enabled. 69 69 Override if mode wants to know that it has been bound to a mouse button. 70 70 ''' … … class MouseMode: 139 139 def uses_wheel(self): 140 140 '''Return True if derived class implements the wheel() method.''' 141 141 return getattr(self, 'wheel') != MouseMode.wheel 142 142 143 143 def pause(self, position): 144 144 ''' 145 145 Supported API. … … class MouseMode: 201 201 cfile = inspect.getfile(cls) 202 202 p = path.join(path.dirname(cfile), file) 203 203 return p 204 204 205 205 class MouseBinding: 206 206 ''' 207 207 Associates a mouse button ('left', 'middle', 'right', 'wheel', 'pause') and … … class MouseBinding: 227 227 ''' 228 228 return button == self.button and set(modifiers) == set(self.modifiers) 229 229 230 231 230 232 class MouseModes: 231 233 ''' 232 234 Keep the list of available mouse modes and also which mode is bound … … class MouseModes: 246 248 247 249 from PyQt5.QtCore import Qt 248 250 # 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))252 251 253 252 # Mouse pause parameters 254 253 self._last_mouse_time = None … … class MouseModes: 261 260 self._last_mode = None # Remember mode at mouse down and stay with it until mouse up 262 261 263 262 from .trackpad import MultitouchTrackpad 264 self.trackpad = MultitouchTrackpad(session )263 self.trackpad = MultitouchTrackpad(session, self) 265 264 266 265 def bind_mouse_mode(self, button, modifiers, mode): 267 266 ''' … … class MouseModes: 331 330 if m.name == name: 332 331 return m 333 332 return None 334 333 335 334 def mouse_pause_tracking(self): 336 335 ''' 337 336 Called periodically to check for mouse pause and invoke pause mode. … … class MouseModes: 382 381 def _mouse_buttons_down(self): 383 382 from PyQt5.QtCore import Qt 384 383 return self.session.ui.mouseButtons() != Qt.NoButton 385 384 386 385 def _dispatch_mouse_event(self, event, action): 387 386 button, modifiers = self._event_type(event) 388 387 if button is None: … … class MouseModes: 404 403 self._last_mode = None 405 404 406 405 def _event_type(self, event): 407 modifiers = self._key_modifiers(event)406 modifiers = key_modifiers(event) 408 407 409 408 # button() gives press/release buttons; buttons() gives move buttons 410 409 from PyQt5.QtCore import Qt … … class MouseModes: 449 448 450 449 return button, modifiers 451 450 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 452 470 def _have_mode(self, button, modifier): 453 471 for b in self.bindings: 454 472 if b.exact_match(button, [modifier]): 455 473 return True 456 474 return False 457 475 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 modifiers462 463 476 def _mouse_pause(self): 464 477 m = self.mode('pause') 465 478 if m: … … class MouseModes: 482 495 def _wheel_event(self, event): 483 496 if self.trackpad.discard_trackpad_wheel_event(event): 484 497 return # Trackpad processing handled this event 485 f = self.mode('wheel', self._key_modifiers(event))498 f = self.mode('wheel', key_modifiers(event)) 486 499 if f: 487 500 f.wheel(MouseEvent(event)) 488 501 … … class MouseEvent: 498 511 # for mouse button emulation. 499 512 self._position = position # x,y in pixels, can be None 500 513 self._wheel_value = wheel_value # wheel clicks (usually 1 click equals 15 degrees rotation). 501 514 502 515 def shift_down(self): 503 516 ''' 504 517 Supported API. … … class MouseEvent: 556 569 delta = min(deltas.x(), deltas.y()) 557 570 return delta/120.0 # Usually one wheel click is delta of 120 558 571 return 0 559 572 560 573 def mod_key_info(key_function): 561 574 """Qt swaps control/meta on Mac, so centralize that knowledge here. 562 575 The possible "key_functions" are: alt, control, command, and shift … … def mod_key_info(key_function): 584 597 return Qt.ControlModifier, "control" 585 598 return Qt.MetaModifier, command_name 586 599 600 _function_keys = ["alt", "control", "command", "shift"] 601 _modifier_bits = [(mod_key_info(fkey)[0], fkey) for fkey in _function_keys] 602 603 def 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 587 609 def keyboard_modifier_names(qt_keyboard_modifiers): 588 610 from PyQt5.QtCore import Qt 589 611 import sys … … def keyboard_modifier_names(qt_keyboard_modifiers): 601 623 mnames = [mname for mflag, mname in modifiers if mflag & qt_keyboard_modifiers] 602 624 return mnames 603 625 626 627 628 629 604 630 def unpickable(drawing): 605 631 return not getattr(drawing, 'pickable', True) 606 632 607 633 def picked_object(window_x, window_y, view, max_transparent_layers = 3, exclude = unpickable): 608 634 xyz1, xyz2 = view.clip_plane_points(window_x, window_y) 609 635 if xyz1 is None or xyz2 is None: … … def picked_object_on_segment(xyz1, xyz2, view, max_transparent_layers = 3, exclu 621 647 else: 622 648 break 623 649 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: 17 17 and three finger drag translate scene, 18 18 and two finger pinch zoom scene. 19 19 ''' 20 def __init__(self, session ):20 def __init__(self, session, mouse_mode_mgr): 21 21 self._session = session 22 self._mouse_mode_mgr = mouse_mode_mgr 22 23 self._view = session.main_view 23 24 self._recent_touches = [] # List of Touch instances 25 self._modifier_keys = [] 24 26 self._last_touch_locations = {} # Map touch id -> (x,y) 25 27 from .settings import settings 26 28 self.trackpad_speed = settings.trackpad_sensitivity # Trackpad position sensitivity … … class MultitouchTrackpad: 50 52 t.remove_handler(h) 51 53 h = None 52 54 self._touch_handler = h 53 55 54 56 def _enable_touch_events(self, graphics_window): 55 57 from sys import platform 56 58 if platform == 'darwin': … … class MultitouchTrackpad: 69 71 w.setAttribute(Qt.WA_AcceptTouchEvents) 70 72 print('graphics widget touch enabled', w.testAttribute(Qt.WA_AcceptTouchEvents)) 71 73 ''' 72 74 73 75 # Appears that Qt has disabled touch events on Mac due to unresolved scrolling lag problems. 74 76 # Searching for qt setAcceptsTouchEvents shows they were disabled Oct 17, 2012. 75 77 # A patch that allows an environment variable QT_MAC_ENABLE_TOUCH_EVENTS to allow touch … … class MultitouchTrackpad: 84 86 85 87 from PyQt5.QtCore import QEvent 86 88 t = event.type() 89 from .mousemodes import key_modifiers 90 self._modifier_keys = key_modifiers(event) 87 91 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 89 93 # time consuming computatation. It appears Qt does not collapse the events. 90 94 # So event processing can get tens of seconds behind. To reduce this problem 91 95 # we only handle the most recent touch update per redraw. … … class MultitouchTrackpad: 100 104 def _collapse_touch_events(self): 101 105 touches = self._recent_touches 102 106 if touches: 103 self._process_touches(touches)107 event = self._process_touches(touches) 104 108 self._recent_touches = [] 109 self._mouse_mode_mgr._dispatch_touch_event(event) 105 110 106 111 def _process_touches(self, touches): 112 pinch = twist = scroll = None 113 two_swipe = None 114 three_swipe = None 115 four_swipe = None 107 116 n = len(touches) 108 117 speed = self.trackpad_speed 109 118 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 110 122 if n == 2: 111 123 (dx0,dy0),(dx1,dy1) = moves[0], moves[1] 112 124 from math import sqrt, exp, atan2, pi 113 125 l0,l1 = sqrt(dx0*dx0 + dy0*dy0),sqrt(dx1*dx1 + dy1*dy1) 114 126 d12 = dx0*dx1+dy0*dy1 115 127 if d12 < 0: 116 # Finger moving in opposite directions: pinch ortwist128 # Finger moving in opposite directions: pinch/twist 117 129 (x0,y0),(x1,y1) = [(t.x,t.y) for t in touches[:2]] 118 130 sx,sy = x1-x0,y1-y0 119 131 sn = sqrt(sx*sx + sy*sy) 120 132 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 141 143 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) 147 145 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 158 154 159 155 def _rotate(self, screen_axis, angle): 160 156 if angle == 0: … … class Touch: 230 226 x,y = self.x, self.y 231 227 last_touch_locations[id] = (x,y) 232 228 return (x-lx, y-ly) 229 230 231 from .mousemodes import MouseEvent 232 class 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