Merge lp:~garyvdm/qbzr/newwtlist into lp:~qbzr-dev/qbzr/trunk
- newwtlist
- Merge into trunk
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 |
Related bugs: |
This proposal supersedes a proposal from 2009-07-12.
Commit message
qcommit/
Description of the change
Gary van der Merwe (garyvdm) wrote : Posted in a previous version of this proposal | # |
Gary van der Merwe (garyvdm) wrote : Posted in a previous version of this proposal | # |
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.
Gary van der Merwe (garyvdm) wrote : Posted in a previous version of this proposal | # |
The above mentioned problem has now been fixed.
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://
http://
- 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
1 | === modified file 'lib/add.py' |
2 | --- lib/add.py 2009-06-11 21:21:01 +0000 |
3 | +++ lib/add.py 2009-07-14 03:00:21 +0000 |
4 | @@ -23,11 +23,16 @@ |
5 | |
6 | from bzrlib.plugins.qbzr.lib.i18n import gettext |
7 | from bzrlib.plugins.qbzr.lib.subprocess import SubProcessDialog |
8 | -from bzrlib.plugins.qbzr.lib.wtlist import ( |
9 | - ChangeDesc, |
10 | - WorkingTreeFileList, |
11 | - closure_in_selected_list, |
12 | - ) |
13 | +from bzrlib.plugins.qbzr.lib.treewidget import ( |
14 | + TreeWidget, |
15 | + SelectAllCheckBox, |
16 | + ) |
17 | +from bzrlib.plugins.qbzr.lib.util import ( |
18 | + ThrobberWidget, |
19 | + runs_in_loading_queue, |
20 | + ) |
21 | +from bzrlib.plugins.qbzr.lib.uifactory import ui_current_widget |
22 | +from bzrlib.plugins.qbzr.lib.trace import reports_exception |
23 | |
24 | |
25 | class AddWindow(SubProcessDialog): |
26 | @@ -46,22 +51,36 @@ |
27 | hide_progress=True, |
28 | ) |
29 | |
30 | + self.throbber = ThrobberWidget(self) |
31 | + |
32 | # Display the list of unversioned files |
33 | groupbox = QtGui.QGroupBox(gettext("Unversioned Files"), self) |
34 | vbox = QtGui.QVBoxLayout(groupbox) |
35 | |
36 | - self.filelist = WorkingTreeFileList(groupbox, self.tree) |
37 | + self.filelist = TreeWidget(groupbox) |
38 | + self.filelist.throbber = self.throbber |
39 | + self.filelist.tree_model.is_item_in_select_all = lambda item: ( |
40 | + item.change is not None and |
41 | + item.change.is_ignored() is None and |
42 | + not item.change.is_versioned()) |
43 | + |
44 | + def filter_context_menu(): |
45 | + self.filelist.action_open_file.setEnabled(True) |
46 | + self.filelist.action_open_file.setVisible(True) |
47 | + self.filelist.action_show_file.setVisible(False) |
48 | + self.filelist.action_show_annotate.setVisible(False) |
49 | + self.filelist.action_show_log.setVisible(False) |
50 | + self.filelist.action_show_diff.setVisible(False) |
51 | + self.filelist.action_add.setVisible(False) |
52 | + self.filelist.action_revert.setVisible(False) |
53 | + self.filelist.filter_context_menu = filter_context_menu |
54 | + |
55 | vbox.addWidget(self.filelist) |
56 | - self.filelist.sortItems(0, QtCore.Qt.AscendingOrder) |
57 | - self.filelist.setup_actions() |
58 | |
59 | - selectall_checkbox = QtGui.QCheckBox( |
60 | - gettext(self.filelist.SELECTALL_MESSAGE), |
61 | - groupbox) |
62 | + selectall_checkbox = SelectAllCheckBox(self.filelist, groupbox) |
63 | vbox.addWidget(selectall_checkbox) |
64 | selectall_checkbox.setCheckState(QtCore.Qt.Checked) |
65 | selectall_checkbox.setEnabled(True) |
66 | - self.filelist.set_selectall_checkbox(selectall_checkbox) |
67 | |
68 | self.show_ignored_checkbox = QtGui.QCheckBox( |
69 | gettext("Show ignored files"), |
70 | @@ -69,12 +88,6 @@ |
71 | vbox.addWidget(self.show_ignored_checkbox) |
72 | self.connect(self.show_ignored_checkbox, QtCore.SIGNAL("toggled(bool)"), self.show_ignored) |
73 | |
74 | - self.tree.lock_read() |
75 | - try: |
76 | - self.filelist.fill(self.iter_changes_and_state()) |
77 | - finally: |
78 | - self.tree.unlock() |
79 | - |
80 | # groupbox gets disabled as we are executing. |
81 | QtCore.QObject.connect(self, |
82 | QtCore.SIGNAL("subprocessStarted(bool)"), |
83 | @@ -88,44 +101,39 @@ |
84 | self.restoreSplitterSizes([150, 150]) |
85 | |
86 | layout = QtGui.QVBoxLayout(self) |
87 | + layout.addWidget(self.throbber) |
88 | layout.addWidget(self.splitter) |
89 | layout.addWidget(self.buttonbox) |
90 | - |
91 | - def iter_changes_and_state(self): |
92 | - """An iterator for the WorkingTreeFileList widget""" |
93 | - |
94 | - in_selected_list = closure_in_selected_list(self.initial_selected_list) |
95 | - |
96 | - show_ignored = self.show_ignored_checkbox.isChecked() |
97 | - |
98 | - for desc in self.tree.iter_changes(self.tree.basis_tree(), |
99 | - want_unversioned=True): |
100 | - |
101 | - desc = ChangeDesc(desc) |
102 | - if desc.is_versioned(): |
103 | - continue |
104 | - |
105 | - pit = desc.path() |
106 | - visible = show_ignored or not self.tree.is_ignored(pit) |
107 | - check_state = visible and in_selected_list(pit) |
108 | - yield desc, visible, check_state |
109 | + self.throbber.show() |
110 | + |
111 | + |
112 | + def show(self): |
113 | + SubProcessDialog.show(self) |
114 | + QtCore.QTimer.singleShot(1, self.initial_load) |
115 | + |
116 | + @runs_in_loading_queue |
117 | + @ui_current_widget |
118 | + @reports_exception() |
119 | + def initial_load(self): |
120 | + self.filelist.tree_model.checkable = True |
121 | + fmodel = self.filelist.tree_filter_model |
122 | + fmodel.setFilter(fmodel.CHANGED, False) |
123 | + self.filelist.set_tree(self.tree, changes_mode = True, |
124 | + initial_checked_paths=self.initial_selected_list) |
125 | + self.throbber.hide() |
126 | |
127 | def start(self): |
128 | """Add the files.""" |
129 | - files = [] |
130 | - for desc in self.filelist.iter_checked(): |
131 | - files.append(desc.path()) |
132 | + files = [ref.path for ref in self.filelist.tree_model.iter_checked()] |
133 | |
134 | - self.process_widget.start(self.tree.basedir, "add", *files) |
135 | + self.process_widget.start(self.tree.basedir, "add", "--no-recurse", |
136 | + *files) |
137 | |
138 | def show_ignored(self, state): |
139 | """Show/hide ignored files.""" |
140 | - state = not state |
141 | - for (tree_item, change_desc) in self.filelist.iter_treeitem_and_desc(True): |
142 | - path = change_desc.path() |
143 | - if self.tree.is_ignored(path): |
144 | - self.filelist.set_item_hidden(tree_item, state) |
145 | - self.filelist.update_selectall_state(None, None) |
146 | + fmodel = self.filelist.tree_filter_model |
147 | + fmodel.setFilter(fmodel.IGNORED, state) |
148 | + #self.filelist.update_selectall_state(None, None) |
149 | |
150 | def saveSize(self): |
151 | SubProcessDialog.saveSize(self) |
152 | |
153 | === modified file 'lib/commit.py' |
154 | --- lib/commit.py 2009-07-12 16:17:52 +0000 |
155 | +++ lib/commit.py 2009-07-14 03:31:17 +0000 |
156 | @@ -36,15 +36,18 @@ |
157 | get_global_config, |
158 | url_for_display, |
159 | ThrobberWidget, |
160 | - ) |
161 | -from bzrlib.plugins.qbzr.lib.wtlist import ( |
162 | - ChangeDesc, |
163 | - WorkingTreeFileList, |
164 | - closure_in_selected_list, |
165 | - ) |
166 | + runs_in_loading_queue, |
167 | + ) |
168 | +from bzrlib.plugins.qbzr.lib.treewidget import TreeWidget |
169 | + |
170 | from bzrlib.plugins.qbzr.lib.logwidget import LogList |
171 | from bzrlib.plugins.qbzr.lib.trace import * |
172 | from bzrlib.plugins.qbzr.lib.uifactory import ui_current_widget |
173 | +from bzrlib.plugins.qbzr.lib.treewidget import ( |
174 | + TreeWidget, |
175 | + SelectAllCheckBox, |
176 | + ) |
177 | +from bzrlib.plugins.qbzr.lib.trace import reports_exception |
178 | |
179 | |
180 | MAX_AUTOCOMPLETE_FILES = 20 |
181 | @@ -143,55 +146,6 @@ |
182 | RevisionIdRole = QtCore.Qt.UserRole + 1 |
183 | ParentIdRole = QtCore.Qt.UserRole + 2 |
184 | |
185 | - def iter_changes_and_state(self): |
186 | - """An iterator for the WorkingTreeFileList widget""" |
187 | - # a word list for message completer |
188 | - words = set() |
189 | - show_nonversioned = self.show_nonversioned_checkbox.isChecked() |
190 | - |
191 | - in_selected_list = closure_in_selected_list(self.initial_selected_list) |
192 | - |
193 | - num_versioned_files = 0 |
194 | - for desc in self.tree.iter_changes(self.tree.basis_tree(), |
195 | - want_unversioned=True): |
196 | - desc = ChangeDesc(desc) |
197 | - |
198 | - if desc.is_tree_root() or desc.is_misadded(): |
199 | - # skip uninteresting enties |
200 | - continue |
201 | - |
202 | - is_versioned = desc.is_versioned() |
203 | - path = desc.path() |
204 | - |
205 | - if not is_versioned and self.tree.is_ignored(path): |
206 | - continue |
207 | - |
208 | - visible = is_versioned or show_nonversioned |
209 | - check_state = None |
210 | - if not self.has_pending_merges: |
211 | - check_state = visible and is_versioned and in_selected_list(path) |
212 | - yield desc, visible, check_state |
213 | - |
214 | - if is_versioned: |
215 | - num_versioned_files += 1 |
216 | - |
217 | - words.update(os.path.split(path)) |
218 | - if desc.is_renamed(): |
219 | - words.update(os.path.split(desc.oldpath())) |
220 | - if num_versioned_files < MAX_AUTOCOMPLETE_FILES: |
221 | - ext = file_extension(path) |
222 | - builder = get_wordlist_builder(ext) |
223 | - if builder is not None: |
224 | - try: |
225 | - abspath = os.path.join(self.tree.basedir, path) |
226 | - file = open(abspath, 'rt') |
227 | - words.update(builder.iter_words(file)) |
228 | - except EnvironmentError: |
229 | - pass |
230 | - words = list(words) |
231 | - words.sort(lambda a, b: cmp(a.lower(), b.lower())) |
232 | - self.completion_words = words |
233 | - |
234 | def __init__(self, tree, selected_list, dialog=True, parent=None, |
235 | local=None, message=None, ui_mode=True): |
236 | super(CommitWindow, self).__init__( |
237 | @@ -202,13 +156,13 @@ |
238 | dialog = dialog, |
239 | parent = parent) |
240 | self.tree = tree |
241 | - tree.lock_read() |
242 | - try: |
243 | - self.basis_tree = self.tree.basis_tree() |
244 | - self.is_bound = bool(tree.branch.get_bound_location()) |
245 | - self.has_pending_merges = len(tree.get_parent_ids())>1 |
246 | - finally: |
247 | - tree.unlock() |
248 | + #tree.lock_read() |
249 | + #try: |
250 | + # self.basis_tree = self.tree.basis_tree() |
251 | + self.is_bound = bool(tree.branch.get_bound_location()) |
252 | + self.has_pending_merges = len(tree.get_parent_ids())>1 |
253 | + #finally: |
254 | + # tree.unlock() |
255 | |
256 | self.windows = [] |
257 | self.initial_selected_list = selected_list |
258 | @@ -263,16 +217,27 @@ |
259 | self.show_nonversioned_checkbox = QtGui.QCheckBox( |
260 | gettext("Show non-versioned files")) |
261 | |
262 | - self.filelist = WorkingTreeFileList(message_groupbox, self.tree) |
263 | - selectall_checkbox = QtGui.QCheckBox( |
264 | - gettext(self.filelist.SELECTALL_MESSAGE)) |
265 | + self.filelist = TreeWidget(self) |
266 | + self.filelist.throbber = self.throbber |
267 | + self.filelist.tree_model.is_item_in_select_all = lambda item: ( |
268 | + item.change is not None and |
269 | + item.change.is_ignored() is None and |
270 | + item.change.is_versioned()) |
271 | + |
272 | + self.file_words = {} |
273 | + self.connect(self.filelist.tree_model, |
274 | + QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"), |
275 | + self.on_filelist_data_changed) |
276 | + |
277 | + selectall_checkbox = SelectAllCheckBox(self.filelist, self) |
278 | selectall_checkbox.setCheckState(QtCore.Qt.Checked) |
279 | - self.filelist.set_selectall_checkbox(selectall_checkbox) |
280 | |
281 | # Equivalent for 'bzr commit --message' |
282 | self.message = TextEdit(message_groupbox, main_window=self) |
283 | self.message.setToolTip(gettext("Enter the commit message")) |
284 | self.completer = QtGui.QCompleter() |
285 | + self.completer_model = QtGui.QStringListModel(self.completer) |
286 | + self.completer.setModel(self.completer_model) |
287 | self.message.setCompleter(self.completer) |
288 | self.message.setAcceptRichText(False) |
289 | |
290 | @@ -328,8 +293,6 @@ |
291 | |
292 | vbox.addWidget(selectall_checkbox) |
293 | |
294 | - self.filelist.sortItems(0, QtCore.Qt.AscendingOrder) |
295 | - |
296 | # Display a list of pending merges |
297 | if self.has_pending_merges: |
298 | selectall_checkbox.setCheckState(QtCore.Qt.Checked) |
299 | @@ -392,7 +355,8 @@ |
300 | # we show the bare form as soon as possible. |
301 | SubProcessDialog.show(self) |
302 | QtCore.QTimer.singleShot(1, self.load) |
303 | - |
304 | + |
305 | + @runs_in_loading_queue |
306 | @ui_current_widget |
307 | @reports_exception() |
308 | def load(self): |
309 | @@ -403,14 +367,58 @@ |
310 | None, |
311 | self.tree) |
312 | self.pending_merges_list.load() |
313 | - self.filelist.fill(self.iter_changes_and_state()) |
314 | + self.processEvents() |
315 | + |
316 | + self.filelist.tree_model.checkable = not self.pending_merges_list |
317 | + fmodel = self.filelist.tree_filter_model |
318 | + fmodel.setFilter(fmodel.UNVERSIONED, False) |
319 | + self.filelist.set_tree(self.tree, changes_mode = True, |
320 | + initial_checked_paths = self.initial_selected_list) |
321 | + self.processEvents() |
322 | + self.update_compleater_words() |
323 | finally: |
324 | self.tree.unlock() |
325 | |
326 | - self.filelist.setup_actions() |
327 | - self.completer.setModel(QtGui.QStringListModel(self.completion_words, |
328 | - self.completer)) |
329 | self.throbber.hide() |
330 | + |
331 | + def on_filelist_data_changed(self, start_index, end_index): |
332 | + self.update_compleater_words() |
333 | + |
334 | + def update_compleater_words(self): |
335 | + num_files_loaded = 0 |
336 | + |
337 | + words = set() |
338 | + for ref in self.filelist.tree_model.iter_checked(): |
339 | + path = ref.path |
340 | + if path not in self.file_words: |
341 | + file_words = set() |
342 | + if num_files_loaded < MAX_AUTOCOMPLETE_FILES: |
343 | + file_words.add(path) |
344 | + file_words.add(os.path.split(path)[-1]) |
345 | + change = self.filelist.tree_model.inventory_data_by_path[ |
346 | + ref.path].change |
347 | + if change.is_renamed(): |
348 | + file_words.add(change.oldpath()) |
349 | + file_words.add(os.path.split(change.oldpath())[-1]) |
350 | + #if num_versioned_files < MAX_AUTOCOMPLETE_FILES: |
351 | + ext = file_extension(path) |
352 | + builder = get_wordlist_builder(ext) |
353 | + if builder is not None: |
354 | + try: |
355 | + abspath = os.path.join(self.tree.basedir, path) |
356 | + file = open(abspath, 'rt') |
357 | + file_words.update(builder.iter_words(file)) |
358 | + self.processEvents() |
359 | + except EnvironmentError: |
360 | + pass |
361 | + self.file_words[path] = file_words |
362 | + num_files_loaded += 1 |
363 | + else: |
364 | + file_words = self.file_words[path] |
365 | + words.update(file_words) |
366 | + words = list(words) |
367 | + words.sort(lambda a, b: cmp(a.lower(), b.lower())) |
368 | + self.completer_model.setStringList(words) |
369 | |
370 | def enableBugs(self, state): |
371 | if state == QtCore.Qt.Checked: |
372 | @@ -460,7 +468,7 @@ |
373 | |
374 | def start(self): |
375 | args = ["commit"] |
376 | - files_to_add = ["add"] |
377 | + files_to_add = ["add", "--no-recurse"] |
378 | |
379 | message = unicode(self.message.toPlainText()).strip() |
380 | if not message: |
381 | @@ -479,12 +487,11 @@ |
382 | checkedFiles = 1 |
383 | if not self.has_pending_merges: |
384 | checkedFiles = 0 |
385 | - for desc in self.filelist.iter_checked(): |
386 | - checkedFiles = checkedFiles+1 |
387 | - path = desc.path() |
388 | - if not desc.is_versioned(): |
389 | - files_to_add.append(path) |
390 | - args.append(path) |
391 | + for ref in self.filelist.tree_model.iter_checked(): |
392 | + if ref.file_id is None: |
393 | + files_to_add.append(ref.path) |
394 | + args.append(ref.path) |
395 | + checkedFiles = 1 |
396 | |
397 | if checkedFiles == 0: # BUG: 295116 |
398 | # check for availability of --exclude option for commit |
399 | @@ -540,11 +547,8 @@ |
400 | |
401 | def show_nonversioned(self, state): |
402 | """Show/hide non-versioned files.""" |
403 | - state = not state |
404 | - for (tree_item, change_desc) in self.filelist.iter_treeitem_and_desc(True): |
405 | - if change_desc[3] == (False, False): |
406 | - self.filelist.set_item_hidden(tree_item, state) |
407 | - self.filelist.update_selectall_state(None, None) |
408 | + fmodel = self.filelist.tree_filter_model |
409 | + fmodel.setFilter(fmodel.UNVERSIONED, state) |
410 | |
411 | def closeEvent(self, event): |
412 | if not self.process_widget.is_running(): |
413 | @@ -570,7 +574,7 @@ |
414 | # update GUI |
415 | self.branch_location.setText(loc) |
416 | self.commit_type_description.setText(desc) |
417 | - |
418 | +# |
419 | def show_diff_for_checked(self, ext_diff=None, dialog_action='commit'): |
420 | """Diff button clicked: show the diff for checked entries. |
421 | |
422 | @@ -580,12 +584,11 @@ |
423 | # XXX make this function universal for both qcommit and qrevert (?) |
424 | checked = [] # checked versioned |
425 | unversioned = [] # checked unversioned (supposed to be added) |
426 | - for desc in self.filelist.iter_checked(): |
427 | - path = desc.path() |
428 | - if desc.is_versioned(): |
429 | - checked.append(path) |
430 | + for ref in self.filelist.tree_model.iter_checked(): |
431 | + if ref.file_id: |
432 | + checked.append(ref.path) |
433 | else: |
434 | - unversioned.append(path) |
435 | + unversioned.append(ref.path) |
436 | |
437 | if checked: |
438 | arg_provider = InternalWTDiffArgProvider( |
439 | |
440 | === modified file 'lib/diff.py' |
441 | --- lib/diff.py 2009-07-13 00:31:49 +0000 |
442 | +++ lib/diff.py 2009-07-14 03:02:00 +0000 |
443 | @@ -69,6 +69,7 @@ |
444 | window.process_widget.hide_progress() |
445 | if parent_window: |
446 | parent_window.windows.append(window) |
447 | + window.show() |
448 | |
449 | |
450 | def has_ext_diff(): |
451 | |
452 | === modified file 'lib/diff_arg.py' |
453 | --- lib/diff_arg.py 2009-06-23 00:38:58 +0000 |
454 | +++ lib/diff_arg.py 2009-07-10 05:16:40 +0000 |
455 | @@ -93,7 +93,9 @@ |
456 | from bzrlib import urlutils |
457 | |
458 | args = [] |
459 | - args.append(self.get_revspec()) |
460 | + revspec = self.get_revspec() |
461 | + if revspec: |
462 | + args.append(revspec) |
463 | |
464 | if not self.old_branch.base == self.new_branch.base: |
465 | args.append("--old=%s" % self.old_branch.base) |
466 | @@ -138,7 +140,10 @@ |
467 | self.specific_files) |
468 | |
469 | def get_revspec(self): |
470 | - return "-r revid:%s" % (self.old_revid,) |
471 | + if self.old_revid is not None: |
472 | + return "-r revid:%s" % (self.old_revid,) |
473 | + else: |
474 | + return None |
475 | |
476 | def need_to_load_paths(self): |
477 | return False |
478 | |
479 | === modified file 'lib/revert.py' |
480 | --- lib/revert.py 2009-06-11 21:21:01 +0000 |
481 | +++ lib/revert.py 2009-07-14 03:00:21 +0000 |
482 | @@ -28,11 +28,16 @@ |
483 | ) |
484 | from bzrlib.plugins.qbzr.lib.i18n import gettext |
485 | from bzrlib.plugins.qbzr.lib.subprocess import SubProcessDialog |
486 | -from bzrlib.plugins.qbzr.lib.wtlist import ( |
487 | - ChangeDesc, |
488 | - WorkingTreeFileList, |
489 | - closure_in_selected_list, |
490 | - ) |
491 | +from bzrlib.plugins.qbzr.lib.treewidget import ( |
492 | + TreeWidget, |
493 | + SelectAllCheckBox, |
494 | + ) |
495 | +from bzrlib.plugins.qbzr.lib.util import ( |
496 | + ThrobberWidget, |
497 | + runs_in_loading_queue, |
498 | + ) |
499 | +from bzrlib.plugins.qbzr.lib.uifactory import ui_current_widget |
500 | +from bzrlib.plugins.qbzr.lib.trace import reports_exception |
501 | |
502 | |
503 | class RevertWindow(SubProcessDialog): |
504 | @@ -51,26 +56,27 @@ |
505 | parent = parent, |
506 | hide_progress=True) |
507 | |
508 | + self.throbber = ThrobberWidget(self) |
509 | + |
510 | # Display the list of changed files |
511 | groupbox = QtGui.QGroupBox(gettext("Changes"), self) |
512 | |
513 | - self.filelist = WorkingTreeFileList(groupbox, self.tree) |
514 | - |
515 | - self.tree.lock_read() |
516 | - try: |
517 | - self.filelist.fill(self.iter_changes_and_state()) |
518 | - finally: |
519 | - self.tree.unlock() |
520 | - |
521 | - self.filelist.setup_actions() |
522 | + self.filelist = TreeWidget(groupbox) |
523 | + self.filelist.throbber = self.throbber |
524 | + self.filelist.tree_model.is_item_in_select_all = lambda item: ( |
525 | + item.change is not None and item.change.is_versioned()) |
526 | + self.filelist.setRootIsDecorated(False) |
527 | + def filter_context_menu(): |
528 | + TreeWidget.filter_context_menu(self.filelist) |
529 | + self.filelist.action_add.setVisible(False) |
530 | + self.filelist.action_revert.setVisible(False) |
531 | + self.filelist.filter_context_menu = filter_context_menu |
532 | |
533 | vbox = QtGui.QVBoxLayout(groupbox) |
534 | vbox.addWidget(self.filelist) |
535 | - selectall_checkbox = QtGui.QCheckBox( |
536 | - gettext(self.filelist.SELECTALL_MESSAGE)) |
537 | + selectall_checkbox = SelectAllCheckBox(self.filelist, groupbox) |
538 | selectall_checkbox.setCheckState(QtCore.Qt.Checked) |
539 | selectall_checkbox.setEnabled(True) |
540 | - self.filelist.set_selectall_checkbox(selectall_checkbox) |
541 | vbox.addWidget(selectall_checkbox) |
542 | |
543 | self.no_backup_checkbox = QtGui.QCheckBox( |
544 | @@ -80,8 +86,6 @@ |
545 | self.no_backup_checkbox.setEnabled(True) |
546 | vbox.addWidget(self.no_backup_checkbox) |
547 | |
548 | - self.filelist.sortItems(0, QtCore.Qt.AscendingOrder) |
549 | - |
550 | # groupbox gets disabled as we are executing. |
551 | QtCore.QObject.connect(self, |
552 | QtCore.SIGNAL("subprocessStarted(bool)"), |
553 | @@ -95,6 +99,7 @@ |
554 | self.restoreSplitterSizes([150, 150]) |
555 | |
556 | layout = QtGui.QVBoxLayout(self) |
557 | + layout.addWidget(self.throbber) |
558 | layout.addWidget(self.splitter) |
559 | |
560 | # Diff button to view changes in files selected to revert |
561 | @@ -107,27 +112,32 @@ |
562 | hbox.addWidget(self.diffbuttons) |
563 | hbox.addWidget(self.buttonbox) |
564 | layout.addLayout(hbox) |
565 | - |
566 | - def iter_changes_and_state(self): |
567 | - """An iterator for the WorkingTreeFileList widget""" |
568 | - |
569 | - in_selected_list = closure_in_selected_list(self.initial_selected_list) |
570 | - |
571 | - for desc in self.tree.iter_changes(self.tree.basis_tree()): |
572 | - desc = ChangeDesc(desc) |
573 | - if desc.is_tree_root(): |
574 | - continue |
575 | - path = desc.path() |
576 | - check_state = in_selected_list(path) |
577 | - yield desc, True, check_state |
578 | + self.throbber.show() |
579 | + |
580 | + |
581 | + def show(self): |
582 | + SubProcessDialog.show(self) |
583 | + QtCore.QTimer.singleShot(1, self.initial_load) |
584 | + |
585 | + @runs_in_loading_queue |
586 | + @ui_current_widget |
587 | + @reports_exception() |
588 | + def initial_load(self): |
589 | + self.filelist.tree_model.checkable = True |
590 | + fmodel = self.filelist.tree_filter_model |
591 | + #fmodel.setFilter(fmodel.UNVERSIONED, False) |
592 | + self.filelist.set_tree(self.tree, changes_mode=True, |
593 | + want_unversioned=False, |
594 | + initial_checked_paths=self.initial_selected_list) |
595 | + self.throbber.hide() |
596 | |
597 | def start(self): |
598 | """Revert the files.""" |
599 | args = ["revert"] |
600 | if self.no_backup_checkbox.checkState(): |
601 | args.append("--no-backup") |
602 | - for desc in self.filelist.iter_checked(): |
603 | - args.append(desc.path()) |
604 | + args.extend([ref.path |
605 | + for ref in self.filelist.tree_model.iter_checked()]) |
606 | self.process_widget.start(self.tree.basedir, *args) |
607 | |
608 | def saveSize(self): |
609 | @@ -141,10 +151,7 @@ |
610 | @param dialog_action: purpose of parent window (main action) |
611 | """ |
612 | # XXX make this function universal for both qcommit and qrevert (?) |
613 | - checked = [] |
614 | - for desc in self.filelist.iter_checked(): |
615 | - path = desc.path() |
616 | - checked.append(path) |
617 | + checked = [ref.path for ref in self.filelist.iter_checked()] |
618 | |
619 | if checked: |
620 | arg_provider = InternalWTDiffArgProvider( |
621 | |
622 | === modified file 'lib/revtreeview.py' |
623 | --- lib/revtreeview.py 2009-07-08 16:20:59 +0000 |
624 | +++ lib/revtreeview.py 2009-07-10 01:04:24 +0000 |
625 | @@ -102,6 +102,8 @@ |
626 | break |
627 | |
628 | revids = list(revids) |
629 | + if len(revids) == 0: |
630 | + return |
631 | |
632 | self.load_revisions_call_count += 1 |
633 | current_call_count = self.load_revisions_call_count |
634 | |
635 | === modified file 'lib/tests/modeltest.py' |
636 | --- lib/tests/modeltest.py 2009-07-09 01:28:49 +0000 |
637 | +++ lib/tests/modeltest.py 2009-07-10 01:54:38 +0000 |
638 | @@ -431,7 +431,8 @@ |
639 | |
640 | # Check that we can get back our real parent |
641 | p = self.model.parent( index ) |
642 | - assert( self.model.parent( index ) == parent ) |
643 | + assert( p.internalId() == parent.internalId() ) |
644 | + assert( p.row() == parent.row() ) |
645 | |
646 | # recursively go down the children |
647 | if self.model.hasChildren(index) and depth < 10: |
648 | |
649 | === modified file 'lib/treewidget.py' |
650 | --- lib/treewidget.py 2009-07-10 00:27:28 +0000 |
651 | +++ lib/treewidget.py 2009-07-14 03:46:05 +0000 |
652 | @@ -20,7 +20,9 @@ |
653 | import os |
654 | from time import (strftime, localtime) |
655 | from PyQt4 import QtCore, QtGui |
656 | +from bzrlib import errors |
657 | from bzrlib.workingtree import WorkingTree |
658 | +from bzrlib.revisiontree import RevisionTree |
659 | |
660 | from bzrlib.plugins.qbzr.lib.cat import QBzrCatWindow |
661 | from bzrlib.plugins.qbzr.lib.annotate import AnnotateWindow |
662 | @@ -37,7 +39,6 @@ |
663 | get_apparent_author_name, |
664 | ) |
665 | from bzrlib.plugins.qbzr.lib.uifactory import ui_current_widget |
666 | -from bzrlib.plugins.qbzr.lib.wtlist import ChangeDesc |
667 | from bzrlib.plugins.qbzr.lib.subprocess import SimpleSubProcessDialog |
668 | from bzrlib.plugins.qbzr.lib.diff import ( |
669 | show_diff, |
670 | @@ -45,18 +46,167 @@ |
671 | ExtDiffMenu, |
672 | InternalWTDiffArgProvider, |
673 | ) |
674 | - |
675 | - |
676 | - |
677 | -class UnversionedItem(): |
678 | - __slots__ = ["name", "path", "kind"] |
679 | - def __init__(self, name, path, kind): |
680 | +from bzrlib.plugins.qbzr.lib.i18n import gettext, N_ |
681 | + |
682 | + |
683 | +class InternalItem(): |
684 | + __slots__ = ["name", "kind", "file_id"] |
685 | + def __init__(self, name, kind, file_id): |
686 | self.name = name |
687 | - self.path = path |
688 | self.kind = kind |
689 | + self.file_id = file_id |
690 | |
691 | revision = property(lambda self:None) |
692 | - file_id = property(lambda self:None) |
693 | + |
694 | +class UnversionedItem(InternalItem): |
695 | + def __init__(self, name, kind): |
696 | + InternalItem.__init__(self, name, kind, None) |
697 | + |
698 | +class ModelItemData(): |
699 | + __slots__ = ["id", "item", "change", "checked", "children_ids", |
700 | + "parent_id", "row", "path"] |
701 | + |
702 | + def __init__(self, item, change, path): |
703 | + self.item = item |
704 | + self.change = change |
705 | + self.path = path |
706 | + if change is not None and change.is_ignored() is None: |
707 | + self.checked = QtCore.Qt.Checked |
708 | + else: |
709 | + self.checked = QtCore.Qt.Unchecked |
710 | + |
711 | + self.children_ids = None |
712 | + self.parent_id = None |
713 | + self.id = None |
714 | + self.row = None |
715 | + |
716 | +class PersistantItemReference(object): |
717 | + """This is use to stores a reference to a item that is persisted when we |
718 | + refresh the model.""" |
719 | + __slots__ = ["file_id", "path"] |
720 | + |
721 | + def __init__(self, file_id, path): |
722 | + self.file_id = file_id |
723 | + self.path = path |
724 | + |
725 | +class ChangeDesc(tuple): |
726 | + """Helper class that "knows" about internals of iter_changes' changed entry |
727 | + description tuple, and provides additional helper methods. |
728 | + |
729 | + iter_changes return tuple with info about changed entry: |
730 | + [0]: file_id -> ascii string |
731 | + [1]: paths -> 2-tuple (old, new) fullpaths unicode/None |
732 | + [2]: changed_content -> bool |
733 | + [3]: versioned -> 2-tuple (bool, bool) |
734 | + [4]: parent -> 2-tuple |
735 | + [5]: name -> 2-tuple (old_name, new_name) utf-8?/None |
736 | + [6]: kind -> 2-tuple (string/None, string/None) |
737 | + [7]: executable -> 2-tuple (bool/None, bool/None) |
738 | + |
739 | + --optional-- |
740 | + [8]: is_ignored -> If the file is ignored, pattern which caused it to |
741 | + be ignored, otherwise None. |
742 | + |
743 | + NOTE: None value used for non-existing entry in corresponding |
744 | + tree, e.g. for added/deleted/ignored/unversioned |
745 | + """ |
746 | + |
747 | + # XXX We should may be try get this into bzrlib. |
748 | + # XXX We should use this in qdiff. |
749 | + |
750 | + def fileid(desc): |
751 | + return desc[0] |
752 | + |
753 | + def path(desc): |
754 | + """Return a suitable entry for a 'specific_files' param to bzr functions.""" |
755 | + oldpath, newpath = desc[1] |
756 | + return newpath or oldpath |
757 | + |
758 | + def oldpath(desc): |
759 | + """Return oldpath for renames.""" |
760 | + return desc[1][0] |
761 | + |
762 | + def kind(desc): |
763 | + oldkind, newkind = desc[6] |
764 | + return newkind or oldkind |
765 | + |
766 | + def is_versioned(desc): |
767 | + return desc[3] != (False, False) |
768 | + |
769 | + def is_modified(desc): |
770 | + return (desc[3] != (False, False) and desc[2]) |
771 | + |
772 | + def is_renamed(desc): |
773 | + return (desc[3] == (True, True) |
774 | + and (desc[4][0], desc[5][0]) != (desc[4][1], desc[5][1])) |
775 | + |
776 | + def is_tree_root(desc): |
777 | + """Check if entry actually tree root.""" |
778 | + if desc[3] != (False, False) and desc[4] == (None, None): |
779 | + # TREE_ROOT has not parents (desc[4]). |
780 | + # But because we could want to see unversioned files |
781 | + # we need to check for versioned flag (desc[3]) |
782 | + return True |
783 | + return False |
784 | + |
785 | + def is_missing(desc): |
786 | + """Check if file was present in previous revision but now it's gone |
787 | + (i.e. deleted manually, without invoking `bzr remove` command) |
788 | + """ |
789 | + return (desc[3] == (True, True) and desc[6][1] is None) |
790 | + |
791 | + def is_misadded(desc): |
792 | + """Check if file was added to the working tree but then gone |
793 | + (i.e. deleted manually, without invoking `bzr remove` command) |
794 | + """ |
795 | + return (desc[3] == (False, True) and desc[6][1] is None) |
796 | + |
797 | + def is_ignored(desc): |
798 | + if len(desc) >= 8: |
799 | + return desc[8] |
800 | + else: |
801 | + return None |
802 | + |
803 | + def status(desc): |
804 | + if len(desc) == 8: |
805 | + (file_id, (path_in_source, path_in_target), |
806 | + changed_content, versioned, parent, name, kind, |
807 | + executable) = desc |
808 | + is_ignored = None |
809 | + elif len(desc) == 9: |
810 | + (file_id, (path_in_source, path_in_target), |
811 | + changed_content, versioned, parent, name, kind, |
812 | + executable, is_ignored) = desc |
813 | + else: |
814 | + raise RuntimeError, "Unkown number of items to unpack." |
815 | + |
816 | + if versioned == (False, False): |
817 | + if is_ignored: |
818 | + return gettext("ignored") |
819 | + else: |
820 | + return gettext("non-versioned") |
821 | + elif versioned == (False, True): |
822 | + return gettext("added") |
823 | + elif versioned == (True, False): |
824 | + return gettext("removed") |
825 | + elif kind[0] is not None and kind[1] is None: |
826 | + return gettext("missing") |
827 | + else: |
828 | + # versioned = True, True - so either renamed or modified |
829 | + # or properties changed (x-bit). |
830 | + renamed = (parent[0], name[0]) != (parent[1], name[1]) |
831 | + if renamed: |
832 | + if changed_content: |
833 | + return gettext("renamed and modified") |
834 | + else: |
835 | + return gettext("renamed") |
836 | + elif changed_content: |
837 | + return gettext("modified") |
838 | + elif executable[0] != executable[1]: |
839 | + return gettext("modified (x-bit)") |
840 | + else: |
841 | + raise RuntimeError, "what status am I missing??" |
842 | + |
843 | |
844 | class TreeModel(QtCore.QAbstractItemModel): |
845 | |
846 | @@ -68,10 +218,6 @@ |
847 | gettext("Status")] |
848 | NAME, DATE, REVNO, MESSAGE, AUTHOR, STATUS = range(len(HEADER_LABELS)) |
849 | |
850 | - REVID = QtCore.Qt.UserRole + 1 |
851 | - FILEID = QtCore.Qt.UserRole + 2 |
852 | - PATH = QtCore.Qt.UserRole + 3 |
853 | - |
854 | def __init__(self, file_icon, dir_icon, symlink_icon, parent=None): |
855 | QtCore.QAbstractTableModel.__init__(self, parent) |
856 | |
857 | @@ -79,39 +225,84 @@ |
858 | self.dir_icon = dir_icon |
859 | self.symlink_icon = symlink_icon |
860 | self.tree = None |
861 | - self.inventory_items = [] |
862 | - self.dir_children_ids = {} |
863 | + self.inventory_data = [] |
864 | + self.inventory_data_by_path = {} |
865 | + self.inventory_data_by_id = {} # Will not contain unversioned items. |
866 | self.parent_ids = [] |
867 | + self.checkable = False |
868 | |
869 | - def set_tree(self, tree, branch): |
870 | + def set_tree(self, tree, branch=None, |
871 | + changes_mode=False, want_unversioned=True, |
872 | + initial_selected_paths=None): |
873 | self.tree = tree |
874 | self.branch = branch |
875 | self.revno_map = None |
876 | + self.changes_mode = changes_mode |
877 | |
878 | self.changes = {} |
879 | self.unver_by_parent = {} |
880 | - |
881 | + self.inventory_data_by_path = {} |
882 | + self.inventory_data_by_id = {} |
883 | + |
884 | if isinstance(self.tree, WorkingTree): |
885 | tree.lock_read() |
886 | try: |
887 | + root_id = self.tree.get_root_id() |
888 | for change in self.tree.iter_changes(self.tree.basis_tree(), |
889 | - want_unversioned=True): |
890 | + want_unversioned=want_unversioned): |
891 | change = ChangeDesc(change) |
892 | path = change.path() |
893 | + fileid = change.fileid() |
894 | is_ignored = self.tree.is_ignored(path) |
895 | change = ChangeDesc(change+(is_ignored,)) |
896 | |
897 | - if change.fileid() is not None: |
898 | + if fileid is not None and not changes_mode: |
899 | self.changes[change.fileid()] = change |
900 | else: |
901 | - (dir_path, slash, name) = path.rpartition('/') |
902 | - dir_fileid = self.tree.path2id(dir_path) |
903 | + if changes_mode: |
904 | + dir_path = path |
905 | + dir_fileid = None |
906 | + relpath = "" |
907 | + while dir_path: |
908 | + (dir_path, slash, name) = dir_path.rpartition('/') |
909 | + relpath = slash + name + relpath |
910 | + if dir_path in self.inventory_data_by_path: |
911 | + dir_item = self.inventory_data_by_path[ |
912 | + dir_path] |
913 | + dir_fileid = dir_item.item.file_id |
914 | + break |
915 | + if dir_fileid is None: |
916 | + dir_fileid = root_id |
917 | + dir_path = "" |
918 | + |
919 | + name = relpath.lstrip("/") |
920 | + if change.is_renamed(): |
921 | + oldpath = change.oldpath() |
922 | + if oldpath.startswith(dir_path): |
923 | + oldpath = oldpath[len(dir_path):] |
924 | + else: |
925 | + # The file was mv from a difirent path. |
926 | + oldpath = '/' + oldpath |
927 | + name = "%s => %s" % (oldpath, name) |
928 | + else: |
929 | + (dir_path, slash, name) = path.rpartition('/') |
930 | + dir_fileid = self.tree.path2id(dir_path) |
931 | + |
932 | + if change.is_versioned(): |
933 | + if changes_mode: |
934 | + item = InternalItem(name, change.kind(), |
935 | + change.fileid()) |
936 | + else: |
937 | + item = self.tree.inventory[change.fileid()] |
938 | + else: |
939 | + item = UnversionedItem(name, change.kind()) |
940 | + |
941 | + item_data = ModelItemData(item, change, path) |
942 | |
943 | if dir_fileid not in self.unver_by_parent: |
944 | self.unver_by_parent[dir_fileid] = [] |
945 | - self.unver_by_parent[dir_fileid].append(( |
946 | - UnversionedItem(name, path, change.kind()), |
947 | - change)) |
948 | + self.unver_by_parent[dir_fileid].append(item_data) |
949 | + self.inventory_data_by_path[path] = item_data |
950 | |
951 | self.process_inventory(self.working_tree_get_children) |
952 | finally: |
953 | @@ -119,20 +310,22 @@ |
954 | else: |
955 | self.process_inventory(self.revision_tree_get_children) |
956 | |
957 | - def revision_tree_get_children(self, item): |
958 | - for child in item.children.itervalues(): |
959 | - yield (child, None) |
960 | + def revision_tree_get_children(self, item_data): |
961 | + for child in item_data.item.children.itervalues(): |
962 | + path = self.tree.id2path(child.file_id) |
963 | + yield ModelItemData(child, None, path) |
964 | |
965 | - def working_tree_get_children(self, item): |
966 | + def working_tree_get_children(self, item_data): |
967 | + item = item_data.item |
968 | if isinstance(item, UnversionedItem): |
969 | - abspath = self.tree.abspath(item.path) |
970 | + abspath = self.tree.abspath(item_data.path) |
971 | |
972 | for name in os.listdir(abspath): |
973 | - path = item.path+"/"+name |
974 | + path = item_data.path+"/"+name |
975 | (kind, |
976 | executable, |
977 | stat_value) = self.tree._comparison_data(None, path) |
978 | - child = UnversionedItem(name, path, kind) |
979 | + child = UnversionedItem(name, kind) |
980 | is_ignored = self.tree.is_ignored(path) |
981 | change = ChangeDesc((None, |
982 | (None, path), |
983 | @@ -143,9 +336,10 @@ |
984 | (None, kind), |
985 | (None, executable), |
986 | is_ignored)) |
987 | - yield (child, change) |
988 | + yield ModelItemData(child, change, path) |
989 | |
990 | - elif item.children is not None: |
991 | + if (not isinstance(item, InternalItem) and |
992 | + item.children is not None and not self.changes_mode): |
993 | #Because we create copies, we have to get the real item. |
994 | item = self.tree.inventory[item.file_id] |
995 | for child in item.children.itervalues(): |
996 | @@ -155,35 +349,36 @@ |
997 | change = self.changes[child.file_id] |
998 | else: |
999 | change = None |
1000 | - yield (child, change) |
1001 | + path = self.tree.id2path(child.file_id) |
1002 | + yield ModelItemData(child, change, path) |
1003 | if item.file_id in self.unver_by_parent: |
1004 | - for (child, change) in self.unver_by_parent[item.file_id]: |
1005 | - yield (child, change) |
1006 | + for item_data in self.unver_by_parent[item.file_id]: |
1007 | + yield item_data |
1008 | |
1009 | def load_dir(self, dir_id): |
1010 | + if dir_id>=len(self.inventory_data): |
1011 | + return |
1012 | + dir_item = self.inventory_data[dir_id] |
1013 | + if dir_item.children_ids is not None: |
1014 | + return |
1015 | + |
1016 | if isinstance(self.tree, WorkingTree): |
1017 | self.tree.lock_read() |
1018 | try: |
1019 | - if dir_id>=len(self.inventory_items): |
1020 | - return |
1021 | - dir_item, dir_change = self.inventory_items[dir_id] |
1022 | - dir_children_ids = [] |
1023 | + dir_item.children_ids = [] |
1024 | children = sorted(self.get_children(dir_item), |
1025 | self.inventory_dirs_first_cmp, |
1026 | - lambda x: x[0]) |
1027 | + lambda x: (x.item.name, x.item.kind)) |
1028 | |
1029 | parent_model_index = self._index_from_id(dir_id, 0) |
1030 | self.beginInsertRows(parent_model_index, 0, len(children)-1) |
1031 | try: |
1032 | - for (child, change) in children: |
1033 | - child_id = self.append_item(child, change, dir_id) |
1034 | - dir_children_ids.append(child_id) |
1035 | - if child.kind == "directory": |
1036 | - self.dir_children_ids[child_id] = None |
1037 | + for child in children: |
1038 | + child_id = self.append_item(child, dir_id) |
1039 | + dir_item.children_ids.append(child_id) |
1040 | |
1041 | - if len(dir_children_ids) % 100 == 0: |
1042 | + if len(dir_item.children_ids) % 100 == 0: |
1043 | QtCore.QCoreApplication.processEvents() |
1044 | - self.dir_children_ids[dir_id] = dir_children_ids |
1045 | finally: |
1046 | self.endInsertRows(); |
1047 | finally: |
1048 | @@ -194,37 +389,72 @@ |
1049 | self.get_children = get_children |
1050 | |
1051 | self.emit(QtCore.SIGNAL("layoutAboutToBeChanged()")) |
1052 | - self.inventory_items = [] |
1053 | - self.dir_children_ids = {} |
1054 | + |
1055 | + is_refresh = len(self.inventory_data)>0 |
1056 | + if is_refresh: |
1057 | + self.beginRemoveRows(QtCore.QModelIndex(), 0, |
1058 | + len(self.inventory_data[0].children_ids)-1) |
1059 | + self.inventory_data = [] |
1060 | self.parent_ids = [] |
1061 | + if is_refresh: |
1062 | + self.endRemoveRows() |
1063 | + |
1064 | + root_item = ModelItemData(self.tree.inventory[self.tree.get_root_id()], |
1065 | + None, '') |
1066 | + root_id = self.append_item(root_item, None) |
1067 | + self.load_dir(root_id) |
1068 | self.emit(QtCore.SIGNAL("layoutChanged()")) |
1069 | - |
1070 | - root_item = self.tree.inventory[self.tree.get_root_id()] |
1071 | - root_id = self.append_item(root_item, None, None) |
1072 | - self.dir_children_ids[root_id] = None |
1073 | - self.load_dir(root_id) |
1074 | |
1075 | - def append_item(self, item, change, parent_id): |
1076 | - id = len(self.inventory_items) |
1077 | - self.inventory_items.append((item, change)) |
1078 | - self.parent_ids.append(parent_id) |
1079 | - return id |
1080 | + def append_item(self, item_data, parent_id): |
1081 | + item_data.id = len(self.inventory_data) |
1082 | + if parent_id is not None: |
1083 | + parent_data = self.inventory_data[parent_id] |
1084 | + if self.is_item_in_select_all(item_data): |
1085 | + item_data.checked = parent_data.checked |
1086 | + else: |
1087 | + item_data.checked = False |
1088 | + item_data.row = len(parent_data.children_ids) |
1089 | + else: |
1090 | + item_data.checked = QtCore.Qt.Checked |
1091 | + item_data.row = 0 |
1092 | + item_data.parent_id = parent_id |
1093 | + self.inventory_data.append(item_data) |
1094 | + self.inventory_data_by_path[item_data.path] = item_data |
1095 | + if not isinstance(item_data.item, UnversionedItem): |
1096 | + self.inventory_data_by_id[item_data.item.file_id] = item_data |
1097 | + return item_data.id |
1098 | |
1099 | def inventory_dirs_first_cmp(self, x, y): |
1100 | - x_is_dir = x.kind =="directory" |
1101 | - y_is_dir = y.kind =="directory" |
1102 | + (x_name, x_kind) = x |
1103 | + (y_name, y_kind) = y |
1104 | + x_a = x_name |
1105 | + y_a = y_name |
1106 | + x_is_dir = x_kind =="directory" |
1107 | + y_is_dir = y_kind =="directory" |
1108 | + while True: |
1109 | + x_b, sep, x_a_t = x_a.partition("/") |
1110 | + y_b, sep, y_a_t = y_a.partition("/") |
1111 | + if x_a_t == "" and y_a_t == "": |
1112 | + break |
1113 | + if (x_is_dir or not x_a_t == "") and not (y_is_dir or not y_a_t == ""): |
1114 | + return -1 |
1115 | + if (y_is_dir or not y_a_t == "") and not (x_is_dir or not x_a_t == ""): |
1116 | + return 1 |
1117 | + cmp_r = cmp(x_b, y_b) |
1118 | + if not cmp_r == 0: |
1119 | + return cmp_r |
1120 | + x_a = x_a_t |
1121 | + y_a = y_a_t |
1122 | if x_is_dir and not y_is_dir: |
1123 | return -1 |
1124 | if y_is_dir and not x_is_dir: |
1125 | return 1 |
1126 | - return cmp(x.name, y.name) |
1127 | + return cmp(x_name, y_name) |
1128 | |
1129 | def set_revno_map(self, revno_map): |
1130 | self.revno_map = revno_map |
1131 | - for id in xrange(1, len(self.inventory_items)): |
1132 | - parent_id = self.parent_ids[id] |
1133 | - row = self.dir_children_ids[parent_id].index(id) |
1134 | - index = self.createIndex (row, self.REVNO, id) |
1135 | + for item_data in self.inventory_data[1:0]: |
1136 | + index = self.createIndex (item_data.row, self.REVNO, item_data.id) |
1137 | self.emit(QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"), |
1138 | index,index) |
1139 | |
1140 | @@ -232,43 +462,48 @@ |
1141 | return len(self.HEADER_LABELS) |
1142 | |
1143 | def rowCount(self, parent): |
1144 | - parent_id = parent.internalId() |
1145 | - if parent_id not in self.dir_children_ids: |
1146 | - return 0 |
1147 | - dir_children_ids = self.dir_children_ids[parent_id] |
1148 | - if dir_children_ids is None: |
1149 | - return 0 |
1150 | - return len(dir_children_ids) |
1151 | + if parent.internalId()>=len(self.inventory_data): |
1152 | + return 0 |
1153 | + parent_data = self.inventory_data[parent.internalId()] |
1154 | + if parent_data.children_ids is None: |
1155 | + return 0 |
1156 | + return len(parent_data.children_ids) |
1157 | |
1158 | def canFetchMore(self, parent): |
1159 | - parent_id = parent.internalId() |
1160 | - return parent_id in self.dir_children_ids \ |
1161 | - and self.dir_children_ids[parent_id] is None |
1162 | + if parent.internalId()>=len(self.inventory_data): |
1163 | + return False |
1164 | + parent_data = self.inventory_data[parent.internalId()] |
1165 | + return (parent_data.children_ids is None and |
1166 | + parent_data.item.kind == "directory") |
1167 | |
1168 | def fetchMore(self, parent): |
1169 | self.load_dir(parent.internalId()) |
1170 | |
1171 | def _index(self, row, column, parent_id): |
1172 | - if parent_id not in self.dir_children_ids: |
1173 | - return QtCore.QModelIndex() |
1174 | - dir_children_ids = self.dir_children_ids[parent_id] |
1175 | - if dir_children_ids is None: |
1176 | - return QtCore.QModelIndex() |
1177 | - if row >= len(dir_children_ids): |
1178 | - return QtCore.QModelIndex() |
1179 | - item_id = dir_children_ids[row] |
1180 | + if parent_id>=len(self.inventory_data): |
1181 | + return QtCore.QModelIndex() |
1182 | + |
1183 | + parent_data = self.inventory_data[parent_id] |
1184 | + if parent_data.children_ids is None: |
1185 | + return QtCore.QModelIndex() |
1186 | + |
1187 | + if (row < 0 or |
1188 | + row >= len(parent_data.children_ids) or |
1189 | + column < 0 or |
1190 | + column >= len(self.HEADER_LABELS)): |
1191 | + return QtCore.QModelIndex() |
1192 | + item_id = parent_data.children_ids[row] |
1193 | return self.createIndex(row, column, item_id) |
1194 | |
1195 | def _index_from_id(self, item_id, column): |
1196 | - parent_id = self.parent_ids[item_id] |
1197 | - if parent_id is None: |
1198 | + if item_id >= len(self.inventory_data): |
1199 | return QtCore.QModelIndex() |
1200 | - row = self.dir_children_ids[parent_id].index(item_id) |
1201 | - return self.createIndex(row, column, item_id) |
1202 | + |
1203 | + item_data = self.inventory_data[item_id] |
1204 | + return self.createIndex(item_data.row, column, item_id) |
1205 | |
1206 | def index(self, row, column, parent = QtCore.QModelIndex()): |
1207 | - parent_id = parent.internalId() |
1208 | - return self._index(row, column, parent_id) |
1209 | + return self._index(row, column, parent.internalId()) |
1210 | |
1211 | def sibling(self, row, column, index): |
1212 | sibling_id = index.internalId() |
1213 | @@ -281,28 +516,109 @@ |
1214 | child_id = child.internalId() |
1215 | if child_id == 0: |
1216 | return QtCore.QModelIndex() |
1217 | - if child_id not in self.parent_ids: |
1218 | - return QtCore.QModelIndex() |
1219 | - item_id = self.parent_ids[child_id] |
1220 | - if item_id == 0 : |
1221 | + item_data = self.inventory_data[child_id] |
1222 | + if item_data.parent_id == 0 : |
1223 | return QtCore.QModelIndex() |
1224 | |
1225 | - return self._index_from_id(item_id, 0) |
1226 | + return self._index_from_id(item_data.parent_id, 0) |
1227 | |
1228 | def hasChildren(self, parent): |
1229 | - parent_id = parent.internalId() |
1230 | - return parent_id in self.dir_children_ids |
1231 | + if self.tree is None: |
1232 | + return False |
1233 | + if parent.internalId() == 0: |
1234 | + return True |
1235 | + item_data = self.inventory_data[parent.internalId()] |
1236 | + return item_data.item.kind == "directory" |
1237 | + |
1238 | + is_item_in_select_all = lambda self, item: True |
1239 | + """Returns wether an item is changed when select all is clicked.""" |
1240 | + |
1241 | + def setData(self, index, value, role): |
1242 | + |
1243 | + |
1244 | + def set_checked(item_data, checked): |
1245 | + old_checked = item_data.checked |
1246 | + item_data.checked = checked |
1247 | + if not old_checked == checked: |
1248 | + index = self.createIndex (item_data.row, self.NAME, item_data.id) |
1249 | + self.emit(QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"), |
1250 | + index,index) |
1251 | + |
1252 | + if index.column() == self.NAME and role == QtCore.Qt.CheckStateRole: |
1253 | + value = value.toInt()[0] |
1254 | + if index.internalId() >= len(self.inventory_data): |
1255 | + return False |
1256 | + |
1257 | + item_data = self.inventory_data[index.internalId()] |
1258 | + set_checked(item_data, value) |
1259 | + |
1260 | + # Recursively set all children to checked. |
1261 | + if item_data.children_ids is not None: |
1262 | + children_ids = list(item_data.children_ids) |
1263 | + while children_ids: |
1264 | + child = self.inventory_data[children_ids.pop(0)] |
1265 | + |
1266 | + # If unchecking, uncheck everything, but if checking, |
1267 | + # only check those in "select_all" get checked. |
1268 | + if (self.is_item_in_select_all(child) or |
1269 | + value == QtCore.Qt.Unchecked): |
1270 | + |
1271 | + set_checked(child, value) |
1272 | + if child.children_ids is not None: |
1273 | + children_ids.extend(child.children_ids) |
1274 | + |
1275 | + # Walk up the tree, and update every dir |
1276 | + parent_data = item_data |
1277 | + while parent_data.parent_id is not None: |
1278 | + parent_data = self.inventory_data[parent_data.parent_id] |
1279 | + has_checked = False |
1280 | + has_unchecked = False |
1281 | + for child_id in parent_data.children_ids: |
1282 | + child = self.inventory_data[child_id] |
1283 | + |
1284 | + if child.checked == QtCore.Qt.Checked: |
1285 | + has_checked = True |
1286 | + elif (child.checked == QtCore.Qt.Unchecked and |
1287 | + self.is_item_in_select_all(child)): |
1288 | + has_unchecked = True |
1289 | + elif child.checked == QtCore.Qt.PartiallyChecked: |
1290 | + has_checked = True |
1291 | + if self.is_item_in_select_all(child): |
1292 | + has_unchecked = True |
1293 | + |
1294 | + if has_checked and has_unchecked: |
1295 | + break |
1296 | + |
1297 | + if has_checked and has_unchecked: |
1298 | + set_checked(parent_data, QtCore.Qt.PartiallyChecked) |
1299 | + elif has_checked: |
1300 | + set_checked(parent_data, QtCore.Qt.Checked) |
1301 | + else: |
1302 | + set_checked(parent_data, QtCore.Qt.Unchecked) |
1303 | + |
1304 | + return True |
1305 | + |
1306 | + return False |
1307 | + |
1308 | + REVID = QtCore.Qt.UserRole + 1 |
1309 | + FILEID = QtCore.Qt.UserRole + 2 |
1310 | + PATH = QtCore.Qt.UserRole + 3 |
1311 | + INTERNALID = QtCore.Qt.UserRole + 4 |
1312 | |
1313 | def data(self, index, role): |
1314 | if not index.isValid(): |
1315 | return QtCore.QVariant() |
1316 | |
1317 | - (item, change) = self.inventory_items[index.internalId()] |
1318 | + if role == self.INTERNALID: |
1319 | + return QtCore.QVariant(index.internalId()) |
1320 | + |
1321 | + item_data = self.inventory_data[index.internalId()] |
1322 | + item = item_data.item |
1323 | |
1324 | if role == self.FILEID: |
1325 | return QtCore.QVariant(item.file_id) |
1326 | |
1327 | - revid = item.revision |
1328 | + revid = item_data.item.revision |
1329 | if role == self.REVID: |
1330 | if revid is None: |
1331 | return QtCore.QVariant() |
1332 | @@ -321,13 +637,18 @@ |
1333 | if item.kind == "symlink": |
1334 | return QtCore.QVariant(self.symlink_icon) |
1335 | return QtCore.QVariant() |
1336 | + if role == QtCore.Qt.CheckStateRole: |
1337 | + if not self.checkable: |
1338 | + return QtCore.QVariant() |
1339 | + else: |
1340 | + return QtCore.QVariant(item_data.checked) |
1341 | |
1342 | if column == self.STATUS: |
1343 | if role == QtCore.Qt.DisplayRole: |
1344 | - if change is not None: |
1345 | - return QtCore.QVariant(change.status()) |
1346 | + if item_data.change is not None: |
1347 | + return QtCore.QVariant(item_data.change.status()) |
1348 | else: |
1349 | - return QtCore.QVariant() |
1350 | + return QtCore.QVariant("") |
1351 | |
1352 | if column == self.REVNO: |
1353 | if role == QtCore.Qt.DisplayRole: |
1354 | @@ -336,7 +657,7 @@ |
1355 | return QtCore.QVariant( |
1356 | ".".join(["%d" % (revno) for revno in revno_sequence])) |
1357 | else: |
1358 | - return QtCore.QVariant() |
1359 | + return QtCore.QVariant("") |
1360 | |
1361 | if role == QtCore.Qt.DisplayRole: |
1362 | if revid in cached_revisions: |
1363 | @@ -353,25 +674,23 @@ |
1364 | localtime(rev.timestamp))) |
1365 | |
1366 | if role == self.PATH: |
1367 | - if isinstance(item, UnversionedItem): |
1368 | - path = item.path |
1369 | - else: |
1370 | - if isinstance(self.tree, WorkingTree): |
1371 | - self.tree.lock_read() |
1372 | - try: |
1373 | - path = self.tree.id2path(item.file_id) |
1374 | - finally: |
1375 | - if isinstance(self.tree, WorkingTree): |
1376 | - self.tree.unlock() |
1377 | - return QtCore.QVariant(path) |
1378 | + return QtCore.QVariant(item_data.path) |
1379 | |
1380 | + if role == QtCore.Qt.DisplayRole: |
1381 | + return QtCore.QVariant("") |
1382 | return QtCore.QVariant() |
1383 | |
1384 | def flags(self, index): |
1385 | - if not index.isValid(): |
1386 | - return QtCore.Qt.ItemIsEnabled |
1387 | + #if not index.isValid(): |
1388 | + # return QtCore.Qt.ItemIsEnabled |
1389 | |
1390 | - return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable |
1391 | + if self.checkable and index.column() == self.NAME: |
1392 | + return (QtCore.Qt.ItemIsEnabled | |
1393 | + QtCore.Qt.ItemIsSelectable | |
1394 | + QtCore.Qt.ItemIsUserCheckable) |
1395 | + else: |
1396 | + return (QtCore.Qt.ItemIsEnabled | |
1397 | + QtCore.Qt.ItemIsSelectable) |
1398 | |
1399 | def headerData(self, section, orientation, role): |
1400 | if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole: |
1401 | @@ -380,19 +699,102 @@ |
1402 | |
1403 | def on_revisions_loaded(self, revisions, last_call): |
1404 | inventory = self.tree.inventory |
1405 | - for id, (item, change) in enumerate(self.inventory_items): |
1406 | - if id == 0: |
1407 | + for item_data in self.inventory_data: |
1408 | + if item_data.id == 0: |
1409 | continue |
1410 | |
1411 | - if item.revision in revisions: |
1412 | - parent_id = self.parent_ids[id] |
1413 | - row = self.dir_children_ids[parent_id].index(id) |
1414 | + if item_data.item.revision in revisions: |
1415 | self.emit(QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"), |
1416 | - self.createIndex (row, self.DATE, id), |
1417 | - self.createIndex (row, self.AUTHOR,id)) |
1418 | + self.createIndex (item_data.row, self.DATE, item_data.id), |
1419 | + self.createIndex (item_data.row, self.AUTHOR, item_data.id)) |
1420 | |
1421 | def get_repo(self): |
1422 | return self.branch.repository |
1423 | + |
1424 | + def _item2ref(self, item_data): |
1425 | + if isinstance(item_data.item, UnversionedItem): |
1426 | + file_id = None |
1427 | + else: |
1428 | + file_id = item_data.item.file_id |
1429 | + return PersistantItemReference(file_id, item_data.path) |
1430 | + |
1431 | + def index2ref(self, index): |
1432 | + item_data = self.inventory_data[index.internalId()] |
1433 | + return self._item2ref(item_data) |
1434 | + |
1435 | + def indexes2refs(self, indexes): |
1436 | + refs = [] |
1437 | + for index in indexes: |
1438 | + refs.append(self.index2ref(index)) |
1439 | + return refs |
1440 | + |
1441 | + def ref2index(self, ref): |
1442 | + if ref.file_id is not None: |
1443 | + key = ref.file_id |
1444 | + dict = self.inventory_data_by_id |
1445 | + def iter_parents(): |
1446 | + parent_id = ref.file_id |
1447 | + parent_ids = [] |
1448 | + while parent_id is not None: |
1449 | + parent_id = self.tree.inventory[parent_id].parent_id |
1450 | + parent_ids.append(parent_id) |
1451 | + return reversed(parent_ids) |
1452 | + else: |
1453 | + key = ref.path |
1454 | + dict = self.inventory_data_by_path |
1455 | + def iter_parents(): |
1456 | + path_split = ref.path.split("/") |
1457 | + parent_dir_path = None |
1458 | + for parent_name in ref.path.split("/")[:-1]: |
1459 | + if parent_dir_path is None:\ |
1460 | + parent_dir_path = parent_name |
1461 | + else: |
1462 | + parent_dir_path += "/" + parent_name |
1463 | + yield parent_dir_path |
1464 | + |
1465 | + if key not in dict: |
1466 | + # Try loading the parents |
1467 | + for parent_key in iter_parents(): |
1468 | + if parent_key not in dict: |
1469 | + break |
1470 | + self.load_dir(dict[parent_key].id) |
1471 | + |
1472 | + if key not in dict: |
1473 | + raise errors.NoSuchFile(ref.path) |
1474 | + |
1475 | + return self._index_from_id(dict[key].id, self.NAME) |
1476 | + |
1477 | + def refs2indexes(self, refs, ignore_no_file_error=False): |
1478 | + indexes = [] |
1479 | + for ref in refs: |
1480 | + try: |
1481 | + indexes.append(self.ref2index(ref)) |
1482 | + except (errors.NoSuchId, errors.NoSuchFile): |
1483 | + if not ignore_no_file_error: |
1484 | + raise |
1485 | + return indexes |
1486 | + |
1487 | + def iter_checked(self): |
1488 | + return [self._item2ref(item_data) |
1489 | + for item_data in sorted( |
1490 | + [item_data for item_data in self.inventory_data[1:] |
1491 | + if item_data.checked == QtCore.Qt.Checked], |
1492 | + self.inventory_dirs_first_cmp, |
1493 | + lambda x: (x.change.path(), x.item.kind))] |
1494 | + |
1495 | + def set_checked_items(self, refs, ignore_no_file_error=False): |
1496 | + # set every thing off |
1497 | + root_index = self._index_from_id(0, self.NAME) |
1498 | + self.setData(root_index, QtCore.QVariant(QtCore.Qt.Unchecked), |
1499 | + QtCore.Qt.CheckStateRole) |
1500 | + |
1501 | + for index in self.refs2indexes(refs, ignore_no_file_error): |
1502 | + self.setData(index, QtCore.QVariant(QtCore.Qt.Checked), |
1503 | + QtCore.Qt.CheckStateRole) |
1504 | + |
1505 | + def set_checked_paths(self, paths): |
1506 | + return self.set_checked_items([PersistantItemReference(None, path) |
1507 | + for path in paths]) |
1508 | |
1509 | class TreeFilterProxyModel(QtGui.QSortFilterProxyModel): |
1510 | source_model = None |
1511 | @@ -430,7 +832,13 @@ |
1512 | |
1513 | model = self.source_model |
1514 | parent_id = source_parent.internalId() |
1515 | - id = model.dir_children_ids[parent_id][source_row] |
1516 | + children_ids = model.inventory_data[parent_id].children_ids |
1517 | + if len(children_ids)<=source_row: |
1518 | + return False |
1519 | + id = children_ids[source_row] |
1520 | + if (model.checkable and |
1521 | + not model.inventory_data[id].checked == QtCore.Qt.Unchecked): |
1522 | + return True |
1523 | return self.filter_id_cached(id) |
1524 | |
1525 | def filter_id_cached(self, id): |
1526 | @@ -445,27 +853,26 @@ |
1527 | (unchanged, changed, unversioned, ignored) = self.filters |
1528 | |
1529 | model = self.source_model |
1530 | - item, change = model.inventory_items[id] |
1531 | - |
1532 | - if change is None and unchanged: return True |
1533 | - |
1534 | - is_versioned = not isinstance(item, UnversionedItem) |
1535 | - |
1536 | - if is_versioned and change is not None and changed: return True |
1537 | + item_data = model.inventory_data[id] |
1538 | + |
1539 | + if item_data.change is None and unchanged: return True |
1540 | + |
1541 | + is_versioned = not isinstance(item_data.item, UnversionedItem) |
1542 | + |
1543 | + if is_versioned and item_data.change is not None and changed: |
1544 | + return True |
1545 | |
1546 | if not is_versioned and unversioned: |
1547 | - is_ignored = change.is_ignored() |
1548 | + is_ignored = item_data.change.is_ignored() |
1549 | if not is_ignored: return True |
1550 | if is_ignored and ignored: return True |
1551 | if is_ignored and not ignored: return False |
1552 | |
1553 | - if id in model.dir_children_ids: |
1554 | - dir_children_ids = model.dir_children_ids[id] |
1555 | - if dir_children_ids is None: |
1556 | + if item_data.item.kind == "directory": |
1557 | + if item_data.children_ids is None: |
1558 | model.load_dir(id) |
1559 | - dir_children_ids = model.dir_children_ids[id] |
1560 | |
1561 | - for child_id in dir_children_ids: |
1562 | + for child_id in item_data.children_ids: |
1563 | if self.filter_id_cached(child_id): |
1564 | return True |
1565 | |
1566 | @@ -540,7 +947,6 @@ |
1567 | self.connect(self, |
1568 | QtCore.SIGNAL("doubleClicked(QModelIndex)"), |
1569 | self.do_default_action) |
1570 | - self.default_action = self.show_file_content |
1571 | self.tree = None |
1572 | self.branch = None |
1573 | |
1574 | @@ -552,7 +958,7 @@ |
1575 | header.setResizeMode(self.tree_model.REVNO, QtGui.QHeaderView.Interactive) |
1576 | header.setResizeMode(self.tree_model.MESSAGE, QtGui.QHeaderView.Stretch) |
1577 | header.setResizeMode(self.tree_model.AUTHOR, QtGui.QHeaderView.Interactive) |
1578 | - header.setResizeMode(self.tree_model.STATUS, QtGui.QHeaderView.ResizeToContents) |
1579 | + header.setResizeMode(self.tree_model.STATUS, QtGui.QHeaderView.Stretch) |
1580 | fm = self.fontMetrics() |
1581 | # XXX Make this dynamic. |
1582 | col_margin = 6 |
1583 | @@ -596,10 +1002,41 @@ |
1584 | gettext("&Revert"), |
1585 | self.revert) |
1586 | |
1587 | - def set_tree(self, tree, branch): |
1588 | + def set_tree(self, tree, branch=None, |
1589 | + changes_mode=False, want_unversioned=True, |
1590 | + initial_checked_paths=None): |
1591 | + """Causes a tree to be loaded, and displayed in the widget. |
1592 | + |
1593 | + @param changes_mode: If in changes mode, a list of changes, and |
1594 | + unversioned items, rather than a tree, is diplayed. |
1595 | + e.g., when not in changes mode, one will get: |
1596 | + |
1597 | + dir1 |
1598 | + file1 changed |
1599 | + file2 changed |
1600 | + |
1601 | + but when in changes mode, one will get: |
1602 | + |
1603 | + dir1/file1 changed |
1604 | + file2 changed |
1605 | + |
1606 | + When in changes mode, no unchanged items are shown. |
1607 | + """ |
1608 | self.tree = tree |
1609 | + if isinstance(tree, RevisionTree) and branch is None: |
1610 | + raise AttributeError("A branch must be provided if the tree is a " |
1611 | + "RevisionTree") |
1612 | self.branch = branch |
1613 | - self.tree_model.set_tree(self.tree, self.branch) |
1614 | + self.changes_mode = changes_mode |
1615 | + self.want_unversioned = want_unversioned |
1616 | + self.tree_model.set_tree(self.tree, self.branch, |
1617 | + changes_mode, want_unversioned) |
1618 | + if initial_checked_paths is not None and not self.tree_model.checkable: |
1619 | + raise AttributeError("You can't have a initial_selection if " |
1620 | + "tree_model.checkable is not True.") |
1621 | + if initial_checked_paths is not None: |
1622 | + self.tree_model.set_checked_paths(initial_checked_paths) |
1623 | + |
1624 | self.tree_filter_model.invalidateFilter() |
1625 | |
1626 | if str(QtCore.QT_VERSION_STR).startswith("4.4"): |
1627 | @@ -623,7 +1060,6 @@ |
1628 | header.showSection(self.tree_model.STATUS) |
1629 | |
1630 | self.context_menu.setDefaultAction(self.action_open_file) |
1631 | - self.default_action = self.open_file |
1632 | else: |
1633 | header.showSection(self.tree_model.DATE) |
1634 | header.showSection(self.tree_model.REVNO) |
1635 | @@ -632,11 +1068,22 @@ |
1636 | header.hideSection(self.tree_model.STATUS) |
1637 | |
1638 | self.context_menu.setDefaultAction(self.action_show_file) |
1639 | - self.default_action = self.show_file_content |
1640 | |
1641 | def refresh(self): |
1642 | - self.tree_model.set_tree(self.tree, self.branch) |
1643 | - self.tree_filter_model.invalidateFilter() |
1644 | + self.tree.lock_read() |
1645 | + try: |
1646 | + if self.tree_model.checkable: |
1647 | + checked = list(self.tree_model.iter_checked()) |
1648 | + |
1649 | + self.tree_model.set_tree(self.tree, self.branch, |
1650 | + self.changes_mode, self.want_unversioned) |
1651 | + self.tree_filter_model.invalidateFilter() |
1652 | + |
1653 | + if self.tree_model.checkable: |
1654 | + self.tree_model.set_checked_items(checked, |
1655 | + ignore_no_file_error=True) |
1656 | + finally: |
1657 | + self.tree.unlock() |
1658 | |
1659 | def contextMenuEvent(self, event): |
1660 | self.filter_context_menu() |
1661 | @@ -644,35 +1091,38 @@ |
1662 | event.accept() |
1663 | |
1664 | def get_selection_indexes(self, indexes=None): |
1665 | - if indexes == None: |
1666 | + if indexes is None or (len(indexes) == 1 and indexes[0] is None): |
1667 | indexes = self.selectedIndexes() |
1668 | rows = {} |
1669 | - for index in self.selectedIndexes(): |
1670 | - if index.row() not in rows: |
1671 | - rows[index.internalId()] = index |
1672 | + for index in indexes: |
1673 | + internal_id = index.data(self.tree_model.INTERNALID).toInt()[0] |
1674 | + if internal_id not in rows: |
1675 | + rows[internal_id] = index |
1676 | return rows.values() |
1677 | |
1678 | def get_selection_items(self, indexes=None): |
1679 | items = [] |
1680 | - if indexes is None: |
1681 | + if indexes is None or len(indexes)==1 and indexes[0] is None: |
1682 | indexes = self.get_selection_indexes(indexes) |
1683 | for index in indexes: |
1684 | source_index = self.tree_filter_model.mapToSource(index) |
1685 | - items.append(self.tree_model.inventory_items[ |
1686 | + items.append(self.tree_model.inventory_data[ |
1687 | source_index.internalId()]) |
1688 | return items |
1689 | |
1690 | def filter_context_menu(self): |
1691 | is_working_tree = isinstance(self.tree, WorkingTree) |
1692 | items = self.get_selection_items() |
1693 | - versioned = [not isinstance(item[0], UnversionedItem) |
1694 | + versioned = [not isinstance(item.item, UnversionedItem) |
1695 | for item in items] |
1696 | - changed = [item[1] is not None |
1697 | + changed = [item.change is not None |
1698 | for item in items] |
1699 | + versioned_changed = [ver and ch for ver,ch in zip(versioned, changed)] |
1700 | + |
1701 | selection_len = len(items) |
1702 | |
1703 | single_versioned_file = (selection_len == 1 and versioned[0] and |
1704 | - items[0][0].kind == "file") |
1705 | + items[0].item.kind == "file") |
1706 | |
1707 | self.action_open_file.setEnabled(is_working_tree) |
1708 | self.action_open_file.setVisible(is_working_tree) |
1709 | @@ -680,24 +1130,37 @@ |
1710 | self.action_show_annotate.setEnabled(single_versioned_file) |
1711 | self.action_show_log.setEnabled(any(versioned)) |
1712 | self.action_show_diff.setVisible(is_working_tree) |
1713 | - self.action_show_diff.setEnabled(any(changed)) |
1714 | + self.action_show_diff.setEnabled(any(versioned_changed)) |
1715 | |
1716 | self.action_add.setVisible(is_working_tree) |
1717 | self.action_add.setDisabled(all(versioned)) |
1718 | self.action_revert.setVisible(is_working_tree) |
1719 | - self.action_revert.setEnabled(any(changed) and any(versioned)) |
1720 | + self.action_revert.setEnabled(any(versioned_changed)) |
1721 | + |
1722 | + if is_working_tree: |
1723 | + if any(versioned_changed): |
1724 | + self.context_menu.setDefaultAction(self.action_show_diff) |
1725 | + else: |
1726 | + self.context_menu.setDefaultAction(self.action_open_file) |
1727 | + |
1728 | |
1729 | - def do_default_action(self, index=None): |
1730 | + def do_default_action(self, index): |
1731 | indexes = self.get_selection_indexes([index]) |
1732 | if not len(indexes) == 1: |
1733 | return |
1734 | |
1735 | - item = self.get_selection_items(indexes)[0] |
1736 | - if item[0].kind == "directory": |
1737 | + item_data = self.get_selection_items(indexes)[0] |
1738 | + if item_data.item.kind == "directory": |
1739 | # Don't do anything, so that the directory can be expanded. |
1740 | return |
1741 | |
1742 | - self.default_action(index) |
1743 | + if isinstance(self.tree, WorkingTree): |
1744 | + if item_data.change is not None and item_data.change.is_versioned(): |
1745 | + self.show_differences(index=index) |
1746 | + else: |
1747 | + self.open_file(index) |
1748 | + else: |
1749 | + self.show_file_content(index) |
1750 | |
1751 | @ui_current_widget |
1752 | def show_file_content(self, index=None): |
1753 | @@ -708,7 +1171,7 @@ |
1754 | return |
1755 | |
1756 | item = self.get_selection_items(indexes)[0] |
1757 | - if isinstance(item[0], UnversionedItem): |
1758 | + if isinstance(item.item, UnversionedItem): |
1759 | return |
1760 | |
1761 | index = indexes[0] |
1762 | @@ -748,8 +1211,8 @@ |
1763 | """Show qlog for one selected file(s).""" |
1764 | |
1765 | items = self.get_selection_items() |
1766 | - fileids = [item[0].file_id for item in items |
1767 | - if item[0].file_id is not None] |
1768 | + fileids = [item.item.file_id for item in items |
1769 | + if item.item.file_id is not None] |
1770 | |
1771 | window = LogWindow(None, self.branch, fileids) |
1772 | window.show() |
1773 | @@ -767,19 +1230,15 @@ |
1774 | self.window().windows.append(window) |
1775 | |
1776 | @ui_current_widget |
1777 | - def show_differences(self, ext_diff=None): |
1778 | + def show_differences(self, ext_diff=None, index=None): |
1779 | """Show differences for selected file(s).""" |
1780 | |
1781 | - items = self.get_selection_items() |
1782 | + items = self.get_selection_items([index]) |
1783 | if len(items) > 0: |
1784 | - self.tree.lock_read() |
1785 | - try: |
1786 | - # Only paths that have changes. |
1787 | - paths = [self.tree.id2path(item[0].file_id) |
1788 | - for item in items |
1789 | - if item[1] is not None] |
1790 | - finally: |
1791 | - self.tree.unlock() |
1792 | + # Only paths that have changes. |
1793 | + paths = [item.path |
1794 | + for item in items |
1795 | + if item.change is not None] |
1796 | else: |
1797 | # Show all. |
1798 | paths = None |
1799 | @@ -800,9 +1259,9 @@ |
1800 | items = self.get_selection_items() |
1801 | |
1802 | # Only paths that are not versioned. |
1803 | - paths = [item[0].path |
1804 | + paths = [item.item.path |
1805 | for item in items |
1806 | - if isinstance(item[0], UnversionedItem)] |
1807 | + if isinstance(item.item, UnversionedItem)] |
1808 | |
1809 | if len(paths) == 0: |
1810 | return |
1811 | @@ -829,10 +1288,10 @@ |
1812 | self.tree.lock_read() |
1813 | try: |
1814 | # Only paths that have changes. |
1815 | - paths = [self.tree.id2path(item[0].file_id) |
1816 | + paths = [item.path |
1817 | for item in items |
1818 | - if item[1] is not None and |
1819 | - not isinstance(item[0], UnversionedItem)] |
1820 | + if item.change is not None and |
1821 | + not isinstance(item.item, UnversionedItem)] |
1822 | finally: |
1823 | self.tree.unlock() |
1824 | |
1825 | @@ -851,4 +1310,39 @@ |
1826 | res = revert_dialog.exec_() |
1827 | if res == QtGui.QDialog.Accepted: |
1828 | self.refresh() |
1829 | - |
1830 | \ No newline at end of file |
1831 | + |
1832 | +class SelectAllCheckBox(QtGui.QCheckBox): |
1833 | + |
1834 | + def __init__(self, tree_widget, parent=None): |
1835 | + QtGui.QCheckBox.__init__(self, gettext("Select / deselect all"), parent) |
1836 | + |
1837 | + self.tree_widget = tree_widget |
1838 | + #self.setTristate(True) |
1839 | + |
1840 | + self.connect(tree_widget.tree_model, |
1841 | + QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"), |
1842 | + self.on_data_changed) |
1843 | + |
1844 | + self.connect(self, QtCore.SIGNAL("clicked(bool)"), |
1845 | + self.clicked) |
1846 | + |
1847 | + def on_data_changed(self, start_index, end_index): |
1848 | + self.update_state() |
1849 | + |
1850 | + def update_state(self): |
1851 | + model = self.tree_widget.tree_model |
1852 | + root_index = model._index_from_id(0, model.NAME) |
1853 | + |
1854 | + state = model.data(root_index, QtCore.Qt.CheckStateRole) |
1855 | + self.setCheckState(state.toInt()[0]) |
1856 | + |
1857 | + def clicked(self, state): |
1858 | + model = self.tree_widget.tree_model |
1859 | + root_index = model._index_from_id(0, model.NAME) |
1860 | + if state: |
1861 | + state = QtCore.QVariant(QtCore.Qt.Checked) |
1862 | + else: |
1863 | + state = QtCore.QVariant(QtCore.Qt.Unchecked) |
1864 | + |
1865 | + model.setData(root_index, QtCore.QVariant(state), |
1866 | + QtCore.Qt.CheckStateRole) |
1867 | |
1868 | === removed file 'lib/wtlist.py' |
1869 | --- lib/wtlist.py 2009-06-18 03:44:48 +0000 |
1870 | +++ lib/wtlist.py 1970-01-01 00:00:00 +0000 |
1871 | @@ -1,449 +0,0 @@ |
1872 | -# -*- coding: utf-8 -*- |
1873 | -# |
1874 | -# QBzr - Qt frontend to Bazaar commands |
1875 | -# Copyright (C) 2006-2007 Lukáš Lalinský <lalinsky@gmail.com> |
1876 | -# Copyright (C) 2006 Trolltech ASA |
1877 | -# Copyright (C) 2006 Jelmer Vernooij <jelmer@samba.org> |
1878 | -# |
1879 | -# This program is free software; you can redistribute it and/or |
1880 | -# modify it under the terms of the GNU General Public License |
1881 | -# as published by the Free Software Foundation; either version 2 |
1882 | -# of the License, or (at your option) any later version. |
1883 | -# |
1884 | -# This program is distributed in the hope that it will be useful, |
1885 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1886 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1887 | -# GNU General Public License for more details. |
1888 | -# |
1889 | -# You should have received a copy of the GNU General Public License |
1890 | -# along with this program; if not, write to the Free Software |
1891 | -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
1892 | - |
1893 | -"""A QTreeWidget that shows the items in a working tree, and includes a common |
1894 | -context menu.""" |
1895 | - |
1896 | -from PyQt4 import QtCore, QtGui |
1897 | - |
1898 | -from bzrlib import osutils |
1899 | - |
1900 | -from bzrlib.plugins.qbzr.lib.diff import ( |
1901 | - show_diff, |
1902 | - has_ext_diff, |
1903 | - ExtDiffMenu, |
1904 | - InternalWTDiffArgProvider, |
1905 | - ) |
1906 | -from bzrlib.plugins.qbzr.lib.i18n import gettext, N_ |
1907 | -from bzrlib.plugins.qbzr.lib.subprocess import SimpleSubProcessDialog |
1908 | -from bzrlib.plugins.qbzr.lib.util import ( |
1909 | - file_extension, |
1910 | - runs_in_loading_queue, |
1911 | - ) |
1912 | - |
1913 | - |
1914 | -class WorkingTreeFileList(QtGui.QTreeWidget): |
1915 | - |
1916 | - SELECTALL_MESSAGE = N_("Select / deselect all") |
1917 | - |
1918 | - def __init__(self, parent, tree): |
1919 | - QtGui.QTreeWidget.__init__(self, parent) |
1920 | - self._ignore_select_all_changes = False |
1921 | - self.selectall_checkbox = None # added by client. |
1922 | - self.tree = tree |
1923 | - |
1924 | - def setup_actions(self): |
1925 | - """Setup double-click and context menu""" |
1926 | - parent = self.parentWidget() |
1927 | - parent.connect(self, |
1928 | - QtCore.SIGNAL("itemDoubleClicked(QTreeWidgetItem *, int)"), |
1929 | - self.itemDoubleClicked) |
1930 | - |
1931 | - self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) |
1932 | - parent.connect(self, |
1933 | - QtCore.SIGNAL("itemSelectionChanged()"), |
1934 | - self.update_context_menu_actions) |
1935 | - parent.connect(self, |
1936 | - QtCore.SIGNAL("customContextMenuRequested(QPoint)"), |
1937 | - self.show_context_menu) |
1938 | - |
1939 | - self.context_menu = QtGui.QMenu(self) |
1940 | - if has_ext_diff(): |
1941 | - self.diff_menu = ExtDiffMenu(self) |
1942 | - self.context_menu.addMenu(self.diff_menu) |
1943 | - self.connect(self.diff_menu, QtCore.SIGNAL("triggered(QString)"), |
1944 | - self.show_differences) |
1945 | - self.show_diff_ui = self.diff_menu |
1946 | - else: |
1947 | - self.show_diff_action = self.context_menu.addAction( |
1948 | - gettext("Show &differences..."), self.show_differences) |
1949 | - self.context_menu.setDefaultAction(self.show_diff_action) |
1950 | - self.show_diff_ui = self.show_diff_action |
1951 | - |
1952 | - self.revert_action = self.context_menu.addAction( |
1953 | - gettext("&Revert..."), self.revert_selected) |
1954 | - # set all actions to disabled so it does the right thing with an empty |
1955 | - # list (our itemSelectionChanged() will fire as soon as we select one) |
1956 | - self.revert_action.setEnabled(False) |
1957 | - self.show_diff_ui.setEnabled(False) |
1958 | - |
1959 | - @runs_in_loading_queue |
1960 | - def fill(self, items_iter): |
1961 | - self.setTextElideMode(QtCore.Qt.ElideMiddle) |
1962 | - self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) |
1963 | - self.setSortingEnabled(True) |
1964 | - self.setHeaderLabels([gettext("File"), gettext("Extension"), gettext("Status")]) |
1965 | - header = self.header() |
1966 | - header.setStretchLastSection(False) |
1967 | - header.setResizeMode(0, QtGui.QHeaderView.Stretch) |
1968 | - header.setResizeMode(1, QtGui.QHeaderView.ResizeToContents) |
1969 | - header.setResizeMode(2, QtGui.QHeaderView.ResizeToContents) |
1970 | - self.setRootIsDecorated(False) |
1971 | - self._ignore_select_all_changes = True # don't update as we add items! |
1972 | - |
1973 | - # Each items_iter returns a tuple of (changes_tuple, is_checked) |
1974 | - # Where changes_tuple is a single item from iter_changes(): |
1975 | - # (file_id, (path_in_source, path_in_target), |
1976 | - # changed_content, versioned, parent, name, kind, |
1977 | - # executable) |
1978 | - # Note that the current filter is used to determine if the items are |
1979 | - # shown or not |
1980 | - self.item_to_data = {} |
1981 | - items = [] |
1982 | - for change_desc, visible, checked in items_iter: |
1983 | - (file_id, (path_in_source, path_in_target), |
1984 | - changed_content, versioned, parent, name, kind, |
1985 | - executable) = change_desc |
1986 | - |
1987 | - if versioned == (False, False): |
1988 | - if self.tree.is_ignored(path_in_target): |
1989 | - status = gettext("ignored") |
1990 | - else: |
1991 | - status = gettext("non-versioned") |
1992 | - ext = file_extension(path_in_target) |
1993 | - name = path_in_target |
1994 | - elif versioned == (False, True): |
1995 | - status = gettext("added") |
1996 | - ext = file_extension(path_in_target) |
1997 | - name = path_in_target + osutils.kind_marker(kind[1]) |
1998 | - elif versioned == (True, False): |
1999 | - status = gettext("removed") |
2000 | - ext = file_extension(path_in_source) |
2001 | - name = path_in_source + osutils.kind_marker(kind[0]) |
2002 | - elif kind[0] is not None and kind[1] is None: |
2003 | - status = gettext("missing") |
2004 | - ext = file_extension(path_in_source) |
2005 | - name = path_in_source + osutils.kind_marker(kind[0]) |
2006 | - else: |
2007 | - # versioned = True, True - so either renamed or modified |
2008 | - # or properties changed (x-bit). |
2009 | - renamed = (parent[0], name[0]) != (parent[1], name[1]) |
2010 | - if renamed: |
2011 | - if changed_content: |
2012 | - status = gettext("renamed and modified") |
2013 | - else: |
2014 | - status = gettext("renamed") |
2015 | - name = "%s%s => %s%s" % (path_in_source, |
2016 | - osutils.kind_marker(kind[0]), |
2017 | - path_in_target, |
2018 | - osutils.kind_marker(kind[0])) |
2019 | - ext = file_extension(path_in_target) |
2020 | - elif changed_content: |
2021 | - status = gettext("modified") |
2022 | - name = path_in_target + osutils.kind_marker(kind[1]) |
2023 | - ext = file_extension(path_in_target) |
2024 | - elif executable[0] != executable[1]: |
2025 | - status = gettext("modified (x-bit)") |
2026 | - name = path_in_target + osutils.kind_marker(kind[1]) |
2027 | - ext = file_extension(path_in_target) |
2028 | - else: |
2029 | - raise RuntimeError, "what status am I missing??" |
2030 | - |
2031 | - item = QtGui.QTreeWidgetItem() |
2032 | - item.setText(0, name) |
2033 | - item.setText(1, ext) |
2034 | - item.setText(2, status) |
2035 | - if visible: |
2036 | - items.append(item) |
2037 | - |
2038 | - if checked is None: |
2039 | - item.setCheckState(0, QtCore.Qt.PartiallyChecked) |
2040 | - item.setFlags(item.flags() & ~QtCore.Qt.ItemIsUserCheckable) |
2041 | - elif checked: |
2042 | - item.setCheckState(0, QtCore.Qt.Checked) |
2043 | - else: |
2044 | - item.setCheckState(0, QtCore.Qt.Unchecked) |
2045 | - self.item_to_data[item] = change_desc |
2046 | - # add them all to the tree in one hit. |
2047 | - self.insertTopLevelItems(0, items) |
2048 | - self._ignore_select_all_changes = False |
2049 | - if self.selectall_checkbox is not None: |
2050 | - self.update_selectall_state(None, None) |
2051 | - |
2052 | - def set_item_hidden(self, item, hide): |
2053 | - # Due to what seems a bug in Qt "hiding" an item isn't always |
2054 | - # reliable - so a "hidden" item is simply not added to the tree! |
2055 | - # See https://bugs.launchpad.net/qbzr/+bug/274295 |
2056 | - index = self.indexOfTopLevelItem(item) |
2057 | - if index == -1 and not hide: |
2058 | - self.addTopLevelItem(item) |
2059 | - elif index != -1 and hide: |
2060 | - self.takeTopLevelItem(index) |
2061 | - |
2062 | - def is_item_hidden(self, item): |
2063 | - return self.indexOfTopLevelItem(item) == -1 |
2064 | - |
2065 | - def iter_treeitem_and_desc(self, include_hidden=False): |
2066 | - """iterators to help work with the selection, checked items, etc""" |
2067 | - for ti, desc in self.item_to_data.iteritems(): |
2068 | - if include_hidden or not self.is_item_hidden(ti): |
2069 | - yield ti, desc |
2070 | - |
2071 | - def iter_selection(self): |
2072 | - for i in self.selectedItems(): |
2073 | - yield self.item_to_data[i] |
2074 | - |
2075 | - def iter_checked(self): |
2076 | - # XXX - just use self.iter_treeitem_and_desc() - no need to hit the |
2077 | - # XXX tree object at all!? |
2078 | - for i in range(self.topLevelItemCount()): |
2079 | - item = self.topLevelItem(i) |
2080 | - if item.checkState(0) == QtCore.Qt.Checked: |
2081 | - yield self.item_to_data[item] |
2082 | - |
2083 | - def show_context_menu(self, pos): |
2084 | - """Context menu and double-click related functions...""" |
2085 | - self.context_menu.popup(self.viewport().mapToGlobal(pos)) |
2086 | - |
2087 | - def update_context_menu_actions(self): |
2088 | - contains_non_versioned = False |
2089 | - for desc in self.iter_selection(): |
2090 | - if desc[3] == (False, False): |
2091 | - contains_non_versioned = True |
2092 | - break |
2093 | - self.revert_action.setEnabled(not contains_non_versioned) |
2094 | - self.show_diff_ui.setEnabled(not contains_non_versioned) |
2095 | - |
2096 | - def revert_selected(self): |
2097 | - """Revert the selected file.""" |
2098 | - items = self.selectedItems() |
2099 | - if not items: |
2100 | - return |
2101 | - |
2102 | - paths = [self.item_to_data[item][1][1] for item in items] |
2103 | - |
2104 | - args = ["revert"] |
2105 | - args.extend(paths) |
2106 | - desc = (gettext("Revert %s to latest revision.") % ", ".join(paths)) |
2107 | - revert_dialog = SimpleSubProcessDialog(gettext("Revert"), |
2108 | - desc=desc, |
2109 | - args=args, |
2110 | - dir=self.tree.basedir, |
2111 | - parent=self, |
2112 | - hide_progress=True, |
2113 | - ) |
2114 | - res = revert_dialog.exec_() |
2115 | - if res == QtGui.QDialog.Accepted: |
2116 | - for item in items: |
2117 | - index = self.indexOfTopLevelItem(item) |
2118 | - self.takeTopLevelItem(index) |
2119 | - |
2120 | - def itemDoubleClicked(self, items=None, column=None): |
2121 | - self.show_differences() |
2122 | - |
2123 | - def show_differences(self, ext_diff=None): |
2124 | - """Show differences between the working copy and the last revision.""" |
2125 | - if not self.show_diff_ui.isEnabled(): |
2126 | - return |
2127 | - |
2128 | - entries = [desc.path() for desc in self.iter_selection()] |
2129 | - if entries: |
2130 | - arg_provider = InternalWTDiffArgProvider( |
2131 | - self.tree.basis_tree().get_revision_id(), self.tree, |
2132 | - self.tree.branch, self.tree.branch, |
2133 | - specific_files=entries) |
2134 | - |
2135 | - show_diff(arg_provider, ext_diff=ext_diff, |
2136 | - parent_window=self.topLevelWidget()) |
2137 | - |
2138 | - def set_selectall_checkbox(self, checkbox): |
2139 | - """Helpers for a 'show all' checkbox. Parent widgets must create the |
2140 | - widget and pass it to us. |
2141 | - """ |
2142 | - checkbox.setTristate(True) |
2143 | - self.selectall_checkbox = checkbox |
2144 | - parent = self.parentWidget() |
2145 | - parent.connect(self, |
2146 | - QtCore.SIGNAL("itemChanged(QTreeWidgetItem *, int)"), |
2147 | - self.update_selectall_state) |
2148 | - |
2149 | - parent.connect(checkbox, QtCore.SIGNAL("stateChanged(int)"), |
2150 | - self.selectall_changed) |
2151 | - |
2152 | - def update_selectall_state(self, item, column): |
2153 | - """Update the state of the 'select all' checkbox to reflect the state |
2154 | - of the items in the list. |
2155 | - """ |
2156 | - if self._ignore_select_all_changes: |
2157 | - return |
2158 | - checked = 0 |
2159 | - num_items = 0 |
2160 | - |
2161 | - for (tree_item, change_desc) in self.iter_treeitem_and_desc(): |
2162 | - if tree_item.checkState(0) == QtCore.Qt.Checked: |
2163 | - checked += 1 |
2164 | - num_items += 1 |
2165 | - self._ignore_select_all_changes = True |
2166 | - if checked == 0: |
2167 | - self.selectall_checkbox.setCheckState(QtCore.Qt.Unchecked) |
2168 | - elif checked == num_items: |
2169 | - self.selectall_checkbox.setCheckState(QtCore.Qt.Checked) |
2170 | - else: |
2171 | - self.selectall_checkbox.setCheckState(QtCore.Qt.PartiallyChecked) |
2172 | - self._ignore_select_all_changes = False |
2173 | - |
2174 | - def selectall_changed(self, state): |
2175 | - if self._ignore_select_all_changes or not self.selectall_checkbox.isEnabled(): |
2176 | - return |
2177 | - if state == QtCore.Qt.PartiallyChecked: |
2178 | - self.selectall_checkbox.setCheckState(QtCore.Qt.Checked) |
2179 | - return |
2180 | - |
2181 | - self._ignore_select_all_changes = True |
2182 | - for (tree_item, change_desc) in self.iter_treeitem_and_desc(): |
2183 | - tree_item.setCheckState(0, QtCore.Qt.CheckState(state)) |
2184 | - self._ignore_select_all_changes = False |
2185 | - |
2186 | - |
2187 | -class ChangeDesc(tuple): |
2188 | - """Helper class that "knows" about internals of iter_changes' changed entry |
2189 | - description tuple, and provides additional helper methods. |
2190 | - |
2191 | - iter_changes return tuple with info about changed entry: |
2192 | - [0]: file_id -> ascii string |
2193 | - [1]: paths -> 2-tuple (old, new) fullpaths unicode/None |
2194 | - [2]: changed_content -> bool |
2195 | - [3]: versioned -> 2-tuple (bool, bool) |
2196 | - [4]: parent -> 2-tuple |
2197 | - [5]: name -> 2-tuple (old_name, new_name) utf-8?/None |
2198 | - [6]: kind -> 2-tuple (string/None, string/None) |
2199 | - [7]: executable -> 2-tuple (bool/None, bool/None) |
2200 | - |
2201 | - --optional-- |
2202 | - [8]: is_ignored -> If the file is ignored, pattern which caused it to |
2203 | - be ignored, otherwise None. |
2204 | - |
2205 | - NOTE: None value used for non-existing entry in corresponding |
2206 | - tree, e.g. for added/deleted/ignored/unversioned |
2207 | - """ |
2208 | - |
2209 | - # XXX We should may be try get this into bzrlib. |
2210 | - # XXX We should use this in qdiff. |
2211 | - |
2212 | - def fileid(desc): |
2213 | - return desc[0] |
2214 | - |
2215 | - def path(desc): |
2216 | - """Return a suitable entry for a 'specific_files' param to bzr functions.""" |
2217 | - oldpath, newpath = desc[1] |
2218 | - return newpath or oldpath |
2219 | - |
2220 | - def oldpath(desc): |
2221 | - """Return oldpath for renames.""" |
2222 | - return desc[1][0] |
2223 | - |
2224 | - def kind(desc): |
2225 | - oldkind, newkind = desc[6] |
2226 | - return newkind or oldkind |
2227 | - |
2228 | - def is_versioned(desc): |
2229 | - return desc[3] != (False, False) |
2230 | - |
2231 | - def is_modified(desc): |
2232 | - return (desc[3] != (False, False) and desc[2]) |
2233 | - |
2234 | - def is_renamed(desc): |
2235 | - return (desc[3] == (True, True) |
2236 | - and (desc[4][0], desc[5][0]) != (desc[4][1], desc[5][1])) |
2237 | - |
2238 | - def is_tree_root(desc): |
2239 | - """Check if entry actually tree root.""" |
2240 | - if desc[3] != (False, False) and desc[4] == (None, None): |
2241 | - # TREE_ROOT has not parents (desc[4]). |
2242 | - # But because we could want to see unversioned files |
2243 | - # we need to check for versioned flag (desc[3]) |
2244 | - return True |
2245 | - return False |
2246 | - |
2247 | - def is_missing(desc): |
2248 | - """Check if file was present in previous revision but now it's gone |
2249 | - (i.e. deleted manually, without invoking `bzr remove` command) |
2250 | - """ |
2251 | - return (desc[3] == (True, True) and desc[6][1] is None) |
2252 | - |
2253 | - def is_misadded(desc): |
2254 | - """Check if file was added to the working tree but then gone |
2255 | - (i.e. deleted manually, without invoking `bzr remove` command) |
2256 | - """ |
2257 | - return (desc[3] == (False, True) and desc[6][1] is None) |
2258 | - |
2259 | - def is_ignored(desc): |
2260 | - if len(desc) >= 8: |
2261 | - return desc[8] |
2262 | - else: |
2263 | - return None |
2264 | - |
2265 | - def status(desc): |
2266 | - if len(desc) == 8: |
2267 | - (file_id, (path_in_source, path_in_target), |
2268 | - changed_content, versioned, parent, name, kind, |
2269 | - executable) = desc |
2270 | - is_ignored = None |
2271 | - elif len(desc) == 9: |
2272 | - (file_id, (path_in_source, path_in_target), |
2273 | - changed_content, versioned, parent, name, kind, |
2274 | - executable, is_ignored) = desc |
2275 | - else: |
2276 | - raise RuntimeError, "Unkown number of items to unpack." |
2277 | - |
2278 | - if versioned == (False, False): |
2279 | - if is_ignored: |
2280 | - return gettext("ignored") |
2281 | - else: |
2282 | - return gettext("non-versioned") |
2283 | - elif versioned == (False, True): |
2284 | - return gettext("added") |
2285 | - elif versioned == (True, False): |
2286 | - return gettext("removed") |
2287 | - elif kind[0] is not None and kind[1] is None: |
2288 | - return gettext("missing") |
2289 | - else: |
2290 | - # versioned = True, True - so either renamed or modified |
2291 | - # or properties changed (x-bit). |
2292 | - renamed = (parent[0], name[0]) != (parent[1], name[1]) |
2293 | - if renamed: |
2294 | - if changed_content: |
2295 | - return gettext("renamed and modified") |
2296 | - else: |
2297 | - return gettext("renamed") |
2298 | - elif changed_content: |
2299 | - return gettext("modified") |
2300 | - elif executable[0] != executable[1]: |
2301 | - return gettext("modified (x-bit)") |
2302 | - else: |
2303 | - raise RuntimeError, "what status am I missing??" |
2304 | - |
2305 | - |
2306 | -def closure_in_selected_list(selected_list): |
2307 | - """Return in_selected_list(path) function for given selected_list.""" |
2308 | - |
2309 | - def in_selected_list(path): |
2310 | - """Check: is path belongs to some selected_list.""" |
2311 | - if path in selected_list: |
2312 | - return True |
2313 | - for p in selected_list: |
2314 | - if path.startswith(p): |
2315 | - return True |
2316 | - return False |
2317 | - |
2318 | - if not selected_list: |
2319 | - return lambda path: True |
2320 | - return in_selected_list |
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.