doc: update v3.3 release notes draft
[git-cola.git] / cola / widgets / cfgactions.py
blob0cefe8c428ff87536a16f79ce24c2bcd51ee6571
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'),
83 enabled=False)
84 self.button_close = qtutils.close_button()
86 # Put them in a horizontal layout at the bottom.
87 self.button_box = QtWidgets.QDialogButtonBox(self)
88 self.button_box.addButton(self.button_abort,
89 QtWidgets.QDialogButtonBox.RejectRole)
90 self.button_box.addButton(self.button_close,
91 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(defs.margin, defs.spacing,
103 self.output_text, self.button_box)
104 self.setLayout(self._layout)
106 self.resize(720, 420)
108 def set_command(self, command):
109 self.command = command
111 def run(self):
112 """Runs the process"""
113 self.proc.start(self.command[0], self.command[1:])
115 def read_stdout(self):
116 text = self.read_stream(self.proc.readAllStandardOutput)
117 self.out += text
119 def read_stderr(self):
120 text = self.read_stream(self.proc.readAllStandardError)
121 self.err += text
123 def read_stream(self, fn):
124 data = fn().data()
125 text = core.decode(data)
126 self.append_text(text)
127 return text
129 def append_text(self, text):
130 cursor = self.output_text.textCursor()
131 cursor.movePosition(cursor.End)
132 cursor.insertText(text)
133 cursor.movePosition(cursor.End)
134 self.output_text.setTextCursor(cursor)
136 def abort(self):
137 if self.proc.state() != QtCore.QProcess.NotRunning:
138 # Terminate seems to do nothing in windows
139 self.proc.terminate()
140 # Kill the process.
141 QtCore.QTimer.singleShot(1000, self.proc.kill)
143 def closeEvent(self, event):
144 if self.proc.state() != QtCore.QProcess.NotRunning:
145 # The process is still running, make sure we really want to abort.
146 title = N_('Abort Action')
147 msg = N_('An action is still running.\n'
148 'Terminating it could result in data loss.')
149 info_text = N_('Abort the action?')
150 ok_text = N_('Abort Action')
151 if Interaction.confirm(title, msg, info_text, ok_text,
152 default=False, icon=icons.close()):
153 self.abort()
154 event.accept()
155 else:
156 event.ignore()
157 else:
158 event.accept()
160 return standard.Dialog.closeEvent(self, event)
162 def proc_state_changed(self, newstate):
163 # State of process has changed - change the abort button state.
164 if newstate == QtCore.QProcess.NotRunning:
165 self.button_abort.setEnabled(False)
166 else:
167 self.button_abort.setEnabled(True)
169 def proc_finished(self, status):
170 self.exitstatus = status
173 class ActionDialog(standard.Dialog):
175 VALUES = {}
177 def __init__(self, context, parent, name, opts):
178 standard.Dialog.__init__(self, parent)
179 self.context = context
180 self.action_name = name
181 self.opts = opts
183 try:
184 values = self.VALUES[name]
185 except KeyError:
186 values = self.VALUES[name] = {}
188 self.setWindowModality(Qt.ApplicationModal)
190 title = opts.get('title')
191 if title:
192 self.setWindowTitle(os.path.expandvars(title))
194 self.prompt = QtWidgets.QLabel()
195 prompt = opts.get('prompt')
196 if prompt:
197 self.prompt.setText(os.path.expandvars(prompt))
199 self.argslabel = QtWidgets.QLabel()
200 if 'argprompt' not in opts or opts.get('argprompt') is True:
201 argprompt = N_('Arguments')
202 else:
203 argprompt = opts.get('argprompt')
204 self.argslabel.setText(argprompt)
206 self.argstxt = LineEdit()
207 if self.opts.get('argprompt'):
208 try:
209 # Remember the previous value
210 saved_value = values['argstxt']
211 self.argstxt.setText(saved_value)
212 except KeyError:
213 pass
214 else:
215 self.argslabel.setMinimumSize(1, 1)
216 self.argstxt.setMinimumSize(1, 1)
217 self.argstxt.hide()
218 self.argslabel.hide()
220 revs = (
221 (N_('Local Branch'), gitcmds.branch_list(context, remote=False)),
222 (N_('Tracking Branch'), gitcmds.branch_list(context, remote=True)),
223 (N_('Tag'), gitcmds.tag_list(context)),
226 if 'revprompt' not in opts or opts.get('revprompt') is True:
227 revprompt = N_('Revision')
228 else:
229 revprompt = opts.get('revprompt')
230 self.revselect = RevisionSelector(context, self, revs)
231 self.revselect.set_revision_label(revprompt)
233 if not opts.get('revprompt'):
234 self.revselect.hide()
236 # Close/Run buttons
237 self.closebtn = qtutils.close_button()
238 self.runbtn = qtutils.create_button(text=N_('Run'), default=True,
239 icon=icons.ok())
241 self.argslayt = qtutils.hbox(defs.margin, defs.spacing,
242 self.argslabel, self.argstxt)
244 self.btnlayt = qtutils.hbox(defs.margin, defs.spacing, qtutils.STRETCH,
245 self.closebtn, self.runbtn)
247 self.layt = qtutils.vbox(defs.margin, defs.spacing,
248 self.prompt, self.argslayt,
249 self.revselect, self.btnlayt)
250 self.setLayout(self.layt)
252 self.argstxt.textChanged.connect(self._argstxt_changed)
253 qtutils.connect_button(self.closebtn, self.reject)
254 qtutils.connect_button(self.runbtn, self.accept)
256 # Widen the dialog by default
257 self.resize(666, self.height())
259 def revision(self):
260 return self.revselect.revision()
262 def args(self):
263 return self.argstxt.text()
265 def _argstxt_changed(self, value):
266 """Store the argstxt value so that we can remember it between calls"""
267 self.VALUES[self.action_name]['argstxt'] = value
270 class RevisionSelector(QtWidgets.QWidget):
272 def __init__(self, context, parent, revs):
273 QtWidgets.QWidget.__init__(self, parent)
275 self.context = context
276 self._revs = revs
277 self._revdict = dict(revs)
279 self._rev_label = QtWidgets.QLabel(self)
280 self._revision = completion.GitRefLineEdit(context, parent=self)
282 # Create the radio buttons
283 radio_btns = []
284 self._radio_btns = {}
285 for label, rev_list in self._revs:
286 radio = qtutils.radio(text=label)
287 radio.setObjectName(label)
288 qtutils.connect_button(radio, self._set_revision_list)
289 radio_btns.append(radio)
290 self._radio_btns[label] = radio
291 radio_btns.append(qtutils.STRETCH)
293 self._rev_list = QtWidgets.QListWidget()
294 label, rev_list = self._revs[0]
295 self._radio_btns[label].setChecked(True)
296 qtutils.set_items(self._rev_list, rev_list)
298 self._rev_layt = qtutils.hbox(defs.no_margin, defs.spacing,
299 self._rev_label, self._revision)
301 self._radio_layt = qtutils.hbox(defs.margin, defs.spacing,
302 *radio_btns)
304 self._layt = qtutils.vbox(defs.no_margin, defs.spacing,
305 self._rev_layt, self._radio_layt,
306 self._rev_list)
307 self.setLayout(self._layt)
309 self._rev_list.itemSelectionChanged.connect(self.selection_changed)
311 def revision(self):
312 return self._revision.text()
314 def set_revision_label(self, txt):
315 self._rev_label.setText(txt)
317 def _set_revision_list(self):
318 sender = self.sender().objectName()
319 revs = self._revdict[sender]
320 qtutils.set_items(self._rev_list, revs)
322 def selection_changed(self):
323 items = self._rev_list.selectedItems()
324 if not items:
325 return
326 self._revision.setText(items[0].text())