diff options
Diffstat (limited to 'mvc/signals.py')
-rw-r--r-- | mvc/signals.py | 299 |
1 files changed, 0 insertions, 299 deletions
diff --git a/mvc/signals.py b/mvc/signals.py deleted file mode 100644 index 2f64dc9..0000000 --- a/mvc/signals.py +++ /dev/null @@ -1,299 +0,0 @@ -# @Base: Miro - an RSS based video player application -# Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 -# Participatory Culture Foundation -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -# In addition, as a special exception, the copyright holders give -# permission to link the code of portions of this program with the OpenSSL -# library. -# -# You must obey the GNU General Public License in all respects for all of -# the code used other than OpenSSL. If you modify file(s) with this -# exception, you may extend this exception to your version of the file(s), -# but you are not obligated to do so. If you do not wish to do so, delete -# this exception statement from your version. If you delete this exception -# statement from all source files in the program, then also delete it here. - -"""signals.py - -GObject-like signal handling for Miro. -""" - -import itertools -import logging -import sys -import weakref - -class NestedSignalError(StandardError): - pass - -class WeakMethodReference: - """Used to handle weak references to a method. - - We can't simply keep a weak reference to method itself, because there - almost certainly aren't any other references to it. Instead we keep a - weak reference to the object, it's class and the unbound method. This - gives us enough info to recreate the bound method when we need it. - """ - - def __init__(self, method): - self.object = weakref.ref(method.im_self) - self.func = weakref.ref(method.im_func) - # don't create a weak reference to the class. That only works for - # new-style classes. It's highly unlikely the class will ever need to - # be garbage collected anyways. - self.cls = method.im_class - - def __call__(self): - func = self.func() - if func is None: return None - obj = self.object() - if obj is None: return None - return func.__get__(obj, self.cls) - -class Callback: - def __init__(self, func, extra_args): - self.func = func - self.extra_args = extra_args - - def invoke(self, obj, args): - return self.func(obj, *(args + self.extra_args)) - - def compare_function(self, func): - return self.func == func - - def is_dead(self): - return False - -class WeakCallback: - def __init__(self, method, extra_args): - self.ref = WeakMethodReference(method) - self.extra_args = extra_args - - def compare_function(self, func): - return self.ref() == func - - def invoke(self, obj, args): - callback = self.ref() - if callback is not None: - return callback(obj, *(args + self.extra_args)) - else: - return None - - def is_dead(self): - return self.ref() is None - -class SignalEmitter(object): - def __init__(self, *signal_names): - self.signal_callbacks = {} - self.id_generator = itertools.count() - self._currently_emitting = set() - self._frozen = False - for name in signal_names: - self.create_signal(name) - - def freeze_signals(self): - self._frozen = True - - def thaw_signals(self): - self._frozen = False - - def create_signal(self, name): - self.signal_callbacks[name] = {} - - def get_callbacks(self, signal_name): - try: - return self.signal_callbacks[signal_name] - except KeyError: - raise KeyError("Signal: %s doesn't exist" % signal_name) - - def _check_already_connected(self, name, func): - for callback in self.get_callbacks(name).values(): - if callback.compare_function(func): - raise ValueError("signal %s already connected to %s" % - (name, func)) - - def connect(self, name, func, *extra_args): - """Connect a callback to a signal. Returns an callback handle that - can be passed into disconnect(). - - If func is already connected to the signal, then a ValueError will be - raised. - """ - self._check_already_connected(name, func) - id_ = self.id_generator.next() - callbacks = self.get_callbacks(name) - callbacks[id_] = Callback(func, extra_args) - return (name, id_) - - def connect_weak(self, name, method, *extra_args): - """Connect a callback weakly. Callback must be a method of some - object. We create a weak reference to the method, so that the - connection doesn't keep the object from being garbage collected. - - If method is already connected to the signal, then a ValueError will be - raised. - """ - self._check_already_connected(name, method) - if not hasattr(method, 'im_self'): - raise TypeError("connect_weak must be called with object methods") - id_ = self.id_generator.next() - callbacks = self.get_callbacks(name) - callbacks[id_] = WeakCallback(method, extra_args) - return (name, id_) - - def disconnect(self, callback_handle): - """Disconnect a signal. callback_handle must be the return value from - connect() or connect_weak(). - """ - callbacks = self.get_callbacks(callback_handle[0]) - if callback_handle[1] in callbacks: - del callbacks[callback_handle[1]] - else: - logging.warning( - "disconnect called but callback_handle not in the callback") - - def disconnect_all(self): - for signal in self.signal_callbacks: - self.signal_callbacks[signal] = {} - - def emit(self, name, *args): - if self._frozen: - return - if name in self._currently_emitting: - raise NestedSignalError("Can't emit %s while handling %s" % - (name, name)) - self._currently_emitting.add(name) - try: - callback_returned_true = self._run_signal(name, args) - finally: - self._currently_emitting.discard(name) - self.clear_old_weak_references() - return callback_returned_true - - def _run_signal(self, name, args): - callback_returned_true = False - try: - self_callback = getattr(self, 'do_' + name.replace('-', '_')) - except AttributeError: - pass - else: - if self_callback(*args): - callback_returned_true = True - if not callback_returned_true: - for callback in self.get_callbacks(name).values(): - if callback.invoke(self, args): - callback_returned_true = True - break - return callback_returned_true - - def clear_old_weak_references(self): - for callback_map in self.signal_callbacks.values(): - for id_ in callback_map.keys(): - if callback_map[id_].is_dead(): - del callback_map[id_] - -class SystemSignals(SignalEmitter): - """System wide signals for Miro. These can be accessed from the singleton - object signals.system. Signals include: - - "error" - A problem occurred in Miro. The frontend should let the user - know this happened, hopefully with a nice dialog box or something that - lets the user report the error to bugzilla. - - Arguments: - - report -- string that can be submitted to the bug tracker - - exception -- Exception object (can be None) - - "startup-success" - The startup process is complete. The frontend should - wait for this signal to show the UI to the user. - - No arguments. - - "startup-failure" - The startup process fails. The frontend should inform - the user that this happened and quit. - - Arguments: - - summary -- Short, user-friendly, summary of the problem - - description -- Longer explanation of the problem - - "shutdown" - The backend has shutdown. The event loop is stopped at this - point. - - No arguments. - - "update-available" - A new version of LibreVideoConverter is available. - - Arguments: - - rssItem -- The RSS item for the latest version (in sparkle - appcast format). - - "new-dialog" - The backend wants to display a dialog to the user. - - Arguments: - - dialog -- The dialog to be displayed. - - "theme-first-run" - A theme was used for the first time - - Arguments: - - theme -- The name of the theme. - - "videos-added" -- Videos were added via the singleclick module. - Arguments: - - view -- A database view than contains the videos. - - "download-complete" -- A download was completed. - Arguments: - - item -- an Item of class Item. - - """ - def __init__(self): - SignalEmitter.__init__(self, 'error', 'startup-success', - 'startup-failure', 'shutdown', - 'update-available', 'new-dialog', - 'theme-first-run', 'videos-added', - 'download-complete') - - def shutdown(self): - self.emit('shutdown') - - def update_available(self, latest): - self.emit('update-available', latest) - - def new_dialog(self, dialog): - self.emit('new-dialog', dialog) - - def theme_first_run(self, theme): - self.emit('theme-first-run', theme) - - def videos_added(self, view): - self.emit('videos-added', view) - - def download_complete(self, item): - self.emit('download-complete', item) - - def failed_exn(self, when, details=None): - self.failed(when, with_exception=True, details=details) - - def failed(self, when, with_exception=False, details=None): - """Used to emit the error signal. Formats a nice crash report.""" - if with_exception: - exc_info = sys.exc_info() - else: - exc_info = None - logging.error('%s: %s' % (when, details), exc_info=exc_info) - -system = SystemSignals() |