Merge lp:~garyvdm/qbzr/newwtlist into lp:~qbzr-dev/qbzr/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
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.

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

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

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

In normal mode, you would see:
Dir1
  File1 Modified

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

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

e.g. in changes_mode, you will get:

dir1 => dir2 Renamed
  File3 Modified

dir4 Added
  file5 Added

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

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

Subscribers

People subscribed via source and target branches

to status/vote changes: