widgets.editremotes: Add a GUI for editing remotes
[git-cola.git] / cola / widgets / editremotes.py
blobd1c2b1727db3fcd94f6295ecab8f5420d5f844c3
1 from PyQt4 import QtCore
2 from PyQt4 import QtGui
3 from PyQt4.QtCore import Qt
4 from PyQt4.QtCore import SIGNAL
6 import cola.app
7 from cola import core
8 from cola import prefs
9 from cola import qtutils
10 from cola import gitcfg
11 from cola.git import git
12 from cola.widgets import defs
13 from cola.widgets import text
16 class RemoteEditor(QtGui.QDialog):
17 def __init__(self, parent):
18 super(RemoteEditor, self).__init__(parent)
20 self.setWindowTitle('Edit Remotes')
21 self.setWindowModality(Qt.WindowModal)
23 self.default_hint = (''
24 'You can add and remote remote git repositories\n'
25 'using the Add(+) and Delete(-) buttons on the left side.')
27 self.remote_list = []
28 self.remotes = QtGui.QListWidget()
29 self.remotes.setToolTip(self.tr(
30 'Remote git repositories - double-click to rename'))
32 self.info = text.HintedTextEdit(self.default_hint)
33 self.info.setReadOnly(True)
34 self.info.enable_hint(True)
35 self.info.setFont(prefs.diff_font())
36 font = self.info.font()
37 metrics = QtGui.QFontMetrics(font)
38 width = metrics.width('_' * 72)
39 height = metrics.height() * 13
40 self.info.setMinimumWidth(width)
41 self.info.setMinimumHeight(height)
42 self.info_thread = RemoteInfoThread(self)
44 self.cfg = gitcfg.instance()
45 self.add_btn = QtGui.QToolButton()
46 self.add_btn.setIcon(qtutils.icon('add.svg'))
47 self.add_btn.setToolTip(self.tr('Add new remote git repository'))
49 self.refresh_btn = QtGui.QToolButton()
50 self.refresh_btn.setIcon(qtutils.icon('view-refresh.svg'))
51 self.refresh_btn.setToolTip(self.tr('Refresh'))
53 self.delete_btn = QtGui.QToolButton()
54 self.delete_btn.setIcon(qtutils.icon('remove.svg'))
55 self.delete_btn.setToolTip(self.tr('Delete remote'))
57 self.close_btn = QtGui.QPushButton(self.tr('Close'))
59 self._top_layout = QtGui.QSplitter()
60 self._top_layout.setOrientation(Qt.Horizontal)
61 self._top_layout.setHandleWidth(defs.handle_width)
62 self._top_layout.addWidget(self.remotes)
63 self._top_layout.addWidget(self.info)
64 width = self._top_layout.width()
65 self._top_layout.setSizes([width/4, width*3/4])
67 self._button_layout = QtGui.QHBoxLayout()
68 self._button_layout.addWidget(self.add_btn)
69 self._button_layout.addWidget(self.delete_btn)
70 self._button_layout.addWidget(self.refresh_btn)
71 self._button_layout.addStretch()
72 self._button_layout.addWidget(self.close_btn)
74 self._layout = QtGui.QVBoxLayout()
75 self._layout.setMargin(defs.margin)
76 self._layout.setSpacing(defs.spacing)
77 self._layout.addWidget(self._top_layout)
78 self._layout.addLayout(self._button_layout)
79 self.setLayout(self._layout)
81 self.refresh()
83 qtutils.connect_button(self.add_btn, self.add)
84 qtutils.connect_button(self.delete_btn, self.delete)
85 qtutils.connect_button(self.refresh_btn, self.refresh)
86 qtutils.connect_button(self.close_btn, self.close)
88 self.connect(self.info_thread, SIGNAL('info'),
89 self.info.set_value)
91 self.connect(self.remotes,
92 SIGNAL('itemChanged(QListWidgetItem*)'),
93 self.remote_renamed)
95 self.connect(self.remotes, SIGNAL('itemSelectionChanged()'),
96 self.selection_changed)
98 def refresh(self):
99 prefix = len('remote.')
100 suffix = len('.url')
101 remote_urls = self.cfg.find('remote.*.url')
102 remotes = [k[prefix:-suffix] for k in sorted(remote_urls.keys())]
103 self.remotes.clear()
104 self.remotes.addItems(remotes)
105 self.remote_list = remotes
106 for idx, r in enumerate(remotes):
107 item = self.remotes.item(idx)
108 item.setFlags(item.flags() | Qt.ItemIsEditable)
110 def add(self):
111 widget = AddRemoteWidget(self)
112 if not widget.add_remote():
113 return
114 name = widget.name.value()
115 url = widget.url.value()
116 status, out = git.remote('add', name, url,
117 with_status=True, with_stderr=True)
118 if status != 0:
119 qtutils.critical('Error creating remote "%s"' % name, out)
120 self.refresh()
122 def delete(self):
123 remote = qtutils.selected_item(self.remotes, self.remote_list)
124 if remote is None:
125 return
127 title = 'Delete Remote'
128 question = 'Delete remote?'
129 info = unicode(self.tr('Delete remote "%s"')) % remote
130 ok_btn = 'Delete'
131 if not qtutils.confirm(title, question, info, ok_btn):
132 return
134 status, out = git.remote('rm', remote,
135 with_status=True, with_stderr=True)
136 if status != 0:
137 qtutils.critical('Error deleting remote "%s"' % remote, out)
138 cola.model().update_status()
140 self.info.set_hint(self.default_hint)
141 self.info.enable_hint(True)
142 self.refresh()
144 def remote_renamed(self, item):
145 idx = self.remotes.row(item)
146 if idx < 0:
147 return
148 if idx >= len(self.remote_list):
149 return
151 old_name = self.remote_list[idx]
152 new_name = unicode(item.text())
153 if new_name == old_name:
154 return
155 if not new_name:
156 item.setText(old_name)
157 return
159 title = 'Rename Remote'
160 question = 'Rename remote?'
161 info = unicode(self.tr(
162 'Rename remote "%s" to "%s"?')) % (old_name, new_name)
163 ok_btn = 'Rename'
165 if qtutils.confirm(title, question, info, ok_btn):
166 git.remote('rename', old_name, new_name)
167 self.remote_list[idx] = new_name
168 else:
169 item.setText(old_name)
171 def selection_changed(self):
172 remote = qtutils.selected_item(self.remotes, self.remote_list)
173 if remote is None:
174 return
175 self.info.set_hint('Gathering info for "%s"...' % remote)
176 self.info.enable_hint(True)
178 self.info_thread.remote = remote
179 self.info_thread.start()
182 class RemoteInfoThread(QtCore.QThread):
183 def __init__(self, parent):
184 super(RemoteInfoThread, self).__init__(parent)
185 self.remote = None
187 def run(self):
188 remote = self.remote
189 if remote is None:
190 return
191 status, out = git.remote('show', remote,
192 with_stderr=True, with_status=True)
193 out = core.decode(out)
194 # This call takes a long time and we may have selected a
195 # different remote...
196 if remote == self.remote:
197 self.emit(SIGNAL('info'), out)
198 else:
199 self.run()
202 class AddRemoteWidget(QtGui.QDialog):
203 def __init__(self, parent):
204 super(AddRemoteWidget, self).__init__(parent)
205 self.setWindowModality(Qt.WindowModal)
207 self.add_btn = QtGui.QPushButton(self.tr('Add Remote'))
208 self.add_btn.setIcon(qtutils.apply_icon())
210 self.cancel_btn = QtGui.QPushButton(self.tr('Cancel'))
212 diff_font = prefs.diff_font()
213 metrics = QtGui.QFontMetrics(diff_font)
215 def lineedit(hint):
216 widget = text.HintedLineEdit(hint)
217 widget.setFont(diff_font)
218 widget.enable_hint(True)
219 widget.setMinimumWidth(metrics.width('_' * 32))
220 return widget
222 self.name = lineedit('Name for the new remote')
223 self.url = lineedit('git://git.example.com/repo.git')
225 self._form = QtGui.QFormLayout()
226 self._form.setMargin(defs.margin)
227 self._form.setSpacing(defs.spacing)
228 self._form.addRow(self.tr('Name'), self.name)
229 self._form.addRow(self.tr('URL'), self.url)
231 self._btn_layout = QtGui.QHBoxLayout()
232 self._btn_layout.setMargin(0)
233 self._btn_layout.setSpacing(defs.button_spacing)
234 self._btn_layout.addStretch()
235 self._btn_layout.addWidget(self.add_btn)
236 self._btn_layout.addWidget(self.cancel_btn)
238 self._layout = QtGui.QVBoxLayout()
239 self._layout.setMargin(defs.margin)
240 self._layout.setSpacing(defs.margin)
241 self._layout.addLayout(self._form)
242 self._layout.addLayout(self._btn_layout)
243 self.setLayout(self._layout)
245 self.connect(self.name, SIGNAL('textChanged(QString)'),
246 self.validate)
248 self.connect(self.url, SIGNAL('textChanged(QString)'),
249 self.validate)
251 self.add_btn.setEnabled(False)
253 qtutils.connect_button(self.add_btn, self.accept)
254 qtutils.connect_button(self.cancel_btn, self.reject)
256 def validate(self, dummy_text):
257 name = self.name.value()
258 url = self.url.value()
259 self.add_btn.setEnabled(bool(name) and bool(url))
261 def add_remote(self):
262 self.show()
263 self.raise_()
264 return self.exec_() == QtGui.QDialog.Accepted
267 def edit():
268 window = RemoteEditor(qtutils.active_window())
269 window.show()
270 window.raise_()
271 window.exec_()
272 return window
274 if __name__ == '__main__':
275 app = cola.app.ColaApplication([])
276 edit()