fetch: add support for the traditional FETCH_HEAD behavior
[git-cola.git] / cola / widgets / cfgactions.py
blobcbdcdc669adeac3d9e7ab6d6036b610c3e9cf537
1 import os
3 from qtpy import QtCore
4 from qtpy import QtWidgets
5 from qtpy.QtCore import Qt
7 from .. import core
8 from .. import gitcmds
9 from .. import icons
10 from .. import qtutils
11 from ..i18n import N_
12 from ..interaction import Interaction
13 from . import defs
14 from . import completion
15 from . import standard
16 from .text import LineEdit
19 def install():
20 Interaction.run_command = staticmethod(run_command)
21 Interaction.confirm_config_action = staticmethod(confirm_config_action)
24 def get_config_actions(context):
25 cfg = context.cfg
26 return cfg.get_guitool_names_and_shortcuts()
29 def confirm_config_action(context, name, opts):
30 dlg = ActionDialog(context, qtutils.active_window(), name, opts)
31 dlg.show()
32 if dlg.exec_() != QtWidgets.QDialog.Accepted:
33 return False
34 rev = dlg.revision()
35 if rev:
36 opts['revision'] = rev
37 args = 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 """Text viewer that reads the output of a command synchronously"""
57 # Keep us in scope otherwise PyQt kills the widget
58 def __init__(self, title, parent=None):
59 standard.Dialog.__init__(self, parent)
60 self.setWindowTitle(title)
61 if parent is not None:
62 self.setWindowModality(Qt.ApplicationModal)
64 # Construct the process
65 self.proc = QtCore.QProcess(self)
66 self.exitstatus = 0
67 self.out = ''
68 self.err = ''
69 self.command = []
71 # Create the text browser
72 self.output_text = QtWidgets.QTextBrowser(self)
73 self.output_text.setAcceptDrops(False)
74 self.output_text.setTabChangesFocus(True)
75 self.output_text.setUndoRedoEnabled(False)
76 self.output_text.setReadOnly(True)
77 self.output_text.setAcceptRichText(False)
79 # Create abort / close buttons
80 # Start with abort disabled - will be enabled when the process is run.
81 self.button_abort = qtutils.create_button(text=N_('Abort'), enabled=False)
82 self.button_close = qtutils.close_button()
84 # Put them in a horizontal layout at the bottom.
85 self.button_box = QtWidgets.QDialogButtonBox(self)
86 self.button_box.addButton(
87 self.button_abort, QtWidgets.QDialogButtonBox.RejectRole
89 self.button_box.addButton(
90 self.button_close, QtWidgets.QDialogButtonBox.AcceptRole
93 # Connect the signals to the process
94 self.proc.readyReadStandardOutput.connect(self.read_stdout)
95 self.proc.readyReadStandardError.connect(self.read_stderr)
96 self.proc.finished.connect(self.proc_finished)
97 self.proc.stateChanged.connect(self.proc_state_changed)
99 qtutils.connect_button(self.button_abort, self.abort)
100 qtutils.connect_button(self.button_close, self.close)
102 self._layout = qtutils.vbox(
103 defs.margin, defs.spacing, self.output_text, self.button_box
105 self.setLayout(self._layout)
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], self.command[1:])
116 def read_stdout(self):
117 text = self.read_stream(self.proc.readAllStandardOutput)
118 self.out += text
120 def read_stderr(self):
121 text = self.read_stream(self.proc.readAllStandardError)
122 self.err += text
124 def read_stream(self, func):
125 data = func().data()
126 text = core.decode(data)
127 self.append_text(text)
128 return text
130 def append_text(self, text):
131 cursor = self.output_text.textCursor()
132 cursor.movePosition(cursor.End)
133 cursor.insertText(text)
134 cursor.movePosition(cursor.End)
135 self.output_text.setTextCursor(cursor)
137 def abort(self):
138 if self.proc.state() != QtCore.QProcess.NotRunning:
139 # Terminate seems to do nothing in windows
140 self.proc.terminate()
141 # Kill the process.
142 QtCore.QTimer.singleShot(1000, self.proc.kill)
144 def closeEvent(self, event):
145 if self.proc.state() != QtCore.QProcess.NotRunning:
146 # The process is still running, make sure we really want to abort.
147 title = N_('Abort Action')
148 msg = N_(
149 'An action is still running.\n'
150 'Terminating it could result in data loss.'
152 info_text = N_('Abort the action?')
153 ok_text = N_('Abort Action')
154 if Interaction.confirm(
155 title, msg, info_text, ok_text, default=False, icon=icons.close()
157 self.abort()
158 event.accept()
159 else:
160 event.ignore()
161 else:
162 event.accept()
164 return standard.Dialog.closeEvent(self, event)
166 def proc_state_changed(self, newstate):
167 # State of process has changed - change the abort button state.
168 if newstate == QtCore.QProcess.NotRunning:
169 self.button_abort.setEnabled(False)
170 else:
171 self.button_abort.setEnabled(True)
173 def proc_finished(self, status):
174 self.exitstatus = status
177 class ActionDialog(standard.Dialog):
178 VALUES = {}
180 def __init__(self, context, parent, name, opts):
181 standard.Dialog.__init__(self, parent)
182 self.context = context
183 self.action_name = name
184 self.opts = opts
186 try:
187 values = self.VALUES[name]
188 except KeyError:
189 values = self.VALUES[name] = {}
191 self.setWindowModality(Qt.ApplicationModal)
193 title = opts.get('title')
194 if title:
195 self.setWindowTitle(os.path.expandvars(title))
197 self.prompt = QtWidgets.QLabel()
198 prompt = opts.get('prompt')
199 if prompt:
200 self.prompt.setText(os.path.expandvars(prompt))
202 self.argslabel = QtWidgets.QLabel()
203 if 'argprompt' not in opts or opts.get('argprompt') is True:
204 argprompt = N_('Arguments')
205 else:
206 argprompt = opts.get('argprompt')
207 self.argslabel.setText(argprompt)
209 self.argstxt = LineEdit()
210 if self.opts.get('argprompt'):
211 try:
212 # Remember the previous value
213 saved_value = values['argstxt']
214 self.argstxt.setText(saved_value)
215 except KeyError:
216 pass
217 else:
218 self.argslabel.setMinimumSize(10, 10)
219 self.argstxt.setMinimumSize(10, 10)
220 self.argstxt.hide()
221 self.argslabel.hide()
223 revs = (
224 (N_('Local Branch'), gitcmds.branch_list(context, remote=False)),
225 (N_('Tracking Branch'), gitcmds.branch_list(context, remote=True)),
226 (N_('Tag'), gitcmds.tag_list(context)),
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(context, self, revs)
234 self.revselect.set_revision_label(revprompt)
236 if not opts.get('revprompt'):
237 self.revselect.hide()
239 # Close/Run buttons
240 self.closebtn = qtutils.close_button()
241 self.runbtn = qtutils.create_button(
242 text=N_('Run'), default=True, icon=icons.ok()
245 self.argslayt = qtutils.hbox(
246 defs.margin, defs.spacing, self.argslabel, self.argstxt
249 self.btnlayt = qtutils.hbox(
250 defs.margin, defs.spacing, qtutils.STRETCH, self.closebtn, self.runbtn
253 self.layt = qtutils.vbox(
254 defs.margin,
255 defs.spacing,
256 self.prompt,
257 self.argslayt,
258 self.revselect,
259 self.btnlayt,
261 self.setLayout(self.layt)
263 self.argstxt.textChanged.connect(self._argstxt_changed)
264 qtutils.connect_button(self.closebtn, self.reject)
265 qtutils.connect_button(self.runbtn, self.accept)
267 # Widen the dialog by default
268 self.resize(666, self.height())
270 def revision(self):
271 return self.revselect.revision()
273 def args(self):
274 return self.argstxt.text()
276 def _argstxt_changed(self, value):
277 """Store the argstxt value so that we can remember it between calls"""
278 self.VALUES[self.action_name]['argstxt'] = value
281 class RevisionSelector(QtWidgets.QWidget):
282 def __init__(self, context, parent, revs):
283 QtWidgets.QWidget.__init__(self, parent)
285 self.context = context
286 self._revs = revs
287 self._revdict = dict(revs)
289 self._rev_label = QtWidgets.QLabel(self)
290 self._revision = completion.GitRefLineEdit(context, parent=self)
292 # Create the radio buttons
293 radio_btns = []
294 self._radio_btns = {}
295 for label, rev_list in self._revs:
296 radio = qtutils.radio(text=label)
297 radio.setObjectName(label)
298 qtutils.connect_button(radio, self._set_revision_list)
299 radio_btns.append(radio)
300 self._radio_btns[label] = radio
301 radio_btns.append(qtutils.STRETCH)
303 self._rev_list = QtWidgets.QListWidget()
304 label, rev_list = self._revs[0]
305 self._radio_btns[label].setChecked(True)
306 qtutils.set_items(self._rev_list, rev_list)
308 self._rev_layt = qtutils.hbox(
309 defs.no_margin, defs.spacing, self._rev_label, self._revision
312 self._radio_layt = qtutils.hbox(defs.margin, defs.spacing, *radio_btns)
314 self._layt = qtutils.vbox(
315 defs.no_margin,
316 defs.spacing,
317 self._rev_layt,
318 self._radio_layt,
319 self._rev_list,
321 self.setLayout(self._layt)
322 self._rev_list.itemSelectionChanged.connect(self.selection_changed)
324 def revision(self):
325 return self._revision.text()
327 def set_revision_label(self, txt):
328 self._rev_label.setText(txt)
330 def _set_revision_list(self):
331 sender = self.sender().objectName()
332 revs = self._revdict[sender]
333 qtutils.set_items(self._rev_list, revs)
335 def selection_changed(self):
336 items = self._rev_list.selectedItems()
337 if not items:
338 return
339 self._revision.setText(items[0].text())