models.main: Add a serialilzer to run generate_remote_helpers()
[git-cola.git] / cola / cmdfactory.py
blobdaa5b66fad5a1ace56e745295cb1cbcd3f11e4fc
1 """
2 Maps Qt signals to Command objects.
4 The command factory connects 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 = {}
43 self.callbacks = {}
45 cola.notifier().connect(signals.undo, self.undo)
46 cola.notifier().connect(signals.redo, self.redo)
48 self.model = cola.model()
49 self.model.add_observer(self)
51 def add_command(self, signal, command):
52 """Register a signal/command pair."""
53 self.signal_to_command[signal] = command
54 cola.notifier().connect(signal, self.cmdrunner(signal))
56 def add_command_wrapper(self, cmd_wrapper):
57 self.callbacks.update(cmd_wrapper.callbacks)
59 def prompt_user(self, name, *args, **opts):
60 try:
61 return self.callbacks[name](*args, **opts)
62 except KeyError:
63 raise NotImplementedError('No callback for "%s' % name)
65 def notify(self, *params):
66 """
67 Observe model changes.
69 This captures model parameters and maps them to signals that
70 are observed by the UIs.
72 """
73 actions = {
74 'diff_text': SLOT(signals.diff_text, self.model.diff_text),
75 'commitmsg': SLOT(signals.editor_text, self.model.commitmsg),
76 'mode': SLOT(signals.mode, self.model.mode),
78 for param in params:
79 action = actions.get(param, lambda: None)
80 action()
82 def clear(self):
83 """Clear the undo and redo stacks."""
84 self.undostack = []
85 self.redostack = []
87 def cmdrunner(self, signal):
88 """Return a function to create and run a signal's command."""
89 def run(*args, **opts):
90 return self.do(signal, *args, **opts)
91 return run
93 def do(self, signal, *args, **opts):
94 """Given a signal and arguments, run its corresponding command."""
95 cmdclass = self.signal_to_command[signal]
96 cmdobj = cmdclass(*args, **opts)
97 # TODO we disable undo/redo for now; views just need to
98 # inspect the stack and add menu entries when we enable it.
99 if self.undoable and cmdobj.is_undoable():
100 self.undostack.append(cmdobj)
101 return cmdobj.do()
103 def undo(self):
104 """Undo the last command and add it to the redo stack."""
105 if self.undostack:
106 cmdobj = self.undostack.pop()
107 cmdobj.undo()
108 self.redostack.append(cmdobj)
109 else:
110 print 'warning: undo stack is empty, doing nothing'
112 def redo(self):
113 """Redo the last command and add it to the undo stack."""
114 if self.redostack:
115 cmdobj = self.redostack.pop()
116 cmdobj.do()
117 self.undo.append(cmd)
118 else:
119 print 'warning: redo stack is empty, doing nothing'
121 def is_undoable(self):
122 """Does the undo stack contain any commands?"""
123 return bool(self.undostack)
125 def is_redoable(self):
126 """Does the redo stack contain any commands?"""
127 return bool(self.redostack)