Add a merge button to the uber-search gui
[ugit.git] / ugit / utilcontroller.py
blob5b5e0ee3f3b50a4bed6c7f1dc425d5fceb6996c7
1 #!/usr/bin/env python
2 import time
3 from PyQt4.QtGui import QDialog
4 from PyQt4.QtGui import QFont
5 import utils
6 import qtutils
7 from qobserver import QObserver
8 from views import BranchGUI
9 from views import CommitGUI
10 from views import OptionsGUI
11 from views import OutputGUI
12 from model import Model
14 def set_diff_font(model, widget):
15 if model.has_param('global_ugit_fontdiff'):
16 font = model.get_param('global_ugit_fontdiff')
17 if not font: return
18 qf = QFont()
19 qf.fromString(font)
20 widget.setFont(qf)
22 def choose_branch(title, parent, branches):
23 dlg = BranchGUI(parent,branches)
24 dlg.setWindowTitle(dlg.tr(title))
25 return dlg.get_selected()
27 def select_commits(model, parent, title, revs, summaries):
28 '''Use the CommitGUI to select commits from a list.'''
29 model = model.clone()
30 model.set_revisions(revs)
31 model.set_summaries(summaries)
32 view = CommitGUI(parent, title)
33 ctl = SelectCommitsController(model, view)
34 return ctl.select_commits()
36 class SelectCommitsController(QObserver):
37 def __init__(self, model, view):
38 QObserver.__init__(self, model, view)
39 set_diff_font(model, self.view.commit_text)
40 self.connect(view.commit_list, 'itemSelectionChanged()',
41 self.commit_sha1_selected )
43 def select_commits(self):
44 summaries = self.model.get_summaries()
45 if not summaries:
46 msg = self.tr('No commits exist in this branch.')
47 qtutils.show_output(msg)
48 return []
49 qtutils.set_items(self.view.commit_list, summaries)
50 self.view.show()
51 if self.view.exec_() != QDialog.Accepted:
52 return []
53 revs = self.model.get_revisions()
54 list_widget = self.view.commit_list
55 return qtutils.get_selection_list(list_widget, revs)
57 def commit_sha1_selected(self):
58 row, selected = qtutils.get_selected_row(self.view.commit_list)
59 if not selected:
60 self.view.commit_text.setText('')
61 self.view.revision.setText('')
62 return
64 # Get the sha1 and put it in the revision line
65 sha1 = self.model.get_revision_sha1(row)
66 self.view.revision.setText(sha1)
67 self.view.revision.selectAll()
69 # Lookup the sha1's commit
70 commit_diff = self.model.get_commit_diff(sha1)
71 self.view.commit_text.setText(commit_diff)
73 # Copy the sha1 into the clipboard
74 qtutils.set_clipboard(sha1)
76 def search_revisions(model, parent):
77 model = model.clone()
78 view = CommitGUI(parent, 'Search Revisions')
79 ctl = SearchRevisionsController(model, view)
80 view.show()
81 view.revision.setFocus()
82 return view.exec_() == QDialog.Accepted
84 class SearchRevisionsController(QObserver):
85 def __init__(self, model, view):
86 QObserver.__init__(self,model,view)
87 set_diff_font(model, self.view.commit_text)
89 self.add_observables('revision')
90 self.add_actions('revision', self.search_revisions)
91 self.connect(view.commit_list, 'itemSelectionChanged()',
92 self.select_summary)
93 self.connect(view.commit_text, 'cursorPositionChanged()',
94 self.select_summary)
95 self.connect(view.commit_list,
96 'itemDoubleClicked(QListWidgetItem*)',
97 self.doubleclick_format_patch)
98 self.last_time = time.time()
99 self.updates_enabled = True
100 self.found = []
101 (self.revisions, self.summaries) = \
102 self.model.log_helper(all=True)
104 def search_revisions(self, *rest):
105 if time.time() - self.last_time < 0.2:
106 self.last_time = time.time()
107 return
108 if not self.updates_enabled:
109 return
110 found = False
111 self.found = []
112 revision = self.model.get_revision()
113 if len(revision) < 2: return
114 num_found = 0
115 for idx, rev in enumerate(self.revisions):
116 if rev.startswith(revision):
117 self.found.append(idx)
119 self.view.commit_list.clear()
120 for idx in self.found:
121 summary = self.summaries[idx]
122 self.view.commit_list.addItem(summary)
124 def _get_sha1_from_gui_index(self, gui_idx):
125 if not self.found:
126 return None
127 idx = self.found[gui_idx]
128 summary = self.summaries[idx]
129 revision = self.revisions[idx]
130 return revision
132 def show_revision(self, gui_idx):
133 revision = self._get_sha1_from_gui_index(gui_idx)
134 qtutils.set_clipboard(revision)
135 diff = self.model.get_commit_diff(revision)
136 self.updates_enabled = False
137 self.view.commit_text.setText(diff)
138 self.updates_enabled = True
140 def select_summary(self,*args):
141 if not self.updates_enabled: return
142 row, selected = qtutils.get_selected_row(
143 self.view.commit_list)
144 if not selected or row >= len(self.found):
145 return
146 revision = self._get_sha1_from_gui_index(row)
147 if not revision:
148 return
149 # Inhibit doing a new search when we set the revision field
150 #+++ Disable updates
151 self.updates_enabled = False
152 if revision:
153 self.view.revision.setText(revision)
154 self.view.revision.selectAll()
155 self.view.revision.setFocus()
156 self.show_revision(row)
157 #+++ Enable updates
158 self.updates_enabled = True
160 def doubleclick_format_patch(self, item):
161 row = self.view.commit_list.row(item)
162 idx = self.found[row]
163 revision = self.revisions[idx]
164 output = self.model.format_patch_helper(revision)
165 qtutils.log(output, quiet=False, doraise=True)
167 def update_options(model, parent):
168 view = OptionsGUI(parent)
169 ctl = OptionsController(model,view)
170 view.show()
171 return view.exec_() == QDialog.Accepted
173 class OptionsController(QObserver):
174 '''ASSUMPTIONS:
175 This controller assumes that the view's widgets are named
176 the same as the model parameters.'''
178 def __init__(self,model,view):
180 # used for telling about interactive font changes
181 self.original_model = model
182 model = model.clone()
184 QObserver.__init__(self,model,view)
185 self.add_observables(
186 'local_user_email',
187 'local_user_name',
188 'local_merge_summary',
189 'local_merge_diffstat',
190 'local_merge_verbosity',
191 'local_gui_diffcontext',
192 'global_user_email',
193 'global_user_name',
194 'global_merge_summary',
195 'global_merge_diffstat',
196 'global_merge_verbosity',
197 'global_gui_diffcontext',
198 'global_ugit_fontdiff_size',
199 'global_ugit_fontdiff',
200 'global_ugit_fontui_size',
201 'global_ugit_fontui',
202 'global_ugit_historybrowser',
203 'global_ugit_savewindowsettings',
204 'global_ugit_saveatexit',
206 self.add_actions('global_ugit_fontdiff_size', self.update_size)
207 self.add_actions('global_ugit_fontui_size', self.update_size)
208 self.add_actions('global_ugit_fontdiff', self.tell_parent_model)
209 self.add_actions('global_ugit_fontui', self.tell_parent_model)
210 self.add_callbacks(save_button = self.save_settings)
211 self.connect(self.view, 'rejected()', self.restore_settings)
213 self.refresh_view()
214 self.backup_model = self.model.clone()
216 def refresh_view(self):
217 font = self.model.get_param('global_ugit_fontui')
218 if font:
219 size = int(font.split(',')[1])
220 self.view.global_ugit_fontui_size.setValue(size)
221 self.model.set_global_ugit_fontui_size(size)
222 fontui = QFont()
223 fontui.fromString(font)
224 self.view.global_ugit_fontui.setCurrentFont(fontui)
226 font = self.model.get_global_ugit_fontdiff()
227 if font:
228 size = int(font.split(',')[1])
229 self.view.global_ugit_fontdiff_size.setValue(size)
230 self.model.set_global_ugit_fontdiff_size(size)
231 fontdiff = QFont()
232 fontdiff.fromString(font)
233 self.view.global_ugit_fontdiff.setCurrentFont(fontdiff)
235 self.view.local_groupbox.setTitle(
236 unicode(self.tr('%s Repository'))
237 % self.model.get_project())
238 QObserver.refresh_view(self)
240 # save button
241 def save_settings(self):
242 params_to_save = []
243 params = self.model.get_config_params()
244 for param in params:
245 value = self.model.get_param(param)
246 backup = self.backup_model.get_param(param)
247 if value != backup:
248 params_to_save.append(param)
249 for param in params_to_save:
250 self.model.save_config_param(param)
252 self.original_model.copy_params(self.model, params_to_save)
253 self.view.done(QDialog.Accepted)
255 # cancel button -> undo changes
256 def restore_settings(self):
257 params = self.backup_model.get_config_params()
258 self.model.copy_params(self.backup_model, params)
259 self.tell_parent_model()
261 def tell_parent_model(self,*rest):
262 params= (
263 'global_ugit_fontdiff',
264 'global_ugit_fontui',
265 'global_ugit_fontdiff_size',
266 'global_ugit_fontui_size',
267 'global_ugit_savewindowsettings',
268 'global_ugit_saveatexit',
270 for param in params:
271 self.original_model.set_param(
272 param, self.model.get_param(param))
274 def update_size(self, *rest):
275 combo = self.view.global_ugit_fontui
276 param = str(combo.objectName())
277 default = str(combo.currentFont().toString())
278 self.model.apply_font_size(param, default)
280 combo = self.view.global_ugit_fontdiff
281 param = str(combo.objectName())
282 default = str(combo.currentFont().toString())
283 self.model.apply_font_size(param, default)
285 self.tell_parent_model()
287 def log_window(parent):
288 model = Model( search_text = '' )
289 view = OutputGUI(parent)
290 ctl = LogWindowController(model,view)
291 return view
293 class LogWindowController(QObserver):
294 def __init__(self, model, view):
295 QObserver.__init__(self, model, view)
297 self.add_observables('search_text')
298 self.add_actions('search_text', self.insta_search)
299 self.add_callbacks(
300 clear_button = self.clear,
301 next_button = self.next,
302 prev_button = self.prev,
304 self.connect(self.view.output_text,
305 'cursorPositionChanged()',
306 self.cursor_position_changed)
307 self.search_offset = 0
309 def insta_search(self,*rest):
310 self.search_offset = 0
311 txt = self.model.get_search_text().lower()
312 if len(txt.strip()):
313 self.next()
314 else:
315 cursor = self.view.output_text.textCursor()
316 cursor.clearSelection()
317 self.view.output_text.setTextCursor(cursor)
319 def clear(self):
320 self.view.output_text.clear()
321 self.search_offset = 0
323 def next(self):
324 text = self.model.get_search_text().lower().strip()
325 if not text: return
326 output = str(self.view.output_text.toPlainText())
327 if self.search_offset + len(text) > len(output):
328 answer = qtutils.question(
329 self.view,
330 unicode(self.tr("%s not found")) % text,
331 unicode(self.tr(
332 "Could not find '%s'.\n"
333 "Search from the beginning?"
334 )) % text,
335 default=False)
337 if answer:
338 self.search_offset = 0
339 else:
340 return
342 find_in = output[self.search_offset:].lower()
343 try:
344 index = find_in.index(text)
345 except:
346 self.search_offset = 0
347 answer = qtutils.question(
348 self.view,
349 unicode(self.tr("%s not found")) % text,
350 unicode(self.tr(
351 "Could not find '%s'.\n"
352 "Search from the beginning?"
353 )) % text,
354 default=False)
355 if answer:
356 self.next()
357 return
358 cursor = self.view.output_text.textCursor()
359 offset = self.search_offset + index
360 new_offset = offset + len(text)
362 cursor.setPosition(offset)
363 cursor.setPosition(new_offset, cursor.KeepAnchor)
365 self.view.output_text.setTextCursor(cursor)
366 self.search_offset = new_offset
368 def prev(self):
369 text = self.model.get_search_text().lower().strip()
370 if not text: return
371 output = str(self.view.output_text.toPlainText())
372 if self.search_offset == 0:
373 self.search_offset = len(output)
375 find_in = output[:self.search_offset].lower()
376 try:
377 offset = find_in.rindex(text)
378 except:
379 self.search_offset = 0
380 if qtutils.question(
381 self.view,
382 unicode(self.tr("%s not found")) % text,
383 unicode(self.tr("Could not find '%s'.\n"
384 "Search from the end?"
385 )) % text):
386 self.prev()
387 return
388 cursor = self.view.output_text.textCursor()
389 new_offset = offset + len(text)
391 cursor.setPosition(offset)
392 cursor.setPosition(new_offset, cursor.KeepAnchor)
394 self.view.output_text.setTextCursor(cursor)
395 self.search_offset = offset
397 def cursor_position_changed(self):
398 cursor = self.view.output_text.textCursor()
399 self.search_offset = cursor.selectionStart()