class EventPlayer(object): def __init__(self, playback_speed=None, comment_display=None): self._playback_speed = playback_speed self._timer = Timer() self._timer.unpause() if comment_display is None: self._comment_display = self._default_comment_display else: self._comment_display = comment_display def play_script(self, path, finish_callback=None): """ Start execution of the given script in a separate thread and return immediately. Note: You should handle any exceptions from the playback script via sys.execpthook. """ _globals = {} _locals = {} """ Calls to events in the playback script like: player.post_event(obj,PyQt4.QtGui.QMouseEvent(...),t) are/were responsible for the xcb-error on Ubuntu, because you may not use a Gui-object from a thread other than the MainThread running the Gui """ execfile(path, _globals, _locals) def run(): _locals['playback_events'](player=self) if finish_callback is not None: finish_callback() th = threading.Thread(target=run) th.daemon = True th.start() def post_event(self, obj, event, timestamp_in_seconds): if self._playback_speed is not None: self._timer.sleep_until(timestamp_in_seconds / self._playback_speed) assert threading.current_thread().name != "MainThread" event.spont = True QApplication.postEvent(obj, event) assert QApplication.instance().thread() == obj.thread() flusher = EventFlusher() flusher.moveToThread(obj.thread()) flusher.setParent(QApplication.instance()) signaler = Signaler() signaler.sig.connect(flusher.set, Qt.QueuedConnection) signaler.sig.emit() flusher.wait() flusher.clear() def display_comment(self, comment): self._comment_display(comment) def _default_comment_display(self, comment): print "--------------------------------------------------" print comment print "--------------------------------------------------"
class EventPlayer(object): def __init__(self, playback_speed=None, comment_display=None): self._playback_speed = playback_speed self._timer = Timer() self._timer.unpause() if comment_display is None: self._comment_display = self._default_comment_display else: self._comment_display = comment_display def play_script(self, path, finish_callback=None): """ Start execution of the given script in a separate thread and return immediately. Note: You should handle any exceptions from the playback script via sys.execpthook. """ _globals = {} _locals = {} """ Calls to events in the playback script like: player.post_event(obj,PyQt4.QtGui.QMouseEvent(...),t) are/were responsible for the xcb-error on Ubuntu, because you may not use a Gui-object from a thread other than the MainThread running the Gui """ execfile(path, _globals, _locals) def run(): _locals['playback_events'](player=self) if finish_callback is not None: finish_callback() th = threading.Thread( target=run ) th.daemon = True th.start() def post_event(self, obj, event, timestamp_in_seconds): if self._playback_speed is not None: self._timer.sleep_until(timestamp_in_seconds / self._playback_speed) assert threading.current_thread().name != "MainThread" event.spont = True QApplication.postEvent(obj, event) assert QApplication.instance().thread() == obj.thread() flusher = EventFlusher() flusher.moveToThread( obj.thread() ) flusher.setParent( QApplication.instance() ) signaler = Signaler() signaler.sig.connect( flusher.set, Qt.QueuedConnection ) signaler.sig.emit() flusher.wait() flusher.clear() def display_comment(self, comment): self._comment_display(comment) def _default_comment_display(self, comment): print "--------------------------------------------------" print comment print "--------------------------------------------------"
class EventRecorder( QObject ): """ Records spontaneous events from the UI and serializes them as strings that can be evaluated in Python. """ def __init__(self, parent=None, ignore_parent_events=True): QObject.__init__(self, parent=parent) self._ignore_parent_events = False if parent is not None and ignore_parent_events: self._ignore_parent_events = True self._parent_name = get_fully_qualified_name(parent) self._captured_events = [] self._timer = Timer() @property def paused(self): return self._timer.paused QEvent_Style = 91 IgnoredEventTypes = set( [ QEvent.Paint, QEvent.KeyboardLayoutChange, QEvent.WindowActivate, QEvent.WindowDeactivate, QEvent.ActivationChange, # These event symbols are not exposed in pyqt, so we pull them from our own enum EventTypes.Style, EventTypes.ApplicationActivate, EventTypes.ApplicationDeactivate, EventTypes.NonClientAreaMouseMove, EventTypes.NonClientAreaMouseButtonPress, EventTypes.NonClientAreaMouseButtonRelease, EventTypes.NonClientAreaMouseButtonDblClick ] ) IgnoredEventClasses = (QChildEvent, QTimerEvent, QGraphicsSceneMouseEvent, QWindowStateChangeEvent, QMoveEvent) def captureEvent(self, watched, event): if self._shouldSaveEvent(event): try: eventstr = event_to_string(event) except KeyError: logger.warn("Don't know how to record event: {}".format( str(event) )) print "Don't know", str(event) else: timestamp_in_seconds = self._timer.seconds() objname = str(get_fully_qualified_name(watched)) if not ( self._ignore_parent_events and objname.startswith(self._parent_name) ): self._captured_events.append( (eventstr, objname, timestamp_in_seconds) ) return False def insertComment(self, comment): self._captured_events.append( (comment, "comment", None) ) def _shouldSaveEvent(self, event): if isinstance(event, QMouseEvent): # Ignore most mouse movement events if the user isn't pressing anything. if event.type() == QEvent.MouseMove \ and int(event.button()) == 0 \ and int(event.buttons()) == 0 \ and int(event.modifiers()) == 0: # Somewhat hackish (and slow), but we have to record mouse movements during combo box usage. # Same for QMenu usage (on Mac, it doesn't seem to matter, but on Fedora it does matter.) widgetUnderCursor = QApplication.instance().widgetAt( QCursor.pos() ) if widgetUnderCursor is not None and widgetUnderCursor.objectName() == "qt_scrollarea_viewport": return has_ancestor(widgetUnderCursor, QComboBox) if isinstance(widgetUnderCursor, QMenu): return True return False else: return True # Ignore non-spontaneous events if not event.spontaneous(): return False if event.type() in self.IgnoredEventTypes: return False if isinstance(event, self.IgnoredEventClasses): return False return True def unpause(self): # Here, we use a special override of QApplication.notify() instead of using QApplication.instance().installEventFilter(). # That's because (contrary to the documentation), the QApplication eventFilter does NOT get to see every event in the application. # Testing shows that events that were "filtered out" by a different event filter may not be seen by the QApplication event filter. self._timer.unpause() def _notify(receiver, event): self.captureEvent(receiver, event) return _orig_QApp_notify(receiver, event) from ilastik.shell.gui.startShellGui import EventRecordingApp assert isinstance( QApplication.instance(), EventRecordingApp ) QApplication.instance()._notify =_notify def pause(self): self._timer.pause() QApplication.instance()._notify = _orig_QApp_notify def writeScript(self, fileobj): # Write header comments fileobj.write( """ # Event Recording # Started at: {} """.format( str(self._timer.start_time) ) ) # Write playback function definition fileobj.write( """ def playback_events(player): import PyQt4.QtCore from PyQt4.QtCore import Qt, QEvent, QPoint import PyQt4.QtGui from ilastik.utility.gui.eventRecorder.objectNameUtils import get_named_object from ilastik.utility.gui.eventRecorder.eventRecorder import EventPlayer from ilastik.shell.gui.startShellGui import shell player.display_comment("SCRIPT STARTING") """) # Write all events and comments for eventstr, objname, timestamp_in_seconds in self._captured_events: if objname == "comment": eventstr = eventstr.replace('\\', '\\\\') eventstr = eventstr.replace('"', '\\"') eventstr = eventstr.replace("'", "\\'") fileobj.write( """ ######################## player.display_comment(\"""{eventstr}\""") ######################## """.format( **locals() ) ) else: fileobj.write( """ obj = get_named_object( '{objname}' ) player.post_event( obj, {eventstr}, {timestamp_in_seconds} ) """.format( **locals() ) ) fileobj.write( """ player.display_comment("SCRIPT COMPLETE") """)
class EventRecorder(QObject): """ Records spontaneous events from the UI and serializes them as strings that can be evaluated in Python. """ def __init__(self, parent=None, ignore_parent_events=True): QObject.__init__(self, parent=parent) self._ignore_parent_events = False if parent is not None and ignore_parent_events: self._ignore_parent_events = True self._parent_name = get_fully_qualified_name(parent) self._captured_events = [] self._timer = Timer() @property def paused(self): return self._timer.paused QEvent_Style = 91 IgnoredEventTypes = set([ QEvent.Paint, QEvent.KeyboardLayoutChange, QEvent.WindowActivate, QEvent.WindowDeactivate, QEvent.ActivationChange, # These event symbols are not exposed in pyqt, so we pull them from our own enum EventTypes.Style, EventTypes.ApplicationActivate, EventTypes.ApplicationDeactivate, EventTypes.NonClientAreaMouseMove, EventTypes.NonClientAreaMouseButtonPress, EventTypes.NonClientAreaMouseButtonRelease, EventTypes.NonClientAreaMouseButtonDblClick ]) IgnoredEventClasses = (QChildEvent, QTimerEvent, QGraphicsSceneMouseEvent, QWindowStateChangeEvent, QMoveEvent) def captureEvent(self, watched, event): if self._shouldSaveEvent(event): try: eventstr = event_to_string(event) except KeyError: logger.warn("Don't know how to record event: {}".format( str(event))) print "Don't know", str(event) else: timestamp_in_seconds = self._timer.seconds() objname = str(get_fully_qualified_name(watched)) if not (self._ignore_parent_events and objname.startswith(self._parent_name)): self._captured_events.append( (eventstr, objname, timestamp_in_seconds)) return False def insertComment(self, comment): self._captured_events.append((comment, "comment", None)) def _shouldSaveEvent(self, event): if isinstance(event, QMouseEvent): # Ignore most mouse movement events if the user isn't pressing anything. if event.type() == QEvent.MouseMove \ and int(event.button()) == 0 \ and int(event.buttons()) == 0 \ and int(event.modifiers()) == 0: # Somewhat hackish (and slow), but we have to record mouse movements during combo box usage. # Same for QMenu usage (on Mac, it doesn't seem to matter, but on Fedora it does matter.) widgetUnderCursor = QApplication.instance().widgetAt( QCursor.pos()) if widgetUnderCursor is not None and widgetUnderCursor.objectName( ) == "qt_scrollarea_viewport": return has_ancestor(widgetUnderCursor, QComboBox) if isinstance(widgetUnderCursor, QMenu): return True return False else: return True # Ignore non-spontaneous events if not event.spontaneous(): return False if event.type() in self.IgnoredEventTypes: return False if isinstance(event, self.IgnoredEventClasses): return False return True def unpause(self): # Here, we use a special override of QApplication.notify() instead of using QApplication.instance().installEventFilter(). # That's because (contrary to the documentation), the QApplication eventFilter does NOT get to see every event in the application. # Testing shows that events that were "filtered out" by a different event filter may not be seen by the QApplication event filter. self._timer.unpause() def _notify(receiver, event): self.captureEvent(receiver, event) return _orig_QApp_notify(receiver, event) from ilastik.shell.gui.startShellGui import EventRecordingApp assert isinstance(QApplication.instance(), EventRecordingApp) QApplication.instance()._notify = _notify def pause(self): self._timer.pause() QApplication.instance()._notify = _orig_QApp_notify def writeScript(self, fileobj): # Write header comments fileobj.write(""" # Event Recording # Started at: {} """.format(str(self._timer.start_time))) # Write playback function definition fileobj.write(""" def playback_events(player): import PyQt4.QtCore from PyQt4.QtCore import Qt, QEvent, QPoint import PyQt4.QtGui from ilastik.utility.gui.eventRecorder.objectNameUtils import get_named_object from ilastik.utility.gui.eventRecorder.eventRecorder import EventPlayer from ilastik.shell.gui.startShellGui import shell player.display_comment("SCRIPT STARTING") """) # Write all events and comments for eventstr, objname, timestamp_in_seconds in self._captured_events: if objname == "comment": eventstr = eventstr.replace('\\', '\\\\') eventstr = eventstr.replace('"', '\\"') eventstr = eventstr.replace("'", "\\'") fileobj.write(""" ######################## player.display_comment(\"""{eventstr}\""") ######################## """.format(**locals())) else: fileobj.write(""" obj = get_named_object( '{objname}' ) player.post_event( obj, {eventstr}, {timestamp_in_seconds} ) """.format(**locals())) fileobj.write(""" player.display_comment("SCRIPT COMPLETE") """)