tests: use pytest fixtures in browse_model_test
[git-cola.git] / cola / widgets / cfgactions.py
blobbba3cec9b306bccff45a250f4889d0cb9534fb6a
1 from __future__ import division, absolute_import, unicode_literals
2 import os
4 from qtpy import QtCore
5 from qtpy import QtWidgets
6 from qtpy.QtCore import Qt
8 from .. import core
9 from .. import gitcmds
10 from .. import icons
11 from .. import qtutils
12 from ..i18n import N_
13 from ..interaction import Interaction
14 from . import defs
15 from . import completion
16 from . import standard
17 from .text import LineEdit
20 def install():
21 Interaction.run_command = staticmethod(run_command)
22 Interaction.confirm_config_action = staticmethod(confirm_config_action)
25 def get_config_actions(context):
26 cfg = context.cfg
27 return cfg.get_guitool_names_and_shortcuts()
30 def confirm_config_action(context, name, opts):
31 dlg = ActionDialog(context, qtutils.active_window(), name, opts)
32 dlg.show()
33 if dlg.exec_() != QtWidgets.QDialog.Accepted:
34 return False
35 rev = dlg.revision()
36 if rev:
37 opts['revision'] = rev
38 args = dlg.args()
39 if args:
40 opts['args'] = args
41 return True
44 def run_command(title, command):
45 """Show a command widget"""
46 view = GitCommandWidget(title, qtutils.active_window())
47 view.set_command(command)
48 view.show()
49 view.raise_()
50 view.run()
51 view.exec_()
52 return (view.exitstatus, view.out, view.err)
55 class GitCommandWidget(standard.Dialog):
56 """Text viewer that reads the output of a command synchronously"""
58 # Keep us in scope otherwise PyQt kills the widget
59 def __init__(self, title, parent=None):
60 standard.Dialog.__init__(self, parent)
61 self.setWindowTitle(title)
62 if parent is not None:
63 self.setWindowModality(Qt.ApplicationModal)
65 # Construct the process
66 self.proc = QtCore.QProcess(self)
67 self.exitstatus = 0
68 self.out = ''
69 self.err = ''
70 self.command = []
72 # Create the text browser
73 self.output_text = QtWidgets.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 # Create abort / close buttons
81 # Start with abort disabled - will be enabled when the process is run.
82 self.button_abort = qtutils.create_button(text=N_('Abort'), enabled=False)
83 self.button_close = qtutils.close_button()
85 # Put them in a horizontal layout at the bottom.
86 self.button_box = QtWidgets.QDialogButtonBox(self)
87 self.button_box.addButton(
88 self.button_abort, QtWidgets.QDialogButtonBox.RejectRole
90 self.button_box.addButton(
91 self.button_close, QtWidgets.QDialogButtonBox.AcceptRole
94 # Connect the signals to the process
95 # pylint: disable=no-member
96 self.proc.readyReadStandardOutput.connect(self.read_stdout)
97 self.proc.readyReadStandardError.connect(self.read_stderr)
98 self.proc.finished.connect(self.proc_finished)
99 self.proc.stateChanged.connect(self.proc_state_changed)
101 qtutils.connect_button(self.button_abort, self.abort)
102 qtutils.connect_button(self.button_close, self.close)
104 self._layout = qtutils.vbox(
105 defs.margin, defs.spacing, self.output_text, self.button_box
107 self.setLayout(self._layout)
109 self.resize(720, 420)
111 def set_command(self, command):
112 self.command = command
114 def run(self):
115 """Runs the process"""
116 self.proc.start(self.command[0], self.command[1:])
118 def read_stdout(self):
119 text = self.read_stream(self.proc.readAllStandardOutput)
120 self.out += text
122 def read_stderr(self):
123 text = self.read_stream(self.proc.readAllStandardError)
124 self.err += text
126 def read_stream(self, fn):
127 data = fn().data()
128 text = core.decode(data)
129 self.append_text(text)
130 return text
132 def append_text(self, text):
133 cursor = self.output_text.textCursor()
134 cursor.movePosition(cursor.End)
135 cursor.insertText(text)
136 cursor.movePosition(cursor.End)
137 self.output_text.setTextCursor(cursor)
139 def abort(self):
140 if self.proc.state() != QtCore.QProcess.NotRunning:
141 # Terminate seems to do nothing in windows
142 self.proc.terminate()
143 # Kill the process.
144 QtCore.QTimer.singleShot(1000, self.proc.kill)
146 def closeEvent(self, event):
147 if self.proc.state() != QtCore.QProcess.NotRunning:
148 # The process is still running, make sure we really want to abort.
149 title = N_('Abort Action')
150 msg = N_(
151 'An action is still running.\n'
152 'Terminating it could result in data loss.'
154 info_text = N_('Abort the action?')
155 ok_text = N_('Abort Action')
156 if Interaction.confirm(
157 title, msg, info_text, ok_text, default=False, icon=icons.close()
159 self.abort()
160 event.accept()
161 else:
162 event.ignore()
163 else:
164 event.accept()
166 return standard.Dialog.closeEvent(self, event)
168 def proc_state_changed(self, newstate):
169 # State of process has changed - change the abort button state.
170 if newstate == QtCore.QProcess.NotRunning:
171 self.button_abort.setEnabled(False)
172 else:
173 self.button_abort.setEnabled(True)
175 def proc_finished(self, status):
176 self.exitstatus = status
179 class ActionDialog(standard.Dialog):
181 VALUES = {}
183 def __init__(self, context, parent, name, opts):
184 standard.Dialog.__init__(self, parent)
185 self.context = context
186 self.action_name = name
187 self.opts = opts
189 try:
190 values = self.VALUES[name]
191 except KeyError:
192 values = self.VALUES[name] = {}
194 self.setWindowModality(Qt.ApplicationModal)
196 title = opts.get('title')
197 if title:
198 self.setWindowTitle(os.path.expandvars(title))
200 self.prompt = QtWidgets.QLabel()
201 prompt = opts.get('prompt')
202 if prompt:
203 self.prompt.setText(os.path.expandvars(prompt))
205 self.argslabel = QtWidgets.QLabel()
206 if 'argprompt' not in opts or opts.get('argprompt') is True:
207 argprompt = N_('Arguments')
208 else:
209 argprompt = opts.get('argprompt')
210 self.argslabel.setText(argprompt)
212 self.argstxt = LineEdit()
213 if self.opts.get('argprompt'):
214 try:
215 # Remember the previous value
216 saved_value = values['argstxt']
217 self.argstxt.setText(saved_value)
218 except KeyError:
219 pass
220 else:
221 self.argslabel.setMinimumSize(10, 10)
222 self.argstxt.setMinimumSize(10, 10)
223 self.argstxt.hide()
224 self.argslabel.hide()
226 revs = (
227 (N_('Local Branch'), gitcmds.branch_list(context, remote=False)),
228 (N_('Tracking Branch'), gitcmds.branch_list(context, remote=True)),
229 (N_('Tag'), gitcmds.tag_list(context)),
232 if 'revprompt' not in opts or opts.get('revprompt') is True:
233 revprompt = N_('Revision')
234 else:
235 revprompt = opts.get('revprompt')
236 self.revselect = RevisionSelector(context, self, revs)
237 self.revselect.set_revision_label(revprompt)
239 if not opts.get('revprompt'):
240 self.revselect.hide()
242 # Close/Run buttons
243 self.closebtn = qtutils.close_button()
244 self.runbtn = qtutils.create_button(
245 text=N_('Run'), default=True, icon=icons.ok()
248 self.argslayt = qtutils.hbox(
249 defs.margin, defs.spacing, self.argslabel, self.argstxt
252 self.btnlayt = qtutils.hbox(
253 defs.margin, defs.spacing, qtutils.STRETCH, self.closebtn, self.runbtn
256 self.layt = qtutils.vbox(
257 defs.margin,
258 defs.spacing,
259 self.prompt,
260 self.argslayt,
261 self.revselect,
262 self.btnlayt,
264 self.setLayout(self.layt)
266 # pylint: disable=no-member
267 self.argstxt.textChanged.connect(self._argstxt_changed)
268 qtutils.connect_button(self.closebtn, self.reject)
269 qtutils.connect_button(self.runbtn, self.accept)
271 # Widen the dialog by default
272 self.resize(666, self.height())
274 def revision(self):
275 return self.revselect.revision()
277 def args(self):
278 return self.argstxt.text()
280 def _argstxt_changed(self, value):
281 """Store the argstxt value so that we can remember it between calls"""
282 self.VALUES[self.action_name]['argstxt'] = value
285 class RevisionSelector(QtWidgets.QWidget):
286 def __init__(self, context, parent, revs):
287 QtWidgets.QWidget.__init__(self, parent)
289 self.context = context
290 self._revs = revs
291 self._revdict = dict(revs)
293 self._rev_label = QtWidgets.QLabel(self)
294 self._revision = completion.GitRefLineEdit(context, parent=self)
296 # Create the radio buttons
297 radio_btns = []
298 self._radio_btns = {}
299 for label, rev_list in self._revs:
300 radio = qtutils.radio(text=label)
301 radio.setObjectName(label)
302 qtutils.connect_button(radio, self._set_revision_list)
303 radio_btns.append(radio)
304 self._radio_btns[label] = radio
305 radio_btns.append(qtutils.STRETCH)
307 self._rev_list = QtWidgets.QListWidget()
308 label, rev_list = self._revs[0]
309 self._radio_btns[label].setChecked(True)
310 qtutils.set_items(self._rev_list, rev_list)
312 self._rev_layt = qtutils.hbox(
313 defs.no_margin, defs.spacing, self._rev_label, self._revision
316 self._radio_layt = qtutils.hbox(defs.margin, defs.spacing, *radio_btns)
318 self._layt = qtutils.vbox(
319 defs.no_margin,
320 defs.spacing,
321 self._rev_layt,
322 self._radio_layt,
323 self._rev_list,
325 self.setLayout(self._layt)
327 # pylint: disable=no-member
328 self._rev_list.itemSelectionChanged.connect(self.selection_changed)
330 def revision(self):
331 return self._revision.text()
333 def set_revision_label(self, txt):
334 self._rev_label.setText(txt)
336 def _set_revision_list(self):
337 sender = self.sender().objectName()
338 revs = self._revdict[sender]
339 qtutils.set_items(self._rev_list, revs)
341 def selection_changed(self):
342 items = self._rev_list.selectedItems()
343 if not items:
344 return
345 self._revision.setText(items[0].text())