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()
='n425' href='#n425'>425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469