Simplified controllers by using qtutils.set_items/add_items
[ugit.git] / ugitlibs / controllers.py
blob755db781d9c93d77d977e520fe16463bfa597b75
1 #!/usr/bin/env python
2 import os
3 import commands
4 from PyQt4 import QtGui
5 from PyQt4 import QtCore
6 from PyQt4.QtGui import QDialog
7 from PyQt4.QtGui import QMessageBox
8 from PyQt4.QtGui import QMenu
9 from qobserver import QObserver
10 import cmds
11 import utils
12 import qtutils
13 import defaults
14 from views import GitPushDialog
15 from views import GitBranchDialog
16 from views import GitCreateBranchDialog
17 from views import GitCommitBrowser
18 from repobrowsercontroller import GitRepoBrowserController
19 from createbranchcontroller import GitCreateBranchController
20 from pushcontroller import GitPushController
22 class GitController(QObserver):
23 '''The controller is a mediator between the model and view.
24 It allows for a clean decoupling between view and model classes.'''
26 def __init__(self, model, view):
27 QObserver.__init__(self, model, view)
29 # The diff-display context menu
30 self.__menu = None
31 self.__staged_diff_in_view = True
33 # Diff display context menu
34 view.displayText.controller = self
35 view.displayText.contextMenuEvent = self.__menu_event
37 # Default to creating a new commit(i.e. not an amend commit)
38 view.newCommitRadio.setChecked(True)
40 # Binds a specific model attribute to a view widget,
41 # and vice versa.
42 self.model_to_view('commitmsg', 'commitText')
43 self.model_to_view('staged', 'stagedList')
44 self.model_to_view('all_unstaged', 'unstagedList')
46 # When a model attribute changes, this runs a specific action
47 self.add_actions('staged', self.action_staged)
48 self.add_actions('all_unstaged', self.action_all_unstaged)
50 # Routes signals for multiple widgets to our callbacks
51 # defined below.
52 self.add_signals('textChanged()', view.commitText)
53 self.add_signals('stateChanged(int)', view.untrackedCheckBox)
55 self.add_signals('released()',
56 view.stageButton, view.commitButton,
57 view.pushButton, view.signOffButton,)
59 self.add_signals('triggered()',
60 view.rescan,
61 view.createBranch, view.checkoutBranch,
62 view.rebaseBranch, view.deleteBranch,
63 view.setCommitMessage, view.commit,
64 view.stageChanged, view.stageUntracked,
65 view.stageSelected, view.unstageAll,
66 view.unstageSelected,
67 view.showDiffstat,
68 view.browseBranch, view.browseOtherBranch,
69 view.visualizeAll, view.visualizeCurrent,
70 view.exportPatches, view.cherryPick,
71 view.loadCommitMsg,
72 view.cut, view.copy, view.paste, view.delete,
73 view.selectAll, view.undo, view.redo,)
75 self.add_signals('itemClicked(QListWidgetItem *)',
76 view.stagedList, view.unstagedList,)
78 self.add_signals('itemSelectionChanged()',
79 view.stagedList, view.unstagedList,)
81 self.add_signals('splitterMoved(int,int)',
82 view.splitter_top, view.splitter_bottom)
84 # App cleanup
85 self.connect(QtGui.qApp, 'lastWindowClosed()',
86 self.last_window_closed)
88 # These callbacks are called in response to the signals
89 # defined above. One property of the QObserver callback
90 # mechanism is that the model is passed in as the first
91 # argument to the callback. This allows for a single
92 # controller to manage multiple models, though this
93 # isn't used at the moment.
94 self.add_callbacks({
95 # Actions that delegate directly to the model
96 'signOffButton': model.add_signoff,
97 'setCommitMessage': model.get_prev_commitmsg,
98 # Push Buttons
99 'stageButton': self.stage_selected,
100 'commitButton': self.commit,
101 'pushButton': self.push,
102 # List Widgets
103 'stagedList': self.diff_staged,
104 'unstagedList': self.diff_unstaged,
105 # Checkboxes
106 'untrackedCheckBox': self.rescan,
107 # Menu Actions
108 'rescan': self.rescan,
109 'createBranch': self.branch_create,
110 'deleteBranch': self.branch_delete,
111 'checkoutBranch': self.checkout_branch,
112 'rebaseBranch': self.rebase,
113 'commit': self.commit,
114 'stageChanged': self.stage_changed,
115 'stageUntracked': self.stage_untracked,
116 'stageSelected': self.stage_selected,
117 'unstageAll': self.unstage_all,
118 'unstageSelected': self.unstage_selected,
119 'showDiffstat': self.show_diffstat,
120 'browseBranch': self.browse_current,
121 'browseOtherBranch': self.browse_other,
122 'visualizeCurrent': self.viz_current,
123 'visualizeAll': self.viz_all,
124 'exportPatches': self.export_patches,
125 'cherryPick': self.cherry_pick,
126 'loadCommitMsg': self.load_commitmsg,
127 'cut': self.cut,
128 'copy': self.copy,
129 'paste': self.paste,
130 'delete': self.delete,
131 'selectAll': self.select_all,
132 'undo': self.view.commitText.undo,
133 'redo': self.redo,
134 # Splitters
135 'splitter_top': self.splitter_top_event,
136 'splitter_bottom': self.splitter_bottom_event,
139 # Handle double-clicks in the staged/unstaged lists.
140 # These are vanilla signal/slots since the qobserver
141 # signal routing is already handling these lists' signals.
142 self.connect(view.unstagedList,
143 'itemDoubleClicked(QListWidgetItem*)',
144 self.stage_selected)
146 self.connect(view.stagedList,
147 'itemDoubleClicked(QListWidgetItem*)',
148 self.unstage_selected )
150 # Delegate window move events here
151 self.view.moveEvent = self.move_event
152 self.view.resizeEvent = self.resize_event
154 # Initialize the GUI
155 self.__read_config_settings()
156 self.rescan()
158 # Setup the inotify watchdog
159 self.__start_inotify_thread()
161 #####################################################################
162 # Actions triggered during model updates
164 def action_staged(self, widget):
165 self.__update_listwidget(widget,
166 self.model.get_staged(), staged=True)
168 def action_all_unstaged(self, widget):
169 self.__update_listwidget(widget,
170 self.model.get_unstaged(), staged=False)
172 if self.view.untrackedCheckBox.isChecked():
173 self.__update_listwidget(widget,
174 self.model.get_untracked(),
175 append=True,
176 staged=False,
177 untracked=True)
179 #####################################################################
180 # Qt callbacks
182 def branch_create(self):
183 view = GitCreateBranchDialog(self.view)
184 controller = GitCreateBranchController(self.model, view)
185 view.show()
186 result = view.exec_()
187 if result == QDialog.Accepted:
188 self.rescan()
190 def branch_delete(self):
191 dlg = GitBranchDialog(self.view, branches=cmds.git_branch())
192 branch = dlg.getSelectedBranch()
193 if not branch: return
194 qtutils.show_command(self.view,
195 cmds.git_branch(name=branch, delete=True))
197 def browse_current(self):
198 self.__browse_branch(cmds.git_current_branch())
200 def browse_other(self):
201 # Prompt for a branch to browse
202 branches = self.model.all_branches()
203 dialog = GitBranchDialog(self.view, branches=branches)
205 # Launch the repobrowser
206 self.__browse_branch(dialog.getSelectedBranch())
208 def checkout_branch(self):
209 dlg = GitBranchDialog(self.view, cmds.git_branch())
210 branch = dlg.getSelectedBranch()
211 if not branch: return
212 qtutils.show_command(self.view, cmds.git_checkout(branch))
213 self.rescan()
215 def cherry_pick(self):
216 '''Starts a cherry-picking session.'''
217 (revs, summaries) = cmds.git_log(all=True)
218 selection, idxs = self.__select_commits(revs, summaries)
219 output = cmds.git_cherry_pick(selection)
220 self.__show_command(self.tr(output))
222 def commit(self):
223 '''Sets up data and calls cmds.commit.'''
224 msg = self.model.get_commitmsg()
225 if not msg:
226 error_msg = self.tr(""
227 + "Please supply a commit message.\n"
228 + "\n"
229 + "A good commit message has the following format:\n"
230 + "\n"
231 + "- First line: Describe in one sentence what you did.\n"
232 + "- Second line: Blank\n"
233 + "- Remaining lines: Describe why this change is good.\n")
235 self.__show_command(error_msg)
236 return
238 files = self.model.get_staged()
239 if not files:
240 errmsg = self.tr(""
241 + "No changes to commit.\n"
242 + "\n"
243 + "You must stage at least 1 file before you can commit.\n")
244 self.__show_command(errmsg)
245 return
247 # Perform the commit
248 output = cmds.git_commit(msg,
249 amend=self.view.amendRadio.isChecked())
251 # Reset state
252 self.view.newCommitRadio.setChecked(True)
253 self.view.amendRadio.setChecked(False)
254 self.model.set_commitmsg('')
255 self.__show_command(output)
257 def commit_sha1_selected(self, browser, revs):
258 '''This callback is called when a commit browser's
259 item is selected. This callback puts the current
260 revision sha1 into the commitText field.
261 This callback also puts shows the commit in the
262 browser's commit textedit and copies it into
263 the global clipboard/selection.'''
264 current = browser.commitList.currentRow()
265 item = browser.commitList.item(current)
266 if not item.isSelected():
267 browser.commitText.setText('')
268 browser.revisionLine.setText('')
269 return
271 # Get the commit's sha1 and put it in the revision line
272 sha1 = revs[current]
273 browser.revisionLine.setText(sha1)
274 browser.revisionLine.selectAll()
276 # Lookup the info for that sha1 and display it
277 commit_diff = cmds.git_show(sha1)
278 browser.commitText.setText(commit_diff)
280 # Copy the sha1 into the clipboard
281 qtutils.set_clipboard(sha1)
283 # use *rest to handle being called from different signals
284 def diff_staged(self, *rest):
285 self.__staged_diff_in_view = True
286 widget = self.view.stagedList
287 row, selected = qtutils.get_selected_row(widget)
289 if not selected:
290 self.__reset_display()
291 return
293 filename = self.model.get_staged()[row]
294 diff = cmds.git_diff(filename, cached=True)
296 if os.path.exists(filename):
297 self.__set_info(self.tr('Staged for commit'))
298 else:
299 self.__set_info(self.tr('Staged for removal'))
301 self.view.displayText.setText(diff)
303 # use *rest to handle being called from different signals
304 def diff_unstaged(self,*rest):
305 self.__staged_diff_in_view = False
306 widget = self.view.unstagedList
308 row, selected = qtutils.get_selected_row(widget)
309 if not selected:
310 self.__reset_display()
311 return
313 filename =(self.model.get_unstaged()
314 + self.model.get_untracked())[row]
315 if os.path.isdir(filename):
316 self.__set_info(self.tr('Untracked directory'))
317 cmd = 'ls -la %s' % utils.shell_quote(filename)
318 output = commands.getoutput(cmd)
319 self.view.displayText.setText(output )
320 return
322 if filename in self.model.get_unstaged():
323 diff = cmds.git_diff(filename, cached=False)
324 msg = diff
325 self.__set_info(self.tr('Modified, not staged'))
326 else:
327 # untracked file
328 cmd = 'file -b %s' % utils.shell_quote(filename)
329 file_type = commands.getoutput(cmd)
331 if 'binary' in file_type or 'data' in file_type:
332 sq_filename = utils.shell_quote(filename)
333 cmd = 'hexdump -C %s' % sq_filename
334 contents = commands.getoutput(cmd)
335 else:
336 if os.path.exists(filename):
337 file = open(filename, 'r')
338 contents = file.read()
339 file.close()
341 else: contents = ''
343 self.__set_info(self.tr('Untracked, not staged')
344 + ': ' + file_type)
345 msg = contents
347 self.view.displayText.setText(msg)
349 def copy_display(self):
350 cursor = self.view.displayText.textCursor()
351 selection = cursor.selection().toPlainText()
352 qtutils.set_clipboard(selection)
354 def export_patches(self):
355 '''Launches the commit browser and exports the selected
356 patches.'''
358 (revs, summaries) = cmds.git_log()
359 selection, idxs = self.__select_commits(revs, summaries)
360 if not selection: return
362 # now get the selected indices to determine whether
363 # a range of consecutive commits were selected
364 selected_range = range(idxs[0], idxs[-1] + 1)
365 export_range = len(idxs) > 1 and idxs == selected_range
367 output = cmds.git_format_patch(selection, export_range)
368 self.__show_command(output)
370 def get_commit_msg(self):
371 self.model.retrieve_latest_commitmsg()
373 def last_window_closed(self):
374 '''Save config settings and cleanup the any inotify threads.'''
376 self.__save_config_settings()
378 if not self.inotify_thread: return
379 if not self.inotify_thread.isRunning(): return
381 self.inotify_thread.abort = True
382 self.inotify_thread.quit()
383 self.inotify_thread.wait()
385 def load_commitmsg(self):
386 file = qtutils.open_dialog(self.view,
387 self.tr('Load Commit Message...'),
388 defaults.DIRECTORY)
390 if file:
391 defaults.DIRECTORY = os.path.dirname(file)
392 slushy = utils.slurp(file)
393 self.model.set_commitmsg(slushy)
396 def rebase(self):
397 dlg = GitBranchDialog(self.view, cmds.git_branch())
398 dlg.setWindowTitle("Select the current branch's new root")
399 branch = dlg.getSelectedBranch()
400 if not branch: return
401 qtutils.show_command(self.view, cmds.git_rebase(branch))
403 # use *rest to handle being called from the checkbox signal
404 def rescan(self, *rest):
405 '''Populates view widgets with results from "git status."'''
407 self.view.statusBar().showMessage(
408 self.tr('Scanning for modified files ...'))
410 # Rescan for repo updates
411 self.model.update_status()
413 # Scan for branch changes
414 self.__set_branch_ui_items()
416 if not self.model.has_squash_msg(): return
418 if self.model.get_commitmsg():
419 answer = qtutils.question(self.view,
420 self.tr('Import Commit Message?'),
421 self.tr('A commit message from an in-progress'
422 + ' merge was found.\nImport it?'))
424 if not answer: return
426 # Set the new commit message
427 self.model.set_commitmsg(self.model.get_squash_msg())
429 def push(self):
430 model = self.model.clone()
431 view = GitPushDialog(self.view)
432 controller = GitPushController(model,view)
433 view.show()
434 view.exec_()
436 def cut(self):
437 self.copy()
438 self.delete()
440 def copy(self):
441 cursor = self.view.commitText.textCursor()
442 selection = cursor.selection().toPlainText()
443 qtutils.set_clipboard(selection)
445 def paste(self): self.view.commitText.paste()
446 def undo(self): self.view.commitText.undo()
447 def redo(self): self.view.commitText.redo()
448 def select_all(self): self.view.commitText.selectAll()
449 def delete(self):
450 self.view.commitText.textCursor().removeSelectedText()
452 def show_diffstat(self):
453 '''Show the diffstat from the latest commit.'''
454 self.__show_command(cmds.git_diff_stat(), rescan=False)
457 #####################################################################
458 # diff gui
460 def __diff_selection(self):
461 cursor = self.view.displayText.textCursor()
462 offset = cursor.position()
463 selection = cursor.selection().toPlainText()
464 num_selected_lines = selection.count(os.linesep)
465 return offset, selection
467 def process_diff_selection(self,
468 items, widget,
469 cached=True,
470 selected=False,
471 reverse=True,
472 noop=False):
473 filename = qtutils.get_selected_item(widget, items)
474 if not filename: return
476 header, diff = cmds.git_diff(filename,
477 with_diff_header=True,
478 cached=cached,
479 reverse=cached)
481 parser = utils.DiffParser(header,diff)
483 header_fwd, diff_fwd = cmds.git_diff(filename,
484 with_diff_header=True,
485 cached=cached,
486 reverse=False)
488 offset, selection = self.__diff_selection()
489 if selection:
490 start = diff_fwd.index(selection)
491 end = start + len(selection)
492 parser.set_diffs_to_range(start, end)
493 else:
494 parser.set_diff_to_offset(offset)
495 selected = False
497 if not parser.diffs: return
499 if selected:
500 for diff in parser.selected:
501 contents = parser.get_diff_subset(diff, start, end)
502 if contents:
503 tmpfile = utils.get_tmp_filename()
504 utils.write(tmpfile, contents)
505 os.unlink(tmpfile)
506 else:
507 for idx, diff in enumerate(parser.diffs):
508 tmpfile = utils.get_tmp_filename()
509 if parser.write_diff(tmpfile,idx):
510 self.model.apply_diff(tmpfile)
511 os.unlink(tmpfile)
512 self.rescan()
514 def stage_hunk(self):
515 self.process_diff_selection(
516 self.model.get_unstaged(),
517 self.view.unstagedList,
518 cached=False)
520 def stage_hunks(self):
521 self.process_diff_selection(
522 self.model.get_unstaged(),
523 self.view.unstagedList,
524 cached=False,
525 selected=True)
527 def unstage_hunk(self, cached=True):
528 self.process_diff_selection(
529 self.model.get_staged(),
530 self.view.stagedList,
531 cached=True)
533 def unstage_hunks(self):
534 self.process_diff_selection(
535 self.model.get_staged(),
536 self.view.stagedList,
537 cached=True,
538 selected=True)
540 # #######################################################################
541 # end diff gui
543 def stage_changed(self):
544 '''Stage all changed files for commit.'''
545 output = cmds.git_add(self.model.get_unstaged())
546 self.__show_command(output)
548 def stage_untracked(self):
549 '''Stage all untracked files for commit.'''
550 output = cmds.git_add(self.model.get_untracked())
551 self.__show_command(output)
553 # use *rest to handle being called from different signals
554 def stage_selected(self,*rest):
555 '''Use "git add" to add items to the git index.
556 This is a thin wrapper around __apply_to_list.'''
557 command = cmds.git_add_or_remove
558 widget = self.view.unstagedList
559 items = self.model.get_all_unstaged()
560 self.__show_command(self.__apply_to_list(command,widget,items))
562 # use *rest to handle being called from different signals
563 def unstage_selected(self, *rest):
564 '''Use "git reset" to remove items from the git index.
565 This is a thin wrapper around __apply_to_list.'''
566 command = cmds.git_reset
567 widget = self.view.stagedList
568 items = self.model.get_staged()
569 self.__show_command(self.__apply_to_list(command, widget, items))
571 def unstage_all(self):
572 '''Use "git reset" to remove all items from the git index.'''
573 output = cmds.git_reset(self.model.get_staged())
574 self.__show_command(output)
576 def viz_all(self):
577 '''Visualizes the entire git history using gitk.'''
578 utils.fork('gitk','--all')
580 def viz_current(self):
581 '''Visualizes the current branch's history using gitk.'''
582 utils.fork('gitk', cmds.git_current_branch())
584 # These actions monitor window resizes, splitter changes, etc.
585 def move_event(self, event):
586 defaults.X = event.pos().x()
587 defaults.Y = event.pos().y()
589 def resize_event(self, event):
590 defaults.WIDTH = event.size().width()
591 defaults.HEIGHT = event.size().height()
593 def splitter_top_event(self,*rest):
594 sizes = self.view.splitter_top.sizes()
595 defaults.SPLITTER_TOP_0 = sizes[0]
596 defaults.SPLITTER_TOP_1 = sizes[1]
598 def splitter_bottom_event(self,*rest):
599 sizes = self.view.splitter_bottom.sizes()
600 defaults.SPLITTER_BOTTOM_0 = sizes[0]
601 defaults.SPLITTER_BOTTOM_1 = sizes[1]
603 #####################################################################
606 def __apply_to_list(self, command, widget, items):
607 '''This is a helper method that retrieves the current
608 selection list, applies a command to that list,
609 displays a dialog showing the output of that command,
610 and calls rescan to pickup changes.'''
611 apply_items = qtutils.get_selection_list(widget, items)
612 output = command(apply_items)
613 self.rescan()
614 return output
616 def __browse_branch(self, branch):
617 if not branch: return
618 # Clone the model to allow opening multiple browsers
619 # with different sets of data
620 model = self.model.clone()
621 model.set_branch(branch)
622 view = GitCommitBrowser()
623 controller = GitRepoBrowserController(model, view)
624 view.show()
625 view.exec_()
627 def __menu_about_to_show(self):
629 unstaged_item = qtutils.get_selected_item(
630 self.view.unstagedList,
631 self.model.get_all_unstaged())
633 is_tracked= unstaged_item not in self.model.get_untracked()
635 enable_staged= (
636 unstaged_item and
637 not self.__staged_diff_in_view
638 and is_tracked)
640 enable_unstaged= (
641 self.__staged_diff_in_view
642 and qtutils.get_selected_item(
643 self.view.stagedList,
644 self.model.get_staged()))
646 self.__stage_hunk_action.setEnabled(bool(enable_staged))
647 self.__stage_hunks_action.setEnabled(bool(enable_staged))
649 self.__unstage_hunk_action.setEnabled(bool(enable_unstaged))
650 self.__unstage_hunks_action.setEnabled(bool(enable_unstaged))
652 def __menu_event(self, event):
653 self.__menu_setup()
654 textedit = self.view.displayText
655 self.__menu.exec_(textedit.mapToGlobal(event.pos()))
657 def __menu_setup(self):
658 if self.__menu: return
660 menu = self.__menu = QMenu(self.view)
661 self.__stage_hunk_action = menu.addAction(
662 self.tr('Stage Hunk For Commit'),
663 self.stage_hunk)
665 self.__stage_hunks_action = menu.addAction(
666 self.tr('Stage Selected Lines'),
667 self.stage_hunks)
669 self.__unstage_hunk_action = menu.addAction(
670 self.tr('Unstage Hunk From Commit'),
671 self.unstage_hunk)
673 self.__unstage_hunks_action = menu.addAction(
674 self.tr('Unstage Selected Lines'),
675 self.unstage_hunks)
677 self.__copy_action = menu.addAction(
678 self.tr('Copy'),
679 self.copy_display)
681 self.connect(self.__menu, 'aboutToShow()', self.__menu_about_to_show)
683 def __file_to_widget_item(self, filename, staged, untracked=False):
684 '''Given a filename, return a QListWidgetItem suitable
685 for adding to a QListWidget. "staged" controls whether
686 to use icons for the staged or unstaged list widget.'''
688 if staged:
689 icon_file = utils.get_staged_icon(filename)
690 elif untracked:
691 icon_file = utils.get_untracked_icon()
692 else:
693 icon_file = utils.get_icon(filename)
695 return qtutils.create_listwidget_item(filename, icon_file)
697 def __read_config_settings(self):
698 (w,h,x,y,
699 st0,st1,
700 sb0,sb1) = utils.parse_geom(cmds.git_config('ugit.geometry'))
701 self.view.resize(w,h)
702 self.view.move(x,y)
703 self.view.splitter_top.setSizes([st0,st1])
704 self.view.splitter_bottom.setSizes([sb0,sb1])
706 def __save_config_settings(self):
707 cmds.git_config('ugit.geometry', utils.get_geom())
709 def __select_commits(self, revs, summaries):
710 '''Use the GitCommitBrowser to select commits from a list.'''
711 if not summaries:
712 msg = self.tr('ERROR: No commits exist in this branch.')
713 self.__show_command(msg)
714 return([],[])
716 browser = GitCommitBrowser(self.view)
717 self.connect(browser.commitList,
718 'itemSelectionChanged()',
719 lambda: self.commit_sha1_selected(
720 browser, revs) )
722 qtutils.set_items(browser.commitList, summaries)
724 browser.show()
725 result = browser.exec_()
726 if result != QDialog.Accepted:
727 return([],[])
729 list_widget = browser.commitList
730 selection = qtutils.get_selection_list(list_widget, revs)
731 if not selection: return([],[])
733 # also return the selected index numbers
734 index_nums = range(len(revs))
735 idxs = qtutils.get_selection_list(list_widget, index_nums)
737 return(selection, idxs)
739 def __set_branch_ui_items(self):
740 '''Sets up items that mention the current branch name.'''
741 branch = cmds.git_current_branch()
743 status_text = self.tr('Current Branch:') + ' ' + branch
744 self.view.statusBar().showMessage(status_text)
746 project = self.model.get_project()
747 title = '%s [%s]' % ( project, branch )
749 self.view.setWindowTitle(title)
751 def __reset_display(self):
752 self.view.displayText.setText('')
753 self.__set_info('')
755 def __set_info(self,text):
756 self.view.displayLabel.setText(text)
758 def __start_inotify_thread(self):
759 # Do we have inotify? If not, return.
760 # Recommend installing inotify if we're on Linux.
761 self.inotify_thread = None
762 try:
763 from inotify import GitNotifier
764 except ImportError:
765 import platform
766 if platform.system() == 'Linux':
767 msg =(self.tr('Unable import pyinotify.\n'
768 + 'inotify support has been'
769 + 'disabled.')
770 + '\n\n')
772 plat = platform.platform().lower()
773 if 'debian' in plat or 'ubuntu' in plat:
774 msg += (self.tr('Hint:')
775 + 'sudo apt-get install'
776 + ' python-pyinotify')
778 qtutils.information(self.view,
779 self.tr('inotify disabled'), msg)
780 return
782 self.inotify_thread = GitNotifier(os.getcwd())
783 self.connect(self.inotify_thread,
784 'timeForRescan()', self.rescan)
786 # Start the notification thread
787 self.inotify_thread.start()
789 def __show_command(self, output, rescan=True):
790 '''Shows output and optionally rescans for changes.'''
791 qtutils.show_command(self.view, output)
792 if rescan: self.rescan()
794 def __update_listwidget(self, widget, items,
795 staged, untracked=False, append=False):
796 '''Populate a QListWidget with the custom icon items.'''
797 if not append: widget.clear()
798 qtutils.add_items( widget,
799 [ self.__file_to_widget_item(i, staged, untracked)
800 for i in items ])