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
=== modified file 'lib/add.py'
--- lib/add.py 2009-06-11 21:21:01 +0000
+++ lib/add.py 2009-07-10 19:43:43 +0000
@@ -23,11 +23,16 @@
2323
24from bzrlib.plugins.qbzr.lib.i18n import gettext24from bzrlib.plugins.qbzr.lib.i18n import gettext
25from bzrlib.plugins.qbzr.lib.subprocess import SubProcessDialog25from bzrlib.plugins.qbzr.lib.subprocess import SubProcessDialog
26from bzrlib.plugins.qbzr.lib.wtlist import (26from bzrlib.plugins.qbzr.lib.treewidget import (
27 ChangeDesc,27 TreeWidget,
28 WorkingTreeFileList,28 SelectAllCheckBox,
29 closure_in_selected_list,29 )
30 )30from bzrlib.plugins.qbzr.lib.util import (
31 ThrobberWidget,
32 runs_in_loading_queue,
33 )
34from bzrlib.plugins.qbzr.lib.uifactory import ui_current_widget
35from bzrlib.plugins.qbzr.lib.trace import reports_exception
3136
3237
33class AddWindow(SubProcessDialog):38class AddWindow(SubProcessDialog):
@@ -46,22 +51,36 @@
46 hide_progress=True,51 hide_progress=True,
47 )52 )
48 53
54 self.throbber = ThrobberWidget(self)
55
49 # Display the list of unversioned files56 # Display the list of unversioned files
50 groupbox = QtGui.QGroupBox(gettext("Unversioned Files"), self)57 groupbox = QtGui.QGroupBox(gettext("Unversioned Files"), self)
51 vbox = QtGui.QVBoxLayout(groupbox)58 vbox = QtGui.QVBoxLayout(groupbox)
5259
53 self.filelist = WorkingTreeFileList(groupbox, self.tree)60 self.filelist = TreeWidget(groupbox)
61 self.filelist.throbber = self.throbber
62 self.filelist.tree_model.is_item_in_select_all = lambda item: (
63 item.change is not None and
64 item.change.is_ignored() is None and
65 not item.change.is_versioned())
66
67 def filter_context_menu():
68 self.filelist.action_open_file.setEnabled(True)
69 self.filelist.action_open_file.setVisible(True)
70 self.filelist.action_show_file.setVisible(False)
71 self.filelist.action_show_annotate.setVisible(False)
72 self.filelist.action_show_log.setVisible(False)
73 self.filelist.action_show_diff.setVisible(False)
74 self.filelist.action_add.setVisible(False)
75 self.filelist.action_revert.setVisible(False)
76 self.filelist.filter_context_menu = filter_context_menu
77
54 vbox.addWidget(self.filelist)78 vbox.addWidget(self.filelist)
55 self.filelist.sortItems(0, QtCore.Qt.AscendingOrder)
56 self.filelist.setup_actions()
57 79
58 selectall_checkbox = QtGui.QCheckBox(80 selectall_checkbox = SelectAllCheckBox(self.filelist, groupbox)
59 gettext(self.filelist.SELECTALL_MESSAGE),
60 groupbox)
61 vbox.addWidget(selectall_checkbox)81 vbox.addWidget(selectall_checkbox)
62 selectall_checkbox.setCheckState(QtCore.Qt.Checked)82 selectall_checkbox.setCheckState(QtCore.Qt.Checked)
63 selectall_checkbox.setEnabled(True)83 selectall_checkbox.setEnabled(True)
64 self.filelist.set_selectall_checkbox(selectall_checkbox)
6584
66 self.show_ignored_checkbox = QtGui.QCheckBox(85 self.show_ignored_checkbox = QtGui.QCheckBox(
67 gettext("Show ignored files"),86 gettext("Show ignored files"),
@@ -69,12 +88,6 @@
69 vbox.addWidget(self.show_ignored_checkbox)88 vbox.addWidget(self.show_ignored_checkbox)
70 self.connect(self.show_ignored_checkbox, QtCore.SIGNAL("toggled(bool)"), self.show_ignored)89 self.connect(self.show_ignored_checkbox, QtCore.SIGNAL("toggled(bool)"), self.show_ignored)
71 90
72 self.tree.lock_read()
73 try:
74 self.filelist.fill(self.iter_changes_and_state())
75 finally:
76 self.tree.unlock()
77
78 # groupbox gets disabled as we are executing.91 # groupbox gets disabled as we are executing.
79 QtCore.QObject.connect(self,92 QtCore.QObject.connect(self,
80 QtCore.SIGNAL("subprocessStarted(bool)"),93 QtCore.SIGNAL("subprocessStarted(bool)"),
@@ -88,44 +101,40 @@
88 self.restoreSplitterSizes([150, 150])101 self.restoreSplitterSizes([150, 150])
89102
90 layout = QtGui.QVBoxLayout(self)103 layout = QtGui.QVBoxLayout(self)
104 layout.addWidget(self.throbber)
91 layout.addWidget(self.splitter)105 layout.addWidget(self.splitter)
92 layout.addWidget(self.buttonbox)106 layout.addWidget(self.buttonbox)
93107 self.throbber.show()
94 def iter_changes_and_state(self):108
95 """An iterator for the WorkingTreeFileList widget"""109
96110 def show(self):
97 in_selected_list = closure_in_selected_list(self.initial_selected_list)111 SubProcessDialog.show(self)
98112 QtCore.QTimer.singleShot(1, self.initial_load)
99 show_ignored = self.show_ignored_checkbox.isChecked()113
100114 @runs_in_loading_queue
101 for desc in self.tree.iter_changes(self.tree.basis_tree(),115 @ui_current_widget
102 want_unversioned=True):116 @reports_exception()
103117 def initial_load(self):
104 desc = ChangeDesc(desc)118 self.filelist.tree_model.checkable = True
105 if desc.is_versioned():119 fmodel = self.filelist.tree_filter_model
106 continue120 fmodel.setFilter(fmodel.CHANGED, False)
107121 self.filelist.set_tree(self.tree, changes_mode = True)
108 pit = desc.path()122 self.throbber.hide()
109 visible = show_ignored or not self.tree.is_ignored(pit)
110 check_state = visible and in_selected_list(pit)
111 yield desc, visible, check_state
112123
113 def start(self):124 def start(self):
114 """Add the files."""125 """Add the files."""
115 files = []126 files = []
116 for desc in self.filelist.iter_checked():127 for item_data in self.filelist.tree_model.iter_checked():
117 files.append(desc.path())128 files.append(item_data.change.path())
118 129
119 self.process_widget.start(self.tree.basedir, "add", *files)130 self.process_widget.start(self.tree.basedir, "add", "--no-recurse",
131 *files)
120132
121 def show_ignored(self, state):133 def show_ignored(self, state):
122 """Show/hide ignored files."""134 """Show/hide ignored files."""
123 state = not state135 fmodel = self.filelist.tree_filter_model
124 for (tree_item, change_desc) in self.filelist.iter_treeitem_and_desc(True):136 fmodel.setFilter(fmodel.IGNORED, state)
125 path = change_desc.path()137 #self.filelist.update_selectall_state(None, None)
126 if self.tree.is_ignored(path):
127 self.filelist.set_item_hidden(tree_item, state)
128 self.filelist.update_selectall_state(None, None)
129138
130 def saveSize(self):139 def saveSize(self):
131 SubProcessDialog.saveSize(self)140 SubProcessDialog.saveSize(self)
132141
=== modified file 'lib/commit.py'
--- lib/commit.py 2009-06-12 03:25:55 +0000
+++ lib/commit.py 2009-07-12 14:01:37 +0000
@@ -36,15 +36,18 @@
36 get_global_config,36 get_global_config,
37 url_for_display,37 url_for_display,
38 ThrobberWidget,38 ThrobberWidget,
39 )39 runs_in_loading_queue,
40from bzrlib.plugins.qbzr.lib.wtlist import (40 )
41 ChangeDesc,41from bzrlib.plugins.qbzr.lib.treewidget import TreeWidget
42 WorkingTreeFileList,42
43 closure_in_selected_list,
44 )
45from bzrlib.plugins.qbzr.lib.logwidget import LogList43from bzrlib.plugins.qbzr.lib.logwidget import LogList
46from bzrlib.plugins.qbzr.lib.trace import *44from bzrlib.plugins.qbzr.lib.trace import *
47from bzrlib.plugins.qbzr.lib.uifactory import ui_current_widget45from bzrlib.plugins.qbzr.lib.uifactory import ui_current_widget
46from bzrlib.plugins.qbzr.lib.treewidget import (
47 TreeWidget,
48 SelectAllCheckBox,
49 )
50from bzrlib.plugins.qbzr.lib.trace import reports_exception
4851
4952
50MAX_AUTOCOMPLETE_FILES = 2053MAX_AUTOCOMPLETE_FILES = 20
@@ -143,55 +146,6 @@
143 RevisionIdRole = QtCore.Qt.UserRole + 1146 RevisionIdRole = QtCore.Qt.UserRole + 1
144 ParentIdRole = QtCore.Qt.UserRole + 2147 ParentIdRole = QtCore.Qt.UserRole + 2
145148
146 def iter_changes_and_state(self):
147 """An iterator for the WorkingTreeFileList widget"""
148 # a word list for message completer
149 words = set()
150 show_nonversioned = self.show_nonversioned_checkbox.isChecked()
151
152 in_selected_list = closure_in_selected_list(self.initial_selected_list)
153
154 num_versioned_files = 0
155 for desc in self.tree.iter_changes(self.tree.basis_tree(),
156 want_unversioned=True):
157 desc = ChangeDesc(desc)
158
159 if desc.is_tree_root() or desc.is_misadded():
160 # skip uninteresting enties
161 continue
162
163 is_versioned = desc.is_versioned()
164 path = desc.path()
165
166 if not is_versioned and self.tree.is_ignored(path):
167 continue
168
169 visible = is_versioned or show_nonversioned
170 check_state = None
171 if not self.has_pending_merges:
172 check_state = visible and is_versioned and in_selected_list(path)
173 yield desc, visible, check_state
174
175 if is_versioned:
176 num_versioned_files += 1
177
178 words.update(os.path.split(path))
179 if desc.is_renamed():
180 words.update(os.path.split(desc.oldpath()))
181 if num_versioned_files < MAX_AUTOCOMPLETE_FILES:
182 ext = file_extension(path)
183 builder = get_wordlist_builder(ext)
184 if builder is not None:
185 try:
186 abspath = os.path.join(self.tree.basedir, path)
187 file = open(abspath, 'rt')
188 words.update(builder.iter_words(file))
189 except EnvironmentError:
190 pass
191 words = list(words)
192 words.sort(lambda a, b: cmp(a.lower(), b.lower()))
193 self.completion_words = words
194
195 def __init__(self, tree, selected_list, dialog=True, parent=None,149 def __init__(self, tree, selected_list, dialog=True, parent=None,
196 local=None, message=None, ui_mode=True):150 local=None, message=None, ui_mode=True):
197 super(CommitWindow, self).__init__(151 super(CommitWindow, self).__init__(
@@ -202,13 +156,13 @@
202 dialog = dialog,156 dialog = dialog,
203 parent = parent)157 parent = parent)
204 self.tree = tree158 self.tree = tree
205 tree.lock_read()159 #tree.lock_read()
206 try:160 #try:
207 self.basis_tree = self.tree.basis_tree()161 # self.basis_tree = self.tree.basis_tree()
208 self.is_bound = bool(tree.branch.get_bound_location())162 self.is_bound = bool(tree.branch.get_bound_location())
209 self.has_pending_merges = len(tree.get_parent_ids())>1163 self.has_pending_merges = len(tree.get_parent_ids())>1
210 finally:164 #finally:
211 tree.unlock()165 # tree.unlock()
212 166
213 self.windows = []167 self.windows = []
214 self.initial_selected_list = selected_list168 self.initial_selected_list = selected_list
@@ -263,16 +217,27 @@
263 self.show_nonversioned_checkbox = QtGui.QCheckBox(217 self.show_nonversioned_checkbox = QtGui.QCheckBox(
264 gettext("Show non-versioned files"))218 gettext("Show non-versioned files"))
265219
266 self.filelist = WorkingTreeFileList(message_groupbox, self.tree)220 self.filelist = TreeWidget(self)
267 selectall_checkbox = QtGui.QCheckBox(221 self.filelist.throbber = self.throbber
268 gettext(self.filelist.SELECTALL_MESSAGE))222 self.filelist.tree_model.is_item_in_select_all = lambda item: (
223 item.change is not None and
224 item.change.is_ignored() is None and
225 item.change.is_versioned())
226
227 self.file_words = {}
228 self.connect(self.filelist.tree_model,
229 QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"),
230 self.on_filelist_data_changed)
231
232 selectall_checkbox = SelectAllCheckBox(self.filelist, self)
269 selectall_checkbox.setCheckState(QtCore.Qt.Checked)233 selectall_checkbox.setCheckState(QtCore.Qt.Checked)
270 self.filelist.set_selectall_checkbox(selectall_checkbox)
271234
272 # Equivalent for 'bzr commit --message'235 # Equivalent for 'bzr commit --message'
273 self.message = TextEdit(message_groupbox, main_window=self)236 self.message = TextEdit(message_groupbox, main_window=self)
274 self.message.setToolTip(gettext("Enter the commit message"))237 self.message.setToolTip(gettext("Enter the commit message"))
275 self.completer = QtGui.QCompleter()238 self.completer = QtGui.QCompleter()
239 self.completer_model = QtGui.QStringListModel(self.completer)
240 self.completer.setModel(self.completer_model)
276 self.message.setCompleter(self.completer)241 self.message.setCompleter(self.completer)
277 self.message.setAcceptRichText(False)242 self.message.setAcceptRichText(False)
278243
@@ -328,8 +293,6 @@
328 293
329 vbox.addWidget(selectall_checkbox)294 vbox.addWidget(selectall_checkbox)
330295
331 self.filelist.sortItems(0, QtCore.Qt.AscendingOrder)
332
333 # Display a list of pending merges296 # Display a list of pending merges
334 if self.has_pending_merges:297 if self.has_pending_merges:
335 selectall_checkbox.setCheckState(QtCore.Qt.Checked)298 selectall_checkbox.setCheckState(QtCore.Qt.Checked)
@@ -392,7 +355,8 @@
392 # we show the bare form as soon as possible.355 # we show the bare form as soon as possible.
393 SubProcessWindow.show(self)356 SubProcessWindow.show(self)
394 QtCore.QTimer.singleShot(1, self.load)357 QtCore.QTimer.singleShot(1, self.load)
395 358
359 @runs_in_loading_queue
396 @ui_current_widget360 @ui_current_widget
397 @reports_exception()361 @reports_exception()
398 def load(self):362 def load(self):
@@ -403,14 +367,56 @@
403 None,367 None,
404 self.tree)368 self.tree)
405 self.pending_merges_list.load()369 self.pending_merges_list.load()
406 self.filelist.fill(self.iter_changes_and_state())370 self.processEvents()
371
372 self.filelist.tree_model.checkable = True
373 fmodel = self.filelist.tree_filter_model
374 fmodel.setFilter(fmodel.UNVERSIONED, False)
375 self.filelist.set_tree(self.tree, changes_mode = True)
376 self.processEvents()
377 self.update_compleater_words()
407 finally:378 finally:
408 self.tree.unlock()379 self.tree.unlock()
409 380
410 self.filelist.setup_actions()
411 self.completer.setModel(QtGui.QStringListModel(self.completion_words,
412 self.completer))
413 self.throbber.hide()381 self.throbber.hide()
382
383 def on_filelist_data_changed(self, start_index, end_index):
384 self.update_compleater_words()
385
386 def update_compleater_words(self):
387 num_files_loaded = 0
388
389 words = set()
390 for item_data in self.filelist.tree_model.iter_checked():
391 path = item_data.change.path()
392 if path not in self.file_words:
393 file_words = set()
394 if num_files_loaded < MAX_AUTOCOMPLETE_FILES:
395 file_words.add(path)
396 file_words.add(os.path.split(path)[-1])
397 if item_data.change.is_renamed():
398 file_words.add(item_data.change.oldpath())
399 file_words.add(
400 os.path.split(item_data.change.oldpath())[-1])
401 #if num_versioned_files < MAX_AUTOCOMPLETE_FILES:
402 ext = file_extension(path)
403 builder = get_wordlist_builder(ext)
404 if builder is not None:
405 try:
406 abspath = os.path.join(self.tree.basedir, path)
407 file = open(abspath, 'rt')
408 file_words.update(builder.iter_words(file))
409 self.processEvents()
410 except EnvironmentError:
411 pass
412 self.file_words[path] = file_words
413 num_files_loaded += 1
414 else:
415 file_words = self.file_words[path]
416 words.update(file_words)
417 words = list(words)
418 words.sort(lambda a, b: cmp(a.lower(), b.lower()))
419 self.completer_model.setStringList(words)
414 420
415 def enableBugs(self, state):421 def enableBugs(self, state):
416 if state == QtCore.Qt.Checked:422 if state == QtCore.Qt.Checked:
@@ -460,7 +466,7 @@
460466
461 def start(self):467 def start(self):
462 args = ["commit"]468 args = ["commit"]
463 files_to_add = ["add"]469 files_to_add = ["add", "--no-recurse"]
464 470
465 message = unicode(self.message.toPlainText()).strip() 471 message = unicode(self.message.toPlainText()).strip()
466 if not message: 472 if not message:
@@ -479,12 +485,12 @@
479 checkedFiles = 1 485 checkedFiles = 1
480 if not self.has_pending_merges:486 if not self.has_pending_merges:
481 checkedFiles = 0487 checkedFiles = 0
482 for desc in self.filelist.iter_checked():488 for item_data in self.filelist.tree_model.iter_checked():
483 checkedFiles = checkedFiles+1489 path = item_data.change.path()
484 path = desc.path()490 if not item_data.change.is_versioned():
485 if not desc.is_versioned():
486 files_to_add.append(path)491 files_to_add.append(path)
487 args.append(path)492 args.append(path)
493 checkedFiles = 1
488 494
489 if checkedFiles == 0: # BUG: 295116495 if checkedFiles == 0: # BUG: 295116
490 # check for availability of --exclude option for commit496 # check for availability of --exclude option for commit
@@ -540,11 +546,8 @@
540546
541 def show_nonversioned(self, state):547 def show_nonversioned(self, state):
542 """Show/hide non-versioned files."""548 """Show/hide non-versioned files."""
543 state = not state549 fmodel = self.filelist.tree_filter_model
544 for (tree_item, change_desc) in self.filelist.iter_treeitem_and_desc(True):550 fmodel.setFilter(fmodel.UNVERSIONED, state)
545 if change_desc[3] == (False, False):
546 self.filelist.set_item_hidden(tree_item, state)
547 self.filelist.update_selectall_state(None, None)
548551
549 def closeEvent(self, event):552 def closeEvent(self, event):
550 if not self.process_widget.is_running():553 if not self.process_widget.is_running():
@@ -580,9 +583,9 @@
580 # XXX make this function universal for both qcommit and qrevert (?)583 # XXX make this function universal for both qcommit and qrevert (?)
581 checked = [] # checked versioned584 checked = [] # checked versioned
582 unversioned = [] # checked unversioned (supposed to be added)585 unversioned = [] # checked unversioned (supposed to be added)
583 for desc in self.filelist.iter_checked():586 for item_data in self.filelist.tree_model.iter_checked():
584 path = desc.path()587 path = item_data.change.path()
585 if desc.is_versioned():588 if item_data.change.is_versioned():
586 checked.append(path)589 checked.append(path)
587 else:590 else:
588 unversioned.append(path)591 unversioned.append(path)
589592
=== modified file 'lib/diff.py'
--- lib/diff.py 2009-06-11 21:21:01 +0000
+++ lib/diff.py 2009-07-10 05:16:40 +0000
@@ -69,6 +69,7 @@
69 window.process_widget.hide_progress()69 window.process_widget.hide_progress()
70 if parent_window:70 if parent_window:
71 parent_window.windows.append(window)71 parent_window.windows.append(window)
72 window.show()
7273
7374
74def has_ext_diff():75def has_ext_diff():
7576
=== modified file 'lib/diff_arg.py'
--- lib/diff_arg.py 2009-06-23 00:38:58 +0000
+++ lib/diff_arg.py 2009-07-10 05:16:40 +0000
@@ -93,7 +93,9 @@
93 from bzrlib import urlutils93 from bzrlib import urlutils
9494
95 args = []95 args = []
96 args.append(self.get_revspec())96 revspec = self.get_revspec()
97 if revspec:
98 args.append(revspec)
97 99
98 if not self.old_branch.base == self.new_branch.base: 100 if not self.old_branch.base == self.new_branch.base:
99 args.append("--old=%s" % self.old_branch.base)101 args.append("--old=%s" % self.old_branch.base)
@@ -138,7 +140,10 @@
138 self.specific_files)140 self.specific_files)
139141
140 def get_revspec(self):142 def get_revspec(self):
141 return "-r revid:%s" % (self.old_revid,)143 if self.old_revid is not None:
144 return "-r revid:%s" % (self.old_revid,)
145 else:
146 return None
142 147
143 def need_to_load_paths(self):148 def need_to_load_paths(self):
144 return False149 return False
145150
=== modified file 'lib/revert.py'
--- lib/revert.py 2009-06-11 21:21:01 +0000
+++ lib/revert.py 2009-07-10 19:43:43 +0000
@@ -28,11 +28,16 @@
28 )28 )
29from bzrlib.plugins.qbzr.lib.i18n import gettext29from bzrlib.plugins.qbzr.lib.i18n import gettext
30from bzrlib.plugins.qbzr.lib.subprocess import SubProcessDialog30from bzrlib.plugins.qbzr.lib.subprocess import SubProcessDialog
31from bzrlib.plugins.qbzr.lib.wtlist import (31from bzrlib.plugins.qbzr.lib.treewidget import (
32 ChangeDesc,32 TreeWidget,
33 WorkingTreeFileList,33 SelectAllCheckBox,
34 closure_in_selected_list,34 )
35 )35from bzrlib.plugins.qbzr.lib.util import (
36 ThrobberWidget,
37 runs_in_loading_queue,
38 )
39from bzrlib.plugins.qbzr.lib.uifactory import ui_current_widget
40from bzrlib.plugins.qbzr.lib.trace import reports_exception
3641
3742
38class RevertWindow(SubProcessDialog):43class RevertWindow(SubProcessDialog):
@@ -51,26 +56,27 @@
51 parent = parent,56 parent = parent,
52 hide_progress=True)57 hide_progress=True)
53 58
59 self.throbber = ThrobberWidget(self)
60
54 # Display the list of changed files61 # Display the list of changed files
55 groupbox = QtGui.QGroupBox(gettext("Changes"), self)62 groupbox = QtGui.QGroupBox(gettext("Changes"), self)
5663
57 self.filelist = WorkingTreeFileList(groupbox, self.tree)64 self.filelist = TreeWidget(groupbox)
5865 self.filelist.throbber = self.throbber
59 self.tree.lock_read()66 self.filelist.tree_model.is_item_in_select_all = lambda item: (
60 try:67 item.change is not None and item.change.is_versioned())
61 self.filelist.fill(self.iter_changes_and_state())68 self.filelist.setRootIsDecorated(False)
62 finally:69 def filter_context_menu():
63 self.tree.unlock()70 TreeWidget.filter_context_menu(self.filelist)
6471 self.filelist.action_add.setVisible(False)
65 self.filelist.setup_actions()72 self.filelist.action_revert.setVisible(False)
73 self.filelist.filter_context_menu = filter_context_menu
6674
67 vbox = QtGui.QVBoxLayout(groupbox)75 vbox = QtGui.QVBoxLayout(groupbox)
68 vbox.addWidget(self.filelist)76 vbox.addWidget(self.filelist)
69 selectall_checkbox = QtGui.QCheckBox(77 selectall_checkbox = SelectAllCheckBox(self.filelist, groupbox)
70 gettext(self.filelist.SELECTALL_MESSAGE))
71 selectall_checkbox.setCheckState(QtCore.Qt.Checked)78 selectall_checkbox.setCheckState(QtCore.Qt.Checked)
72 selectall_checkbox.setEnabled(True)79 selectall_checkbox.setEnabled(True)
73 self.filelist.set_selectall_checkbox(selectall_checkbox)
74 vbox.addWidget(selectall_checkbox)80 vbox.addWidget(selectall_checkbox)
7581
76 self.no_backup_checkbox = QtGui.QCheckBox(82 self.no_backup_checkbox = QtGui.QCheckBox(
@@ -80,8 +86,6 @@
80 self.no_backup_checkbox.setEnabled(True)86 self.no_backup_checkbox.setEnabled(True)
81 vbox.addWidget(self.no_backup_checkbox)87 vbox.addWidget(self.no_backup_checkbox)
8288
83 self.filelist.sortItems(0, QtCore.Qt.AscendingOrder)
84
85 # groupbox gets disabled as we are executing.89 # groupbox gets disabled as we are executing.
86 QtCore.QObject.connect(self,90 QtCore.QObject.connect(self,
87 QtCore.SIGNAL("subprocessStarted(bool)"),91 QtCore.SIGNAL("subprocessStarted(bool)"),
@@ -95,6 +99,7 @@
95 self.restoreSplitterSizes([150, 150])99 self.restoreSplitterSizes([150, 150])
96100
97 layout = QtGui.QVBoxLayout(self)101 layout = QtGui.QVBoxLayout(self)
102 layout.addWidget(self.throbber)
98 layout.addWidget(self.splitter)103 layout.addWidget(self.splitter)
99104
100 # Diff button to view changes in files selected to revert105 # Diff button to view changes in files selected to revert
@@ -107,27 +112,31 @@
107 hbox.addWidget(self.diffbuttons)112 hbox.addWidget(self.diffbuttons)
108 hbox.addWidget(self.buttonbox)113 hbox.addWidget(self.buttonbox)
109 layout.addLayout(hbox)114 layout.addLayout(hbox)
110115 self.throbber.show()
111 def iter_changes_and_state(self):116
112 """An iterator for the WorkingTreeFileList widget"""117
113118 def show(self):
114 in_selected_list = closure_in_selected_list(self.initial_selected_list)119 SubProcessDialog.show(self)
115120 QtCore.QTimer.singleShot(1, self.initial_load)
116 for desc in self.tree.iter_changes(self.tree.basis_tree()):121
117 desc = ChangeDesc(desc)122 @runs_in_loading_queue
118 if desc.is_tree_root():123 @ui_current_widget
119 continue124 @reports_exception()
120 path = desc.path()125 def initial_load(self):
121 check_state = in_selected_list(path)126 self.filelist.tree_model.checkable = True
122 yield desc, True, check_state127 fmodel = self.filelist.tree_filter_model
128 #fmodel.setFilter(fmodel.UNVERSIONED, False)
129 self.filelist.set_tree(self.tree, changes_mode=True,
130 want_unversioned=False)
131 self.throbber.hide()
123132
124 def start(self):133 def start(self):
125 """Revert the files."""134 """Revert the files."""
126 args = ["revert"]135 args = ["revert"]
127 if self.no_backup_checkbox.checkState():136 if self.no_backup_checkbox.checkState():
128 args.append("--no-backup")137 args.append("--no-backup")
129 for desc in self.filelist.iter_checked():138 for item_data in self.filelist.tree_model.iter_checked():
130 args.append(desc.path())139 args.append(item_data.change.path())
131 self.process_widget.start(self.tree.basedir, *args)140 self.process_widget.start(self.tree.basedir, *args)
132141
133 def saveSize(self):142 def saveSize(self):
134143
=== modified file 'lib/revtreeview.py'
--- lib/revtreeview.py 2009-07-08 16:20:59 +0000
+++ lib/revtreeview.py 2009-07-10 01:04:24 +0000
@@ -102,6 +102,8 @@
102 break102 break
103 103
104 revids = list(revids)104 revids = list(revids)
105 if len(revids) == 0:
106 return
105 107
106 self.load_revisions_call_count += 1108 self.load_revisions_call_count += 1
107 current_call_count = self.load_revisions_call_count109 current_call_count = self.load_revisions_call_count
108110
=== modified file 'lib/tests/modeltest.py'
--- lib/tests/modeltest.py 2009-07-09 01:28:49 +0000
+++ lib/tests/modeltest.py 2009-07-10 01:54:38 +0000
@@ -431,7 +431,8 @@
431431
432 # Check that we can get back our real parent432 # Check that we can get back our real parent
433 p = self.model.parent( index )433 p = self.model.parent( index )
434 assert( self.model.parent( index ) == parent )434 assert( p.internalId() == parent.internalId() )
435 assert( p.row() == parent.row() )
435436
436 # recursively go down the children437 # recursively go down the children
437 if self.model.hasChildren(index) and depth < 10:438 if self.model.hasChildren(index) and depth < 10:
438439
=== modified file 'lib/treewidget.py'
--- lib/treewidget.py 2009-07-10 00:27:28 +0000
+++ lib/treewidget.py 2009-07-12 13:46:24 +0000
@@ -21,6 +21,7 @@
21from time import (strftime, localtime)21from time import (strftime, localtime)
22from PyQt4 import QtCore, QtGui22from PyQt4 import QtCore, QtGui
23from bzrlib.workingtree import WorkingTree23from bzrlib.workingtree import WorkingTree
24from bzrlib.revisiontree import RevisionTree
2425
25from bzrlib.plugins.qbzr.lib.cat import QBzrCatWindow26from bzrlib.plugins.qbzr.lib.cat import QBzrCatWindow
26from bzrlib.plugins.qbzr.lib.annotate import AnnotateWindow27from bzrlib.plugins.qbzr.lib.annotate import AnnotateWindow
@@ -37,7 +38,6 @@
37 get_apparent_author_name,38 get_apparent_author_name,
38 )39 )
39from bzrlib.plugins.qbzr.lib.uifactory import ui_current_widget40from bzrlib.plugins.qbzr.lib.uifactory import ui_current_widget
40from bzrlib.plugins.qbzr.lib.wtlist import ChangeDesc
41from bzrlib.plugins.qbzr.lib.subprocess import SimpleSubProcessDialog41from bzrlib.plugins.qbzr.lib.subprocess import SimpleSubProcessDialog
42from bzrlib.plugins.qbzr.lib.diff import (42from bzrlib.plugins.qbzr.lib.diff import (
43 show_diff,43 show_diff,
@@ -45,18 +45,159 @@
45 ExtDiffMenu,45 ExtDiffMenu,
46 InternalWTDiffArgProvider,46 InternalWTDiffArgProvider,
47 )47 )
4848from bzrlib.plugins.qbzr.lib.i18n import gettext, N_
4949
5050
51class UnversionedItem():51class InternalItem():
52 __slots__ = ["name", "path", "kind"]52 __slots__ = ["name", "path", "kind", "file_id"]
53 def __init__(self, name, path, kind):53 def __init__(self, name, path, kind, file_id):
54 self.name = name54 self.name = name
55 self.path = path55 self.path = path
56 self.kind = kind56 self.kind = kind
57 self.file_id = file_id
57 58
58 revision = property(lambda self:None)59 revision = property(lambda self:None)
59 file_id = property(lambda self:None)60
61class UnversionedItem(InternalItem):
62 def __init__(self, name, path, kind):
63 InternalItem.__init__(self, name, path, kind, None)
64
65class ModelItemData():
66 __slots__ = ["id", "item", "change", "chekced", "children_ids",
67 "parent_id", "row"]
68
69 def __init__(self, item, change):
70 self.item = item
71 self.change = change
72 if change is not None and change.is_ignored() is None:
73 self.checked = QtCore.Qt.Checked
74 else:
75 self.checked = QtCore.Qt.Unchecked
76
77 self.children_ids = None
78 self.parent_id = None
79 self.id = None
80 self.row = None
81
82
83class ChangeDesc(tuple):
84 """Helper class that "knows" about internals of iter_changes' changed entry
85 description tuple, and provides additional helper methods.
86
87 iter_changes return tuple with info about changed entry:
88 [0]: file_id -> ascii string
89 [1]: paths -> 2-tuple (old, new) fullpaths unicode/None
90 [2]: changed_content -> bool
91 [3]: versioned -> 2-tuple (bool, bool)
92 [4]: parent -> 2-tuple
93 [5]: name -> 2-tuple (old_name, new_name) utf-8?/None
94 [6]: kind -> 2-tuple (string/None, string/None)
95 [7]: executable -> 2-tuple (bool/None, bool/None)
96
97 --optional--
98 [8]: is_ignored -> If the file is ignored, pattern which caused it to
99 be ignored, otherwise None.
100
101 NOTE: None value used for non-existing entry in corresponding
102 tree, e.g. for added/deleted/ignored/unversioned
103 """
104
105 # XXX We should may be try get this into bzrlib.
106 # XXX We should use this in qdiff.
107
108 def fileid(desc):
109 return desc[0]
110
111 def path(desc):
112 """Return a suitable entry for a 'specific_files' param to bzr functions."""
113 oldpath, newpath = desc[1]
114 return newpath or oldpath
115
116 def oldpath(desc):
117 """Return oldpath for renames."""
118 return desc[1][0]
119
120 def kind(desc):
121 oldkind, newkind = desc[6]
122 return newkind or oldkind
123
124 def is_versioned(desc):
125 return desc[3] != (False, False)
126
127 def is_modified(desc):
128 return (desc[3] != (False, False) and desc[2])
129
130 def is_renamed(desc):
131 return (desc[3] == (True, True)
132 and (desc[4][0], desc[5][0]) != (desc[4][1], desc[5][1]))
133
134 def is_tree_root(desc):
135 """Check if entry actually tree root."""
136 if desc[3] != (False, False) and desc[4] == (None, None):
137 # TREE_ROOT has not parents (desc[4]).
138 # But because we could want to see unversioned files
139 # we need to check for versioned flag (desc[3])
140 return True
141 return False
142
143 def is_missing(desc):
144 """Check if file was present in previous revision but now it's gone
145 (i.e. deleted manually, without invoking `bzr remove` command)
146 """
147 return (desc[3] == (True, True) and desc[6][1] is None)
148
149 def is_misadded(desc):
150 """Check if file was added to the working tree but then gone
151 (i.e. deleted manually, without invoking `bzr remove` command)
152 """
153 return (desc[3] == (False, True) and desc[6][1] is None)
154
155 def is_ignored(desc):
156 if len(desc) >= 8:
157 return desc[8]
158 else:
159 return None
160
161 def status(desc):
162 if len(desc) == 8:
163 (file_id, (path_in_source, path_in_target),
164 changed_content, versioned, parent, name, kind,
165 executable) = desc
166 is_ignored = None
167 elif len(desc) == 9:
168 (file_id, (path_in_source, path_in_target),
169 changed_content, versioned, parent, name, kind,
170 executable, is_ignored) = desc
171 else:
172 raise RuntimeError, "Unkown number of items to unpack."
173
174 if versioned == (False, False):
175 if is_ignored:
176 return gettext("ignored")
177 else:
178 return gettext("non-versioned")
179 elif versioned == (False, True):
180 return gettext("added")
181 elif versioned == (True, False):
182 return gettext("removed")
183 elif kind[0] is not None and kind[1] is None:
184 return gettext("missing")
185 else:
186 # versioned = True, True - so either renamed or modified
187 # or properties changed (x-bit).
188 renamed = (parent[0], name[0]) != (parent[1], name[1])
189 if renamed:
190 if changed_content:
191 return gettext("renamed and modified")
192 else:
193 return gettext("renamed")
194 elif changed_content:
195 return gettext("modified")
196 elif executable[0] != executable[1]:
197 return gettext("modified (x-bit)")
198 else:
199 raise RuntimeError, "what status am I missing??"
200
60201
61class TreeModel(QtCore.QAbstractItemModel):202class TreeModel(QtCore.QAbstractItemModel):
62 203
@@ -68,10 +209,6 @@
68 gettext("Status")]209 gettext("Status")]
69 NAME, DATE, REVNO, MESSAGE, AUTHOR, STATUS = range(len(HEADER_LABELS))210 NAME, DATE, REVNO, MESSAGE, AUTHOR, STATUS = range(len(HEADER_LABELS))
70211
71 REVID = QtCore.Qt.UserRole + 1
72 FILEID = QtCore.Qt.UserRole + 2
73 PATH = QtCore.Qt.UserRole + 3
74
75 def __init__(self, file_icon, dir_icon, symlink_icon, parent=None):212 def __init__(self, file_icon, dir_icon, symlink_icon, parent=None):
76 QtCore.QAbstractTableModel.__init__(self, parent)213 QtCore.QAbstractTableModel.__init__(self, parent)
77214
@@ -79,39 +216,82 @@
79 self.dir_icon = dir_icon216 self.dir_icon = dir_icon
80 self.symlink_icon = symlink_icon217 self.symlink_icon = symlink_icon
81 self.tree = None218 self.tree = None
82 self.inventory_items = []219 self.inventory_data = []
83 self.dir_children_ids = {}220 self.dir_children_ids = {}
84 self.parent_ids = []221 self.parent_ids = []
222 self.checkable = False
85 223
86 def set_tree(self, tree, branch):224 def set_tree(self, tree, branch=None,
225 changes_mode=False, want_unversioned=True):
87 self.tree = tree226 self.tree = tree
88 self.branch = branch227 self.branch = branch
89 self.revno_map = None228 self.revno_map = None
229 self.changes_mode = changes_mode
90 230
91 self.changes = {}231 self.changes = {}
92 self.unver_by_parent = {}232 self.unver_by_parent = {}
93233
94 if isinstance(self.tree, WorkingTree):234 if isinstance(self.tree, WorkingTree):
95 tree.lock_read()235 tree.lock_read()
96 try:236 try:
237 root_id = self.tree.get_root_id()
238 dir_fileid_by_path = {}
97 for change in self.tree.iter_changes(self.tree.basis_tree(),239 for change in self.tree.iter_changes(self.tree.basis_tree(),
98 want_unversioned=True):240 want_unversioned=want_unversioned):
99 change = ChangeDesc(change)241 change = ChangeDesc(change)
100 path = change.path()242 path = change.path()
243 fileid = change.fileid()
101 is_ignored = self.tree.is_ignored(path)244 is_ignored = self.tree.is_ignored(path)
102 change = ChangeDesc(change+(is_ignored,))245 change = ChangeDesc(change+(is_ignored,))
103 246
104 if change.fileid() is not None:247 if fileid is not None and not changes_mode:
105 self.changes[change.fileid()] = change248 self.changes[change.fileid()] = change
106 else:249 else:
107 (dir_path, slash, name) = path.rpartition('/')250 if changes_mode:
108 dir_fileid = self.tree.path2id(dir_path)251 if (fileid is not None and
252 change.kind() == "directory"):
253 dir_fileid_by_path[path] = fileid
254 dir_path = path
255 dir_fileid = None
256 relpath = ""
257 while dir_path:
258 (dir_path, slash, name) = dir_path.rpartition('/')
259 relpath = slash + name + relpath
260 if dir_path in dir_fileid_by_path:
261 dir_fileid = dir_fileid_by_path[dir_path]
262 break
263 if dir_fileid is None:
264 dir_fileid = root_id
265 dir_path = ""
266
267 name = relpath.lstrip("/")
268 if change.is_renamed():
269 oldpath = change.oldpath()
270 if oldpath.startswith(dir_path):
271 oldpath = oldpath[len(dir_path):]
272 else:
273 # The file was mv from a difirent path.
274 oldpath = '/' + oldpath
275 name = "%s => %s" % (oldpath, name)
276 else:
277 (dir_path, slash, name) = path.rpartition('/')
278 dir_fileid = self.tree.path2id(dir_path)
279
109 280
110 if dir_fileid not in self.unver_by_parent:281 if dir_fileid not in self.unver_by_parent:
111 self.unver_by_parent[dir_fileid] = []282 self.unver_by_parent[dir_fileid] = []
112 self.unver_by_parent[dir_fileid].append((283
113 UnversionedItem(name, path, change.kind()),284 if change.is_versioned():
114 change))285 if changes_mode:
286 item = InternalItem(name, path, change.kind(),
287 change.fileid())
288 else:
289 item = self.tree.inventory[change.fileid()]
290 else:
291 item = UnversionedItem(name, path, change.kind())
292
293 self.unver_by_parent[dir_fileid].append(
294 ModelItemData(item, change))
115 295
116 self.process_inventory(self.working_tree_get_children)296 self.process_inventory(self.working_tree_get_children)
117 finally:297 finally:
@@ -119,11 +299,12 @@
119 else:299 else:
120 self.process_inventory(self.revision_tree_get_children)300 self.process_inventory(self.revision_tree_get_children)
121 301
122 def revision_tree_get_children(self, item):302 def revision_tree_get_children(self, item_data):
123 for child in item.children.itervalues():303 for child in item_data.item.children.itervalues():
124 yield (child, None)304 yield ModelItemData(child, None)
125 305
126 def working_tree_get_children(self, item):306 def working_tree_get_children(self, item_data):
307 item = item_data.item
127 if isinstance(item, UnversionedItem):308 if isinstance(item, UnversionedItem):
128 abspath = self.tree.abspath(item.path)309 abspath = self.tree.abspath(item.path)
129 310
@@ -143,9 +324,10 @@
143 (None, kind),324 (None, kind),
144 (None, executable),325 (None, executable),
145 is_ignored))326 is_ignored))
146 yield (child, change)327 yield ModelItemData(child, change)
147 328
148 elif item.children is not None:329 if (not isinstance(item, InternalItem) and
330 item.children is not None and not self.changes_mode):
149 #Because we create copies, we have to get the real item.331 #Because we create copies, we have to get the real item.
150 item = self.tree.inventory[item.file_id]332 item = self.tree.inventory[item.file_id]
151 for child in item.children.itervalues():333 for child in item.children.itervalues():
@@ -155,35 +337,32 @@
155 change = self.changes[child.file_id]337 change = self.changes[child.file_id]
156 else:338 else:
157 change = None339 change = None
158 yield (child, change)340 yield ModelItemData(child, change)
159 if item.file_id in self.unver_by_parent:341 if item.file_id in self.unver_by_parent:
160 for (child, change) in self.unver_by_parent[item.file_id]:342 for item_data in self.unver_by_parent[item.file_id]:
161 yield (child, change)343 yield item_data
162 344
163 def load_dir(self, dir_id):345 def load_dir(self, dir_id):
164 if isinstance(self.tree, WorkingTree):346 if isinstance(self.tree, WorkingTree):
165 self.tree.lock_read()347 self.tree.lock_read()
166 try:348 try:
167 if dir_id>=len(self.inventory_items):349 if dir_id>=len(self.inventory_data):
168 return350 return
169 dir_item, dir_change = self.inventory_items[dir_id]351 dir_item = self.inventory_data[dir_id]
170 dir_children_ids = []352 dir_item.children_ids = []
171 children = sorted(self.get_children(dir_item),353 children = sorted(self.get_children(dir_item),
172 self.inventory_dirs_first_cmp,354 self.inventory_dirs_first_cmp,
173 lambda x: x[0])355 lambda x: (x.item.name, x.item.kind))
174 356
175 parent_model_index = self._index_from_id(dir_id, 0)357 parent_model_index = self._index_from_id(dir_id, 0)
176 self.beginInsertRows(parent_model_index, 0, len(children)-1)358 self.beginInsertRows(parent_model_index, 0, len(children)-1)
177 try:359 try:
178 for (child, change) in children:360 for child in children:
179 child_id = self.append_item(child, change, dir_id)361 child_id = self.append_item(child, dir_id)
180 dir_children_ids.append(child_id)362 dir_item.children_ids.append(child_id)
181 if child.kind == "directory":
182 self.dir_children_ids[child_id] = None
183 363
184 if len(dir_children_ids) % 100 == 0:364 if len(dir_item.children_ids) % 100 == 0:
185 QtCore.QCoreApplication.processEvents()365 QtCore.QCoreApplication.processEvents()
186 self.dir_children_ids[dir_id] = dir_children_ids
187 finally:366 finally:
188 self.endInsertRows();367 self.endInsertRows();
189 finally:368 finally:
@@ -194,37 +373,64 @@
194 self.get_children = get_children373 self.get_children = get_children
195 374
196 self.emit(QtCore.SIGNAL("layoutAboutToBeChanged()"))375 self.emit(QtCore.SIGNAL("layoutAboutToBeChanged()"))
197 self.inventory_items = []376 self.inventory_data = []
198 self.dir_children_ids = {}377 self.dir_children_ids = {}
199 self.parent_ids = []378 self.parent_ids = []
379
380 root_item = ModelItemData(
381 self.tree.inventory[self.tree.get_root_id()],
382 None)
383 root_id = self.append_item(root_item, None)
384 self.load_dir(root_id)
200 self.emit(QtCore.SIGNAL("layoutChanged()"))385 self.emit(QtCore.SIGNAL("layoutChanged()"))
201
202 root_item = self.tree.inventory[self.tree.get_root_id()]
203 root_id = self.append_item(root_item, None, None)
204 self.dir_children_ids[root_id] = None
205 self.load_dir(root_id)
206 386
207 def append_item(self, item, change, parent_id):387 def append_item(self, item_data, parent_id):
208 id = len(self.inventory_items)388 item_data.id = len(self.inventory_data)
209 self.inventory_items.append((item, change))389 if parent_id is not None:
210 self.parent_ids.append(parent_id)390 parent_data = self.inventory_data[parent_id]
211 return id391 if self.is_item_in_select_all(item_data):
392 item_data.checked = parent_data.checked
393 else:
394 item_data.checked = False
395 item_data.row = len(parent_data.children_ids)
396 else:
397 item_data.checked = QtCore.Qt.Checked
398 item_data.row = 0
399 item_data.parent_id = parent_id
400 self.inventory_data.append(item_data)
401 return item_data.id
212 402
213 def inventory_dirs_first_cmp(self, x, y):403 def inventory_dirs_first_cmp(self, x, y):
214 x_is_dir = x.kind =="directory"404 (x_name, x_kind) = x
215 y_is_dir = y.kind =="directory"405 (y_name, y_kind) = y
406 x_a = x_name
407 y_a = y_name
408 x_is_dir = x_kind =="directory"
409 y_is_dir = y_kind =="directory"
410 while True:
411 x_b, sep, x_a_t = x_a.partition("/")
412 y_b, sep, y_a_t = y_a.partition("/")
413 if x_a_t == "" and y_a_t == "":
414 break
415 if (x_is_dir or not x_a_t == "") and not (y_is_dir or not y_a_t == ""):
416 return -1
417 if (y_is_dir or not y_a_t == "") and not (x_is_dir or not x_a_t == ""):
418 return 1
419 cmp_r = cmp(x_b, y_b)
420 if not cmp_r == 0:
421 return cmp_r
422 x_a = x_a_t
423 y_a = y_a_t
216 if x_is_dir and not y_is_dir:424 if x_is_dir and not y_is_dir:
217 return -1425 return -1
218 if y_is_dir and not x_is_dir:426 if y_is_dir and not x_is_dir:
219 return 1427 return 1
220 return cmp(x.name, y.name)428 return cmp(x_name, y_name)
221 429
222 def set_revno_map(self, revno_map):430 def set_revno_map(self, revno_map):
223 self.revno_map = revno_map431 self.revno_map = revno_map
224 for id in xrange(1, len(self.inventory_items)):432 for item_data in self.inventory_data[1:0]:
225 parent_id = self.parent_ids[id]433 index = self.createIndex (item_data.row, self.REVNO, item_data.id)
226 row = self.dir_children_ids[parent_id].index(id)
227 index = self.createIndex (row, self.REVNO, id)
228 self.emit(QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"),434 self.emit(QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"),
229 index,index) 435 index,index)
230 436
@@ -232,43 +438,48 @@
232 return len(self.HEADER_LABELS)438 return len(self.HEADER_LABELS)
233439
234 def rowCount(self, parent):440 def rowCount(self, parent):
235 parent_id = parent.internalId()441 if parent.internalId()>=len(self.inventory_data):
236 if parent_id not in self.dir_children_ids:442 return 0
237 return 0443 parent_data = self.inventory_data[parent.internalId()]
238 dir_children_ids = self.dir_children_ids[parent_id]444 if parent_data.children_ids is None:
239 if dir_children_ids is None:445 return 0
240 return 0446 return len(parent_data.children_ids)
241 return len(dir_children_ids)
242 447
243 def canFetchMore(self, parent):448 def canFetchMore(self, parent):
244 parent_id = parent.internalId()449 if parent.internalId()>=len(self.inventory_data):
245 return parent_id in self.dir_children_ids \450 return False
246 and self.dir_children_ids[parent_id] is None451 parent_data = self.inventory_data[parent.internalId()]
452 return (parent_data.children_ids is None and
453 parent_data.item.kind == "directory")
247 454
248 def fetchMore(self, parent):455 def fetchMore(self, parent):
249 self.load_dir(parent.internalId())456 self.load_dir(parent.internalId())
250457
251 def _index(self, row, column, parent_id):458 def _index(self, row, column, parent_id):
252 if parent_id not in self.dir_children_ids:459 if parent_id>=len(self.inventory_data):
253 return QtCore.QModelIndex()460 return QtCore.QModelIndex()
254 dir_children_ids = self.dir_children_ids[parent_id]461
255 if dir_children_ids is None:462 parent_data = self.inventory_data[parent_id]
256 return QtCore.QModelIndex()463 if parent_data.children_ids is None:
257 if row >= len(dir_children_ids):464 return QtCore.QModelIndex()
258 return QtCore.QModelIndex()465
259 item_id = dir_children_ids[row]466 if (row < 0 or
467 row >= len(parent_data.children_ids) or
468 column < 0 or
469 column >= len(self.HEADER_LABELS)):
470 return QtCore.QModelIndex()
471 item_id = parent_data.children_ids[row]
260 return self.createIndex(row, column, item_id)472 return self.createIndex(row, column, item_id)
261 473
262 def _index_from_id(self, item_id, column):474 def _index_from_id(self, item_id, column):
263 parent_id = self.parent_ids[item_id]475 if item_id >= len(self.inventory_data):
264 if parent_id is None:
265 return QtCore.QModelIndex()476 return QtCore.QModelIndex()
266 row = self.dir_children_ids[parent_id].index(item_id)477
267 return self.createIndex(row, column, item_id) 478 item_data = self.inventory_data[item_id]
479 return self.createIndex(item_data.row, column, item_id)
268 480
269 def index(self, row, column, parent = QtCore.QModelIndex()):481 def index(self, row, column, parent = QtCore.QModelIndex()):
270 parent_id = parent.internalId()482 return self._index(row, column, parent.internalId())
271 return self._index(row, column, parent_id)
272 483
273 def sibling(self, row, column, index):484 def sibling(self, row, column, index):
274 sibling_id = index.internalId()485 sibling_id = index.internalId()
@@ -281,28 +492,109 @@
281 child_id = child.internalId()492 child_id = child.internalId()
282 if child_id == 0:493 if child_id == 0:
283 return QtCore.QModelIndex()494 return QtCore.QModelIndex()
284 if child_id not in self.parent_ids:495 item_data = self.inventory_data[child_id]
285 return QtCore.QModelIndex()496 if item_data.parent_id == 0 :
286 item_id = self.parent_ids[child_id]
287 if item_id == 0 :
288 return QtCore.QModelIndex()497 return QtCore.QModelIndex()
289 498
290 return self._index_from_id(item_id, 0)499 return self._index_from_id(item_data.parent_id, 0)
291500
292 def hasChildren(self, parent):501 def hasChildren(self, parent):
293 parent_id = parent.internalId()502 if self.tree is None:
294 return parent_id in self.dir_children_ids503 return False
504 if parent.internalId() == 0:
505 return True
506 item_data = self.inventory_data[parent.internalId()]
507 return item_data.item.kind == "directory"
508
509 is_item_in_select_all = lambda self, item: True
510 """Returns wether an item is changed when select all is clicked."""
511
512 def setData(self, index, value, role):
513
514
515 def set_checked(item_data, checked):
516 old_checked = item_data.checked
517 item_data.checked = checked
518 if not old_checked == checked:
519 index = self.createIndex (item_data.row, self.NAME, item_data.id)
520 self.emit(QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"),
521 index,index)
522
523 if index.column() == self.NAME and role == QtCore.Qt.CheckStateRole:
524 value = value.toInt()[0]
525 if index.internalId() >= len(self.inventory_data):
526 return False
527
528 item_data = self.inventory_data[index.internalId()]
529 set_checked(item_data, value)
530
531 # Recursively set all children to checked.
532 if item_data.children_ids is not None:
533 children_ids = list(item_data.children_ids)
534 while children_ids:
535 child = self.inventory_data[children_ids.pop(0)]
536
537 # If unchecking, uncheck everything, but if checking,
538 # only check those in "select_all" get checked.
539 if (self.is_item_in_select_all(child) or
540 value == QtCore.Qt.Unchecked):
541
542 set_checked(child, value)
543 if child.children_ids is not None:
544 children_ids.extend(child.children_ids)
545
546 # Walk up the tree, and update every dir
547 parent_data = item_data
548 while parent_data.parent_id is not None:
549 parent_data = self.inventory_data[parent_data.parent_id]
550 has_checked = False
551 has_unchecked = False
552 for child_id in parent_data.children_ids:
553 child = self.inventory_data[child_id]
554
555 if child.checked == QtCore.Qt.Checked:
556 has_checked = True
557 elif (child.checked == QtCore.Qt.Unchecked and
558 self.is_item_in_select_all(child)):
559 has_unchecked = True
560 elif child.checked == QtCore.Qt.PartiallyChecked:
561 has_checked = True
562 if self.is_item_in_select_all(child):
563 has_unchecked = True
564
565 if has_checked and has_unchecked:
566 break
567
568 if has_checked and has_unchecked:
569 set_checked(parent_data, QtCore.Qt.PartiallyChecked)
570 elif has_checked:
571 set_checked(parent_data, QtCore.Qt.Checked)
572 else:
573 set_checked(parent_data, QtCore.Qt.Unchecked)
574
575 return True
576
577 return False
578
579 REVID = QtCore.Qt.UserRole + 1
580 FILEID = QtCore.Qt.UserRole + 2
581 PATH = QtCore.Qt.UserRole + 3
582 INTERNALID = QtCore.Qt.UserRole + 4
295 583
296 def data(self, index, role):584 def data(self, index, role):
297 if not index.isValid():585 if not index.isValid():
298 return QtCore.QVariant()586 return QtCore.QVariant()
299 587
300 (item, change) = self.inventory_items[index.internalId()]588 if role == self.INTERNALID:
589 return QtCore.QVariant(index.internalId())
590
591 item_data = self.inventory_data[index.internalId()]
592 item = item_data.item
301 593
302 if role == self.FILEID:594 if role == self.FILEID:
303 return QtCore.QVariant(item.file_id)595 return QtCore.QVariant(item.file_id)
304 596
305 revid = item.revision597 revid = item_data.item.revision
306 if role == self.REVID:598 if role == self.REVID:
307 if revid is None:599 if revid is None:
308 return QtCore.QVariant()600 return QtCore.QVariant()
@@ -321,13 +613,18 @@
321 if item.kind == "symlink":613 if item.kind == "symlink":
322 return QtCore.QVariant(self.symlink_icon)614 return QtCore.QVariant(self.symlink_icon)
323 return QtCore.QVariant()615 return QtCore.QVariant()
616 if role == QtCore.Qt.CheckStateRole:
617 if not self.checkable:
618 return QtCore.QVariant()
619 else:
620 return QtCore.QVariant(item_data.checked)
324 621
325 if column == self.STATUS:622 if column == self.STATUS:
326 if role == QtCore.Qt.DisplayRole:623 if role == QtCore.Qt.DisplayRole:
327 if change is not None:624 if item_data.change is not None:
328 return QtCore.QVariant(change.status())625 return QtCore.QVariant(item_data.change.status())
329 else:626 else:
330 return QtCore.QVariant()627 return QtCore.QVariant("")
331 628
332 if column == self.REVNO:629 if column == self.REVNO:
333 if role == QtCore.Qt.DisplayRole:630 if role == QtCore.Qt.DisplayRole:
@@ -336,7 +633,7 @@
336 return QtCore.QVariant(633 return QtCore.QVariant(
337 ".".join(["%d" % (revno) for revno in revno_sequence]))634 ".".join(["%d" % (revno) for revno in revno_sequence]))
338 else:635 else:
339 return QtCore.QVariant()636 return QtCore.QVariant("")
340637
341 if role == QtCore.Qt.DisplayRole:638 if role == QtCore.Qt.DisplayRole:
342 if revid in cached_revisions:639 if revid in cached_revisions:
@@ -365,13 +662,21 @@
365 self.tree.unlock()662 self.tree.unlock()
366 return QtCore.QVariant(path)663 return QtCore.QVariant(path)
367 664
665 if role == QtCore.Qt.DisplayRole:
666 return QtCore.QVariant("")
368 return QtCore.QVariant()667 return QtCore.QVariant()
369 668
370 def flags(self, index):669 def flags(self, index):
371 if not index.isValid():670 #if not index.isValid():
372 return QtCore.Qt.ItemIsEnabled671 # return QtCore.Qt.ItemIsEnabled
373672
374 return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable673 if self.checkable and index.column() == self.NAME:
674 return (QtCore.Qt.ItemIsEnabled |
675 QtCore.Qt.ItemIsSelectable |
676 QtCore.Qt.ItemIsUserCheckable)
677 else:
678 return (QtCore.Qt.ItemIsEnabled |
679 QtCore.Qt.ItemIsSelectable)
375680
376 def headerData(self, section, orientation, role):681 def headerData(self, section, orientation, role):
377 if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:682 if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
@@ -380,19 +685,23 @@
380 685
381 def on_revisions_loaded(self, revisions, last_call):686 def on_revisions_loaded(self, revisions, last_call):
382 inventory = self.tree.inventory687 inventory = self.tree.inventory
383 for id, (item, change) in enumerate(self.inventory_items):688 for item_data in self.inventory_data:
384 if id == 0:689 if item_data.id == 0:
385 continue690 continue
386 691
387 if item.revision in revisions:692 if item_data.item.revision in revisions:
388 parent_id = self.parent_ids[id]
389 row = self.dir_children_ids[parent_id].index(id)
390 self.emit(QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"),693 self.emit(QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"),
391 self.createIndex (row, self.DATE, id),694 self.createIndex (item_data.row, self.DATE, item_data.id),
392 self.createIndex (row, self.AUTHOR,id))695 self.createIndex (item_data.row, self.AUTHOR, item_data.id))
393696
394 def get_repo(self):697 def get_repo(self):
395 return self.branch.repository698 return self.branch.repository
699
700 def iter_checked(self):
701 return sorted([item_data for item_data in self.inventory_data[1:]
702 if item_data.checked == QtCore.Qt.Checked],
703 self.inventory_dirs_first_cmp,
704 lambda x: (x.change.path(), x.item.kind))
396705
397class TreeFilterProxyModel(QtGui.QSortFilterProxyModel):706class TreeFilterProxyModel(QtGui.QSortFilterProxyModel):
398 source_model = None707 source_model = None
@@ -430,7 +739,10 @@
430 739
431 model = self.source_model740 model = self.source_model
432 parent_id = source_parent.internalId()741 parent_id = source_parent.internalId()
433 id = model.dir_children_ids[parent_id][source_row]742 children_ids = model.inventory_data[parent_id].children_ids
743 if len(children_ids)<=source_row:
744 return False
745 id = children_ids[source_row]
434 return self.filter_id_cached(id)746 return self.filter_id_cached(id)
435 747
436 def filter_id_cached(self, id):748 def filter_id_cached(self, id):
@@ -445,27 +757,26 @@
445 (unchanged, changed, unversioned, ignored) = self.filters757 (unchanged, changed, unversioned, ignored) = self.filters
446758
447 model = self.source_model759 model = self.source_model
448 item, change = model.inventory_items[id]760 item_data = model.inventory_data[id]
449 761
450 if change is None and unchanged: return True762 if item_data.change is None and unchanged: return True
451 763
452 is_versioned = not isinstance(item, UnversionedItem)764 is_versioned = not isinstance(item_data.item, UnversionedItem)
453 765
454 if is_versioned and change is not None and changed: return True766 if is_versioned and item_data.change is not None and changed:
767 return True
455 768
456 if not is_versioned and unversioned:769 if not is_versioned and unversioned:
457 is_ignored = change.is_ignored()770 is_ignored = item_data.change.is_ignored()
458 if not is_ignored: return True771 if not is_ignored: return True
459 if is_ignored and ignored: return True772 if is_ignored and ignored: return True
460 if is_ignored and not ignored: return False773 if is_ignored and not ignored: return False
461 774
462 if id in model.dir_children_ids:775 if item_data.item.kind == "directory":
463 dir_children_ids = model.dir_children_ids[id]776 if item_data.children_ids is None:
464 if dir_children_ids is None:
465 model.load_dir(id)777 model.load_dir(id)
466 dir_children_ids = model.dir_children_ids[id]
467 778
468 for child_id in dir_children_ids:779 for child_id in item_data.children_ids:
469 if self.filter_id_cached(child_id):780 if self.filter_id_cached(child_id):
470 return True781 return True
471 782
@@ -540,7 +851,6 @@
540 self.connect(self,851 self.connect(self,
541 QtCore.SIGNAL("doubleClicked(QModelIndex)"),852 QtCore.SIGNAL("doubleClicked(QModelIndex)"),
542 self.do_default_action)853 self.do_default_action)
543 self.default_action = self.show_file_content
544 self.tree = None854 self.tree = None
545 self.branch = None855 self.branch = None
546 856
@@ -552,7 +862,7 @@
552 header.setResizeMode(self.tree_model.REVNO, QtGui.QHeaderView.Interactive)862 header.setResizeMode(self.tree_model.REVNO, QtGui.QHeaderView.Interactive)
553 header.setResizeMode(self.tree_model.MESSAGE, QtGui.QHeaderView.Stretch)863 header.setResizeMode(self.tree_model.MESSAGE, QtGui.QHeaderView.Stretch)
554 header.setResizeMode(self.tree_model.AUTHOR, QtGui.QHeaderView.Interactive) 864 header.setResizeMode(self.tree_model.AUTHOR, QtGui.QHeaderView.Interactive)
555 header.setResizeMode(self.tree_model.STATUS, QtGui.QHeaderView.ResizeToContents) 865 header.setResizeMode(self.tree_model.STATUS, QtGui.QHeaderView.Stretch)
556 fm = self.fontMetrics()866 fm = self.fontMetrics()
557 # XXX Make this dynamic.867 # XXX Make this dynamic.
558 col_margin = 6868 col_margin = 6
@@ -596,10 +906,34 @@
596 gettext("&Revert"),906 gettext("&Revert"),
597 self.revert)907 self.revert)
598 908
599 def set_tree(self, tree, branch):909 def set_tree(self, tree, branch=None,
910 changes_mode=False, want_unversioned=True):
911 """Causes a tree to be loaded, and displayed in the widget.
912
913 @param changes_mode: If in changes mode, a list of changes, and
914 unversioned items, rather than a tree, is diplayed.
915 e.g., when not in changes mode, one will get:
916
917 dir1
918 file1 changed
919 file2 changed
920
921 but when in changes mode, one will get:
922
923 dir1/file1 changed
924 file2 changed
925
926 When in changes mode, no unchanged items are shown.
927 """
600 self.tree = tree928 self.tree = tree
929 if isinstance(tree, RevisionTree) and branch is None:
930 raise AttributeError("A branch must be provided if the tree is a \
931 RevisionTree")
601 self.branch = branch932 self.branch = branch
602 self.tree_model.set_tree(self.tree, self.branch)933 self.changes_mode = changes_mode
934 self.want_unversioned = want_unversioned
935 self.tree_model.set_tree(self.tree, self.branch,
936 changes_mode, want_unversioned)
603 self.tree_filter_model.invalidateFilter()937 self.tree_filter_model.invalidateFilter()
604 938
605 if str(QtCore.QT_VERSION_STR).startswith("4.4"):939 if str(QtCore.QT_VERSION_STR).startswith("4.4"):
@@ -623,7 +957,6 @@
623 header.showSection(self.tree_model.STATUS)957 header.showSection(self.tree_model.STATUS)
624 958
625 self.context_menu.setDefaultAction(self.action_open_file)959 self.context_menu.setDefaultAction(self.action_open_file)
626 self.default_action = self.open_file
627 else:960 else:
628 header.showSection(self.tree_model.DATE)961 header.showSection(self.tree_model.DATE)
629 header.showSection(self.tree_model.REVNO)962 header.showSection(self.tree_model.REVNO)
@@ -632,10 +965,10 @@
632 header.hideSection(self.tree_model.STATUS)965 header.hideSection(self.tree_model.STATUS)
633 966
634 self.context_menu.setDefaultAction(self.action_show_file)967 self.context_menu.setDefaultAction(self.action_show_file)
635 self.default_action = self.show_file_content
636 968
637 def refresh(self):969 def refresh(self):
638 self.tree_model.set_tree(self.tree, self.branch)970 self.tree_model.set_tree(self.tree, self.branch,
971 self.changes_mode, self.want_unversioned)
639 self.tree_filter_model.invalidateFilter()972 self.tree_filter_model.invalidateFilter()
640973
641 def contextMenuEvent(self, event):974 def contextMenuEvent(self, event):
@@ -647,32 +980,35 @@
647 if indexes == None:980 if indexes == None:
648 indexes = self.selectedIndexes()981 indexes = self.selectedIndexes()
649 rows = {}982 rows = {}
650 for index in self.selectedIndexes():983 for index in indexes:
651 if index.row() not in rows:984 internal_id = index.data(self.tree_model.INTERNALID).toInt()[0]
652 rows[index.internalId()] = index985 if internal_id not in rows:
986 rows[internal_id] = index
653 return rows.values()987 return rows.values()
654 988
655 def get_selection_items(self, indexes=None):989 def get_selection_items(self, indexes=None):
656 items = []990 items = []
657 if indexes is None:991 if indexes is None or len(indexes)==1 and indexes[0] is None:
658 indexes = self.get_selection_indexes(indexes) 992 indexes = self.get_selection_indexes(indexes)
659 for index in indexes:993 for index in indexes:
660 source_index = self.tree_filter_model.mapToSource(index)994 source_index = self.tree_filter_model.mapToSource(index)
661 items.append(self.tree_model.inventory_items[995 items.append(self.tree_model.inventory_data[
662 source_index.internalId()])996 source_index.internalId()])
663 return items997 return items
664998
665 def filter_context_menu(self):999 def filter_context_menu(self):
666 is_working_tree = isinstance(self.tree, WorkingTree)1000 is_working_tree = isinstance(self.tree, WorkingTree)
667 items = self.get_selection_items()1001 items = self.get_selection_items()
668 versioned = [not isinstance(item[0], UnversionedItem)1002 versioned = [not isinstance(item.item, UnversionedItem)
669 for item in items]1003 for item in items]
670 changed = [item[1] is not None1004 changed = [item.change is not None
671 for item in items]1005 for item in items]
1006 versioned_changed = [ver and ch for ver,ch in zip(versioned, changed)]
1007
672 selection_len = len(items)1008 selection_len = len(items)
673 1009
674 single_versioned_file = (selection_len == 1 and versioned[0] and1010 single_versioned_file = (selection_len == 1 and versioned[0] and
675 items[0][0].kind == "file")1011 items[0].item.kind == "file")
676 1012
677 self.action_open_file.setEnabled(is_working_tree)1013 self.action_open_file.setEnabled(is_working_tree)
678 self.action_open_file.setVisible(is_working_tree)1014 self.action_open_file.setVisible(is_working_tree)
@@ -680,24 +1016,37 @@
680 self.action_show_annotate.setEnabled(single_versioned_file)1016 self.action_show_annotate.setEnabled(single_versioned_file)
681 self.action_show_log.setEnabled(any(versioned))1017 self.action_show_log.setEnabled(any(versioned))
682 self.action_show_diff.setVisible(is_working_tree)1018 self.action_show_diff.setVisible(is_working_tree)
683 self.action_show_diff.setEnabled(any(changed))1019 self.action_show_diff.setEnabled(any(versioned_changed))
684 1020
685 self.action_add.setVisible(is_working_tree)1021 self.action_add.setVisible(is_working_tree)
686 self.action_add.setDisabled(all(versioned))1022 self.action_add.setDisabled(all(versioned))
687 self.action_revert.setVisible(is_working_tree)1023 self.action_revert.setVisible(is_working_tree)
688 self.action_revert.setEnabled(any(changed) and any(versioned))1024 self.action_revert.setEnabled(any(versioned_changed))
1025
1026 if is_working_tree:
1027 if any(versioned_changed):
1028 self.context_menu.setDefaultAction(self.action_show_diff)
1029 else:
1030 self.context_menu.setDefaultAction(self.action_open_file)
1031
689 1032
690 def do_default_action(self, index=None):1033 def do_default_action(self, index):
691 indexes = self.get_selection_indexes([index])1034 indexes = self.get_selection_indexes([index])
692 if not len(indexes) == 1:1035 if not len(indexes) == 1:
693 return1036 return
694 1037
695 item = self.get_selection_items(indexes)[0]1038 item_data = self.get_selection_items(indexes)[0]
696 if item[0].kind == "directory":1039 if item_data.item.kind == "directory":
697 # Don't do anything, so that the directory can be expanded.1040 # Don't do anything, so that the directory can be expanded.
698 return1041 return
699 1042
700 self.default_action(index)1043 if isinstance(self.tree, WorkingTree):
1044 if item_data.change is not None and item_data.change.is_versioned():
1045 self.show_differences(index=index)
1046 else:
1047 self.open_file(index)
1048 else:
1049 self.show_file_content(index)
7011050
702 @ui_current_widget1051 @ui_current_widget
703 def show_file_content(self, index=None):1052 def show_file_content(self, index=None):
@@ -708,7 +1057,7 @@
708 return1057 return
709 1058
710 item = self.get_selection_items(indexes)[0]1059 item = self.get_selection_items(indexes)[0]
711 if isinstance(item[0], UnversionedItem):1060 if isinstance(item.item, UnversionedItem):
712 return1061 return
713 1062
714 index = indexes[0]1063 index = indexes[0]
@@ -748,8 +1097,8 @@
748 """Show qlog for one selected file(s)."""1097 """Show qlog for one selected file(s)."""
749 1098
750 items = self.get_selection_items()1099 items = self.get_selection_items()
751 fileids = [item[0].file_id for item in items1100 fileids = [item.item.file_id for item in items
752 if item[0].file_id is not None]1101 if item.item.file_id is not None]
753 1102
754 window = LogWindow(None, self.branch, fileids)1103 window = LogWindow(None, self.branch, fileids)
755 window.show()1104 window.show()
@@ -767,17 +1116,17 @@
767 self.window().windows.append(window)1116 self.window().windows.append(window)
7681117
769 @ui_current_widget1118 @ui_current_widget
770 def show_differences(self, ext_diff=None):1119 def show_differences(self, ext_diff=None, index=None):
771 """Show differences for selected file(s)."""1120 """Show differences for selected file(s)."""
772 1121
773 items = self.get_selection_items()1122 items = self.get_selection_items([index])
774 if len(items) > 0:1123 if len(items) > 0:
775 self.tree.lock_read()1124 self.tree.lock_read()
776 try:1125 try:
777 # Only paths that have changes.1126 # Only paths that have changes.
778 paths = [self.tree.id2path(item[0].file_id)1127 paths = [self.tree.id2path(item.item.file_id)
779 for item in items1128 for item in items
780 if item[1] is not None]1129 if item.change is not None]
781 finally:1130 finally:
782 self.tree.unlock()1131 self.tree.unlock()
783 else:1132 else:
@@ -800,9 +1149,9 @@
800 items = self.get_selection_items()1149 items = self.get_selection_items()
801 1150
802 # Only paths that are not versioned.1151 # Only paths that are not versioned.
803 paths = [item[0].path1152 paths = [item.item.path
804 for item in items1153 for item in items
805 if isinstance(item[0], UnversionedItem)]1154 if isinstance(item.item, UnversionedItem)]
806 1155
807 if len(paths) == 0:1156 if len(paths) == 0:
808 return1157 return
@@ -829,10 +1178,10 @@
829 self.tree.lock_read()1178 self.tree.lock_read()
830 try:1179 try:
831 # Only paths that have changes.1180 # Only paths that have changes.
832 paths = [self.tree.id2path(item[0].file_id)1181 paths = [self.tree.id2path(item.item.file_id)
833 for item in items1182 for item in items
834 if item[1] is not None and1183 if item.change is not None and
835 not isinstance(item[0], UnversionedItem)]1184 not isinstance(item.item, UnversionedItem)]
836 finally:1185 finally:
837 self.tree.unlock()1186 self.tree.unlock()
838 1187
@@ -851,4 +1200,39 @@
851 res = revert_dialog.exec_()1200 res = revert_dialog.exec_()
852 if res == QtGui.QDialog.Accepted:1201 if res == QtGui.QDialog.Accepted:
853 self.refresh()1202 self.refresh()
854
855\ No newline at end of file1203\ No newline at end of file
1204
1205class SelectAllCheckBox(QtGui.QCheckBox):
1206
1207 def __init__(self, tree_widget, parent=None):
1208 QtGui.QCheckBox.__init__(self, gettext("Select / deselect all"), parent)
1209
1210 self.tree_widget = tree_widget
1211 #self.setTristate(True)
1212
1213 self.connect(tree_widget.tree_model,
1214 QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"),
1215 self.on_data_changed)
1216
1217 self.connect(self, QtCore.SIGNAL("clicked(bool)"),
1218 self.clicked)
1219
1220 def on_data_changed(self, start_index, end_index):
1221 self.update_state()
1222
1223 def update_state(self):
1224 model = self.tree_widget.tree_model
1225 root_index = model._index_from_id(0, model.NAME)
1226
1227 state = model.data(root_index, QtCore.Qt.CheckStateRole)
1228 self.setCheckState(state.toInt()[0])
1229
1230 def clicked(self, state):
1231 model = self.tree_widget.tree_model
1232 root_index = model._index_from_id(0, model.NAME)
1233 if state:
1234 state = QtCore.QVariant(QtCore.Qt.Checked)
1235 else:
1236 state = QtCore.QVariant(QtCore.Qt.Unchecked)
1237
1238 model.setData(root_index, QtCore.QVariant(state),
1239 QtCore.Qt.CheckStateRole)
8561240
=== removed file 'lib/wtlist.py'
--- lib/wtlist.py 2009-06-18 03:44:48 +0000
+++ lib/wtlist.py 1970-01-01 00:00:00 +0000
@@ -1,449 +0,0 @@
1# -*- coding: utf-8 -*-
2#
3# QBzr - Qt frontend to Bazaar commands
4# Copyright (C) 2006-2007 Lukáš Lalinský <lalinsky@gmail.com>
5# Copyright (C) 2006 Trolltech ASA
6# Copyright (C) 2006 Jelmer Vernooij <jelmer@samba.org>
7#
8# This program is free software; you can redistribute it and/or
9# modify it under the terms of the GNU General Public License
10# as published by the Free Software Foundation; either version 2
11# of the License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with this program; if not, write to the Free Software
20# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21
22"""A QTreeWidget that shows the items in a working tree, and includes a common
23context menu."""
24
25from PyQt4 import QtCore, QtGui
26
27from bzrlib import osutils
28
29from bzrlib.plugins.qbzr.lib.diff import (
30 show_diff,
31 has_ext_diff,
32 ExtDiffMenu,
33 InternalWTDiffArgProvider,
34 )
35from bzrlib.plugins.qbzr.lib.i18n import gettext, N_
36from bzrlib.plugins.qbzr.lib.subprocess import SimpleSubProcessDialog
37from bzrlib.plugins.qbzr.lib.util import (
38 file_extension,
39 runs_in_loading_queue,
40 )
41
42
43class WorkingTreeFileList(QtGui.QTreeWidget):
44
45 SELECTALL_MESSAGE = N_("Select / deselect all")
46
47 def __init__(self, parent, tree):
48 QtGui.QTreeWidget.__init__(self, parent)
49 self._ignore_select_all_changes = False
50 self.selectall_checkbox = None # added by client.
51 self.tree = tree
52
53 def setup_actions(self):
54 """Setup double-click and context menu"""
55 parent = self.parentWidget()
56 parent.connect(self,
57 QtCore.SIGNAL("itemDoubleClicked(QTreeWidgetItem *, int)"),
58 self.itemDoubleClicked)
59
60 self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
61 parent.connect(self,
62 QtCore.SIGNAL("itemSelectionChanged()"),
63 self.update_context_menu_actions)
64 parent.connect(self,
65 QtCore.SIGNAL("customContextMenuRequested(QPoint)"),
66 self.show_context_menu)
67
68 self.context_menu = QtGui.QMenu(self)
69 if has_ext_diff():
70 self.diff_menu = ExtDiffMenu(self)
71 self.context_menu.addMenu(self.diff_menu)
72 self.connect(self.diff_menu, QtCore.SIGNAL("triggered(QString)"),
73 self.show_differences)
74 self.show_diff_ui = self.diff_menu
75 else:
76 self.show_diff_action = self.context_menu.addAction(
77 gettext("Show &differences..."), self.show_differences)
78 self.context_menu.setDefaultAction(self.show_diff_action)
79 self.show_diff_ui = self.show_diff_action
80
81 self.revert_action = self.context_menu.addAction(
82 gettext("&Revert..."), self.revert_selected)
83 # set all actions to disabled so it does the right thing with an empty
84 # list (our itemSelectionChanged() will fire as soon as we select one)
85 self.revert_action.setEnabled(False)
86 self.show_diff_ui.setEnabled(False)
87
88 @runs_in_loading_queue
89 def fill(self, items_iter):
90 self.setTextElideMode(QtCore.Qt.ElideMiddle)
91 self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
92 self.setSortingEnabled(True)
93 self.setHeaderLabels([gettext("File"), gettext("Extension"), gettext("Status")])
94 header = self.header()
95 header.setStretchLastSection(False)
96 header.setResizeMode(0, QtGui.QHeaderView.Stretch)
97 header.setResizeMode(1, QtGui.QHeaderView.ResizeToContents)
98 header.setResizeMode(2, QtGui.QHeaderView.ResizeToContents)
99 self.setRootIsDecorated(False)
100 self._ignore_select_all_changes = True # don't update as we add items!
101
102 # Each items_iter returns a tuple of (changes_tuple, is_checked)
103 # Where changes_tuple is a single item from iter_changes():
104 # (file_id, (path_in_source, path_in_target),
105 # changed_content, versioned, parent, name, kind,
106 # executable)
107 # Note that the current filter is used to determine if the items are
108 # shown or not
109 self.item_to_data = {}
110 items = []
111 for change_desc, visible, checked in items_iter:
112 (file_id, (path_in_source, path_in_target),
113 changed_content, versioned, parent, name, kind,
114 executable) = change_desc
115
116 if versioned == (False, False):
117 if self.tree.is_ignored(path_in_target):
118 status = gettext("ignored")
119 else:
120 status = gettext("non-versioned")
121 ext = file_extension(path_in_target)
122 name = path_in_target
123 elif versioned == (False, True):
124 status = gettext("added")
125 ext = file_extension(path_in_target)
126 name = path_in_target + osutils.kind_marker(kind[1])
127 elif versioned == (True, False):
128 status = gettext("removed")
129 ext = file_extension(path_in_source)
130 name = path_in_source + osutils.kind_marker(kind[0])
131 elif kind[0] is not None and kind[1] is None:
132 status = gettext("missing")
133 ext = file_extension(path_in_source)
134 name = path_in_source + osutils.kind_marker(kind[0])
135 else:
136 # versioned = True, True - so either renamed or modified
137 # or properties changed (x-bit).
138 renamed = (parent[0], name[0]) != (parent[1], name[1])
139 if renamed:
140 if changed_content:
141 status = gettext("renamed and modified")
142 else:
143 status = gettext("renamed")
144 name = "%s%s => %s%s" % (path_in_source,
145 osutils.kind_marker(kind[0]),
146 path_in_target,
147 osutils.kind_marker(kind[0]))
148 ext = file_extension(path_in_target)
149 elif changed_content:
150 status = gettext("modified")
151 name = path_in_target + osutils.kind_marker(kind[1])
152 ext = file_extension(path_in_target)
153 elif executable[0] != executable[1]:
154 status = gettext("modified (x-bit)")
155 name = path_in_target + osutils.kind_marker(kind[1])
156 ext = file_extension(path_in_target)
157 else:
158 raise RuntimeError, "what status am I missing??"
159
160 item = QtGui.QTreeWidgetItem()
161 item.setText(0, name)
162 item.setText(1, ext)
163 item.setText(2, status)
164 if visible:
165 items.append(item)
166
167 if checked is None:
168 item.setCheckState(0, QtCore.Qt.PartiallyChecked)
169 item.setFlags(item.flags() & ~QtCore.Qt.ItemIsUserCheckable)
170 elif checked:
171 item.setCheckState(0, QtCore.Qt.Checked)
172 else:
173 item.setCheckState(0, QtCore.Qt.Unchecked)
174 self.item_to_data[item] = change_desc
175 # add them all to the tree in one hit.
176 self.insertTopLevelItems(0, items)
177 self._ignore_select_all_changes = False
178 if self.selectall_checkbox is not None:
179 self.update_selectall_state(None, None)
180
181 def set_item_hidden(self, item, hide):
182 # Due to what seems a bug in Qt "hiding" an item isn't always
183 # reliable - so a "hidden" item is simply not added to the tree!
184 # See https://bugs.launchpad.net/qbzr/+bug/274295
185 index = self.indexOfTopLevelItem(item)
186 if index == -1 and not hide:
187 self.addTopLevelItem(item)
188 elif index != -1 and hide:
189 self.takeTopLevelItem(index)
190
191 def is_item_hidden(self, item):
192 return self.indexOfTopLevelItem(item) == -1
193
194 def iter_treeitem_and_desc(self, include_hidden=False):
195 """iterators to help work with the selection, checked items, etc"""
196 for ti, desc in self.item_to_data.iteritems():
197 if include_hidden or not self.is_item_hidden(ti):
198 yield ti, desc
199
200 def iter_selection(self):
201 for i in self.selectedItems():
202 yield self.item_to_data[i]
203
204 def iter_checked(self):
205 # XXX - just use self.iter_treeitem_and_desc() - no need to hit the
206 # XXX tree object at all!?
207 for i in range(self.topLevelItemCount()):
208 item = self.topLevelItem(i)
209 if item.checkState(0) == QtCore.Qt.Checked:
210 yield self.item_to_data[item]
211
212 def show_context_menu(self, pos):
213 """Context menu and double-click related functions..."""
214 self.context_menu.popup(self.viewport().mapToGlobal(pos))
215
216 def update_context_menu_actions(self):
217 contains_non_versioned = False
218 for desc in self.iter_selection():
219 if desc[3] == (False, False):
220 contains_non_versioned = True
221 break
222 self.revert_action.setEnabled(not contains_non_versioned)
223 self.show_diff_ui.setEnabled(not contains_non_versioned)
224
225 def revert_selected(self):
226 """Revert the selected file."""
227 items = self.selectedItems()
228 if not items:
229 return
230
231 paths = [self.item_to_data[item][1][1] for item in items]
232
233 args = ["revert"]
234 args.extend(paths)
235 desc = (gettext("Revert %s to latest revision.") % ", ".join(paths))
236 revert_dialog = SimpleSubProcessDialog(gettext("Revert"),
237 desc=desc,
238 args=args,
239 dir=self.tree.basedir,
240 parent=self,
241 hide_progress=True,
242 )
243 res = revert_dialog.exec_()
244 if res == QtGui.QDialog.Accepted:
245 for item in items:
246 index = self.indexOfTopLevelItem(item)
247 self.takeTopLevelItem(index)
248
249 def itemDoubleClicked(self, items=None, column=None):
250 self.show_differences()
251
252 def show_differences(self, ext_diff=None):
253 """Show differences between the working copy and the last revision."""
254 if not self.show_diff_ui.isEnabled():
255 return
256
257 entries = [desc.path() for desc in self.iter_selection()]
258 if entries:
259 arg_provider = InternalWTDiffArgProvider(
260 self.tree.basis_tree().get_revision_id(), self.tree,
261 self.tree.branch, self.tree.branch,
262 specific_files=entries)
263
264 show_diff(arg_provider, ext_diff=ext_diff,
265 parent_window=self.topLevelWidget())
266
267 def set_selectall_checkbox(self, checkbox):
268 """Helpers for a 'show all' checkbox. Parent widgets must create the
269 widget and pass it to us.
270 """
271 checkbox.setTristate(True)
272 self.selectall_checkbox = checkbox
273 parent = self.parentWidget()
274 parent.connect(self,
275 QtCore.SIGNAL("itemChanged(QTreeWidgetItem *, int)"),
276 self.update_selectall_state)
277
278 parent.connect(checkbox, QtCore.SIGNAL("stateChanged(int)"),
279 self.selectall_changed)
280
281 def update_selectall_state(self, item, column):
282 """Update the state of the 'select all' checkbox to reflect the state
283 of the items in the list.
284 """
285 if self._ignore_select_all_changes:
286 return
287 checked = 0
288 num_items = 0
289
290 for (tree_item, change_desc) in self.iter_treeitem_and_desc():
291 if tree_item.checkState(0) == QtCore.Qt.Checked:
292 checked += 1
293 num_items += 1
294 self._ignore_select_all_changes = True
295 if checked == 0:
296 self.selectall_checkbox.setCheckState(QtCore.Qt.Unchecked)
297 elif checked == num_items:
298 self.selectall_checkbox.setCheckState(QtCore.Qt.Checked)
299 else:
300 self.selectall_checkbox.setCheckState(QtCore.Qt.PartiallyChecked)
301 self._ignore_select_all_changes = False
302
303 def selectall_changed(self, state):
304 if self._ignore_select_all_changes or not self.selectall_checkbox.isEnabled():
305 return
306 if state == QtCore.Qt.PartiallyChecked:
307 self.selectall_checkbox.setCheckState(QtCore.Qt.Checked)
308 return
309
310 self._ignore_select_all_changes = True
311 for (tree_item, change_desc) in self.iter_treeitem_and_desc():
312 tree_item.setCheckState(0, QtCore.Qt.CheckState(state))
313 self._ignore_select_all_changes = False
314
315
316class ChangeDesc(tuple):
317 """Helper class that "knows" about internals of iter_changes' changed entry
318 description tuple, and provides additional helper methods.
319
320 iter_changes return tuple with info about changed entry:
321 [0]: file_id -> ascii string
322 [1]: paths -> 2-tuple (old, new) fullpaths unicode/None
323 [2]: changed_content -> bool
324 [3]: versioned -> 2-tuple (bool, bool)
325 [4]: parent -> 2-tuple
326 [5]: name -> 2-tuple (old_name, new_name) utf-8?/None
327 [6]: kind -> 2-tuple (string/None, string/None)
328 [7]: executable -> 2-tuple (bool/None, bool/None)
329
330 --optional--
331 [8]: is_ignored -> If the file is ignored, pattern which caused it to
332 be ignored, otherwise None.
333
334 NOTE: None value used for non-existing entry in corresponding
335 tree, e.g. for added/deleted/ignored/unversioned
336 """
337
338 # XXX We should may be try get this into bzrlib.
339 # XXX We should use this in qdiff.
340
341 def fileid(desc):
342 return desc[0]
343
344 def path(desc):
345 """Return a suitable entry for a 'specific_files' param to bzr functions."""
346 oldpath, newpath = desc[1]
347 return newpath or oldpath
348
349 def oldpath(desc):
350 """Return oldpath for renames."""
351 return desc[1][0]
352
353 def kind(desc):
354 oldkind, newkind = desc[6]
355 return newkind or oldkind
356
357 def is_versioned(desc):
358 return desc[3] != (False, False)
359
360 def is_modified(desc):
361 return (desc[3] != (False, False) and desc[2])
362
363 def is_renamed(desc):
364 return (desc[3] == (True, True)
365 and (desc[4][0], desc[5][0]) != (desc[4][1], desc[5][1]))
366
367 def is_tree_root(desc):
368 """Check if entry actually tree root."""
369 if desc[3] != (False, False) and desc[4] == (None, None):
370 # TREE_ROOT has not parents (desc[4]).
371 # But because we could want to see unversioned files
372 # we need to check for versioned flag (desc[3])
373 return True
374 return False
375
376 def is_missing(desc):
377 """Check if file was present in previous revision but now it's gone
378 (i.e. deleted manually, without invoking `bzr remove` command)
379 """
380 return (desc[3] == (True, True) and desc[6][1] is None)
381
382 def is_misadded(desc):
383 """Check if file was added to the working tree but then gone
384 (i.e. deleted manually, without invoking `bzr remove` command)
385 """
386 return (desc[3] == (False, True) and desc[6][1] is None)
387
388 def is_ignored(desc):
389 if len(desc) >= 8:
390 return desc[8]
391 else:
392 return None
393
394 def status(desc):
395 if len(desc) == 8:
396 (file_id, (path_in_source, path_in_target),
397 changed_content, versioned, parent, name, kind,
398 executable) = desc
399 is_ignored = None
400 elif len(desc) == 9:
401 (file_id, (path_in_source, path_in_target),
402 changed_content, versioned, parent, name, kind,
403 executable, is_ignored) = desc
404 else:
405 raise RuntimeError, "Unkown number of items to unpack."
406
407 if versioned == (False, False):
408 if is_ignored:
409 return gettext("ignored")
410 else:
411 return gettext("non-versioned")
412 elif versioned == (False, True):
413 return gettext("added")
414 elif versioned == (True, False):
415 return gettext("removed")
416 elif kind[0] is not None and kind[1] is None:
417 return gettext("missing")
418 else:
419 # versioned = True, True - so either renamed or modified
420 # or properties changed (x-bit).
421 renamed = (parent[0], name[0]) != (parent[1], name[1])
422 if renamed:
423 if changed_content:
424 return gettext("renamed and modified")
425 else:
426 return gettext("renamed")
427 elif changed_content:
428 return gettext("modified")
429 elif executable[0] != executable[1]:
430 return gettext("modified (x-bit)")
431 else:
432 raise RuntimeError, "what status am I missing??"
433
434
435def closure_in_selected_list(selected_list):
436 """Return in_selected_list(path) function for given selected_list."""
437
438 def in_selected_list(path):
439 """Check: is path belongs to some selected_list."""
440 if path in selected_list:
441 return True
442 for p in selected_list:
443 if path.startswith(p):
444 return True
445 return False
446
447 if not selected_list:
448 return lambda path: True
449 return in_selected_list

Subscribers

People subscribed via source and target branches

to status/vote changes: