test: Add a test case for the model's 'remote_branches' attribute
[git-cola.git] / cola / cmdfactory.py
blob4dcbc71279ad08eb362924ba4b286e1f1eaa2772
1 """
2 Maps Qt signals to Command objects.
4 The command factory listens to the global notifier and
5 creates commands objects as registered signals are
6 encountered.
8 The factory itself is undoable in that it responds to
9 the undo and redo signals and manages the undo/redo stack.
11 """
12 import cola
13 from cola import signals
16 _factory = None
17 def factory():
18 """Return a static instance of the command factory."""
19 global _factory
20 if _factory:
21 return _factory
22 _factory = CommandFactory()
23 return _factory
26 def SLOT(signal, *args, **opts):
27 """
28 Returns a callback that broadcasts a message over the notifier.
30 """
31 def broadcast(*local_args, **opts):
32 cola.notifier().broadcast(signal, *args, **opts)
33 return broadcast
36 class CommandFactory(object):
37 def __init__(self):
38 """Setup the undo/redo stacks and register for notifications."""
39 self.undoable = True
40 self.undostack = []
41 self.redostack = []
42 self.signal_to_command = {}
44 cola.notifier().listen(signals.undo, self.undo)
45 cola.notifier().listen(signals.redo, self.redo)
47 self.model = cola.model()
48 self.model.add_observer(self)
50 def add_command(self, signal, command):
51 """Register a signal/command pair."""
52 self.signal_to_command[signal] = command
53 cola.notifier().listen(signal, self.cmdrunner(signal))
55 def notify(self, *params):
56 """
57 Observe model changes.
59 This captures model parameters and maps them to signals that
60 are observed by the UIs.
62 """
63 actions = {
64 'diff_text': SLOT(signals.diff_text, self.model.diff_text),
65 'commitmsg': SLOT(signals.editor_text, self.model.commitmsg),
66 'mode': SLOT(signals.mode, self.model.mode),
68 for param in params:
69 action = actions.get(param, lambda: None)
70 action()
72 def clear(self):
73 """Clear the undo and redo stacks."""
74 self.undostack = []
75 self.redostack = []
77 def cmdrunner(self, signal):
78 """Return a function to create and run a signal's command."""
79 def run(*args, **opts):
80 return self.do(signal, *args, **opts)
81 return run
83 def do(self, signal, *args, **opts):
84 """Given a signal and arguments, run its corresponding command."""
85 cmdclass = self.signal_to_command[signal]
86 cmdobj = cmdclass(*args, **opts)
87 # TODO we disable undo/redo for now; views just need to
88 # inspect the stack and add menu entries when we enable it.
89 if self.undoable and cmdobj.is_undoable():
90 self.undostack.append(cmdobj)
91 return cmdobj.do()
93 def undo(self):
94 """Undo the last command and add it to the redo stack."""
95 if self.undostack:
96 cmdobj = self.undostack.pop()
97 cmdobj.undo()
98 self.redostack.append(cmdobj)
99 else:
100 print 'warning: undo stack is empty, doing nothing'
102 def redo(self):
103 """Redo the last command and add it to the undo stack."""
104 if self.redostack:
105 cmdobj = self.redostack.pop()
106 cmdobj.do()
107 self.undo.append(cmd)
108 else:
109 print 'warning: redo stack is empty, doing nothing'
111 def is_undoable(self):
112 """Does the undo stack contain any commands?"""
113 return bool(self.undostack)
115 def is_redoable(self):
116 """Does the redo stack contain any commands?"""
117 return bool(self.redostack)