cola: Remove the 'cola.views' package
[git-cola.git] / cola / widgets / cfgactions.py
blobea6c0cf0797a25be9d4de4aa87140e26c32bf9f3
1 import os
2 from PyQt4 import QtCore
3 from PyQt4 import QtGui
4 from PyQt4.QtCore import SIGNAL
6 import cola
7 from cola import core
8 from cola import gitcfg
9 from cola import gitcmds
10 from cola import qt
11 from cola import qtutils
12 from cola import signals
13 from cola.qt import GitRefLineEdit
14 from cola.widgets import defs
15 from cola.widgets import standard
18 def install_command_wrapper():
19 cmd_wrapper = ActionCommandWrapper()
20 cola.factory().add_command_wrapper(cmd_wrapper)
23 def get_config_actions():
24 cfg = gitcfg.instance()
25 names = cfg.get_guitool_names()
26 return names or []
29 def run_command(title, command):
30 """Show a command widget"""
31 view = GitCommandWidget(qtutils.active_window())
32 view.setWindowModality(QtCore.Qt.ApplicationModal)
33 view.set_command(command)
34 view.setWindowTitle(title)
35 view.show()
36 view.raise_()
37 view.run()
38 view.exec_()
39 return (view.exitstatus, view.out, view.err)
42 class GitCommandWidget(standard.Dialog):
43 """Nice TextView that reads the output of a command syncronously"""
44 # Keep us in scope otherwise PyQt kills the widget
45 def __init__(self, parent=None):
46 standard.Dialog.__init__(self, parent)
47 self.resize(720, 420)
49 # Construct the process
50 self.proc = QtCore.QProcess(self)
51 self.exitstatus = 0
52 self.out = ''
53 self.err = ''
55 self._layout = QtGui.QVBoxLayout(self)
56 self._layout.setContentsMargins(3, 3, 3, 3)
58 # Create the text browser
59 self.output_text = QtGui.QTextBrowser(self)
60 self.output_text.setAcceptDrops(False)
61 self.output_text.setTabChangesFocus(True)
62 self.output_text.setUndoRedoEnabled(False)
63 self.output_text.setReadOnly(True)
64 self.output_text.setAcceptRichText(False)
66 self._layout.addWidget(self.output_text)
68 # Create abort / close buttons
69 self.button_abort = QtGui.QPushButton(self)
70 self.button_abort.setText(self.tr('Abort'))
71 self.button_close = QtGui.QPushButton(self)
72 self.button_close.setText(self.tr('Close'))
74 # Put them in a horizontal layout at the bottom.
75 self.button_box = QtGui.QDialogButtonBox(self)
76 self.button_box.addButton(self.button_abort, QtGui.QDialogButtonBox.RejectRole)
77 self.button_box.addButton(self.button_close, QtGui.QDialogButtonBox.AcceptRole)
78 self._layout.addWidget(self.button_box)
80 # Connect the signals to the process
81 self.connect(self.proc, SIGNAL('readyReadStandardOutput()'), self.readOutput)
82 self.connect(self.proc, SIGNAL('readyReadStandardError()'), self.readErrors)
83 self.connect(self.proc, SIGNAL('finished(int)'), self.finishProc)
84 self.connect(self.proc, SIGNAL('stateChanged(QProcess::ProcessState)'), self.stateChanged)
86 # Connect the signlas to the buttons
87 self.connect(self.button_abort, SIGNAL('clicked()'), self.abortProc)
88 self.connect(self.button_close, SIGNAL('clicked()'), self.close)
89 # Start with abort disabled - will be enabled when the process is run.
90 self.button_abort.setEnabled(False)
92 def set_command(self, command):
93 self.command = command
95 def run(self):
96 """Runs the process"""
97 self.proc.start(self.command[0], QtCore.QStringList(self.command[1:]))
99 def readOutput(self):
100 rawbytes = self.proc.readAllStandardOutput()
101 data = ''
102 for b in rawbytes:
103 data += b
104 self.out += data
105 self.append_text(data)
107 def readErrors(self):
108 rawbytes = self.proc.readAllStandardError()
109 data = ''
110 for b in rawbytes:
111 data += b
112 self.err += data
113 self.append_text(data)
115 def append_text(self, txt):
116 cursor = self.output_text.textCursor()
117 cursor.movePosition(cursor.End)
118 cursor.insertText(core.decode(txt))
119 cursor.movePosition(cursor.End)
120 self.output_text.setTextCursor(cursor)
122 def abortProc(self):
123 if self.proc.state() != QtCore.QProcess.NotRunning:
124 # Terminate seems to do nothing in windows
125 self.proc.terminate()
126 # Kill the process.
127 QtCore.QTimer.singleShot(1000, self.proc, QtCore.SLOT('kill()'))
129 def closeEvent(self, event):
130 if self.proc.state() != QtCore.QProcess.NotRunning:
131 # The process is still running, make sure we really want to abort.
132 title = 'Abort Action'
133 msg = ('An action is still running.\n'
134 'Terminating it could result in data loss.')
135 info_text = 'Abort the action?'
136 ok_text = 'Abort Action'
137 if qtutils.confirm(title, msg, info_text, ok_text,
138 default=False, icon=qtutils.discard_icon()):
139 self.abortProc()
140 event.accept()
141 else:
142 event.ignore()
143 else:
144 event.accept()
146 return standard.Dialog.closeEvent(self, event)
148 def stateChanged(self, newstate):
149 # State of process has changed - change the abort button state.
150 if newstate == QtCore.QProcess.NotRunning:
151 self.button_abort.setEnabled(False)
152 else:
153 self.button_abort.setEnabled(True)
155 def finishProc(self, status ):
156 self.exitstatus = status
159 class ActionCommandWrapper(object):
160 def __init__(self):
161 self.callbacks = {
162 signals.run_config_action: self.run_config_action,
163 signals.run_command: run_command,
166 def run_config_action(self, name, opts):
167 dlg = ActionDialog(qtutils.active_window(), name, opts)
168 dlg.show()
169 if dlg.exec_() != QtGui.QDialog.Accepted:
170 return False
171 rev = unicode(dlg.revision())
172 if rev:
173 opts['revision'] = rev
174 args = unicode(dlg.args())
175 if args:
176 opts['args'] = args
177 return True
180 class ActionDialog(standard.Dialog):
181 def __init__(self, parent, name, opts):
182 standard.Dialog.__init__(self, parent)
183 self.setWindowModality(QtCore.Qt.ApplicationModal)
184 self.name = name
185 self.opts = opts
187 self.layt = QtGui.QVBoxLayout()
188 self.layt.setMargin(defs.margin)
189 self.layt.setSpacing(defs.spacing)
190 self.setLayout(self.layt)
192 title = opts.get('title')
193 if title:
194 self.setWindowTitle(os.path.expandvars(title))
196 self.prompt = QtGui.QLabel()
198 prompt = opts.get('prompt')
199 if prompt:
200 self.prompt.setText(os.path.expandvars(prompt))
201 self.layt.addWidget(self.prompt)
204 self.argslabel = QtGui.QLabel()
205 if 'argprompt' not in opts or opts.get('argprompt') is True:
206 argprompt = qtutils.tr('Arguments')
207 else:
208 argprompt = opts.get('argprompt')
210 self.argslabel.setText(argprompt)
212 self.argstxt = QtGui.QLineEdit()
213 self.argslayt = QtGui.QHBoxLayout()
214 self.argslayt.addWidget(self.argslabel)
215 self.argslayt.addWidget(self.argstxt)
216 self.layt.addLayout(self.argslayt)
218 if not self.opts.get('argprompt'):
219 self.argslabel.setMinimumSize(1, 1)
220 self.argstxt.setMinimumSize(1, 1)
221 self.argstxt.hide()
222 self.argslabel.hide()
224 revs = (
225 ('Local Branch', gitcmds.branch_list(remote=False)),
226 ('Tracking Branch', gitcmds.branch_list(remote=True)),
227 ('Tag', gitcmds.tag_list()),
230 if 'revprompt' not in opts or opts.get('revprompt') is True:
231 revprompt = qtutils.tr('Revision')
232 else:
233 revprompt = opts.get('revprompt')
234 self.revselect = RevisionSelector(self, revs)
235 self.revselect.set_revision_label(revprompt)
236 self.layt.addWidget(self.revselect)
238 if not opts.get('revprompt'):
239 self.revselect.hide()
241 # Close/Run buttons
242 self.btnlayt = QtGui.QHBoxLayout()
243 self.btnlayt.addStretch()
244 self.closebtn = qt.create_button(text=self.tr('Close'), layout=self.btnlayt)
245 self.runbtn = qt.create_button(text=self.tr('Run'), layout=self.btnlayt)
246 self.runbtn.setDefault(True)
247 self.layt.addLayout(self.btnlayt)
249 self.connect(self.closebtn, SIGNAL('clicked()'), self.reject)
250 self.connect(self.runbtn, SIGNAL('clicked()'), self.accept)
252 # Widen the dialog by default
253 self.resize(666, self.height())
255 def revision(self):
256 return self.revselect.revision()
258 def args(self):
259 return self.argstxt.text()
262 class RevisionSelector(QtGui.QWidget):
263 def __init__(self, parent, revs):
264 QtGui.QWidget.__init__(self, parent)
266 self._revs = revs
267 self._revdict = dict(revs)
269 self._layt = QtGui.QVBoxLayout()
270 self._layt.setMargin(0)
271 self.setLayout(self._layt)
273 self._rev_layt = QtGui.QHBoxLayout()
274 self._rev_layt.setMargin(0)
276 self._rev_label = QtGui.QLabel()
277 self._rev_layt.addWidget(self._rev_label)
279 self._revision = GitRefLineEdit()
280 self._rev_layt.addWidget(self._revision)
282 self._layt.addLayout(self._rev_layt)
284 self._radio_layt = QtGui.QHBoxLayout()
285 self._radio_btns = {}
287 # Create the radio buttons
288 for label, rev_list in self._revs:
289 radio = QtGui.QRadioButton()
290 radio.setText(self.tr(label))
291 radio.setObjectName(label)
292 self.connect(radio, SIGNAL('clicked()'), self._set_revision_list)
293 self._radio_layt.addWidget(radio)
294 self._radio_btns[label] = radio
296 self._radio_layt.addStretch()
298 self._layt.addLayout(self._radio_layt)
300 self._rev_list = QtGui.QListWidget()
301 self._layt.addWidget(self._rev_list)
303 label, rev_list = self._revs[0]
304 self._radio_btns[label].setChecked(True)
305 qtutils.set_items(self._rev_list, rev_list)
307 self.connect(self._rev_list, SIGNAL('itemSelectionChanged()'),
308 self._rev_list_selection_changed)
310 def revision(self):
311 return self._revision.text()
313 def set_revision_label(self, txt):
314 self._rev_label.setText(txt)
316 def _set_revision_list(self):
317 sender = str(self.sender().objectName())
318 revs = self._revdict[sender]
319 qtutils.set_items(self._rev_list, revs)
321 def _rev_list_selection_changed(self):
322 items = self._rev_list.selectedItems()
323 if not items:
324 return
325 self._revision.setText(items[0].text())