summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--ranger/core/fm.py4
-rw-r--r--ranger/ext/signal_dispatcher.py79
-rw-r--r--test/tc_signal.py79
3 files changed, 161 insertions, 1 deletions
diff --git a/ranger/core/fm.py b/ranger/core/fm.py
index 94d0d85d..aef10150 100644
--- a/ranger/core/fm.py
+++ b/ranger/core/fm.py
@@ -26,18 +26,20 @@ from ranger.container import Bookmarks
 from ranger.core.runner import Runner
 from ranger import relpath_conf
 from ranger.ext.get_executables import get_executables
+from ranger.ext.signal_dispatcher import SignalDispatcher
 from ranger import __version__
 from ranger.fsobject import Loader
 
 CTRL_C = 3
 TICKS_BEFORE_COLLECTING_GARBAGE = 100
 
-class FM(Actions):
+class FM(Actions, SignalDispatcher):
 	input_blocked = False
 	input_blocked_until = 0
 	def __init__(self, ui=None, bookmarks=None, tags=None):
 		"""Initialize FM."""
 		Actions.__init__(self)
+		SignalDispatcher.__init__(self)
 		self.ui = ui
 		self.log = deque(maxlen=20)
 		self.bookmarks = bookmarks
diff --git a/ranger/ext/signal_dispatcher.py b/ranger/ext/signal_dispatcher.py
new file mode 100644
index 00000000..542fb472
--- /dev/null
+++ b/ranger/ext/signal_dispatcher.py
@@ -0,0 +1,79 @@
+# Copyright (C) 2009, 2010  Roman Zimbelmann <romanz@lavabit.com>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+class Signal(dict):
+	stopped = False
+	def __init__(self, **keywords):
+		dict.__init__(self, keywords)
+		self.__dict__ = self
+
+	def stop(self):
+		self.stopped = True
+
+
+class SignalHandler(object):
+	active = True
+	def __init__(self, signal_name, function, priority, pass_signal):
+		self.priority = max(0, min(1, priority))
+		self.signal_name = signal_name
+		self.function = function
+		self.pass_signal = pass_signal
+
+
+class SignalDispatcher(object):
+	def __init__(self):
+		self._signals = dict()
+
+	signal_clear = __init__
+
+	def signal_bind(self, signal_name, function, priority=0.5):
+		assert isinstance(signal_name, str)
+		try:
+			handlers = self._signals[signal_name]
+		except:
+			handlers = self._signals[signal_name] = []
+		nargs = function.__code__.co_argcount - hasattr(function, 'im_func')
+		handler = SignalHandler(signal_name, function, priority, nargs > 0)
+		handlers.append(handler)
+		handlers.sort(key=lambda handler: -handler.priority)
+		return handler
+
+	def signal_unbind(self, signal_handler):
+		try:
+			handlers = self._signals[signal_handler.signal_name]
+		except KeyError:
+			pass
+		else:
+			handlers.remove(signal_handler)
+
+	def signal_emit(self, signal_name, **kw):
+		assert isinstance(signal_name, str)
+		try:
+			handlers = self._signals[signal_name]
+		except:
+			return
+		if not handlers:
+			return
+
+		signal = Signal(origin=self, name=signal_name, **kw)
+
+		for handler in handlers:  # propagate
+			if handler.active:
+				if handler.pass_signal:
+					handler.function(signal)
+				else:
+					handler.function()
+				if signal.stopped:
+					return
diff --git a/test/tc_signal.py b/test/tc_signal.py
new file mode 100644
index 00000000..67625505
--- /dev/null
+++ b/test/tc_signal.py
@@ -0,0 +1,79 @@
+# Copyright (C) 2009, 2010  Roman Zimbelmann <romanz@lavabit.com>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+if __name__ == '__main__': from __init__ import init; init()
+import unittest
+from ranger.ext.signal_dispatcher import SignalDispatcher
+
+class TestSignal(unittest.TestCase):
+	def setUp(self):
+		self.sd = SignalDispatcher()
+
+	def test_signal_register_emit(self):
+		sd = self.sd
+		def poo(sig):
+			self.assert_('works' in sig)
+			self.assertEqual('yes', sig.works)
+		handler = sd.signal_bind('x', poo)
+
+		sd.signal_emit('x', works='yes')
+		sd.signal_unbind(handler)
+		sd.signal_emit('x')
+
+	def test_signal_order(self):
+		sd = self.sd
+		lst = []
+		def addn(n):
+			return lambda _: lst.append(n)
+
+		sd.signal_bind('x', addn(6))
+		sd.signal_bind('x', addn(3), priority=1)
+		sd.signal_bind('x', addn(2), priority=1)
+		sd.signal_bind('x', addn(9), priority=0)
+		sd.signal_bind('x', addn(1337), priority=0.7)
+		sd.signal_emit('x')
+
+		self.assert_(lst.index(3) < lst.index(6))
+		self.assert_(lst.index(2) < lst.index(6))
+		self.assert_(lst.index(6) < lst.index(9))
+		self.assert_(lst.index(1337) < lst.index(6))
+		self.assert_(lst.index(1337) < lst.index(9))
+		self.assert_(lst.index(1337) > lst.index(2))
+
+	def test_modifying_arguments(self):
+		sd = self.sd
+		lst = []
+		def modify(s):
+			s.number = 5
+		def set_number(s):
+			lst.append(s.number)
+		def stopit(s):
+			s.stop()
+
+		sd.signal_bind('setnumber', set_number)
+		sd.signal_emit('setnumber', number=100)
+		self.assertEqual(100, lst[-1])
+
+		sd.signal_bind('setnumber', modify, priority=1)
+		sd.signal_emit('setnumber', number=100)
+		self.assertEqual(5, lst[-1])
+
+		lst.append(None)
+		sd.signal_bind('setnumber', stopit, priority=1)
+		sd.signal_emit('setnumber', number=100)
+		self.assertEqual(None, lst[-1])
+
+if __name__ == '__main__':
+	unittest.main()