commitmsg: move the progress bar into the dock title
[git-cola.git] / cola / widgets / editremotes.py
blob09fcaecaa55a441dc7bce1ef4703b4881db740ff
1 import operator
3 from qtpy import QtCore
4 from qtpy import QtWidgets
5 from qtpy.QtCore import Qt
6 from qtpy.QtCore import Signal
8 from ..git import STDOUT
9 from ..i18n import N_
10 from ..qtutils import get
11 from .. import cmds
12 from .. import core
13 from .. import gitcmds
14 from .. import icons
15 from .. import qtutils
16 from .. import utils
17 from . import completion
18 from . import defs
19 from . import standard
20 from . import text
23 def editor(context, run=True):
24 view = RemoteEditor(context, parent=qtutils.active_window())
25 if run:
26 view.show()
27 view.exec_()
28 return view
31 class RemoteEditor(standard.Dialog):
32 def __init__(self, context, parent=None):
33 standard.Dialog.__init__(self, parent)
34 self.setWindowTitle(N_('Edit Remotes'))
35 if parent is not None:
36 self.setWindowModality(Qt.WindowModal)
38 self.context = context
39 self.current_name = ''
40 self.current_url = ''
42 hint = N_('Edit remotes by selecting them from the list')
43 self.default_hint = hint
45 self.remote_list = []
46 self.remotes = QtWidgets.QListWidget()
47 self.remotes.setToolTip(N_('Remote git repositories - double-click to rename'))
49 self.editor = RemoteWidget(context, self)
51 self.save_button = qtutils.create_button(
52 text=N_('Save'), icon=icons.save(), default=True
55 self.reset_button = qtutils.create_button(
56 text=N_('Reset'), icon=icons.discard()
59 tooltip = N_(
60 'Add and remove remote repositories using the \n'
61 'Add(+) and Delete(-) buttons on the left-hand side.\n'
62 '\n'
63 'Remotes can be renamed by selecting one from the list\n'
64 'and pressing "enter", or by double-clicking.'
66 hint = self.default_hint
67 self.info = text.VimHintedPlainTextEdit(context, hint, parent=self)
68 self.info.setToolTip(tooltip)
70 text_width, text_height = qtutils.text_size(self.info.font(), 'M')
71 width = text_width * 42
72 height = text_height * 13
73 self.info.setMinimumWidth(width)
74 self.info.setMinimumHeight(height)
75 self.info_thread = RemoteInfoThread(context, self)
77 icon = icons.add()
78 tooltip = N_('Add new remote git repository')
79 self.add_button = qtutils.create_toolbutton(icon=icon, tooltip=tooltip)
81 self.refresh_button = qtutils.create_toolbutton(
82 icon=icons.sync(), tooltip=N_('Refresh')
84 self.delete_button = qtutils.create_toolbutton(
85 icon=icons.remove(), tooltip=N_('Delete remote')
87 self.close_button = qtutils.close_button()
89 self._edit_button_layout = qtutils.vbox(
90 defs.no_margin, defs.spacing, self.save_button, self.reset_button
93 self._edit_layout = qtutils.hbox(
94 defs.no_margin, defs.spacing, self.editor, self._edit_button_layout
97 self._display_layout = qtutils.vbox(
98 defs.no_margin, defs.spacing, self._edit_layout, self.info
100 self._display_widget = QtWidgets.QWidget(self)
101 self._display_widget.setLayout(self._display_layout)
103 self._top_layout = qtutils.splitter(
104 Qt.Horizontal, self.remotes, self._display_widget
106 width = self._top_layout.width()
107 self._top_layout.setSizes([width // 4, width * 3 // 4])
109 self._button_layout = qtutils.hbox(
110 defs.margin,
111 defs.spacing,
112 self.add_button,
113 self.delete_button,
114 self.refresh_button,
115 qtutils.STRETCH,
116 self.close_button,
119 self._layout = qtutils.vbox(
120 defs.margin, defs.spacing, self._top_layout, self._button_layout
122 self.setLayout(self._layout)
124 qtutils.connect_button(self.add_button, self.add)
125 qtutils.connect_button(self.delete_button, self.delete)
126 qtutils.connect_button(self.refresh_button, self.refresh)
127 qtutils.connect_button(self.close_button, self.accept)
128 qtutils.connect_button(self.save_button, self.save)
129 qtutils.connect_button(self.reset_button, self.reset)
130 qtutils.add_close_action(self)
132 thread = self.info_thread
133 thread.result.connect(self.set_info, type=Qt.QueuedConnection)
135 # pylint: disable=no-member
136 self.editor.remote_name.returnPressed.connect(self.save)
137 self.editor.remote_url.returnPressed.connect(self.save)
138 self.editor.valid.connect(self.editor_valid)
140 self.remotes.itemChanged.connect(self.remote_item_renamed)
141 self.remotes.itemSelectionChanged.connect(self.selection_changed)
143 self.disable_editor()
144 self.init_state(None, self.resize_widget, parent)
145 self.remotes.setFocus(Qt.OtherFocusReason)
146 self.refresh()
148 def reset(self):
149 focus = self.focusWidget()
150 if self.current_name:
151 self.activate_remote(self.current_name, gather_info=False)
152 restore_focus(focus)
154 @property
155 def changed(self):
156 url = self.editor.url
157 name = self.editor.name
158 return url != self.current_url or name != self.current_name
160 def save(self):
161 if not self.changed:
162 return
163 context = self.context
164 name = self.editor.name
165 url = self.editor.url
166 old_url = self.current_url
167 old_name = self.current_name
169 name_changed = name and name != old_name
170 url_changed = url and url != old_url
171 focus = self.focusWidget()
173 name_ok = url_ok = False
175 # Run the corresponding command
176 if name_changed and url_changed:
177 name_ok, url_ok = cmds.do(cmds.RemoteEdit, context, old_name, name, url)
178 elif name_changed:
179 result = cmds.do(cmds.RemoteRename, context, old_name, name)
180 name_ok = result[0]
181 elif url_changed:
182 result = cmds.do(cmds.RemoteSetURL, context, name, url)
183 url_ok = result[0]
185 # Update state if the change succeeded
186 gather = False
187 if url_changed and url_ok:
188 self.current_url = url
189 gather = True
191 # A name change requires a refresh
192 if name_changed and name_ok:
193 self.current_name = name
195 self.refresh(select=False)
196 remotes = utils.Sequence(self.remote_list)
197 idx = remotes.index(name)
198 self.select_remote(idx)
199 gather = False # already done by select_remote()
201 restore_focus(focus)
202 if gather:
203 self.gather_info()
205 def editor_valid(self, valid):
206 changed = self.changed
207 self.reset_button.setEnabled(changed)
208 self.save_button.setEnabled(changed and valid)
210 def disable_editor(self):
211 self.save_button.setEnabled(False)
212 self.reset_button.setEnabled(False)
213 self.editor.setEnabled(False)
214 self.editor.name = ''
215 self.editor.url = ''
216 self.info.hint.set_value(self.default_hint)
217 self.info.set_value('')
219 def resize_widget(self, parent):
220 """Set the initial size of the widget"""
221 width, height = qtutils.default_size(parent, 720, 445)
222 self.resize(width, height)
224 def set_info(self, info):
225 self.info.hint.set_value(self.default_hint)
226 self.info.set_value(info)
228 def select_remote(self, index):
229 if index >= 0:
230 item = self.remotes.item(index)
231 if item:
232 item.setSelected(True)
234 def refresh(self, select=True):
235 git = self.context.git
236 remotes = git.remote()[STDOUT].splitlines()
237 # Ignore notifications from self.remotes while mutating.
238 with qtutils.BlockSignals(self.remotes):
239 self.remotes.clear()
240 self.remotes.addItems(remotes)
241 self.remote_list = remotes
243 for idx in range(len(remotes)):
244 item = self.remotes.item(idx)
245 item.setFlags(item.flags() | Qt.ItemIsEditable)
247 if select:
248 if not self.current_name and remotes:
249 # Nothing is selected; select the first item
250 self.select_remote(0)
251 elif self.current_name and remotes:
252 # Reselect the previously selected item
253 remote_seq = utils.Sequence(remotes)
254 idx = remote_seq.index(self.current_name)
255 if idx >= 0:
256 item = self.remotes.item(idx)
257 if item:
258 item.setSelected(True)
260 def add(self):
261 if add_remote(self.context, self):
262 self.refresh(select=False)
263 # Newly added remote will be last; select it
264 self.select_remote(len(self.remote_list) - 1)
266 def delete(self):
267 remote = qtutils.selected_item(self.remotes, self.remote_list)
268 if not remote:
269 return
270 cmds.do(cmds.RemoteRemove, self.context, remote)
271 self.refresh(select=False)
273 def remote_item_renamed(self, item):
274 idx = self.remotes.row(item)
275 if idx < 0:
276 return
277 if idx >= len(self.remote_list):
278 return
279 old_name = self.remote_list[idx]
280 new_name = item.text()
281 if new_name == old_name:
282 return
283 if not new_name:
284 item.setText(old_name)
285 return
286 context = self.context
287 ok, status, _, _ = cmds.do(cmds.RemoteRename, context, old_name, new_name)
288 if ok and status == 0:
289 self.remote_list[idx] = new_name
290 self.activate_remote(new_name)
291 else:
292 item.setText(old_name)
294 def selection_changed(self):
295 remote = qtutils.selected_item(self.remotes, self.remote_list)
296 if not remote:
297 self.disable_editor()
298 return
299 self.activate_remote(remote)
301 def activate_remote(self, name, gather_info=True):
302 url = gitcmds.remote_url(self.context, name)
303 self.current_name = name
304 self.current_url = url
306 self.editor.setEnabled(True)
307 self.editor.name = name
308 self.editor.url = url
310 if gather_info:
311 self.gather_info()
313 def gather_info(self):
314 name = self.current_name
315 self.info.hint.set_value(N_('Gathering info for "%s"...') % name)
316 self.info.set_value('')
318 self.info_thread.remote = name
319 self.info_thread.start()
322 def add_remote(context, parent, name='', url='', readonly_url=False):
323 """Bring up the "Add Remote" dialog"""
324 widget = AddRemoteDialog(context, parent, readonly_url=readonly_url)
325 if name:
326 widget.name = name
327 if url:
328 widget.url = url
329 if widget.run():
330 cmds.do(cmds.RemoteAdd, context, widget.name, widget.url)
331 result = True
332 else:
333 result = False
334 return result
337 def restore_focus(focus):
338 if focus:
339 focus.setFocus(Qt.OtherFocusReason)
340 if hasattr(focus, 'selectAll'):
341 focus.selectAll()
344 class RemoteInfoThread(QtCore.QThread):
345 result = Signal(object)
347 def __init__(self, context, parent):
348 QtCore.QThread.__init__(self, parent)
349 self.context = context
350 self.remote = None
352 def run(self):
353 remote = self.remote
354 if remote is None:
355 return
356 git = self.context.git
357 _, out, err = git.remote('show', '-n', remote)
358 self.result.emit(out + err)
361 class AddRemoteDialog(QtWidgets.QDialog):
362 def __init__(self, context, parent, readonly_url=False):
363 super().__init__(parent)
364 self.context = context
365 if parent:
366 self.setWindowModality(Qt.WindowModal)
368 self.context = context
369 self.widget = RemoteWidget(context, self, readonly_url=readonly_url)
370 self.add_button = qtutils.create_button(
371 text=N_('Add Remote'), icon=icons.ok(), enabled=False
373 self.close_button = qtutils.close_button()
375 self._button_layout = qtutils.hbox(
376 defs.no_margin,
377 defs.button_spacing,
378 qtutils.STRETCH,
379 self.close_button,
380 self.add_button,
383 self._layout = qtutils.vbox(
384 defs.margin, defs.spacing, self.widget, self._button_layout
386 self.setLayout(self._layout)
388 self.widget.valid.connect(self.add_button.setEnabled)
389 qtutils.connect_button(self.add_button, self.accept)
390 qtutils.connect_button(self.close_button, self.reject)
392 def set_name(self, value):
393 self.widget.name = value
395 def set_url(self, value):
396 self.widget.url = value
398 name = property(operator.attrgetter('widget.name'), set_name)
399 url = property(operator.attrgetter('widget.url'), set_url)
401 def run(self):
402 self.show()
403 self.raise_()
404 return self.exec_() == QtWidgets.QDialog.Accepted
407 def lineedit(context, hint):
408 """Create a HintedLineEdit with a preset minimum width"""
409 widget = text.HintedLineEdit(context, hint)
410 width = qtutils.text_width(widget.font(), 'M')
411 widget.setMinimumWidth(width * 32)
412 return widget
415 class RemoteWidget(QtWidgets.QWidget):
416 name = property(
417 lambda self: get(self.remote_name),
418 lambda self, value: self.remote_name.set_value(value),
420 url = property(
421 lambda self: get(self.remote_url),
422 lambda self, value: self.remote_url.set_value(value),
424 valid = Signal(bool)
426 def __init__(self, context, parent, readonly_url=False):
427 super().__init__(parent)
428 self.setWindowModality(Qt.WindowModal)
429 self.context = context
430 self.setWindowTitle(N_('Add remote'))
431 self.remote_name = lineedit(context, N_('Name for the new remote'))
432 self.remote_url = lineedit(context, 'git://git.example.com/repo.git')
433 self.open_button = qtutils.create_button(
434 text=N_('Browse...'), icon=icons.folder(), tooltip=N_('Select repository')
437 self.url_layout = qtutils.hbox(
438 defs.no_margin, defs.spacing, self.remote_url, self.open_button
441 validate_remote = completion.RemoteValidator(self.remote_name)
442 self.remote_name.setValidator(validate_remote)
444 self._form = qtutils.form(
445 defs.margin,
446 defs.spacing,
447 (N_('Name'), self.remote_name),
448 (N_('URL'), self.url_layout),
451 self._layout = qtutils.vbox(defs.margin, defs.spacing, self._form)
452 self.setLayout(self._layout)
454 # pylint: disable=no-member
455 self.remote_name.textChanged.connect(self.validate)
456 self.remote_url.textChanged.connect(self.validate)
457 qtutils.connect_button(self.open_button, self.open_repo)
459 if readonly_url:
460 self.remote_url.setReadOnly(True)
461 self.open_button.setEnabled(False)
463 def validate(self, _text):
464 name = self.name
465 url = self.url
466 self.valid.emit(bool(name) and bool(url))
468 def open_repo(self):
469 git = self.context.git
470 repo = qtutils.opendir_dialog(N_('Open Git Repository'), core.getcwd())
471 if repo and git.is_git_repository(repo):
472 self.url = repo