widgets: do not set window modality without a parent
[git-cola.git] / cola / widgets / cfgactions.py
blob1b877fe5334d80856733d32d8ee6a7fb7d8b5ccb
1 import os
2 from PyQt4 import QtCore
3 from PyQt4 import QtGui
4 from PyQt4.QtCore import Qt
5 from PyQt4.QtCore import SIGNAL
7 from cola import core
8 from cola import gitcfg
9 from cola import gitcmds
10 from cola import qtutils
11 from cola.i18n import N_
12 from cola.interaction import Interaction
13 from cola.qtutils import create_button
14 from cola.widgets import defs
15 from cola.widgets import completion
16 from cola.widgets import standard
19 def install():
20 Interaction.run_command = staticmethod(run_command)
21 Interaction.confirm_config_action = staticmethod(confirm_config_action)
24 def get_config_actions():
25 cfg = gitcfg.instance()
26 return cfg.get_guitool_names()
29 def confirm_config_action(name, opts):
30 dlg = ActionDialog(qtutils.active_window(), name, opts)
31 dlg.show()
32 if dlg.exec_() != QtGui.QDialog.Accepted:
33 return False
34 rev = unicode(dlg.revision())
35 if rev:
36 opts['revision'] = rev
37 args = unicode(dlg.args())
38 if args:
39 opts['args'] = args
40 return True
43 def run_command(title, command):
44 """Show a command widget"""
45 view = GitCommandWidget(title, qtutils.active_window())
46 view.set_command(command)
47 view.show()
48 view.raise_()
49 view.run()
50 view.exec_()
51 return (view.exitstatus, view.out, view.err)
54 class GitCommandWidget(standard.Dialog):
55 """Nice TextView that reads the output of a command syncronously"""
56 # Keep us in scope otherwise PyQt kills the widget
57 def __init__(self, title, parent=None):
58 standard.Dialog.__init__(self, parent)
59 self.setWindowTitle(title)
60 if parent is not None:
61 self.setWindowModality(Qt.ApplicationModal)
63 # Construct the process
64 self.proc = QtCore.QProcess(self)
65 self.exitstatus = 0
66 self.out = ''
67 self.err = ''
69 self._layout = QtGui.QVBoxLayout(self)
70 self._layout.setContentsMargins(3, 3, 3, 3)
72 # Create the text browser
73 self.output_text = QtGui.QTextBrowser(self)
74 self.output_text.setAcceptDrops(False)
75 self.output_text.setTabChangesFocus(True)
76 self.output_text.setUndoRedoEnabled(False)
77 self.output_text.setReadOnly(True)
78 self.output_text.setAcceptRichText(False)
80 self._layout.addWidget(self.output_text)
82 # Create abort / close buttons
83 self.button_abort = QtGui.QPushButton(self)
84 self.button_abort.setText(N_('Abort'))
85 self.button_close = QtGui.QPushButton(self)
86 self.button_close.setText(N_('Close'))
88 # Put them in a horizontal layout at the bottom.
89 self.button_box = QtGui.QDialogButtonBox(self)
90 self.button_box.addButton(self.button_abort, QtGui.QDialogButtonBox.RejectRole)
91 self.button_box.addButton(self.button_close, QtGui.QDialogButtonBox.AcceptRole)
92 self._layout.addWidget(self.button_box)
94 # Connect the signals to the process
95 self.connect(self.proc, SIGNAL('readyReadStandardOutput()'),
96 self.read_stdout)
97 self.connect(self.proc, SIGNAL('readyReadStandardError()'),
98 self.read_stderr)
99 self.connect(self.proc, SIGNAL('finished(int)'), self.finishProc)
100 self.connect(self.proc, SIGNAL('stateChanged(QProcess::ProcessState)'), self.stateChanged)
102 # Start with abort disabled - will be enabled when the process is run.
103 self.button_abort.setEnabled(False)
105 qtutils.connect_button(self.button_abort, self.abortProc)
106 qtutils.connect_button(self.button_close, self.close)
107 self.resize(720, 420)
109 def set_command(self, command):
110 self.command = command
112 def run(self):
113 """Runs the process"""
114 self.proc.start(self.command[0], QtCore.QStringList(self.command[1:]))
116 def read_stdout(self):
117 rawbytes = self.proc.readAllStandardOutput()
118 data = ''
119 for b in rawbytes:
120 data += b
121 text = core.decode(data)
122 self.out += text
123 self.append_text(text)
125 def read_stderr(self):
126 rawbytes = self.proc.readAllStandardError()
127 data = ''
128 for b in rawbytes:
129 data += b
130 text = core.decode(data)
131 self.err += text
132 self.append_text(text)
134 def append_text(self, text):
135 cursor = self.output_text.textCursor()
136 cursor.movePosition(cursor.End)
137 cursor.insertText(text)
138 cursor.movePosition(cursor.End)
139 self.output_text.setTextCursor(cursor)
141 def abortProc(self):
142 if self.proc.state() != QtCore.QProcess.NotRunning:
143 # Terminate seems to do nothing in windows
144 self.proc.terminate()
145 # Kill the process.
146 QtCore.QTimer.singleShot(1000, self.proc, QtCore.SLOT('kill()'))
148 def closeEvent(self, event):
149 if self.proc.state() != QtCore.QProcess.NotRunning:
150 # The process is still running, make sure we really want to abort.
151 title = N_('Abort Action')
152 msg = N_('An action is still running.\n'
153 'Terminating it could result in data loss.')
154 info_text = N_('Abort the action?')
155 ok_text = N_('Abort Action')
156 if qtutils.confirm(title, msg, info_text, ok_text,
157 default=False, icon=qtutils.discard_icon()):
158 self.abortProc()
159 event.accept()
160 else:
161 event.ignore()
162 else:
163 event.accept()
165 return standard.Dialog.closeEvent(self, event)
167 def stateChanged(self, newstate):
168 # State of process has changed - change the abort button state.
169 if newstate == QtCore.QProcess.NotRunning:
170 self.button_abort.setEnabled(False)
171 else:
172 self.button_abort.setEnabled(True)
174 def finishProc(self, status ):
175 self.exitstatus = status
178 class ActionDialog(standard.Dialog):
179 def __init__(self, parent, name, opts):
180 standard.Dialog.__init__(self, parent)
181 self.name = name
182 self.opts = opts
184 self.setWindowModality(Qt.ApplicationModal)
186 self.layt = QtGui.QVBoxLayout()
187 self.layt.setMargin(defs.margin)
188 self.layt.setSpacing(defs.spacing)
189 self.setLayout(self.layt)
191 title = opts.get('title')
192 if title:
193 self.setWindowTitle(os.path.expandvars(title))
195 self.prompt = QtGui.QLabel()
197 prompt = opts.get('prompt')
198 if prompt:
199 self.prompt.setText(os.path.expandvars(prompt))
200 self.layt.addWidget(self.prompt)
203 self.argslabel = QtGui.QLabel()
204 if 'argprompt' not in opts or opts.get('argprompt') is True:
205 argprompt = N_('Arguments')
206 else:
207 argprompt = opts.get('argprompt')
209 self.argslabel.setText(argprompt)
211 self.argstxt = QtGui.QLineEdit()
212 self.argslayt = QtGui.QHBoxLayout()
213 self.argslayt.addWidget(self.argslabel)
214 self.argslayt.addWidget(self.argstxt)
215 self.layt.addLayout(self.argslayt)
217 if not self.opts.get('argprompt'):
218 self.argslabel.setMinimumSize(1, 1)
219 self.argstxt.setMinimumSize(1, 1)
220 self.argstxt.hide()
221 self.argslabel.hide()
223 revs = (
224 (N_('Local Branch'), gitcmds.branch_list(remote=False)),
225 (N_('Tracking Branch'), gitcmds.branch_list(remote=True)),
226 (N_('Tag'), gitcmds.tag_list()),
229 if 'revprompt' not in opts or opts.get('revprompt') is True:
230 revprompt = N_('Revision')
231 else:
232 revprompt = opts.get('revprompt')
233 self.revselect = RevisionSelector(self, revs)
234 self.revselect.set_revision_label(revprompt)
235 self.layt.addWidget(self.revselect)
237 if not opts.get('revprompt'):
238 self.revselect.hide()
240 # Close/Run buttons
241 self.btnlayt = QtGui.QHBoxLayout()
242 self.btnlayt.addStretch()
243 self.closebtn = create_button(text=N_('Close'), layout=self.btnlayt)
244 self.runbtn = create_button(text=N_('Run'), layout=self.btnlayt)
245 self.runbtn.setDefault(True)
246 self.layt.addLayout(self.btnlayt)
248 # Widen the dialog by default
249 self.resize(666, self.height())
251 qtutils.connect_button(self.closebtn, self.reject)
252 qtutils.connect_button(self.runbtn, self.accept)
254 def revision(self):
255 return self.revselect.revision()
257 def args(self):
258 return self.argstxt.text()
261 class RevisionSelector(QtGui.QWidget):
262 def __init__(self, parent, revs):
263 QtGui.QWidget.__init__(self, parent)
265 self._revs = revs
266 self._revdict = dict(revs)
268 self._layt = QtGui.QVBoxLayout()
269 self._layt.setMargin(0)
270 self.setLayout(self._layt)
272 self._rev_layt = QtGui.QHBoxLayout()
273 self._rev_layt.setMargin(0)
275 self._rev_label = QtGui.QLabel()
276 self._rev_layt.addWidget(self._rev_label)
278 self._revision = completion.GitRefLineEdit()
279 self._rev_layt.addWidget(self._revision)
281 self._layt.addLayout(self._rev_layt)
283 self._radio_layt = QtGui.QHBoxLayout()
284 self._radio_btns = {}
286 # Create the radio buttons
287 for label, rev_list in self._revs:
288 radio = QtGui.QRadioButton()
289 radio.setText(label)
290 radio.setObjectName(label)
291 qtutils.connect_button(radio, self._set_revision_list)
292 self._radio_layt.addWidget(radio)
293 self._radio_btns[label] = radio
295 self._radio_layt.addStretch()
297 self._layt.addLayout(self._radio_layt)
299 self._rev_list = QtGui.QListWidget()
300 self._layt.addWidget(self._rev_list)
302 label, rev_list = self._revs[0]
303 self._radio_btns[label].setChecked(True)
304 qtutils.set_items(self._rev_list, rev_list)
306 self.connect(self._rev_list, SIGNAL('itemSelectionChanged()'),
307 self._rev_list_selection_changed)
309 def revision(self):
310 return self._revision.text()
312 def set_revision_label(self, txt):
313 self._rev_label.setText(txt)
315 def _set_revision_list(self):
316 sender = unicode(self.sender().objectName())
317 revs = self._revdict[sender]
318 qtutils.set_items(self._rev_list, revs)
320 def _rev_list_selection_changed(self):
321 items = self._rev_list.selectedItems()
322 if not items:
323 return
324 self._revision.setText(items[0].text())