%PDF- %PDF-
Direktori : /lib/calibre/calibre/gui2/ |
Current File : //lib/calibre/calibre/gui2/gestures.py |
#!/usr/bin/env python3 # License: GPLv3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net> import sys, os from qt.core import ( QApplication, QEvent, QMouseEvent, QObject, QPointF, QScroller, Qt, QTouchDevice, pyqtSignal ) from calibre.constants import iswindows from calibre.utils.monotonic import monotonic from polyglot.builtins import itervalues touch_supported = False if iswindows and sys.getwindowsversion()[:2] >= (6, 2): # At least windows 7 touch_supported = True if 'CALIBRE_NO_TOUCH' in os.environ: touch_supported = False HOLD_THRESHOLD = 1.0 # seconds TAP_THRESHOLD = 50 # manhattan pixels Tap, TapAndHold, Flick = 'Tap', 'TapAndHold', 'Flick' Left, Right, Up, Down = 'Left', 'Right', 'Up', 'Down' class TouchPoint: def __init__(self, tp): self.creation_time = self.last_update_time = self.time_of_last_move = monotonic() self.start_screen_position = self.current_screen_position = self.previous_screen_position = QPointF(tp.screenPos()) self.time_since_last_update = -1 self.total_movement = 0 self.start_position = self.current_position = tp.pos() self.extra_data = None def update(self, tp): self.current_position = tp.pos() now = monotonic() self.time_since_last_update = now - self.last_update_time self.last_update_time = now self.previous_screen_position, self.current_screen_position = self.current_screen_position, QPointF(tp.screenPos()) movement = (self.current_screen_position - self.previous_screen_position).manhattanLength() self.total_movement += movement if movement > 5: self.time_of_last_move = now class State(QObject): tapped = pyqtSignal(object) flicking = pyqtSignal(object, object) tap_hold_started = pyqtSignal(object) tap_hold_updated = pyqtSignal(object) tap_hold_finished = pyqtSignal(object) def __init__(self): QObject.__init__(self) self.clear() def clear(self): self.possible_gestures = set() self.touch_points = {} self.hold_started = False self.hold_data = None def start(self): self.clear() self.possible_gestures = {Tap, TapAndHold, Flick} def update(self, ev, boundary='update'): if boundary == 'start': self.start() for tp in ev.touchPoints(): tpid = tp.id() if tpid not in self.touch_points: self.touch_points[tpid] = TouchPoint(tp) else: self.touch_points[tpid].update(tp) if len(self.touch_points) > 1: self.possible_gestures.clear() if boundary == 'end': self.check_for_holds() self.finalize() self.clear() else: self.check_for_holds() if Flick in self.possible_gestures: tp = next(itervalues(self.touch_points)) self.flicking.emit(tp, False) def check_for_holds(self): if not {TapAndHold} & self.possible_gestures: return now = monotonic() tp = next(itervalues(self.touch_points)) if now - tp.time_of_last_move < HOLD_THRESHOLD: return if self.hold_started: if TapAndHold in self.possible_gestures: self.tap_hold_updated.emit(tp) else: self.possible_gestures &= {TapAndHold} if tp.total_movement > TAP_THRESHOLD: self.possible_gestures.clear() else: self.possible_gestures = {TapAndHold} self.hold_started = True self.hold_data = now self.tap_hold_started.emit(tp) def finalize(self): if Tap in self.possible_gestures: tp = next(itervalues(self.touch_points)) if tp.total_movement <= TAP_THRESHOLD: self.tapped.emit(tp) return if Flick in self.possible_gestures: tp = next(itervalues(self.touch_points)) self.flicking.emit(tp, True) if not self.hold_started: return if TapAndHold in self.possible_gestures: tp = next(itervalues(self.touch_points)) self.tap_hold_finished.emit(tp) return def send_click(view, pos, button=Qt.MouseButton.LeftButton, double_click=False): mods = QApplication.keyboardModifiers() if double_click: ev = QMouseEvent(QEvent.Type.MouseButtonDblClick, pos, button, button, mods) QApplication.postEvent(view.viewport(), ev) return ev = QMouseEvent(QEvent.Type.MouseButtonPress, pos, button, button, mods) QApplication.postEvent(view.viewport(), ev) ev = QMouseEvent(QEvent.Type.MouseButtonRelease, pos, button, button, mods) QApplication.postEvent(view.viewport(), ev) class GestureManager(QObject): def __init__(self, view): QObject.__init__(self, view) if touch_supported: view.viewport().setAttribute(Qt.WidgetAttribute.WA_AcceptTouchEvents) self.state = State() self.state.tapped.connect( self.handle_tap, type=Qt.ConnectionType.QueuedConnection) # has to be queued otherwise QApplication.keyboardModifiers() does not work self.state.flicking.connect(self.handle_flicking) connect_lambda(self.state.tap_hold_started, self, lambda self, tp: self.handle_tap_hold('start', tp)) connect_lambda(self.state.tap_hold_updated, self, lambda self, tp: self.handle_tap_hold('update', tp)) connect_lambda(self.state.tap_hold_finished, self, lambda self, tp: self.handle_tap_hold('end', tp)) self.evmap = {QEvent.Type.TouchBegin: 'start', QEvent.Type.TouchUpdate: 'update', QEvent.Type.TouchEnd: 'end'} self.last_tap_at = 0 if touch_supported: self.scroller = QScroller.scroller(view.viewport()) def handle_event(self, ev): if not touch_supported: return etype = ev.type() if etype in (QEvent.Type.MouseButtonPress, QEvent.Type.MouseMove, QEvent.Type.MouseButtonRelease, QEvent.Type.MouseButtonDblClick): if ev.source() in (Qt.MouseEventSource.MouseEventSynthesizedBySystem, Qt.MouseEventSource.MouseEventSynthesizedByQt): # swallow fake mouse events generated from touch events ev.ignore() return False self.scroller.stop() return if etype == QEvent.Type.Wheel and self.scroller.state() != QScroller.State.Inactive: ev.ignore() return False boundary = self.evmap.get(etype, None) if boundary is None or ev.device().type() != QTouchDevice.DeviceType.TouchScreen: return self.state.update(ev, boundary=boundary) ev.accept() return True def close_open_menu(self): m = getattr(self.parent(), 'context_menu', None) if m is not None and m.isVisible(): m.close() return True def handle_flicking(self, touch_point, is_end): if is_end: it = QScroller.Input.InputRelease else: it = QScroller.Input.InputPress if touch_point.extra_data is None else QScroller.Input.InputMove touch_point.extra_data = True self.scroller.handleInput(it, touch_point.current_position, int(touch_point.last_update_time * 1000)) def handle_tap(self, tp): self.scroller.stop() last_tap_at, self.last_tap_at = self.last_tap_at, monotonic() if self.close_open_menu(): return interval = QApplication.instance().doubleClickInterval() / 1000 double_tap = self.last_tap_at - last_tap_at < interval send_click(self.parent(), tp.start_position, double_click=double_tap) def handle_tap_hold(self, action, tp): self.scroller.stop() if action == 'end': send_click(self.parent(), tp.start_position, button=Qt.MouseButton.RightButton)