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