Merge lp:~garyvdm/qbzr/newwtlist into lp:~qbzr-dev/qbzr/trunk

Proposed by Gary van der Merwe
Status: Merged
Merged at revision: not available
Proposed branch: lp:~garyvdm/qbzr/newwtlist
Merge into: lp:~qbzr-dev/qbzr/trunk
Diff against target: None lines
To merge this branch: bzr merge lp:~garyvdm/qbzr/newwtlist

This proposal supersedes a proposal from 2009-07-12.

Commit message

qcommit/qadd/qrevert: Replace old wtlist with the new TreeWidget from qbrowse.

To post a comment you must log in.
Revision history for this message
Gary van der Merwe (garyvdm) wrote : Posted in a previous version of this proposal

This replaces wtlist with treewidget in qcommit, qadd, and qrevert.

To do this I have added a mode to the treewidget called changes_mode. This only show changes, and not the whole tree. Like the old wtlist, changes from subdirs are shown in the root, e.g.

In normal mode, you would see:
Dir1
  File1 Modified

but in changes_mode, you will see:
Dir1/File1 Modified

Unless a parent directory is shown, which may happen if you have a unversioned, added, or renamed directory.

e.g. in changes_mode, you will get:

dir1 => dir2 Renamed
  File3 Modified

dir4 Added
  file5 Added

Drive by improvement: qcommit now adjusts the auto complete word list if you change which files are selected in the file list.

Revision history for this message
Gary van der Merwe (garyvdm) wrote : Posted in a previous version of this proposal
Revision history for this message
Gary van der Merwe (garyvdm) wrote : Posted in a previous version of this proposal

I just realised that this does not honer the selected list passed from the command line. I need to fix that.

Revision history for this message
Gary van der Merwe (garyvdm) wrote : Posted in a previous version of this proposal

The above mentioned problem has now been fixed.

Revision history for this message
Gary van der Merwe (garyvdm) wrote :

This replaces wtlist with treewidget in qcommit, qadd, and qrevert.

To do this I have added a mode to the treewidget called changes_mode. This only
show changes, and not the whole tree. Like the old wtlist, changes from subdirs
are shown in the root, e.g.

In normal mode, you would see:
Dir1
  File1 Modified

but in changes_mode, you will see:
Dir1/File1 Modified

Unless a parent directory is shown, which may happen if you have a unversioned,
added, or renamed directory.

e.g. in changes_mode, you will get:

dir1 => dir2 Renamed
  File3 Modified

dir4 Added
  file5 Added

Drive by improvement: qcommit now adjusts the auto complete word list if you
change which files are selected in the file list.

Some screen shots:

http://garyvdm.googlepages.com/qcommit_new_widget.png
http://garyvdm.googlepages.com/qadd_new_widget.png

lp:~garyvdm/qbzr/newwtlist updated
816. By Gary van der Merwe

Merge Javier's qsend dialog.

817. By Gary van der Merwe

qbrowse: Don't check for is_ignored on the tree root.

818. By Gary van der Merwe

qcommit: Show spelling suggestions in the message box context menu.

819. By Gary van der Merwe

Ext diff - Don't pass old revspec if none was provided for working trees.

820. By Gary van der Merwe

Change qcommit, qadd, and qrevert to use new TreeWidget. Remove lib/wtlist.py.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/add.py'
--- lib/add.py 2009-06-11 21:21:01 +0000
+++ lib/add.py 2009-07-14 03:00:21 +0000
@@ -23,11 +23,16 @@
2323
24from bzrlib.plugins.qbzr.lib.i18n import gettext24from bzrlib.plugins.qbzr.lib.i18n import gettext
25from bzrlib.plugins.qbzr.lib.subprocess import SubProcessDialog25from bzrlib.plugins.qbzr.lib.subprocess import SubProcessDialog
26from bzrlib.plugins.qbzr.lib.wtlist import (26from bzrlib.plugins.qbzr.lib.treewidget import (
27 ChangeDesc,27 TreeWidget,
28 WorkingTreeFileList,28 SelectAllCheckBox,
29 closure_in_selected_list,29 )
30 )30from bzrlib.plugins.qbzr.lib.util import (
31 ThrobberWidget,
32 runs_in_loading_queue,
33 )
34from bzrlib.plugins.qbzr.lib.uifactory import ui_current_widget
35from bzrlib.plugins.qbzr.lib.trace import reports_exception
3136
3237
33class AddWindow(SubProcessDialog):38class AddWindow(SubProcessDialog):
@@ -46,22 +51,36 @@
46 hide_progress=True,51 hide_progress=True,
47 )52 )
48 53
54 self.throbber = ThrobberWidget(self)
55
49 # Display the list of unversioned files56 # Display the list of unversioned files
50 groupbox = QtGui.QGroupBox(gettext("Unversioned Files"), self)57 groupbox = QtGui.QGroupBox(gettext("Unversioned Files"), self)
51 vbox = QtGui.QVBoxLayout(groupbox)58 vbox = QtGui.QVBoxLayout(groupbox)
5259
53 self.filelist = WorkingTreeFileList(groupbox, self.tree)60 self.filelist = TreeWidget(groupbox)
61 self.filelist.throbber = self.throbber
62 self.filelist.tree_model.is_item_in_select_all = lambda item: (
63 item.change is not None and
64 item.change.is_ignored() is None and
65 not item.change.is_versioned())
66
67 def filter_context_menu():
68 self.filelist.action_open_file.setEnabled(True)
69 self.filelist.action_open_file.setVisible(True)
70 self.filelist.action_show_file.setVisible(False)
71 self.filelist.action_show_annotate.setVisible(False)
72 self.filelist.action_show_log.setVisible(False)
73 self.filelist.action_show_diff.setVisible(False)
74 self.filelist.action_add.setVisible(False)
75 self.filelist.action_revert.setVisible(False)
76 self.filelist.filter_context_menu = filter_context_menu
77
54 vbox.addWidget(self.filelist)78 vbox.addWidget(self.filelist)
55 self.filelist.sortItems(0, QtCore.Qt.AscendingOrder)
56 self.filelist.setup_actions()
57 79
58 selectall_checkbox = QtGui.QCheckBox(80 selectall_checkbox = SelectAllCheckBox(self.filelist, groupbox)
59 gettext(self.filelist.SELECTALL_MESSAGE),
60 groupbox)
61 vbox.addWidget(selectall_checkbox)81 vbox.addWidget(selectall_checkbox)
62 selectall_checkbox.setCheckState(QtCore.Qt.Checked)82 selectall_checkbox.setCheckState(QtCore.Qt.Checked)
63 selectall_checkbox.setEnabled(True)83 selectall_checkbox.setEnabled(True)
64 self.filelist.set_selectall_checkbox(selectall_checkbox)
6584
66 self.show_ignored_checkbox = QtGui.QCheckBox(85 self.show_ignored_checkbox = QtGui.QCheckBox(
67 gettext("Show ignored files"),86 gettext("Show ignored files"),
@@ -69,12 +88,6 @@
69 vbox.addWidget(self.show_ignored_checkbox)88 vbox.addWidget(self.show_ignored_checkbox)
70 self.connect(self.show_ignored_checkbox, QtCore.SIGNAL("toggled(bool)"), self.show_ignored)89 self.connect(self.show_ignored_checkbox, QtCore.SIGNAL("toggled(bool)"), self.show_ignored)
71 90
72 self.tree.lock_read()
73 try:
74 self.filelist.fill(self.iter_changes_and_state())
75 finally:
76 self.tree.unlock()
77
78 # groupbox gets disabled as we are executing.91 # groupbox gets disabled as we are executing.
79 QtCore.QObject.connect(self,92 QtCore.QObject.connect(self,
80 QtCore.SIGNAL("subprocessStarted(bool)"),93 QtCore.SIGNAL("subprocessStarted(bool)"),
@@ -88,44 +101,39 @@
88 self.restoreSplitterSizes([150, 150])101 self.restoreSplitterSizes([150, 150])
89102
90 layout = QtGui.QVBoxLayout(self)103 layout = QtGui.QVBoxLayout(self)
104 layout.addWidget(self.throbber)
91 layout.addWidget(self.splitter)105 layout.addWidget(self.splitter)
92 layout.addWidget(self.buttonbox)106 layout.addWidget(self.buttonbox)
93107 self.throbber.show()
94 def iter_changes_and_state(self):108
95 """An iterator for the WorkingTreeFileList widget"""109
96110 def show(self):
97 in_selected_list = closure_in_selected_list(self.initial_selected_list)111 SubProcessDialog.show(self)
98112 QtCore.QTimer.singleShot(1, self.initial_load)
99 show_ignored = self.show_ignored_checkbox.isChecked()113
100114 @runs_in_loading_queue
101 for desc in self.tree.iter_changes(self.tree.basis_tree(),115 @ui_current_widget
102 want_unversioned=True):116 @reports_exception()
103117 def initial_load(self):
104 desc = ChangeDesc(desc)118 self.filelist.tree_model.checkable = True
105 if desc.is_versioned():119 fmodel = self.filelist.tree_filter_model
106 continue120 fmodel.setFilter(fmodel.CHANGED, False)
107121 self.filelist.set_tree(self.tree, changes_mode = True,
108 pit = desc.path()122 initial_checked_paths=self.initial_selected_list)
109 visible = show_ignored or not self.tree.is_ignored(pit)123 self.throbber.hide()
110 check_state = visible and in_selected_list(pit)
111 yield desc, visible, check_state
112124
113 def start(self):125 def start(self):
114 """Add the files."""126 """Add the files."""
115 files = []127 files = [ref.path for ref in self.filelist.tree_model.iter_checked()]
116 for desc in self.filelist.iter_checked():
117 files.append(desc.path())
118 128
119 self.process_widget.start(self.tree.basedir, "add", *files)129 self.process_widget.start(self.tree.basedir, "add", "--no-recurse",
130 *files)
120131
121 def show_ignored(self, state):132 def show_ignored(self, state):
122 """Show/hide ignored files."""133 """Show/hide ignored files."""
123 state = not state134 fmodel = self.filelist.tree_filter_model
124 for (tree_item, change_desc) in self.filelist.iter_treeitem_and_desc(True):135 fmodel.setFilter(fmodel.IGNORED, state)
125 path = change_desc.path()136 #self.filelist.update_selectall_state(None, None)
126 if self.tree.is_ignored(path):
127 self.filelist.set_item_hidden(tree_item, state)
128 self.filelist.update_selectall_state(None, None)
129137
130 def saveSize(self):138 def saveSize(self):
131 SubProcessDialog.saveSize(self)139 SubProcessDialog.saveSize(self)
132140
=== modified file 'lib/commit.py'
--- lib/commit.py 2009-07-12 16:17:52 +0000
+++ lib/commit.py 2009-07-14 03:31:17 +0000
@@ -36,15 +36,18 @@
36 get_global_config,36 get_global_config,
37 url_for_display,37 url_for_display,
38 ThrobberWidget,38 ThrobberWidget,
39 )39 runs_in_loading_queue,
40from bzrlib.plugins.qbzr.lib.wtlist import (40 )
41 ChangeDesc,41from bzrlib.plugins.qbzr.lib.treewidget import TreeWidget
42 WorkingTreeFileList,42
43 closure_in_selected_list,
44 )
45from bzrlib.plugins.qbzr.lib.logwidget import LogList43from bzrlib.plugins.qbzr.lib.logwidget import LogList
46from bzrlib.plugins.qbzr.lib.trace import *44from bzrlib.plugins.qbzr.lib.trace import *
47from bzrlib.plugins.qbzr.lib.uifactory import ui_current_widget45from bzrlib.plugins.qbzr.lib.uifactory import ui_current_widget
46from bzrlib.plugins.qbzr.lib.treewidget import (
47 TreeWidget,
48 SelectAllCheckBox,
49 )
50from bzrlib.plugins.qbzr.lib.trace import reports_exception
4851
4952
50MAX_AUTOCOMPLETE_FILES = 2053MAX_AUTOCOMPLETE_FILES = 20
@@ -143,55 +146,6 @@
143 RevisionIdRole = QtCore.Qt.UserRole + 1146 RevisionIdRole = QtCore.Qt.UserRole + 1
144 ParentIdRole = QtCore.Qt.UserRole + 2147 ParentIdRole = QtCore.Qt.UserRole + 2
145148
146 def iter_changes_and_state(self):
147 """An iterator for the WorkingTreeFileList widget"""
148 # a word list for message completer
149 words = set()
150 show_nonversioned = self.show_nonversioned_checkbox.isChecked()
151
152 in_selected_list = closure_in_selected_list(self.initial_selected_list)
153
154 num_versioned_files = 0
155 for desc in self.tree.iter_changes(self.tree.basis_tree(),
156 want_unversioned=True):
157 desc = ChangeDesc(desc)
158
159 if desc.is_tree_root() or desc.is_misadded():
160 # skip uninteresting enties
161 continue
162
163 is_versioned = desc.is_versioned()
164 path = desc.path()
165
166 if not is_versioned and self.tree.is_ignored(path):
167 continue
168
169 visible = is_versioned or show_nonversioned
170 check_state = None
171 if not self.has_pending_merges:
172 check_state = visible and is_versioned and in_selected_list(path)
173 yield desc, visible, check_state
174
175 if is_versioned:
176 num_versioned_files += 1
177
178 words.update(os.path.split(path))
179 if desc.is_renamed():
180 words.update(os.path.split(desc.oldpath()))
181 if num_versioned_files < MAX_AUTOCOMPLETE_FILES:
182 ext = file_extension(path)
183 builder = get_wordlist_builder(ext)
184 if builder is not None:
185 try:
186 abspath = os.path.join(self.tree.basedir, path)
187 file = open(abspath, 'rt')
188 words.update(builder.iter_words(file))
189 except EnvironmentError:
190 pass
191 words = list(words)
192 words.sort(lambda a, b: cmp(a.lower(), b.lower()))
193 self.completion_words = words
194
195 def __init__(self, tree, selected_list, dialog=True, parent=None,149 def __init__(self, tree, selected_list, dialog=True, parent=None,
196 local=None, message=None, ui_mode=True):150 local=None, message=None, ui_mode=True):
197 super(CommitWindow, self).__init__(151 super(CommitWindow, self).__init__(
@@ -202,13 +156,13 @@
202 dialog = dialog,156 dialog = dialog,
203 parent = parent)157 parent = parent)
204 self.tree = tree158 self.tree = tree
205 tree.lock_read()159 #tree.lock_read()
206 try:160 #try:
207 self.basis_tree = self.tree.basis_tree()161 # self.basis_tree = self.tree.basis_tree()
208 self.is_bound = bool(tree.branch.get_bound_location())162 self.is_bound = bool(tree.branch.get_bound_location())
209 self.has_pending_merges = len(tree.get_parent_ids())>1163 self.has_pending_merges = len(tree.get_parent_ids())>1
210 finally:164 #finally:
211 tree.unlock()165 # tree.unlock()
212 166
213 self.windows = []167 self.windows = []
214 self.initial_selected_list = selected_list168 self.initial_selected_list = selected_list
@@ -263,16 +217,27 @@
263 self.show_nonversioned_checkbox = QtGui.QCheckBox(217 self.show_nonversioned_checkbox = QtGui.QCheckBox(
264 gettext("Show non-versioned files"))218 gettext("Show non-versioned files"))
265219
266 self.filelist = WorkingTreeFileList(message_groupbox, self.tree)220 self.filelist = TreeWidget(self)
267 selectall_checkbox = QtGui.QCheckBox(221 self.filelist.throbber = self.throbber
268 gettext(self.filelist.SELECTALL_MESSAGE))222 self.filelist.tree_model.is_item_in_select_all = lambda item: (
223 item.change is not None and
224 item.change.is_ignored() is None and
225 item.change.is_versioned())
226
227 self.file_words = {}
228 self.connect(self.filelist.tree_model,
229 QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"),
230 self.on_filelist_data_changed)
231
232 selectall_checkbox = SelectAllCheckBox(self.filelist, self)
269 selectall_checkbox.setCheckState(QtCore.Qt.Checked)233 selectall_checkbox.setCheckState(QtCore.Qt.Checked)
270 self.filelist.set_selectall_checkbox(selectall_checkbox)
271234
272 # Equivalent for 'bzr commit --message'235 # Equivalent for 'bzr commit --message'
273 self.message = TextEdit(message_groupbox, main_window=self)236 self.message = TextEdit(message_groupbox, main_window=self)
274 self.message.setToolTip(gettext("Enter the commit message"))237 self.message.setToolTip(gettext("Enter the commit message"))
275 self.completer = QtGui.QCompleter()238 self.completer = QtGui.QCompleter()
239 self.completer_model = QtGui.QStringListModel(self.completer)
240 self.completer.setModel(self.completer_model)
276 self.message.setCompleter(self.completer)241 self.message.setCompleter(self.completer)
277 self.message.setAcceptRichText(False)242 self.message.setAcceptRichText(False)
278243
@@ -328,8 +293,6 @@
328 293
329 vbox.addWidget(selectall_checkbox)294 vbox.addWidget(selectall_checkbox)
330295
331 self.filelist.sortItems(0, QtCore.Qt.AscendingOrder)
332
333 # Display a list of pending merges296 # Display a list of pending merges
334 if self.has_pending_merges:297 if self.has_pending_merges:
335 selectall_checkbox.setCheckState(QtCore.Qt.Checked)298 selectall_checkbox.setCheckState(QtCore.Qt.Checked)
@@ -392,7 +355,8 @@
392 # we show the bare form as soon as possible.355 # we show the bare form as soon as possible.
393 SubProcessDialog.show(self)356 SubProcessDialog.show(self)
394 QtCore.QTimer.singleShot(1, self.load)357 QtCore.QTimer.singleShot(1, self.load)
395 358
359 @runs_in_loading_queue
396 @ui_current_widget360 @ui_current_widget
397 @reports_exception()361 @reports_exception()
398 def load(self):362 def load(self):
@@ -403,14 +367,58 @@
403 None,367 None,
404 self.tree)368 self.tree)
405 self.pending_merges_list.load()369 self.pending_merges_list.load()
406 self.filelist.fill(self.iter_changes_and_state())370 self.processEvents()
371
372 self.filelist.tree_model.checkable = not self.pending_merges_list
373 fmodel = self.filelist.tree_filter_model
374 fmodel.setFilter(fmodel.UNVERSIONED, False)
375 self.filelist.set_tree(self.tree, changes_mode = True,
376 initial_checked_paths = self.initial_selected_list)
377 self.processEvents()
378 self.update_compleater_words()
407 finally:379 finally:
408 self.tree.unlock()380 self.tree.unlock()
409 381
410 self.filelist.setup_actions()
411 self.completer.setModel(QtGui.QStringListModel(self.completion_words,
412 self.completer))
413 self.throbber.hide()382 self.throbber.hide()
383
384 def on_filelist_data_changed(self, start_index, end_index):
385 self.update_compleater_words()
386
387 def update_compleater_words(self):
388 num_files_loaded = 0
389
390 words = set()
391 for ref in self.filelist.tree_model.iter_checked():
392 path = ref.path
393 if path not in self.file_words:
394 file_words = set()
395 if num_files_loaded < MAX_AUTOCOMPLETE_FILES:
396 file_words.add(path)
397 file_words.add(os.path.split(path)[-1])
398 change = self.filelist.tree_model.inventory_data_by_path[
399 ref.path].change
400 if change.is_renamed():
401 file_words.add(change.oldpath())
402 file_words.add(os.path.split(change.oldpath())[-1])
403 #if num_versioned_files < MAX_AUTOCOMPLETE_FILES:
404 ext = file_extension(path)
405 builder = get_wordlist_builder(ext)
406 if builder is not None:
407 try:
408 abspath = os.path.join(self.tree.basedir, path)
409 file = open(abspath, 'rt')
410 file_words.update(builder.iter_words(file))
411 self.processEvents()
412 except EnvironmentError:
413 pass
414 self.file_words[path] = file_words
415 num_files_loaded += 1
416 else:
417 file_words = self.file_words[path]
418 words.update(file_words)
419 words = list(words)
420 words.sort(lambda a, b: cmp(a.lower(), b.lower()))
421 self.completer_model.setStringList(words)
414 422
415 def enableBugs(self, state):423 def enableBugs(self, state):
416 if state == QtCore.Qt.Checked:424 if state == QtCore.Qt.Checked:
@@ -460,7 +468,7 @@
460468
461 def start(self):469 def start(self):
462 args = ["commit"]470 args = ["commit"]
463 files_to_add = ["add"]471 files_to_add = ["add", "--no-recurse"]
464 472
465 message = unicode(self.message.toPlainText()).strip() 473 message = unicode(self.message.toPlainText()).strip()
466 if not message: 474 if not message:
@@ -479,12 +487,11 @@
479 checkedFiles = 1 487 checkedFiles = 1
480 if not self.has_pending_merges:488 if not self.has_pending_merges:
481 checkedFiles = 0489 checkedFiles = 0
482 for desc in self.filelist.iter_checked():490 for ref in self.filelist.tree_model.iter_checked():
483 checkedFiles = checkedFiles+1491 if ref.file_id is None:
484 path = desc.path()492 files_to_add.append(ref.path)
485 if not desc.is_versioned():493 args.append(ref.path)
486 files_to_add.append(path)494 checkedFiles = 1
487 args.append(path)
488 495
489 if checkedFiles == 0: # BUG: 295116496 if checkedFiles == 0: # BUG: 295116
490 # check for availability of --exclude option for commit497 # check for availability of --exclude option for commit
@@ -540,11 +547,8 @@
540547
541 def show_nonversioned(self, state):548 def show_nonversioned(self, state):
542 """Show/hide non-versioned files."""549 """Show/hide non-versioned files."""
543 state = not state550 fmodel = self.filelist.tree_filter_model
544 for (tree_item, change_desc) in self.filelist.iter_treeitem_and_desc(True):551 fmodel.setFilter(fmodel.UNVERSIONED, state)
545 if change_desc[3] == (False, False):
546 self.filelist.set_item_hidden(tree_item, state)
547 self.filelist.update_selectall_state(None, None)
548552
549 def closeEvent(self, event):553 def closeEvent(self, event):
550 if not self.process_widget.is_running():554 if not self.process_widget.is_running():
@@ -570,7 +574,7 @@
570 # update GUI574 # update GUI
571 self.branch_location.setText(loc)575 self.branch_location.setText(loc)
572 self.commit_type_description.setText(desc)576 self.commit_type_description.setText(desc)
573577#
574 def show_diff_for_checked(self, ext_diff=None, dialog_action='commit'):578 def show_diff_for_checked(self, ext_diff=None, dialog_action='commit'):
575 """Diff button clicked: show the diff for checked entries.579 """Diff button clicked: show the diff for checked entries.
576580
@@ -580,12 +584,11 @@
580 # XXX make this function universal for both qcommit and qrevert (?)584 # XXX make this function universal for both qcommit and qrevert (?)
581 checked = [] # checked versioned585 checked = [] # checked versioned
582 unversioned = [] # checked unversioned (supposed to be added)586 unversioned = [] # checked unversioned (supposed to be added)
583 for desc in self.filelist.iter_checked():587 for ref in self.filelist.tree_model.iter_checked():
584 path = desc.path()588 if ref.file_id:
585 if desc.is_versioned():589 checked.append(ref.path)
586 checked.append(path)
587 else:590 else:
588 unversioned.append(path)591 unversioned.append(ref.path)
589592
590 if checked:593 if checked:
591 arg_provider = InternalWTDiffArgProvider(594 arg_provider = InternalWTDiffArgProvider(
592595
=== modified file 'lib/diff.py'
--- lib/diff.py 2009-07-13 00:31:49 +0000
+++ lib/diff.py 2009-07-14 03:02:00 +0000
@@ -69,6 +69,7 @@
69 window.process_widget.hide_progress()69 window.process_widget.hide_progress()
70 if parent_window:70 if parent_window:
71 parent_window.windows.append(window)71 parent_window.windows.append(window)
72 window.show()
7273
7374
74def has_ext_diff():75def has_ext_diff():
7576
=== modified file 'lib/diff_arg.py'
--- lib/diff_arg.py 2009-06-23 00:38:58 +0000
+++ lib/diff_arg.py 2009-07-10 05:16:40 +0000
@@ -93,7 +93,9 @@
93 from bzrlib import urlutils93 from bzrlib import urlutils
9494
95 args = []95 args = []
96 args.append(self.get_revspec())96 revspec = self.get_revspec()
97 if revspec:
98 args.append(revspec)
97 99
98 if not self.old_branch.base == self.new_branch.base: 100 if not self.old_branch.base == self.new_branch.base:
99 args.append("--old=%s" % self.old_branch.base)101 args.append("--old=%s" % self.old_branch.base)
@@ -138,7 +140,10 @@
138 self.specific_files)140 self.specific_files)
139141
140 def get_revspec(self):142 def get_revspec(self):
141 return "-r revid:%s" % (self.old_revid,)143 if self.old_revid is not None:
144 return "-r revid:%s" % (self.old_revid,)
145 else:
146 return None
142 147
143 def need_to_load_paths(self):148 def need_to_load_paths(self):
144 return False149 return False
145150
=== modified file 'lib/revert.py'
--- lib/revert.py 2009-06-11 21:21:01 +0000
+++ lib/revert.py 2009-07-14 03:00:21 +0000
@@ -28,11 +28,16 @@
28 )28 )
29from bzrlib.plugins.qbzr.lib.i18n import gettext29from bzrlib.plugins.qbzr.lib.i18n import gettext
30from bzrlib.plugins.qbzr.lib.subprocess import SubProcessDialog30from bzrlib.plugins.qbzr.lib.subprocess import SubProcessDialog
31from bzrlib.plugins.qbzr.lib.wtlist import (31from bzrlib.plugins.qbzr.lib.treewidget import (
32 ChangeDesc,32 TreeWidget,
33 WorkingTreeFileList,33 SelectAllCheckBox,
34 closure_in_selected_list,34 )
35 )35from bzrlib.plugins.qbzr.lib.util import (
36 ThrobberWidget,
37 runs_in_loading_queue,
38 )
39from bzrlib.plugins.qbzr.lib.uifactory import ui_current_widget
40from bzrlib.plugins.qbzr.lib.trace import reports_exception
3641
3742
38class RevertWindow(SubProcessDialog):43class RevertWindow(SubProcessDialog):
@@ -51,26 +56,27 @@
51 parent = parent,56 parent = parent,
52 hide_progress=True)57 hide_progress=True)
53 58
59 self.throbber = ThrobberWidget(self)
60
54 # Display the list of changed files61 # Display the list of changed files
55 groupbox = QtGui.QGroupBox(gettext("Changes"), self)62 groupbox = QtGui.QGroupBox(gettext("Changes"), self)
5663
57 self.filelist = WorkingTreeFileList(groupbox, self.tree)64 self.filelist = TreeWidget(groupbox)
5865 self.filelist.throbber = self.throbber
59 self.tree.lock_read()66 self.filelist.tree_model.is_item_in_select_all = lambda item: (
60 try:67 item.change is not None and item.change.is_versioned())
61 self.filelist.fill(self.iter_changes_and_state())68 self.filelist.setRootIsDecorated(False)
62 finally:69 def filter_context_menu():
63 self.tree.unlock()70 TreeWidget.filter_context_menu(self.filelist)
6471 self.filelist.action_add.setVisible(False)
65 self.filelist.setup_actions()72 self.filelist.action_revert.setVisible(False)
73 self.filelist.filter_context_menu = filter_context_menu
6674
67 vbox = QtGui.QVBoxLayout(groupbox)75 vbox = QtGui.QVBoxLayout(groupbox)
68 vbox.addWidget(self.filelist)76 vbox.addWidget(self.filelist)
69 selectall_checkbox = QtGui.QCheckBox(77 selectall_checkbox = SelectAllCheckBox(self.filelist, groupbox)
70 gettext(self.filelist.SELECTALL_MESSAGE))
71 selectall_checkbox.setCheckState(QtCore.Qt.Checked)78 selectall_checkbox.setCheckState(QtCore.Qt.Checked)
72 selectall_checkbox.setEnabled(True)79 selectall_checkbox.setEnabled(True)
73 self.filelist.set_selectall_checkbox(selectall_checkbox)
74 vbox.addWidget(selectall_checkbox)80 vbox.addWidget(selectall_checkbox)
7581
76 self.no_backup_checkbox = QtGui.QCheckBox(82 self.no_backup_checkbox = QtGui.QCheckBox(
@@ -80,8 +86,6 @@
80 self.no_backup_checkbox.setEnabled(True)86 self.no_backup_checkbox.setEnabled(True)
81 vbox.addWidget(self.no_backup_checkbox)87 vbox.addWidget(self.no_backup_checkbox)
8288
83 self.filelist.sortItems(0, QtCore.Qt.AscendingOrder)
84
85 # groupbox gets disabled as we are executing.89 # groupbox gets disabled as we are executing.
86 QtCore.QObject.connect(self,90 QtCore.QObject.connect(self,
87 QtCore.SIGNAL("subprocessStarted(bool)"),91 QtCore.SIGNAL("subprocessStarted(bool)"),
@@ -95,6 +99,7 @@
95 self.restoreSplitterSizes([150, 150])99 self.restoreSplitterSizes([150, 150])
96100
97 layout = QtGui.QVBoxLayout(self)101 layout = QtGui.QVBoxLayout(self)
102 layout.addWidget(self.throbber)
98 layout.addWidget(self.splitter)103 layout.addWidget(self.splitter)
99104
100 # Diff button to view changes in files selected to revert105 # Diff button to view changes in files selected to revert
@@ -107,27 +112,32 @@
107 hbox.addWidget(self.diffbuttons)112 hbox.addWidget(self.diffbuttons)
108 hbox.addWidget(self.buttonbox)113 hbox.addWidget(self.buttonbox)
109 layout.addLayout(hbox)114 layout.addLayout(hbox)
110115 self.throbber.show()
111 def iter_changes_and_state(self):116
112 """An iterator for the WorkingTreeFileList widget"""117
113118 def show(self):
114 in_selected_list = closure_in_selected_list(self.initial_selected_list)119 SubProcessDialog.show(self)
115120 QtCore.QTimer.singleShot(1, self.initial_load)
116 for desc in self.tree.iter_changes(self.tree.basis_tree()):121
117 desc = ChangeDesc(desc)122 @runs_in_loading_queue
118 if desc.is_tree_root():123 @ui_current_widget
119 continue124 @reports_exception()
120 path = desc.path()125 def initial_load(self):
121 check_state = in_selected_list(path)126 self.filelist.tree_model.checkable = True
122 yield desc, True, check_state127 fmodel = self.filelist.tree_filter_model
128 #fmodel.setFilter(fmodel.UNVERSIONED, False)
129 self.filelist.set_tree(self.tree, changes_mode=True,
130 want_unversioned=False,
131 initial_checked_paths=self.initial_selected_list)
132 self.throbber.hide()
123133
124 def start(self):134 def start(self):
125 """Revert the files."""135 """Revert the files."""
126 args = ["revert"]136 args = ["revert"]
127 if self.no_backup_checkbox.checkState():137 if self.no_backup_checkbox.checkState():
128 args.append("--no-backup")138 args.append("--no-backup")
129 for desc in self.filelist.iter_checked():139 args.extend([ref.path
130 args.append(desc.path())140 for ref in self.filelist.tree_model.iter_checked()])
131 self.process_widget.start(self.tree.basedir, *args)141 self.process_widget.start(self.tree.basedir, *args)
132142
133 def saveSize(self):143 def saveSize(self):
@@ -141,10 +151,7 @@
141 @param dialog_action: purpose of parent window (main action)151 @param dialog_action: purpose of parent window (main action)
142 """152 """
143 # XXX make this function universal for both qcommit and qrevert (?)153 # XXX make this function universal for both qcommit and qrevert (?)
144 checked = []154 checked = [ref.path for ref in self.filelist.iter_checked()]
145 for desc in self.filelist.iter_checked():
146 path = desc.path()
147 checked.append(path)
148155
149 if checked:156 if checked:
150 arg_provider = InternalWTDiffArgProvider(157 arg_provider = InternalWTDiffArgProvider(
151158
=== modified file 'lib/revtreeview.py'
--- lib/revtreeview.py 2009-07-08 16:20:59 +0000
+++ lib/revtreeview.py 2009-07-10 01:04:24 +0000
@@ -102,6 +102,8 @@
102 break102 break
103 103
104 revids = list(revids)104 revids = list(revids)
105 if len(revids) == 0:
106 return
105 107
106 self.load_revisions_call_count += 1108 self.load_revisions_call_count += 1
107 current_call_count = self.load_revisions_call_count109 current_call_count = self.load_revisions_call_count
108110
=== modified file 'lib/tests/modeltest.py'
--- lib/tests/modeltest.py 2009-07-09 01:28:49 +0000
+++ lib/tests/modeltest.py 2009-07-10 01:54:38 +0000
@@ -431,7 +431,8 @@
431431
432 # Check that we can get back our real parent432 # Check that we can get back our real parent
433 p = self.model.parent( index )433 p = self.model.parent( index )
434 assert( self.model.parent( index ) == parent )434 assert( p.internalId() == parent.internalId() )
435 assert( p.row() == parent.row() )
435436
436 # recursively go down the children437 # recursively go down the children
437 if self.model.hasChildren(index) and depth < 10:438 if self.model.hasChildren(index) and depth < 10:
438439
=== modified file 'lib/treewidget.py'
--- lib/treewidget.py 2009-07-10 00:27:28 +0000
+++ lib/treewidget.py 2009-07-14 03:46:05 +0000
@@ -20,7 +20,9 @@
20import os20import os
21from time import (strftime, localtime)21from time import (strftime, localtime)
22from PyQt4 import QtCore, QtGui22from PyQt4 import QtCore, QtGui
23from bzrlib import errors
23from bzrlib.workingtree import WorkingTree24from bzrlib.workingtree import WorkingTree
25from bzrlib.revisiontree import RevisionTree
2426
25from bzrlib.plugins.qbzr.lib.cat import QBzrCatWindow27from bzrlib.plugins.qbzr.lib.cat import QBzrCatWindow
26from bzrlib.plugins.qbzr.lib.annotate import AnnotateWindow28from bzrlib.plugins.qbzr.lib.annotate import AnnotateWindow
@@ -37,7 +39,6 @@
37 get_apparent_author_name,39 get_apparent_author_name,
38 )40 )
39from bzrlib.plugins.qbzr.lib.uifactory import ui_current_widget41from bzrlib.plugins.qbzr.lib.uifactory import ui_current_widget
40from bzrlib.plugins.qbzr.lib.wtlist import ChangeDesc
41from bzrlib.plugins.qbzr.lib.subprocess import SimpleSubProcessDialog42from bzrlib.plugins.qbzr.lib.subprocess import SimpleSubProcessDialog
42from bzrlib.plugins.qbzr.lib.diff import (43from bzrlib.plugins.qbzr.lib.diff import (
43 show_diff,44 show_diff,
@@ -45,18 +46,167 @@
45 ExtDiffMenu,46 ExtDiffMenu,
46 InternalWTDiffArgProvider,47 InternalWTDiffArgProvider,
47 )48 )
4849from bzrlib.plugins.qbzr.lib.i18n import gettext, N_
4950
5051
51class UnversionedItem():52class InternalItem():
52 __slots__ = ["name", "path", "kind"]53 __slots__ = ["name", "kind", "file_id"]
53 def __init__(self, name, path, kind):54 def __init__(self, name, kind, file_id):
54 self.name = name55 self.name = name
55 self.path = path
56 self.kind = kind56 self.kind = kind
57 self.file_id = file_id
57 58
58 revision = property(lambda self:None)59 revision = property(lambda self:None)
59 file_id = property(lambda self:None)60
61class UnversionedItem(InternalItem):
62 def __init__(self, name, kind):
63 InternalItem.__init__(self, name, kind, None)
64
65class ModelItemData():
66 __slots__ = ["id", "item", "change", "checked", "children_ids",
67 "parent_id", "row", "path"]
68
69 def __init__(self, item, change, path):
70 self.item = item
71 self.change = change
72 self.path = path
73 if change is not None and change.is_ignored() is None:
74 self.checked = QtCore.Qt.Checked
75 else:
76 self.checked = QtCore.Qt.Unchecked
77
78 self.children_ids = None
79 self.parent_id = None
80 self.id = None
81 self.row = None
82
83class PersistantItemReference(object):
84 """This is use to stores a reference to a item that is persisted when we
85 refresh the model."""
86 __slots__ = ["file_id", "path"]
87
88 def __init__(self, file_id, path):
89 self.file_id = file_id
90 self.path = path
91
92class ChangeDesc(tuple):
93 """Helper class that "knows" about internals of iter_changes' changed entry
94 description tuple, and provides additional helper methods.
95
96 iter_changes return tuple with info about changed entry:
97 [0]: file_id -> ascii string
98 [1]: paths -> 2-tuple (old, new) fullpaths unicode/None
99 [2]: changed_content -> bool
100 [3]: versioned -> 2-tuple (bool, bool)
101 [4]: parent -> 2-tuple
102 [5]: name -> 2-tuple (old_name, new_name) utf-8?/None
103 [6]: kind -> 2-tuple (string/None, string/None)
104 [7]: executable -> 2-tuple (bool/None, bool/None)
105
106 --optional--
107 [8]: is_ignored -> If the file is ignored, pattern which caused it to
108 be ignored, otherwise None.
109
110 NOTE: None value used for non-existing entry in corresponding
111 tree, e.g. for added/deleted/ignored/unversioned
112 """
113
114 # XXX We should may be try get this into bzrlib.
115 # XXX We should use this in qdiff.
116
117 def fileid(desc):
118 return desc[0]
119
120 def path(desc):
121 """Return a suitable entry for a 'specific_files' param to bzr functions."""
122 oldpath, newpath = desc[1]
123 return newpath or oldpath
124
125 def oldpath(desc):
126 """Return oldpath for renames."""
127 return desc[1][0]
128
129 def kind(desc):
130 oldkind, newkind = desc[6]
131 return newkind or oldkind
132
133 def is_versioned(desc):
134 return desc[3] != (False, False)
135
136 def is_modified(desc):
137 return (desc[3] != (False, False) and desc[2])
138
139 def is_renamed(desc):
140 return (desc[3] == (True, True)
141 and (desc[4][0], desc[5][0]) != (desc[4][1], desc[5][1]))
142
143 def is_tree_root(desc):
144 """Check if entry actually tree root."""
145 if desc[3] != (False, False) and desc[4] == (None, None):
146 # TREE_ROOT has not parents (desc[4]).
147 # But because we could want to see unversioned files
148 # we need to check for versioned flag (desc[3])
149 return True
150 return False
151
152 def is_missing(desc):
153 """Check if file was present in previous revision but now it's gone
154 (i.e. deleted manually, without invoking `bzr remove` command)
155 """
156 return (desc[3] == (True, True) and desc[6][1] is None)
157
158 def is_misadded(desc):
159 """Check if file was added to the working tree but then gone
160 (i.e. deleted manually, without invoking `bzr remove` command)
161 """
162 return (desc[3] == (False, True) and desc[6][1] is None)
163
164 def is_ignored(desc):
165 if len(desc) >= 8:
166 return desc[8]
167 else:
168 return None
169
170 def status(desc):
171 if len(desc) == 8:
172 (file_id, (path_in_source, path_in_target),
173 changed_content, versioned, parent, name, kind,
174 executable) = desc
175 is_ignored = None
176 elif len(desc) == 9:
177 (file_id, (path_in_source, path_in_target),
178 changed_content, versioned, parent, name, kind,
179 executable, is_ignored) = desc
180 else:
181 raise RuntimeError, "Unkown number of items to unpack."
182
183 if versioned == (False, False):
184 if is_ignored:
185 return gettext("ignored")
186 else:
187 return gettext("non-versioned")
188 elif versioned == (False, True):
189 return gettext("added")
190 elif versioned == (True, False):
191 return gettext("removed")
192 elif kind[0] is not None and kind[1] is None:
193 return gettext("missing")
194 else:
195 # versioned = True, True - so either renamed or modified
196 # or properties changed (x-bit).
197 renamed = (parent[0], name[0]) != (parent[1], name[1])
198 if renamed:
199 if changed_content:
200 return gettext("renamed and modified")
201 else:
202 return gettext("renamed")
203 elif changed_content:
204 return gettext("modified")
205 elif executable[0] != executable[1]:
206 return gettext("modified (x-bit)")
207 else:
208 raise RuntimeError, "what status am I missing??"
209
60210
61class TreeModel(QtCore.QAbstractItemModel):211class TreeModel(QtCore.QAbstractItemModel):
62 212
@@ -68,10 +218,6 @@
68 gettext("Status")]218 gettext("Status")]
69 NAME, DATE, REVNO, MESSAGE, AUTHOR, STATUS = range(len(HEADER_LABELS))219 NAME, DATE, REVNO, MESSAGE, AUTHOR, STATUS = range(len(HEADER_LABELS))
70220
71 REVID = QtCore.Qt.UserRole + 1
72 FILEID = QtCore.Qt.UserRole + 2
73 PATH = QtCore.Qt.UserRole + 3
74
75 def __init__(self, file_icon, dir_icon, symlink_icon, parent=None):221 def __init__(self, file_icon, dir_icon, symlink_icon, parent=None):
76 QtCore.QAbstractTableModel.__init__(self, parent)222 QtCore.QAbstractTableModel.__init__(self, parent)
77223
@@ -79,39 +225,84 @@
79 self.dir_icon = dir_icon225 self.dir_icon = dir_icon
80 self.symlink_icon = symlink_icon226 self.symlink_icon = symlink_icon
81 self.tree = None227 self.tree = None
82 self.inventory_items = []228 self.inventory_data = []
83 self.dir_children_ids = {}229 self.inventory_data_by_path = {}
230 self.inventory_data_by_id = {} # Will not contain unversioned items.
84 self.parent_ids = []231 self.parent_ids = []
232 self.checkable = False
85 233
86 def set_tree(self, tree, branch):234 def set_tree(self, tree, branch=None,
235 changes_mode=False, want_unversioned=True,
236 initial_selected_paths=None):
87 self.tree = tree237 self.tree = tree
88 self.branch = branch238 self.branch = branch
89 self.revno_map = None239 self.revno_map = None
240 self.changes_mode = changes_mode
90 241
91 self.changes = {}242 self.changes = {}
92 self.unver_by_parent = {}243 self.unver_by_parent = {}
93244 self.inventory_data_by_path = {}
245 self.inventory_data_by_id = {}
246
94 if isinstance(self.tree, WorkingTree):247 if isinstance(self.tree, WorkingTree):
95 tree.lock_read()248 tree.lock_read()
96 try:249 try:
250 root_id = self.tree.get_root_id()
97 for change in self.tree.iter_changes(self.tree.basis_tree(),251 for change in self.tree.iter_changes(self.tree.basis_tree(),
98 want_unversioned=True):252 want_unversioned=want_unversioned):
99 change = ChangeDesc(change)253 change = ChangeDesc(change)
100 path = change.path()254 path = change.path()
255 fileid = change.fileid()
101 is_ignored = self.tree.is_ignored(path)256 is_ignored = self.tree.is_ignored(path)
102 change = ChangeDesc(change+(is_ignored,))257 change = ChangeDesc(change+(is_ignored,))
103 258
104 if change.fileid() is not None:259 if fileid is not None and not changes_mode:
105 self.changes[change.fileid()] = change260 self.changes[change.fileid()] = change
106 else:261 else:
107 (dir_path, slash, name) = path.rpartition('/')262 if changes_mode:
108 dir_fileid = self.tree.path2id(dir_path)263 dir_path = path
264 dir_fileid = None
265 relpath = ""
266 while dir_path:
267 (dir_path, slash, name) = dir_path.rpartition('/')
268 relpath = slash + name + relpath
269 if dir_path in self.inventory_data_by_path:
270 dir_item = self.inventory_data_by_path[
271 dir_path]
272 dir_fileid = dir_item.item.file_id
273 break
274 if dir_fileid is None:
275 dir_fileid = root_id
276 dir_path = ""
277
278 name = relpath.lstrip("/")
279 if change.is_renamed():
280 oldpath = change.oldpath()
281 if oldpath.startswith(dir_path):
282 oldpath = oldpath[len(dir_path):]
283 else:
284 # The file was mv from a difirent path.
285 oldpath = '/' + oldpath
286 name = "%s => %s" % (oldpath, name)
287 else:
288 (dir_path, slash, name) = path.rpartition('/')
289 dir_fileid = self.tree.path2id(dir_path)
290
291 if change.is_versioned():
292 if changes_mode:
293 item = InternalItem(name, change.kind(),
294 change.fileid())
295 else:
296 item = self.tree.inventory[change.fileid()]
297 else:
298 item = UnversionedItem(name, change.kind())
299
300 item_data = ModelItemData(item, change, path)
109 301
110 if dir_fileid not in self.unver_by_parent:302 if dir_fileid not in self.unver_by_parent:
111 self.unver_by_parent[dir_fileid] = []303 self.unver_by_parent[dir_fileid] = []
112 self.unver_by_parent[dir_fileid].append((304 self.unver_by_parent[dir_fileid].append(item_data)
113 UnversionedItem(name, path, change.kind()),305 self.inventory_data_by_path[path] = item_data
114 change))
115 306
116 self.process_inventory(self.working_tree_get_children)307 self.process_inventory(self.working_tree_get_children)
117 finally:308 finally:
@@ -119,20 +310,22 @@
119 else:310 else:
120 self.process_inventory(self.revision_tree_get_children)311 self.process_inventory(self.revision_tree_get_children)
121 312
122 def revision_tree_get_children(self, item):313 def revision_tree_get_children(self, item_data):
123 for child in item.children.itervalues():314 for child in item_data.item.children.itervalues():
124 yield (child, None)315 path = self.tree.id2path(child.file_id)
316 yield ModelItemData(child, None, path)
125 317
126 def working_tree_get_children(self, item):318 def working_tree_get_children(self, item_data):
319 item = item_data.item
127 if isinstance(item, UnversionedItem):320 if isinstance(item, UnversionedItem):
128 abspath = self.tree.abspath(item.path)321 abspath = self.tree.abspath(item_data.path)
129 322
130 for name in os.listdir(abspath):323 for name in os.listdir(abspath):
131 path = item.path+"/"+name324 path = item_data.path+"/"+name
132 (kind,325 (kind,
133 executable,326 executable,
134 stat_value) = self.tree._comparison_data(None, path)327 stat_value) = self.tree._comparison_data(None, path)
135 child = UnversionedItem(name, path, kind)328 child = UnversionedItem(name, kind)
136 is_ignored = self.tree.is_ignored(path)329 is_ignored = self.tree.is_ignored(path)
137 change = ChangeDesc((None,330 change = ChangeDesc((None,
138 (None, path),331 (None, path),
@@ -143,9 +336,10 @@
143 (None, kind),336 (None, kind),
144 (None, executable),337 (None, executable),
145 is_ignored))338 is_ignored))
146 yield (child, change)339 yield ModelItemData(child, change, path)
147 340
148 elif item.children is not None:341 if (not isinstance(item, InternalItem) and
342 item.children is not None and not self.changes_mode):
149 #Because we create copies, we have to get the real item.343 #Because we create copies, we have to get the real item.
150 item = self.tree.inventory[item.file_id]344 item = self.tree.inventory[item.file_id]
151 for child in item.children.itervalues():345 for child in item.children.itervalues():
@@ -155,35 +349,36 @@
155 change = self.changes[child.file_id]349 change = self.changes[child.file_id]
156 else:350 else:
157 change = None351 change = None
158 yield (child, change)352 path = self.tree.id2path(child.file_id)
353 yield ModelItemData(child, change, path)
159 if item.file_id in self.unver_by_parent:354 if item.file_id in self.unver_by_parent:
160 for (child, change) in self.unver_by_parent[item.file_id]:355 for item_data in self.unver_by_parent[item.file_id]:
161 yield (child, change)356 yield item_data
162 357
163 def load_dir(self, dir_id):358 def load_dir(self, dir_id):
359 if dir_id>=len(self.inventory_data):
360 return
361 dir_item = self.inventory_data[dir_id]
362 if dir_item.children_ids is not None:
363 return
364
164 if isinstance(self.tree, WorkingTree):365 if isinstance(self.tree, WorkingTree):
165 self.tree.lock_read()366 self.tree.lock_read()
166 try:367 try:
167 if dir_id>=len(self.inventory_items):368 dir_item.children_ids = []
168 return
169 dir_item, dir_change = self.inventory_items[dir_id]
170 dir_children_ids = []
171 children = sorted(self.get_children(dir_item),369 children = sorted(self.get_children(dir_item),
172 self.inventory_dirs_first_cmp,370 self.inventory_dirs_first_cmp,
173 lambda x: x[0])371 lambda x: (x.item.name, x.item.kind))
174 372
175 parent_model_index = self._index_from_id(dir_id, 0)373 parent_model_index = self._index_from_id(dir_id, 0)
176 self.beginInsertRows(parent_model_index, 0, len(children)-1)374 self.beginInsertRows(parent_model_index, 0, len(children)-1)
177 try:375 try:
178 for (child, change) in children:376 for child in children:
179 child_id = self.append_item(child, change, dir_id)377 child_id = self.append_item(child, dir_id)
180 dir_children_ids.append(child_id)378 dir_item.children_ids.append(child_id)
181 if child.kind == "directory":
182 self.dir_children_ids[child_id] = None
183 379
184 if len(dir_children_ids) % 100 == 0:380 if len(dir_item.children_ids) % 100 == 0:
185 QtCore.QCoreApplication.processEvents()381 QtCore.QCoreApplication.processEvents()
186 self.dir_children_ids[dir_id] = dir_children_ids
187 finally:382 finally:
188 self.endInsertRows();383 self.endInsertRows();
189 finally:384 finally:
@@ -194,37 +389,72 @@
194 self.get_children = get_children389 self.get_children = get_children
195 390
196 self.emit(QtCore.SIGNAL("layoutAboutToBeChanged()"))391 self.emit(QtCore.SIGNAL("layoutAboutToBeChanged()"))
197 self.inventory_items = []392
198 self.dir_children_ids = {}393 is_refresh = len(self.inventory_data)>0
394 if is_refresh:
395 self.beginRemoveRows(QtCore.QModelIndex(), 0,
396 len(self.inventory_data[0].children_ids)-1)
397 self.inventory_data = []
199 self.parent_ids = []398 self.parent_ids = []
399 if is_refresh:
400 self.endRemoveRows()
401
402 root_item = ModelItemData(self.tree.inventory[self.tree.get_root_id()],
403 None, '')
404 root_id = self.append_item(root_item, None)
405 self.load_dir(root_id)
200 self.emit(QtCore.SIGNAL("layoutChanged()"))406 self.emit(QtCore.SIGNAL("layoutChanged()"))
201
202 root_item = self.tree.inventory[self.tree.get_root_id()]
203 root_id = self.append_item(root_item, None, None)
204 self.dir_children_ids[root_id] = None
205 self.load_dir(root_id)
206 407
207 def append_item(self, item, change, parent_id):408 def append_item(self, item_data, parent_id):
208 id = len(self.inventory_items)409 item_data.id = len(self.inventory_data)
209 self.inventory_items.append((item, change))410 if parent_id is not None:
210 self.parent_ids.append(parent_id)411 parent_data = self.inventory_data[parent_id]
211 return id412 if self.is_item_in_select_all(item_data):
413 item_data.checked = parent_data.checked
414 else:
415 item_data.checked = False
416 item_data.row = len(parent_data.children_ids)
417 else:
418 item_data.checked = QtCore.Qt.Checked
419 item_data.row = 0
420 item_data.parent_id = parent_id
421 self.inventory_data.append(item_data)
422 self.inventory_data_by_path[item_data.path] = item_data
423 if not isinstance(item_data.item, UnversionedItem):
424 self.inventory_data_by_id[item_data.item.file_id] = item_data
425 return item_data.id
212 426
213 def inventory_dirs_first_cmp(self, x, y):427 def inventory_dirs_first_cmp(self, x, y):
214 x_is_dir = x.kind =="directory"428 (x_name, x_kind) = x
215 y_is_dir = y.kind =="directory"429 (y_name, y_kind) = y
430 x_a = x_name
431 y_a = y_name
432 x_is_dir = x_kind =="directory"
433 y_is_dir = y_kind =="directory"
434 while True:
435 x_b, sep, x_a_t = x_a.partition("/")
436 y_b, sep, y_a_t = y_a.partition("/")
437 if x_a_t == "" and y_a_t == "":
438 break
439 if (x_is_dir or not x_a_t == "") and not (y_is_dir or not y_a_t == ""):
440 return -1
441 if (y_is_dir or not y_a_t == "") and not (x_is_dir or not x_a_t == ""):
442 return 1
443 cmp_r = cmp(x_b, y_b)
444 if not cmp_r == 0:
445 return cmp_r
446 x_a = x_a_t
447 y_a = y_a_t
216 if x_is_dir and not y_is_dir:448 if x_is_dir and not y_is_dir:
217 return -1449 return -1
218 if y_is_dir and not x_is_dir:450 if y_is_dir and not x_is_dir:
219 return 1451 return 1
220 return cmp(x.name, y.name)452 return cmp(x_name, y_name)
221 453
222 def set_revno_map(self, revno_map):454 def set_revno_map(self, revno_map):
223 self.revno_map = revno_map455 self.revno_map = revno_map
224 for id in xrange(1, len(self.inventory_items)):456 for item_data in self.inventory_data[1:0]:
225 parent_id = self.parent_ids[id]457 index = self.createIndex (item_data.row, self.REVNO, item_data.id)
226 row = self.dir_children_ids[parent_id].index(id)
227 index = self.createIndex (row, self.REVNO, id)
228 self.emit(QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"),458 self.emit(QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"),
229 index,index) 459 index,index)
230 460
@@ -232,43 +462,48 @@
232 return len(self.HEADER_LABELS)462 return len(self.HEADER_LABELS)
233463
234 def rowCount(self, parent):464 def rowCount(self, parent):
235 parent_id = parent.internalId()465 if parent.internalId()>=len(self.inventory_data):
236 if parent_id not in self.dir_children_ids:466 return 0
237 return 0467 parent_data = self.inventory_data[parent.internalId()]
238 dir_children_ids = self.dir_children_ids[parent_id]468 if parent_data.children_ids is None:
239 if dir_children_ids is None:469 return 0
240 return 0470 return len(parent_data.children_ids)
241 return len(dir_children_ids)
242 471
243 def canFetchMore(self, parent):472 def canFetchMore(self, parent):
244 parent_id = parent.internalId()473 if parent.internalId()>=len(self.inventory_data):
245 return parent_id in self.dir_children_ids \474 return False
246 and self.dir_children_ids[parent_id] is None475 parent_data = self.inventory_data[parent.internalId()]
476 return (parent_data.children_ids is None and
477 parent_data.item.kind == "directory")
247 478
248 def fetchMore(self, parent):479 def fetchMore(self, parent):
249 self.load_dir(parent.internalId())480 self.load_dir(parent.internalId())
250481
251 def _index(self, row, column, parent_id):482 def _index(self, row, column, parent_id):
252 if parent_id not in self.dir_children_ids:483 if parent_id>=len(self.inventory_data):
253 return QtCore.QModelIndex()484 return QtCore.QModelIndex()
254 dir_children_ids = self.dir_children_ids[parent_id]485
255 if dir_children_ids is None:486 parent_data = self.inventory_data[parent_id]
256 return QtCore.QModelIndex()487 if parent_data.children_ids is None:
257 if row >= len(dir_children_ids):488 return QtCore.QModelIndex()
258 return QtCore.QModelIndex()489
259 item_id = dir_children_ids[row]490 if (row < 0 or
491 row >= len(parent_data.children_ids) or
492 column < 0 or
493 column >= len(self.HEADER_LABELS)):
494 return QtCore.QModelIndex()
495 item_id = parent_data.children_ids[row]
260 return self.createIndex(row, column, item_id)496 return self.createIndex(row, column, item_id)
261 497
262 def _index_from_id(self, item_id, column):498 def _index_from_id(self, item_id, column):
263 parent_id = self.parent_ids[item_id]499 if item_id >= len(self.inventory_data):
264 if parent_id is None:
265 return QtCore.QModelIndex()500 return QtCore.QModelIndex()
266 row = self.dir_children_ids[parent_id].index(item_id)501
267 return self.createIndex(row, column, item_id) 502 item_data = self.inventory_data[item_id]
503 return self.createIndex(item_data.row, column, item_id)
268 504
269 def index(self, row, column, parent = QtCore.QModelIndex()):505 def index(self, row, column, parent = QtCore.QModelIndex()):
270 parent_id = parent.internalId()506 return self._index(row, column, parent.internalId())
271 return self._index(row, column, parent_id)
272 507
273 def sibling(self, row, column, index):508 def sibling(self, row, column, index):
274 sibling_id = index.internalId()509 sibling_id = index.internalId()
@@ -281,28 +516,109 @@
281 child_id = child.internalId()516 child_id = child.internalId()
282 if child_id == 0:517 if child_id == 0:
283 return QtCore.QModelIndex()518 return QtCore.QModelIndex()
284 if child_id not in self.parent_ids:519 item_data = self.inventory_data[child_id]
285 return QtCore.QModelIndex()520 if item_data.parent_id == 0 :
286 item_id = self.parent_ids[child_id]
287 if item_id == 0 :
288 return QtCore.QModelIndex()521 return QtCore.QModelIndex()
289 522
290 return self._index_from_id(item_id, 0)523 return self._index_from_id(item_data.parent_id, 0)
291524
292 def hasChildren(self, parent):525 def hasChildren(self, parent):
293 parent_id = parent.internalId()526 if self.tree is None:
294 return parent_id in self.dir_children_ids527 return False
528 if parent.internalId() == 0:
529 return True
530 item_data = self.inventory_data[parent.internalId()]
531 return item_data.item.kind == "directory"
532
533 is_item_in_select_all = lambda self, item: True
534 """Returns wether an item is changed when select all is clicked."""
535
536 def setData(self, index, value, role):
537
538
539 def set_checked(item_data, checked):
540 old_checked = item_data.checked
541 item_data.checked = checked
542 if not old_checked == checked:
543 index = self.createIndex (item_data.row, self.NAME, item_data.id)
544 self.emit(QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"),
545 index,index)
546
547 if index.column() == self.NAME and role == QtCore.Qt.CheckStateRole:
548 value = value.toInt()[0]
549 if index.internalId() >= len(self.inventory_data):
550 return False
551
552 item_data = self.inventory_data[index.internalId()]
553 set_checked(item_data, value)
554
555 # Recursively set all children to checked.
556 if item_data.children_ids is not None:
557 children_ids = list(item_data.children_ids)
558 while children_ids:
559 child = self.inventory_data[children_ids.pop(0)]
560
561 # If unchecking, uncheck everything, but if checking,
562 # only check those in "select_all" get checked.
563 if (self.is_item_in_select_all(child) or
564 value == QtCore.Qt.Unchecked):
565
566 set_checked(child, value)
567 if child.children_ids is not None:
568 children_ids.extend(child.children_ids)
569
570 # Walk up the tree, and update every dir
571 parent_data = item_data
572 while parent_data.parent_id is not None:
573 parent_data = self.inventory_data[parent_data.parent_id]
574 has_checked = False
575 has_unchecked = False
576 for child_id in parent_data.children_ids:
577 child = self.inventory_data[child_id]
578
579 if child.checked == QtCore.Qt.Checked:
580 has_checked = True
581 elif (child.checked == QtCore.Qt.Unchecked and
582 self.is_item_in_select_all(child)):
583 has_unchecked = True
584 elif child.checked == QtCore.Qt.PartiallyChecked:
585 has_checked = True
586 if self.is_item_in_select_all(child):
587 has_unchecked = True
588
589 if has_checked and has_unchecked:
590 break
591
592 if has_checked and has_unchecked:
593 set_checked(parent_data, QtCore.Qt.PartiallyChecked)
594 elif has_checked:
595 set_checked(parent_data, QtCore.Qt.Checked)
596 else:
597 set_checked(parent_data, QtCore.Qt.Unchecked)
598
599 return True
600
601 return False
602
603 REVID = QtCore.Qt.UserRole + 1
604 FILEID = QtCore.Qt.UserRole + 2
605 PATH = QtCore.Qt.UserRole + 3
606 INTERNALID = QtCore.Qt.UserRole + 4
295 607
296 def data(self, index, role):608 def data(self, index, role):
297 if not index.isValid():609 if not index.isValid():
298 return QtCore.QVariant()610 return QtCore.QVariant()
299 611
300 (item, change) = self.inventory_items[index.internalId()]612 if role == self.INTERNALID:
613 return QtCore.QVariant(index.internalId())
614
615 item_data = self.inventory_data[index.internalId()]
616 item = item_data.item
301 617
302 if role == self.FILEID:618 if role == self.FILEID:
303 return QtCore.QVariant(item.file_id)619 return QtCore.QVariant(item.file_id)
304 620
305 revid = item.revision621 revid = item_data.item.revision
306 if role == self.REVID:622 if role == self.REVID:
307 if revid is None:623 if revid is None:
308 return QtCore.QVariant()624 return QtCore.QVariant()
@@ -321,13 +637,18 @@
321 if item.kind == "symlink":637 if item.kind == "symlink":
322 return QtCore.QVariant(self.symlink_icon)638 return QtCore.QVariant(self.symlink_icon)
323 return QtCore.QVariant()639 return QtCore.QVariant()
640 if role == QtCore.Qt.CheckStateRole:
641 if not self.checkable:
642 return QtCore.QVariant()
643 else:
644 return QtCore.QVariant(item_data.checked)
324 645
325 if column == self.STATUS:646 if column == self.STATUS:
326 if role == QtCore.Qt.DisplayRole:647 if role == QtCore.Qt.DisplayRole:
327 if change is not None:648 if item_data.change is not None:
328 return QtCore.QVariant(change.status())649 return QtCore.QVariant(item_data.change.status())
329 else:650 else:
330 return QtCore.QVariant()651 return QtCore.QVariant("")
331 652
332 if column == self.REVNO:653 if column == self.REVNO:
333 if role == QtCore.Qt.DisplayRole:654 if role == QtCore.Qt.DisplayRole:
@@ -336,7 +657,7 @@
336 return QtCore.QVariant(657 return QtCore.QVariant(
337 ".".join(["%d" % (revno) for revno in revno_sequence]))658 ".".join(["%d" % (revno) for revno in revno_sequence]))
338 else:659 else:
339 return QtCore.QVariant()660 return QtCore.QVariant("")
340661
341 if role == QtCore.Qt.DisplayRole:662 if role == QtCore.Qt.DisplayRole:
342 if revid in cached_revisions:663 if revid in cached_revisions:
@@ -353,25 +674,23 @@
353 localtime(rev.timestamp)))674 localtime(rev.timestamp)))
354 675
355 if role == self.PATH:676 if role == self.PATH:
356 if isinstance(item, UnversionedItem):677 return QtCore.QVariant(item_data.path)
357 path = item.path
358 else:
359 if isinstance(self.tree, WorkingTree):
360 self.tree.lock_read()
361 try:
362 path = self.tree.id2path(item.file_id)
363 finally:
364 if isinstance(self.tree, WorkingTree):
365 self.tree.unlock()
366 return QtCore.QVariant(path)
367 678
679 if role == QtCore.Qt.DisplayRole:
680 return QtCore.QVariant("")
368 return QtCore.QVariant()681 return QtCore.QVariant()
369 682
370 def flags(self, index):683 def flags(self, index):
371 if not index.isValid():684 #if not index.isValid():
372 return QtCore.Qt.ItemIsEnabled685 # return QtCore.Qt.ItemIsEnabled
373686
374 return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable687 if self.checkable and index.column() == self.NAME:
688 return (QtCore.Qt.ItemIsEnabled |
689 QtCore.Qt.ItemIsSelectable |
690 QtCore.Qt.ItemIsUserCheckable)
691 else:
692 return (QtCore.Qt.ItemIsEnabled |
693 QtCore.Qt.ItemIsSelectable)
375694
376 def headerData(self, section, orientation, role):695 def headerData(self, section, orientation, role):
377 if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:696 if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
@@ -380,19 +699,102 @@
380 699
381 def on_revisions_loaded(self, revisions, last_call):700 def on_revisions_loaded(self, revisions, last_call):
382 inventory = self.tree.inventory701 inventory = self.tree.inventory
383 for id, (item, change) in enumerate(self.inventory_items):702 for item_data in self.inventory_data:
384 if id == 0:703 if item_data.id == 0:
385 continue704 continue
386 705
387 if item.revision in revisions:706 if item_data.item.revision in revisions:
388 parent_id = self.parent_ids[id]
389 row = self.dir_children_ids[parent_id].index(id)
390 self.emit(QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"),707 self.emit(QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"),
391 self.createIndex (row, self.DATE, id),708 self.createIndex (item_data.row, self.DATE, item_data.id),
392 self.createIndex (row, self.AUTHOR,id))709 self.createIndex (item_data.row, self.AUTHOR, item_data.id))
393710
394 def get_repo(self):711 def get_repo(self):
395 return self.branch.repository712 return self.branch.repository
713
714 def _item2ref(self, item_data):
715 if isinstance(item_data.item, UnversionedItem):
716 file_id = None
717 else:
718 file_id = item_data.item.file_id
719 return PersistantItemReference(file_id, item_data.path)
720
721 def index2ref(self, index):
722 item_data = self.inventory_data[index.internalId()]
723 return self._item2ref(item_data)
724
725 def indexes2refs(self, indexes):
726 refs = []
727 for index in indexes:
728 refs.append(self.index2ref(index))
729 return refs
730
731 def ref2index(self, ref):
732 if ref.file_id is not None:
733 key = ref.file_id
734 dict = self.inventory_data_by_id
735 def iter_parents():
736 parent_id = ref.file_id
737 parent_ids = []
738 while parent_id is not None:
739 parent_id = self.tree.inventory[parent_id].parent_id
740 parent_ids.append(parent_id)
741 return reversed(parent_ids)
742 else:
743 key = ref.path
744 dict = self.inventory_data_by_path
745 def iter_parents():
746 path_split = ref.path.split("/")
747 parent_dir_path = None
748 for parent_name in ref.path.split("/")[:-1]:
749 if parent_dir_path is None:\
750 parent_dir_path = parent_name
751 else:
752 parent_dir_path += "/" + parent_name
753 yield parent_dir_path
754
755 if key not in dict:
756 # Try loading the parents
757 for parent_key in iter_parents():
758 if parent_key not in dict:
759 break
760 self.load_dir(dict[parent_key].id)
761
762 if key not in dict:
763 raise errors.NoSuchFile(ref.path)
764
765 return self._index_from_id(dict[key].id, self.NAME)
766
767 def refs2indexes(self, refs, ignore_no_file_error=False):
768 indexes = []
769 for ref in refs:
770 try:
771 indexes.append(self.ref2index(ref))
772 except (errors.NoSuchId, errors.NoSuchFile):
773 if not ignore_no_file_error:
774 raise
775 return indexes
776
777 def iter_checked(self):
778 return [self._item2ref(item_data)
779 for item_data in sorted(
780 [item_data for item_data in self.inventory_data[1:]
781 if item_data.checked == QtCore.Qt.Checked],
782 self.inventory_dirs_first_cmp,
783 lambda x: (x.change.path(), x.item.kind))]
784
785 def set_checked_items(self, refs, ignore_no_file_error=False):
786 # set every thing off
787 root_index = self._index_from_id(0, self.NAME)
788 self.setData(root_index, QtCore.QVariant(QtCore.Qt.Unchecked),
789 QtCore.Qt.CheckStateRole)
790
791 for index in self.refs2indexes(refs, ignore_no_file_error):
792 self.setData(index, QtCore.QVariant(QtCore.Qt.Checked),
793 QtCore.Qt.CheckStateRole)
794
795 def set_checked_paths(self, paths):
796 return self.set_checked_items([PersistantItemReference(None, path)
797 for path in paths])
396798
397class TreeFilterProxyModel(QtGui.QSortFilterProxyModel):799class TreeFilterProxyModel(QtGui.QSortFilterProxyModel):
398 source_model = None800 source_model = None
@@ -430,7 +832,13 @@
430 832
431 model = self.source_model833 model = self.source_model
432 parent_id = source_parent.internalId()834 parent_id = source_parent.internalId()
433 id = model.dir_children_ids[parent_id][source_row]835 children_ids = model.inventory_data[parent_id].children_ids
836 if len(children_ids)<=source_row:
837 return False
838 id = children_ids[source_row]
839 if (model.checkable and
840 not model.inventory_data[id].checked == QtCore.Qt.Unchecked):
841 return True
434 return self.filter_id_cached(id)842 return self.filter_id_cached(id)
435 843
436 def filter_id_cached(self, id):844 def filter_id_cached(self, id):
@@ -445,27 +853,26 @@
445 (unchanged, changed, unversioned, ignored) = self.filters853 (unchanged, changed, unversioned, ignored) = self.filters
446854
447 model = self.source_model855 model = self.source_model
448 item, change = model.inventory_items[id]856 item_data = model.inventory_data[id]
449 857
450 if change is None and unchanged: return True858 if item_data.change is None and unchanged: return True
451 859
452 is_versioned = not isinstance(item, UnversionedItem)860 is_versioned = not isinstance(item_data.item, UnversionedItem)
453 861
454 if is_versioned and change is not None and changed: return True862 if is_versioned and item_data.change is not None and changed:
863 return True
455 864
456 if not is_versioned and unversioned:865 if not is_versioned and unversioned:
457 is_ignored = change.is_ignored()866 is_ignored = item_data.change.is_ignored()
458 if not is_ignored: return True867 if not is_ignored: return True
459 if is_ignored and ignored: return True868 if is_ignored and ignored: return True
460 if is_ignored and not ignored: return False869 if is_ignored and not ignored: return False
461 870
462 if id in model.dir_children_ids:871 if item_data.item.kind == "directory":
463 dir_children_ids = model.dir_children_ids[id]872 if item_data.children_ids is None:
464 if dir_children_ids is None:
465 model.load_dir(id)873 model.load_dir(id)
466 dir_children_ids = model.dir_children_ids[id]
467 874
468 for child_id in dir_children_ids:875 for child_id in item_data.children_ids:
469 if self.filter_id_cached(child_id):876 if self.filter_id_cached(child_id):
470 return True877 return True
471 878
@@ -540,7 +947,6 @@
540 self.connect(self,947 self.connect(self,
541 QtCore.SIGNAL("doubleClicked(QModelIndex)"),948 QtCore.SIGNAL("doubleClicked(QModelIndex)"),
542 self.do_default_action)949 self.do_default_action)
543 self.default_action = self.show_file_content
544 self.tree = None950 self.tree = None
545 self.branch = None951 self.branch = None
546 952
@@ -552,7 +958,7 @@
552 header.setResizeMode(self.tree_model.REVNO, QtGui.QHeaderView.Interactive)958 header.setResizeMode(self.tree_model.REVNO, QtGui.QHeaderView.Interactive)
553 header.setResizeMode(self.tree_model.MESSAGE, QtGui.QHeaderView.Stretch)959 header.setResizeMode(self.tree_model.MESSAGE, QtGui.QHeaderView.Stretch)
554 header.setResizeMode(self.tree_model.AUTHOR, QtGui.QHeaderView.Interactive) 960 header.setResizeMode(self.tree_model.AUTHOR, QtGui.QHeaderView.Interactive)
555 header.setResizeMode(self.tree_model.STATUS, QtGui.QHeaderView.ResizeToContents) 961 header.setResizeMode(self.tree_model.STATUS, QtGui.QHeaderView.Stretch)
556 fm = self.fontMetrics()962 fm = self.fontMetrics()
557 # XXX Make this dynamic.963 # XXX Make this dynamic.
558 col_margin = 6964 col_margin = 6
@@ -596,10 +1002,41 @@
596 gettext("&Revert"),1002 gettext("&Revert"),
597 self.revert)1003 self.revert)
598 1004
599 def set_tree(self, tree, branch):1005 def set_tree(self, tree, branch=None,
1006 changes_mode=False, want_unversioned=True,
1007 initial_checked_paths=None):
1008 """Causes a tree to be loaded, and displayed in the widget.
1009
1010 @param changes_mode: If in changes mode, a list of changes, and
1011 unversioned items, rather than a tree, is diplayed.
1012 e.g., when not in changes mode, one will get:
1013
1014 dir1
1015 file1 changed
1016 file2 changed
1017
1018 but when in changes mode, one will get:
1019
1020 dir1/file1 changed
1021 file2 changed
1022
1023 When in changes mode, no unchanged items are shown.
1024 """
600 self.tree = tree1025 self.tree = tree
1026 if isinstance(tree, RevisionTree) and branch is None:
1027 raise AttributeError("A branch must be provided if the tree is a "
1028 "RevisionTree")
601 self.branch = branch1029 self.branch = branch
602 self.tree_model.set_tree(self.tree, self.branch)1030 self.changes_mode = changes_mode
1031 self.want_unversioned = want_unversioned
1032 self.tree_model.set_tree(self.tree, self.branch,
1033 changes_mode, want_unversioned)
1034 if initial_checked_paths is not None and not self.tree_model.checkable:
1035 raise AttributeError("You can't have a initial_selection if "
1036 "tree_model.checkable is not True.")
1037 if initial_checked_paths is not None:
1038 self.tree_model.set_checked_paths(initial_checked_paths)
1039
603 self.tree_filter_model.invalidateFilter()1040 self.tree_filter_model.invalidateFilter()
604 1041
605 if str(QtCore.QT_VERSION_STR).startswith("4.4"):1042 if str(QtCore.QT_VERSION_STR).startswith("4.4"):
@@ -623,7 +1060,6 @@
623 header.showSection(self.tree_model.STATUS)1060 header.showSection(self.tree_model.STATUS)
624 1061
625 self.context_menu.setDefaultAction(self.action_open_file)1062 self.context_menu.setDefaultAction(self.action_open_file)
626 self.default_action = self.open_file
627 else:1063 else:
628 header.showSection(self.tree_model.DATE)1064 header.showSection(self.tree_model.DATE)
629 header.showSection(self.tree_model.REVNO)1065 header.showSection(self.tree_model.REVNO)
@@ -632,11 +1068,22 @@
632 header.hideSection(self.tree_model.STATUS)1068 header.hideSection(self.tree_model.STATUS)
633 1069
634 self.context_menu.setDefaultAction(self.action_show_file)1070 self.context_menu.setDefaultAction(self.action_show_file)
635 self.default_action = self.show_file_content
636 1071
637 def refresh(self):1072 def refresh(self):
638 self.tree_model.set_tree(self.tree, self.branch)1073 self.tree.lock_read()
639 self.tree_filter_model.invalidateFilter()1074 try:
1075 if self.tree_model.checkable:
1076 checked = list(self.tree_model.iter_checked())
1077
1078 self.tree_model.set_tree(self.tree, self.branch,
1079 self.changes_mode, self.want_unversioned)
1080 self.tree_filter_model.invalidateFilter()
1081
1082 if self.tree_model.checkable:
1083 self.tree_model.set_checked_items(checked,
1084 ignore_no_file_error=True)
1085 finally:
1086 self.tree.unlock()
6401087
641 def contextMenuEvent(self, event):1088 def contextMenuEvent(self, event):
642 self.filter_context_menu()1089 self.filter_context_menu()
@@ -644,35 +1091,38 @@
644 event.accept()1091 event.accept()
645 1092
646 def get_selection_indexes(self, indexes=None):1093 def get_selection_indexes(self, indexes=None):
647 if indexes == None:1094 if indexes is None or (len(indexes) == 1 and indexes[0] is None):
648 indexes = self.selectedIndexes()1095 indexes = self.selectedIndexes()
649 rows = {}1096 rows = {}
650 for index in self.selectedIndexes():1097 for index in indexes:
651 if index.row() not in rows:1098 internal_id = index.data(self.tree_model.INTERNALID).toInt()[0]
652 rows[index.internalId()] = index1099 if internal_id not in rows:
1100 rows[internal_id] = index
653 return rows.values()1101 return rows.values()
654 1102
655 def get_selection_items(self, indexes=None):1103 def get_selection_items(self, indexes=None):
656 items = []1104 items = []
657 if indexes is None:1105 if indexes is None or len(indexes)==1 and indexes[0] is None:
658 indexes = self.get_selection_indexes(indexes) 1106 indexes = self.get_selection_indexes(indexes)
659 for index in indexes:1107 for index in indexes:
660 source_index = self.tree_filter_model.mapToSource(index)1108 source_index = self.tree_filter_model.mapToSource(index)
661 items.append(self.tree_model.inventory_items[1109 items.append(self.tree_model.inventory_data[
662 source_index.internalId()])1110 source_index.internalId()])
663 return items1111 return items
6641112
665 def filter_context_menu(self):1113 def filter_context_menu(self):
666 is_working_tree = isinstance(self.tree, WorkingTree)1114 is_working_tree = isinstance(self.tree, WorkingTree)
667 items = self.get_selection_items()1115 items = self.get_selection_items()
668 versioned = [not isinstance(item[0], UnversionedItem)1116 versioned = [not isinstance(item.item, UnversionedItem)
669 for item in items]1117 for item in items]
670 changed = [item[1] is not None1118 changed = [item.change is not None
671 for item in items]1119 for item in items]
1120 versioned_changed = [ver and ch for ver,ch in zip(versioned, changed)]
1121
672 selection_len = len(items)1122 selection_len = len(items)
673 1123
674 single_versioned_file = (selection_len == 1 and versioned[0] and1124 single_versioned_file = (selection_len == 1 and versioned[0] and
675 items[0][0].kind == "file")1125 items[0].item.kind == "file")
676 1126
677 self.action_open_file.setEnabled(is_working_tree)1127 self.action_open_file.setEnabled(is_working_tree)
678 self.action_open_file.setVisible(is_working_tree)1128 self.action_open_file.setVisible(is_working_tree)
@@ -680,24 +1130,37 @@
680 self.action_show_annotate.setEnabled(single_versioned_file)1130 self.action_show_annotate.setEnabled(single_versioned_file)
681 self.action_show_log.setEnabled(any(versioned))1131 self.action_show_log.setEnabled(any(versioned))
682 self.action_show_diff.setVisible(is_working_tree)1132 self.action_show_diff.setVisible(is_working_tree)
683 self.action_show_diff.setEnabled(any(changed))1133 self.action_show_diff.setEnabled(any(versioned_changed))
684 1134
685 self.action_add.setVisible(is_working_tree)1135 self.action_add.setVisible(is_working_tree)
686 self.action_add.setDisabled(all(versioned))1136 self.action_add.setDisabled(all(versioned))
687 self.action_revert.setVisible(is_working_tree)1137 self.action_revert.setVisible(is_working_tree)
688 self.action_revert.setEnabled(any(changed) and any(versioned))1138 self.action_revert.setEnabled(any(versioned_changed))
1139
1140 if is_working_tree:
1141 if any(versioned_changed):
1142 self.context_menu.setDefaultAction(self.action_show_diff)
1143 else:
1144 self.context_menu.setDefaultAction(self.action_open_file)
1145
689 1146
690 def do_default_action(self, index=None):1147 def do_default_action(self, index):
691 indexes = self.get_selection_indexes([index])1148 indexes = self.get_selection_indexes([index])
692 if not len(indexes) == 1:1149 if not len(indexes) == 1:
693 return1150 return
694 1151
695 item = self.get_selection_items(indexes)[0]1152 item_data = self.get_selection_items(indexes)[0]
696 if item[0].kind == "directory":1153 if item_data.item.kind == "directory":
697 # Don't do anything, so that the directory can be expanded.1154 # Don't do anything, so that the directory can be expanded.
698 return1155 return
699 1156
700 self.default_action(index)1157 if isinstance(self.tree, WorkingTree):
1158 if item_data.change is not None and item_data.change.is_versioned():
1159 self.show_differences(index=index)
1160 else:
1161 self.open_file(index)
1162 else:
1163 self.show_file_content(index)
7011164
702 @ui_current_widget1165 @ui_current_widget
703 def show_file_content(self, index=None):1166 def show_file_content(self, index=None):
@@ -708,7 +1171,7 @@
708 return1171 return
709 1172
710 item = self.get_selection_items(indexes)[0]1173 item = self.get_selection_items(indexes)[0]
711 if isinstance(item[0], UnversionedItem):1174 if isinstance(item.item, UnversionedItem):
712 return1175 return
713 1176
714 index = indexes[0]1177 index = indexes[0]
@@ -748,8 +1211,8 @@
748 """Show qlog for one selected file(s)."""1211 """Show qlog for one selected file(s)."""
749 1212
750 items = self.get_selection_items()1213 items = self.get_selection_items()
751 fileids = [item[0].file_id for item in items1214 fileids = [item.item.file_id for item in items
752 if item[0].file_id is not None]1215 if item.item.file_id is not None]
753 1216
754 window = LogWindow(None, self.branch, fileids)1217 window = LogWindow(None, self.branch, fileids)
755 window.show()1218 window.show()
@@ -767,19 +1230,15 @@
767 self.window().windows.append(window)1230 self.window().windows.append(window)
7681231
769 @ui_current_widget1232 @ui_current_widget
770 def show_differences(self, ext_diff=None):1233 def show_differences(self, ext_diff=None, index=None):
771 """Show differences for selected file(s)."""1234 """Show differences for selected file(s)."""
772 1235
773 items = self.get_selection_items()1236 items = self.get_selection_items([index])
774 if len(items) > 0:1237 if len(items) > 0:
775 self.tree.lock_read()1238 # Only paths that have changes.
776 try:1239 paths = [item.path
777 # Only paths that have changes.1240 for item in items
778 paths = [self.tree.id2path(item[0].file_id)1241 if item.change is not None]
779 for item in items
780 if item[1] is not None]
781 finally:
782 self.tree.unlock()
783 else:1242 else:
784 # Show all.1243 # Show all.
785 paths = None1244 paths = None
@@ -800,9 +1259,9 @@
800 items = self.get_selection_items()1259 items = self.get_selection_items()
801 1260
802 # Only paths that are not versioned.1261 # Only paths that are not versioned.
803 paths = [item[0].path1262 paths = [item.item.path
804 for item in items1263 for item in items
805 if isinstance(item[0], UnversionedItem)]1264 if isinstance(item.item, UnversionedItem)]
806 1265
807 if len(paths) == 0:1266 if len(paths) == 0:
808 return1267 return
@@ -829,10 +1288,10 @@
829 self.tree.lock_read()1288 self.tree.lock_read()
830 try:1289 try:
831 # Only paths that have changes.1290 # Only paths that have changes.
832 paths = [self.tree.id2path(item[0].file_id)1291 paths = [item.path
833 for item in items1292 for item in items
834 if item[1] is not None and1293 if item.change is not None and
835 not isinstance(item[0], UnversionedItem)]1294 not isinstance(item.item, UnversionedItem)]
836 finally:1295 finally:
837 self.tree.unlock()1296 self.tree.unlock()
838 1297
@@ -851,4 +1310,39 @@
851 res = revert_dialog.exec_()1310 res = revert_dialog.exec_()
852 if res == QtGui.QDialog.Accepted:1311 if res == QtGui.QDialog.Accepted:
853 self.refresh()1312 self.refresh()
854
855\ No newline at end of file1313\ No newline at end of file
1314
1315class SelectAllCheckBox(QtGui.QCheckBox):
1316
1317 def __init__(self, tree_widget, parent=None):
1318 QtGui.QCheckBox.__init__(self, gettext("Select / deselect all"), parent)
1319
1320 self.tree_widget = tree_widget
1321 #self.setTristate(True)
1322
1323 self.connect(tree_widget.tree_model,
1324 QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"),
1325 self.on_data_changed)
1326
1327 self.connect(self, QtCore.SIGNAL("clicked(bool)"),
1328 self.clicked)
1329
1330 def on_data_changed(self, start_index, end_index):
1331 self.update_state()
1332
1333 def update_state(self):
1334 model = self.tree_widget.tree_model
1335 root_index = model._index_from_id(0, model.NAME)
1336
1337 state = model.data(root_index, QtCore.Qt.CheckStateRole)
1338 self.setCheckState(state.toInt()[0])
1339
1340 def clicked(self, state):
1341 model = self.tree_widget.tree_model
1342 root_index = model._index_from_id(0, model.NAME)
1343 if state:
1344 state = QtCore.QVariant(QtCore.Qt.Checked)
1345 else:
1346 state = QtCore.QVariant(QtCore.Qt.Unchecked)
1347
1348 model.setData(root_index, QtCore.QVariant(state),
1349 QtCore.Qt.CheckStateRole)
8561350
=== removed file 'lib/wtlist.py'
--- lib/wtlist.py 2009-06-18 03:44:48 +0000
+++ lib/wtlist.py 1970-01-01 00:00:00 +0000
@@ -1,449 +0,0 @@
1# -*- coding: utf-8 -*-
2#
3# QBzr - Qt frontend to Bazaar commands
4# Copyright (C) 2006-2007 Lukáš Lalinský <lalinsky@gmail.com>
5# Copyright (C) 2006 Trolltech ASA
6# Copyright (C) 2006 Jelmer Vernooij <jelmer@samba.org>
7#
8# This program is free software; you can redistribute it and/or
9# modify it under the terms of the GNU General Public License
10# as published by the Free Software Foundation; either version 2
11# of the License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with this program; if not, write to the Free Software
20# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21
22"""A QTreeWidget that shows the items in a working tree, and includes a common
23context menu."""
24
25from PyQt4 import QtCore, QtGui
26
27from bzrlib import osutils
28
29from bzrlib.plugins.qbzr.lib.diff import (
30 show_diff,
31 has_ext_diff,
32 ExtDiffMenu,
33 InternalWTDiffArgProvider,
34 )
35from bzrlib.plugins.qbzr.lib.i18n import gettext, N_
36from bzrlib.plugins.qbzr.lib.subprocess import SimpleSubProcessDialog
37from bzrlib.plugins.qbzr.lib.util import (
38 file_extension,
39 runs_in_loading_queue,
40 )
41
42
43class WorkingTreeFileList(QtGui.QTreeWidget):
44
45 SELECTALL_MESSAGE = N_("Select / deselect all")
46
47 def __init__(self, parent, tree):
48 QtGui.QTreeWidget.__init__(self, parent)
49 self._ignore_select_all_changes = False
50 self.selectall_checkbox = None # added by client.
51 self.tree = tree
52
53 def setup_actions(self):
54 """Setup double-click and context menu"""
55 parent = self.parentWidget()
56 parent.connect(self,
57 QtCore.SIGNAL("itemDoubleClicked(QTreeWidgetItem *, int)"),
58 self.itemDoubleClicked)
59
60 self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
61 parent.connect(self,
62 QtCore.SIGNAL("itemSelectionChanged()"),
63 self.update_context_menu_actions)
64 parent.connect(self,
65 QtCore.SIGNAL("customContextMenuRequested(QPoint)"),
66 self.show_context_menu)
67
68 self.context_menu = QtGui.QMenu(self)
69 if has_ext_diff():
70 self.diff_menu = ExtDiffMenu(self)
71 self.context_menu.addMenu(self.diff_menu)
72 self.connect(self.diff_menu, QtCore.SIGNAL("triggered(QString)"),
73 self.show_differences)
74 self.show_diff_ui = self.diff_menu
75 else:
76 self.show_diff_action = self.context_menu.addAction(
77 gettext("Show &differences..."), self.show_differences)
78 self.context_menu.setDefaultAction(self.show_diff_action)
79 self.show_diff_ui = self.show_diff_action
80
81 self.revert_action = self.context_menu.addAction(
82 gettext("&Revert..."), self.revert_selected)
83 # set all actions to disabled so it does the right thing with an empty
84 # list (our itemSelectionChanged() will fire as soon as we select one)
85 self.revert_action.setEnabled(False)
86 self.show_diff_ui.setEnabled(False)
87
88 @runs_in_loading_queue
89 def fill(self, items_iter):
90 self.setTextElideMode(QtCore.Qt.ElideMiddle)
91 self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
92 self.setSortingEnabled(True)
93 self.setHeaderLabels([gettext("File"), gettext("Extension"), gettext("Status")])
94 header = self.header()
95 header.setStretchLastSection(False)
96 header.setResizeMode(0, QtGui.QHeaderView.Stretch)
97 header.setResizeMode(1, QtGui.QHeaderView.ResizeToContents)
98 header.setResizeMode(2, QtGui.QHeaderView.ResizeToContents)
99 self.setRootIsDecorated(False)
100 self._ignore_select_all_changes = True # don't update as we add items!
101
102 # Each items_iter returns a tuple of (changes_tuple, is_checked)
103 # Where changes_tuple is a single item from iter_changes():
104 # (file_id, (path_in_source, path_in_target),
105 # changed_content, versioned, parent, name, kind,
106 # executable)
107 # Note that the current filter is used to determine if the items are
108 # shown or not
109 self.item_to_data = {}
110 items = []
111 for change_desc, visible, checked in items_iter:
112 (file_id, (path_in_source, path_in_target),
113 changed_content, versioned, parent, name, kind,
114 executable) = change_desc
115
116 if versioned == (False, False):
117 if self.tree.is_ignored(path_in_target):
118 status = gettext("ignored")
119 else:
120 status = gettext("non-versioned")
121 ext = file_extension(path_in_target)
122 name = path_in_target
123 elif versioned == (False, True):
124 status = gettext("added")
125 ext = file_extension(path_in_target)
126 name = path_in_target + osutils.kind_marker(kind[1])
127 elif versioned == (True, False):
128 status = gettext("removed")
129 ext = file_extension(path_in_source)
130 name = path_in_source + osutils.kind_marker(kind[0])
131 elif kind[0] is not None and kind[1] is None:
132 status = gettext("missing")
133 ext = file_extension(path_in_source)
134 name = path_in_source + osutils.kind_marker(kind[0])
135 else:
136 # versioned = True, True - so either renamed or modified
137 # or properties changed (x-bit).
138 renamed = (parent[0], name[0]) != (parent[1], name[1])
139 if renamed:
140 if changed_content:
141 status = gettext("renamed and modified")
142 else:
143 status = gettext("renamed")
144 name = "%s%s => %s%s" % (path_in_source,
145 osutils.kind_marker(kind[0]),
146 path_in_target,
147 osutils.kind_marker(kind[0]))
148 ext = file_extension(path_in_target)
149 elif changed_content:
150 status = gettext("modified")
151 name = path_in_target + osutils.kind_marker(kind[1])
152 ext = file_extension(path_in_target)
153 elif executable[0] != executable[1]:
154 status = gettext("modified (x-bit)")
155 name = path_in_target + osutils.kind_marker(kind[1])
156 ext = file_extension(path_in_target)
157 else:
158 raise RuntimeError, "what status am I missing??"
159
160 item = QtGui.QTreeWidgetItem()
161 item.setText(0, name)
162 item.setText(1, ext)
163 item.setText(2, status)
164 if visible:
165 items.append(item)
166
167 if checked is None:
168 item.setCheckState(0, QtCore.Qt.PartiallyChecked)
169 item.setFlags(item.flags() & ~QtCore.Qt.ItemIsUserCheckable)
170 elif checked:
171 item.setCheckState(0, QtCore.Qt.Checked)
172 else:
173 item.setCheckState(0, QtCore.Qt.Unchecked)
174 self.item_to_data[item] = change_desc
175 # add them all to the tree in one hit.
176 self.insertTopLevelItems(0, items)
177 self._ignore_select_all_changes = False
178 if self.selectall_checkbox is not None:
179 self.update_selectall_state(None, None)
180
181 def set_item_hidden(self, item, hide):
182 # Due to what seems a bug in Qt "hiding" an item isn't always
183 # reliable - so a "hidden" item is simply not added to the tree!
184 # See https://bugs.launchpad.net/qbzr/+bug/274295
185 index = self.indexOfTopLevelItem(item)
186 if index == -1 and not hide:
187 self.addTopLevelItem(item)
188 elif index != -1 and hide:
189 self.takeTopLevelItem(index)
190
191 def is_item_hidden(self, item):
192 return self.indexOfTopLevelItem(item) == -1
193
194 def iter_treeitem_and_desc(self, include_hidden=False):
195 """iterators to help work with the selection, checked items, etc"""
196 for ti, desc in self.item_to_data.iteritems():
197 if include_hidden or not self.is_item_hidden(ti):
198 yield ti, desc
199
200 def iter_selection(self):
201 for i in self.selectedItems():
202 yield self.item_to_data[i]
203
204 def iter_checked(self):
205 # XXX - just use self.iter_treeitem_and_desc() - no need to hit the
206 # XXX tree object at all!?
207 for i in range(self.topLevelItemCount()):
208 item = self.topLevelItem(i)
209 if item.checkState(0) == QtCore.Qt.Checked:
210 yield self.item_to_data[item]
211
212 def show_context_menu(self, pos):
213 """Context menu and double-click related functions..."""
214 self.context_menu.popup(self.viewport().mapToGlobal(pos))
215
216 def update_context_menu_actions(self):
217 contains_non_versioned = False
218 for desc in self.iter_selection():
219 if desc[3] == (False, False):
220 contains_non_versioned = True
221 break
222 self.revert_action.setEnabled(not contains_non_versioned)
223 self.show_diff_ui.setEnabled(not contains_non_versioned)
224
225 def revert_selected(self):
226 """Revert the selected file."""
227 items = self.selectedItems()
228 if not items:
229 return
230
231 paths = [self.item_to_data[item][1][1] for item in items]
232
233 args = ["revert"]
234 args.extend(paths)
235 desc = (gettext("Revert %s to latest revision.") % ", ".join(paths))
236 revert_dialog = SimpleSubProcessDialog(gettext("Revert"),
237 desc=desc,
238 args=args,
239 dir=self.tree.basedir,
240 parent=self,
241 hide_progress=True,
242 )
243 res = revert_dialog.exec_()
244 if res == QtGui.QDialog.Accepted:
245 for item in items:
246 index = self.indexOfTopLevelItem(item)
247 self.takeTopLevelItem(index)
248
249 def itemDoubleClicked(self, items=None, column=None):
250 self.show_differences()
251
252 def show_differences(self, ext_diff=None):
253 """Show differences between the working copy and the last revision."""
254 if not self.show_diff_ui.isEnabled():
255 return
256
257 entries = [desc.path() for desc in self.iter_selection()]
258 if entries:
259 arg_provider = InternalWTDiffArgProvider(
260 self.tree.basis_tree().get_revision_id(), self.tree,
261 self.tree.branch, self.tree.branch,
262 specific_files=entries)
263
264 show_diff(arg_provider, ext_diff=ext_diff,
265 parent_window=self.topLevelWidget())
266
267 def set_selectall_checkbox(self, checkbox):
268 """Helpers for a 'show all' checkbox. Parent widgets must create the
269 widget and pass it to us.
270 """
271 checkbox.setTristate(True)
272 self.selectall_checkbox = checkbox
273 parent = self.parentWidget()
274 parent.connect(self,
275 QtCore.SIGNAL("itemChanged(QTreeWidgetItem *, int)"),
276 self.update_selectall_state)
277
278 parent.connect(checkbox, QtCore.SIGNAL("stateChanged(int)"),
279 self.selectall_changed)
280
281 def update_selectall_state(self, item, column):
282 """Update the state of the 'select all' checkbox to reflect the state
283 of the items in the list.
284 """
285 if self._ignore_select_all_changes:
286 return
287 checked = 0
288 num_items = 0
289
290 for (tree_item, change_desc) in self.iter_treeitem_and_desc():
291 if tree_item.checkState(0) == QtCore.Qt.Checked:
292 checked += 1
293 num_items += 1
294 self._ignore_select_all_changes = True
295 if checked == 0:
296 self.selectall_checkbox.setCheckState(QtCore.Qt.Unchecked)
297 elif checked == num_items:
298 self.selectall_checkbox.setCheckState(QtCore.Qt.Checked)
299 else:
300 self.selectall_checkbox.setCheckState(QtCore.Qt.PartiallyChecked)
301 self._ignore_select_all_changes = False
302
303 def selectall_changed(self, state):
304 if self._ignore_select_all_changes or not self.selectall_checkbox.isEnabled():
305 return
306 if state == QtCore.Qt.PartiallyChecked:
307 self.selectall_checkbox.setCheckState(QtCore.Qt.Checked)
308 return
309
310 self._ignore_select_all_changes = True
311 for (tree_item, change_desc) in self.iter_treeitem_and_desc():
312 tree_item.setCheckState(0, QtCore.Qt.CheckState(state))
313 self._ignore_select_all_changes = False
314
315
316class ChangeDesc(tuple):
317 """Helper class that "knows" about internals of iter_changes' changed entry
318 description tuple, and provides additional helper methods.
319
320 iter_changes return tuple with info about changed entry:
321 [0]: file_id -> ascii string
322 [1]: paths -> 2-tuple (old, new) fullpaths unicode/None
323 [2]: changed_content -> bool
324 [3]: versioned -> 2-tuple (bool, bool)
325 [4]: parent -> 2-tuple
326 [5]: name -> 2-tuple (old_name, new_name) utf-8?/None
327 [6]: kind -> 2-tuple (string/None, string/None)
328 [7]: executable -> 2-tuple (bool/None, bool/None)
329
330 --optional--
331 [8]: is_ignored -> If the file is ignored, pattern which caused it to
332 be ignored, otherwise None.
333
334 NOTE: None value used for non-existing entry in corresponding
335 tree, e.g. for added/deleted/ignored/unversioned
336 """
337
338 # XXX We should may be try get this into bzrlib.
339 # XXX We should use this in qdiff.
340
341 def fileid(desc):
342 return desc[0]
343
344 def path(desc):
345 """Return a suitable entry for a 'specific_files' param to bzr functions."""
346 oldpath, newpath = desc[1]
347 return newpath or oldpath
348
349 def oldpath(desc):
350 """Return oldpath for renames."""
351 return desc[1][0]
352
353 def kind(desc):
354 oldkind, newkind = desc[6]
355 return newkind or oldkind
356
357 def is_versioned(desc):
358 return desc[3] != (False, False)
359
360 def is_modified(desc):
361 return (desc[3] != (False, False) and desc[2])
362
363 def is_renamed(desc):
364 return (desc[3] == (True, True)
365 and (desc[4][0], desc[5][0]) != (desc[4][1], desc[5][1]))
366
367 def is_tree_root(desc):
368 """Check if entry actually tree root."""
369 if desc[3] != (False, False) and desc[4] == (None, None):
370 # TREE_ROOT has not parents (desc[4]).
371 # But because we could want to see unversioned files
372 # we need to check for versioned flag (desc[3])
373 return True
374 return False
375
376 def is_missing(desc):
377 """Check if file was present in previous revision but now it's gone
378 (i.e. deleted manually, without invoking `bzr remove` command)
379 """
380 return (desc[3] == (True, True) and desc[6][1] is None)
381
382 def is_misadded(desc):
383 """Check if file was added to the working tree but then gone
384 (i.e. deleted manually, without invoking `bzr remove` command)
385 """
386 return (desc[3] == (False, True) and desc[6][1] is None)
387
388 def is_ignored(desc):
389 if len(desc) >= 8:
390 return desc[8]
391 else:
392 return None
393
394 def status(desc):
395 if len(desc) == 8:
396 (file_id, (path_in_source, path_in_target),
397 changed_content, versioned, parent, name, kind,
398 executable) = desc
399 is_ignored = None
400 elif len(desc) == 9:
401 (file_id, (path_in_source, path_in_target),
402 changed_content, versioned, parent, name, kind,
403 executable, is_ignored) = desc
404 else:
405 raise RuntimeError, "Unkown number of items to unpack."
406
407 if versioned == (False, False):
408 if is_ignored:
409 return gettext("ignored")
410 else:
411 return gettext("non-versioned")
412 elif versioned == (False, True):
413 return gettext("added")
414 elif versioned == (True, False):
415 return gettext("removed")
416 elif kind[0] is not None and kind[1] is None:
417 return gettext("missing")
418 else:
419 # versioned = True, True - so either renamed or modified
420 # or properties changed (x-bit).
421 renamed = (parent[0], name[0]) != (parent[1], name[1])
422 if renamed:
423 if changed_content:
424 return gettext("renamed and modified")
425 else:
426 return gettext("renamed")
427 elif changed_content:
428 return gettext("modified")
429 elif executable[0] != executable[1]:
430 return gettext("modified (x-bit)")
431 else:
432 raise RuntimeError, "what status am I missing??"
433
434
435def closure_in_selected_list(selected_list):
436 """Return in_selected_list(path) function for given selected_list."""
437
438 def in_selected_list(path):
439 """Check: is path belongs to some selected_list."""
440 if path in selected_list:
441 return True
442 for p in selected_list:
443 if path.startswith(p):
444 return True
445 return False
446
447 if not selected_list:
448 return lambda path: True
449 return in_selected_list

Subscribers

People subscribed via source and target branches

to status/vote changes: