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