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