qt: Make the 'text' field optional in create_button()
[git-cola.git] / cola / cmdfactory.py
blobca19fe48683972302d19785ab4c1f230221477c1
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
14 from cola import errors
15 from cola.decorators import memoize
18 @memoize
19 def factory():
20 """Return a static instance of the command factory."""
21 return CommandFactory()
24 def SLOT(signal, *args, **opts):
25 """
26 Returns a callback that broadcasts a message over the notifier.
28 """
29 def broadcast(*local_args, **opts):
30 cola.notifier().broadcast(signal, *args, **opts)
31 return broadcast
34 class CommandFactory(object):
35 def __init__(self, context=None):
36 """Setup the undo/redo stacks and register for notifications."""
37 self.undoable = True
38 self.undostack = []
39 self.redostack = []
40 self.signal_to_command = {}
41 self.callbacks = {}
42 self.context = context
44 def has_command(self, signal):
45 return signal in self.signal_to_command
47 def add_command(self, signal, command):
48 """Register a signal/command pair."""
49 self.signal_to_command[signal] = command
51 def add_global_command(self, signal, command):
52 """Register a global signal/command pair."""
53 self.add_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 clear(self):
66 """Clear the undo and redo stacks."""
67 self.undostack = []
68 self.redostack = []
70 def cmdrunner(self, signal):
71 """Return a function to create and run a signal's command."""
72 def run(*args, **opts):
73 return self.do(signal, *args, **opts)
74 return run
76 def do(self, signal, *args, **opts):
77 """Given a signal and arguments, run its corresponding command."""
78 cmdclass = self.signal_to_command[signal]
79 cmdobj = cmdclass(*args, **opts)
80 cmdobj.context = self.context
81 # TODO we disable undo/redo for now; views just need to
82 # inspect the stack and add menu entries when we enable it.
83 ok, result = self._do(cmdobj)
84 if ok and self.undoable and cmdobj.is_undoable():
85 self.undostack.append(cmdobj)
86 return result
88 def _do(self, cmdobj):
89 try:
90 result = cmdobj.do()
91 except errors.UsageError, e:
92 self.prompt_user(signals.information, e.title, e.message)
93 return False, None
94 else:
95 return True, result
97 def undo(self):
98 """Undo the last command and add it to the redo stack."""
99 if self.undostack:
100 cmdobj = self.undostack.pop()
101 result = cmdobj.undo()
102 self.redostack.append(cmdobj)
103 return result
104 else:
105 print 'warning: undo stack is empty, doing nothing'
106 return None
108 def redo(self):
109 """Redo the last command and add it to the undo stack."""
110 if self.redostack:
111 cmdobj = self.redostack.pop()
112 ok, result = self._do(cmdobj)
113 if ok and cmdobj.is_undoable():
114 self.undo.append(cmdobj)
115 else:
116 self.redostack.push(cmdobj)
117 return result
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)