~qbzr-dev/qbzr/trunk

230.1.4 by Gary van der Merwe
qlog: Move Model to separate file. I'm going to intergrate linegraph into the model, so it' going to get big.
1
# -*- coding: utf-8 -*-
2
#
3
# QBzr - Qt frontend to Bazaar commands
4
# Copyright (C) 2006-2007 Gary van der Merwe <garyvdm@gmail.com> 
5
#
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License
8
# as published by the Free Software Foundation; either version 2
9
# of the License, or (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
272.1.1 by Gary van der Merwe
qlog: Remove unnessary imports from log.py and logmodel.py.
20
from PyQt4 import QtCore
230.1.4 by Gary van der Merwe
qlog: Move Model to separate file. I'm going to intergrate linegraph into the model, so it' going to get big.
21
from time import (strftime, localtime)
272.1.1 by Gary van der Merwe
qlog: Remove unnessary imports from log.py and logmodel.py.
22
from bzrlib import lazy_regex
230.1.4 by Gary van der Merwe
qlog: Move Model to separate file. I'm going to intergrate linegraph into the model, so it' going to get big.
23
from bzrlib.revision import NULL_REVISION
230.1.5 by Gary van der Merwe
Move linegraph.py code to logmodel.py.
24
from bzrlib.tsort import merge_sort
230.1.4 by Gary van der Merwe
qlog: Move Model to separate file. I'm going to intergrate linegraph into the model, so it' going to get big.
25
from bzrlib.plugins.qbzr.lib.diff import DiffWindow
26
from bzrlib.plugins.qbzr.lib.i18n import gettext
27
from bzrlib.plugins.qbzr.lib.util import (
28
    extract_name,
29
    )
30
31
TagsRole = QtCore.Qt.UserRole + 1
32
BugIdsRole = QtCore.Qt.UserRole + 2
33
GraphNodeRole = QtCore.Qt.UserRole + 3
230.1.12 by Gary van der Merwe
qLog: Expandable / Colapseable branchs
34
GraphLinesRole = QtCore.Qt.UserRole + 4
230.1.27 by Gary van der Merwe
qlog: Simplfy twisties
35
GraphTwistyStateRole = QtCore.Qt.UserRole + 5
230.1.20 by Gary van der Merwe
qlog: Get revisions from logmodel.
36
RevIdRole = QtCore.Qt.UserRole + 6
230.1.4 by Gary van der Merwe
qlog: Move Model to separate file. I'm going to intergrate linegraph into the model, so it' going to get big.
37
38
FilterIdRole = QtCore.Qt.UserRole + 100
39
FilterMessageRole = QtCore.Qt.UserRole + 101
40
FilterAuthorRole = QtCore.Qt.UserRole + 102
41
FilterRevnoRole = QtCore.Qt.UserRole + 103
42
43
COL_REV = 0
230.1.26 by Gary van der Merwe
qlog: Make the Graph and Message columns one.
44
COL_MESSAGE = 1
230.1.4 by Gary van der Merwe
qlog: Move Model to separate file. I'm going to intergrate linegraph into the model, so it' going to get big.
45
COL_DATE = 2
46
COL_AUTHOR = 3
47
48
_bug_id_re = lazy_regex.lazy_compile(r'(?:bugs/|ticket/|show_bug\.cgi\?id=)(\d+)(?:\b|$)')
49
50
def get_bug_id(branch, bug_url):
51
    match = _bug_id_re.search(bug_url)
52
    if match:
53
        return match.group(1)
54
    return None
55
56
class TreeModel(QtCore.QAbstractTableModel):
57
    def __init__(self, parent=None):
58
        QtCore.QAbstractTableModel.__init__(self, parent)
59
        
60
        self.horizontalHeaderLabels = [gettext("Rev"),
230.1.26 by Gary van der Merwe
qlog: Make the Graph and Message columns one.
61
                                       gettext("Message"),
230.1.4 by Gary van der Merwe
qlog: Move Model to separate file. I'm going to intergrate linegraph into the model, so it' going to get big.
62
                                       gettext("Date"),
63
                                       gettext("Author"),
64
                                       ]
65
        
66
        self.linegraphdata = []
67
        self.columns_len = 0
68
        self.revisions = {}
69
        self.tags = {}
230.1.24 by Gary van der Merwe
qlog: Get search working again.
70
        self.searchMode = False
230.1.9 by Gary van der Merwe
Make the code able to hide a branch.
71
    
230.1.38 by Gary van der Merwe
qlog: Fix specific_fileid.
72
    def loadBranch(self, branch, start_revs = None, specific_fileid = None):
230.1.4 by Gary van der Merwe
qlog: Move Model to separate file. I'm going to intergrate linegraph into the model, so it' going to get big.
73
        self.branch = branch
74
        branch.lock_read()
75
        try:
230.1.5 by Gary van der Merwe
Move linegraph.py code to logmodel.py.
76
            self.revisions = {}
230.1.4 by Gary van der Merwe
qlog: Move Model to separate file. I'm going to intergrate linegraph into the model, so it' going to get big.
77
            if start_revs is None:
78
                start_revs = [branch.last_revision()]
230.1.5 by Gary van der Merwe
Move linegraph.py code to logmodel.py.
79
            graph = branch.repository.get_graph()
230.1.6 by Gary van der Merwe
Split the graph loading and sorting and the line computing into 2 methods.
80
            self.graph_parents = {}
230.1.5 by Gary van der Merwe
Move linegraph.py code to logmodel.py.
81
            ghosts = set()
230.1.6 by Gary van der Merwe
Split the graph loading and sorting and the line computing into 2 methods.
82
            self.graph_children = {}
230.1.5 by Gary van der Merwe
Move linegraph.py code to logmodel.py.
83
            for (revid, parent_revids) in graph.iter_ancestry(start_revs):
84
                if parent_revids is None:
85
                    ghosts.add(revid)
86
                    continue
87
                if parent_revids == (NULL_REVISION,):
230.1.6 by Gary van der Merwe
Split the graph loading and sorting and the line computing into 2 methods.
88
                    self.graph_parents[revid] = ()
230.1.5 by Gary van der Merwe
Move linegraph.py code to logmodel.py.
89
                else:
230.1.6 by Gary van der Merwe
Split the graph loading and sorting and the line computing into 2 methods.
90
                    self.graph_parents[revid] = parent_revids
230.1.5 by Gary van der Merwe
Move linegraph.py code to logmodel.py.
91
                for parent in parent_revids:
230.1.6 by Gary van der Merwe
Split the graph loading and sorting and the line computing into 2 methods.
92
                    self.graph_children.setdefault(parent, []).append(revid)
93
                self.graph_children.setdefault(revid, [])
230.1.42 by Gary van der Merwe
qlog: Intelligent background revision loading.
94
                if len(self.graph_parents) % 100 == 0 :
95
                    QtCore.QCoreApplication.processEvents()
230.1.5 by Gary van der Merwe
Move linegraph.py code to logmodel.py.
96
            for ghost in ghosts:
230.1.6 by Gary van der Merwe
Split the graph loading and sorting and the line computing into 2 methods.
97
                for ghost_child in self.graph_children[ghost]:
98
                    self.graph_parents[ghost_child] = [p for p in self.graph_parents[ghost_child]
230.1.5 by Gary van der Merwe
Move linegraph.py code to logmodel.py.
99
                                                  if p not in ghosts]
230.1.6 by Gary van der Merwe
Split the graph loading and sorting and the line computing into 2 methods.
100
            self.graph_parents["top:"] = start_revs
230.1.5 by Gary van der Merwe
Move linegraph.py code to logmodel.py.
101
        
230.1.6 by Gary van der Merwe
Split the graph loading and sorting and the line computing into 2 methods.
102
            if len(self.graph_parents)>0:
103
                self.merge_sorted_revisions = merge_sort(
104
                    self.graph_parents,
230.1.5 by Gary van der Merwe
Move linegraph.py code to logmodel.py.
105
                    "top:",
106
                    generate_revno=True)
107
            else:
230.1.6 by Gary van der Merwe
Split the graph loading and sorting and the line computing into 2 methods.
108
                self.merge_sorted_revisions = ()
109
            
110
            assert self.merge_sorted_revisions[0][1] == "top:"
111
            self.merge_sorted_revisions = self.merge_sorted_revisions[1:]
230.1.5 by Gary van der Merwe
Move linegraph.py code to logmodel.py.
112
            
230.1.39 by Gary van der Merwe
qlog: Fix bug when clicking on line when in search mode.
113
            self.visible_msri = None
114
            
230.1.38 by Gary van der Merwe
qlog: Fix specific_fileid.
115
            if specific_fileid is not None:
116
                text_keys = [(specific_fileid, revid) for (sequence_number,
117
                                                            revid,
118
                                                            merge_depth,
119
                                                            revno_sequence,
120
                                                            end_of_merge) in self.merge_sorted_revisions]
121
                modified_text_versions = branch.repository.texts.get_parent_map(text_keys)
122
                
123
                self.visible_msri = [rev_index for (rev_index,(sequence_number,
124
                                                               revid,
125
                                                               merge_depth,
126
                                                               revno_sequence,
127
                                                               end_of_merge)) \
128
                                     in enumerate(self.merge_sorted_revisions) \
129
                                     if (specific_fileid, revid) in modified_text_versions]
130
          
230.1.8 by Gary van der Merwe
Add field to store if a branch line is visible.
131
            # This will hold, for each "branch", [a list of revision indexes in
230.1.29 by Gary van der Merwe
qlog: Colapse parent branchs that are widdowed.
132
            # the branch, is the branch visible, parents, children].
230.1.5 by Gary van der Merwe
Move linegraph.py code to logmodel.py.
133
            #
134
            # For a revisions, the revsion number less the least significant
135
            # digit is the branch_id, and used as the key for the dict. Hence
136
            # revision with the same revsion number less the least significant
137
            # digit are considered to be in the same branch line. e.g.: for
138
            # revisions 290.12.1 and 290.12.2, the branch_id would be 290.12,
139
            # and these two revisions will be in the same branch line. 
230.1.6 by Gary van der Merwe
Split the graph loading and sorting and the line computing into 2 methods.
140
            self.branch_lines = {}
230.1.11 by Gary van der Merwe
Calculate the parent color if it is hidden.
141
            self.revid_msri = {}
230.1.14 by Gary van der Merwe
qlog: Refactor linegraphdata structure to remove dup data.
142
            self.revno_msri = {}
230.1.6 by Gary van der Merwe
Split the graph loading and sorting and the line computing into 2 methods.
143
            
144
            for (rev_index, (sequence_number,
145
                             revid,
146
                             merge_depth,
147
                             revno_sequence,
148
                             end_of_merge)) in enumerate(self.merge_sorted_revisions):
149
                branch_id = revno_sequence[0:-1]
150
                
230.1.11 by Gary van der Merwe
Calculate the parent color if it is hidden.
151
                self.revid_msri[revid] = rev_index
230.1.14 by Gary van der Merwe
qlog: Refactor linegraphdata structure to remove dup data.
152
                self.revno_msri[revno_sequence] = rev_index
230.1.38 by Gary van der Merwe
qlog: Fix specific_fileid.
153
                
154
                if self.visible_msri is None or rev_index in self.visible_msri:
155
                    branch_line = None
156
                    if branch_id not in self.branch_lines:
157
                        #initialy, only the main line is visible
158
                        branch_line = [[],
159
                                       len(branch_id)==0,
160
                                       set(),
161
                                       set()]
162
                        self.branch_lines[branch_id] = branch_line
163
                    else:
164
                        branch_line = self.branch_lines[branch_id]
165
                    
166
                    branch_line[0].append(rev_index)
167
                    for child_revid in self.graph_children[revid]:
168
                        child_msri = self.revid_msri[child_revid]
169
                        child_branch_id = self.merge_sorted_revisions[child_msri][3][0:-1]
170
                        if not child_branch_id == branch_id and child_branch_id in self.branch_lines:
171
                            branch_line[3].add(child_branch_id)
172
                            self.branch_lines[child_branch_id][2].add(branch_id)
230.1.29 by Gary van der Merwe
qlog: Colapse parent branchs that are widdowed.
173
            
230.1.6 by Gary van der Merwe
Split the graph loading and sorting and the line computing into 2 methods.
174
            self.branch_ids = self.branch_lines.keys()
175
        
176
            def branch_id_cmp(x, y):
177
                """Compaire branch_id's first by the number of digits, then reversed
178
                by their value"""
179
                len_x = len(x)
180
                len_y = len(y)
181
                if len_x == len_y:
182
                    return -cmp(x, y)
183
                return cmp(len_x, len_y)
184
            
185
            self.branch_ids.sort(branch_id_cmp)
230.1.41 by Gary van der Merwe
qlog: Display revisions before calculating graph.
186
            
230.1.6 by Gary van der Merwe
Split the graph loading and sorting and the line computing into 2 methods.
187
            self.tags = branch.tags.get_reverse_tag_dict()
230.1.17 by Gary van der Merwe
qlog: Turn off broken_lines. Make twisty clickable area bigger.
188
            
230.1.41 by Gary van der Merwe
qlog: Display revisions before calculating graph.
189
            self.compute_lines(update_before_lines = True)
190
            
191
            QtCore.QCoreApplication.processEvents()
230.1.42 by Gary van der Merwe
qlog: Intelligent background revision loading.
192
            
272.1.3 by Gary van der Merwe
qlog: Improve background revision loading.
193
            self._nextRevisionToLoadGen = self._nextRevisionToLoad()
194
            self._loadNextRevision()
230.1.6 by Gary van der Merwe
Split the graph loading and sorting and the line computing into 2 methods.
195
        finally:
196
            branch.unlock
197
        
230.1.41 by Gary van der Merwe
qlog: Display revisions before calculating graph.
198
    def compute_lines(self, broken_line_length = None, update_before_lines = False):
279 by Gary van der Merwe
qlog: Fix bugs with using bzr qlog FILE.
199
        if self.visible_msri is not None:
200
            return self.compute_search()
201
        
230.1.6 by Gary van der Merwe
Split the graph loading and sorting and the line computing into 2 methods.
202
        self.emit(QtCore.SIGNAL("layoutAboutToBeChanged()"))
203
        try:
230.1.14 by Gary van der Merwe
qlog: Refactor linegraphdata structure to remove dup data.
204
            # This will hold for each revision, a list of (msri,
230.1.10 by Gary van der Merwe
Show merge line for hidden parents.
205
            #                                              node,
206
            #                                              lines,
230.1.27 by Gary van der Merwe
qlog: Simplfy twisties
207
            #                                              twisty_state,
208
            #                                              twisty_branch_ids).
230.1.5 by Gary van der Merwe
Move linegraph.py code to logmodel.py.
209
            #
230.1.11 by Gary van der Merwe
Calculate the parent color if it is hidden.
210
            # Node is a tuple of (column, color) with column being a
230.1.5 by Gary van der Merwe
Move linegraph.py code to logmodel.py.
211
            # zero-indexed column number of the graph that this revision
230.1.11 by Gary van der Merwe
Calculate the parent color if it is hidden.
212
            # represents and color being a zero-indexed color (which doesn't
213
            # specify any actual color in particular) to draw the node in.
230.1.5 by Gary van der Merwe
Move linegraph.py code to logmodel.py.
214
            #
215
            # Lines is a list of tuples which represent lines you should draw
216
            # away from the revision, if you also need to draw lines into the
217
            # revision you should use the lines list from the previous
218
            # iteration. Each tuples in the list is in the form (start, end,
230.1.11 by Gary van der Merwe
Calculate the parent color if it is hidden.
219
            # color) with start and end being zero-indexed column numbers and
220
            # color as in node.
230.1.10 by Gary van der Merwe
Show merge line for hidden parents.
221
            #
230.1.27 by Gary van der Merwe
qlog: Simplfy twisties
222
            # twisties are +- buttons to show/hide branches. list branch_ids
230.1.5 by Gary van der Merwe
Move linegraph.py code to logmodel.py.
223
            self.linegraphdata = []
230.1.14 by Gary van der Merwe
qlog: Refactor linegraphdata structure to remove dup data.
224
            
230.1.21 by Gary van der Merwe
qlog: Fix regression where you get an error when clicking on a qlog-revid link.
225
            self.msri_index = {}
230.1.14 by Gary van der Merwe
qlog: Refactor linegraphdata structure to remove dup data.
226
            
227
            for branch_id in self.branch_ids:
230.1.29 by Gary van der Merwe
qlog: Colapse parent branchs that are widdowed.
228
                (branch_rev_msri,
229
                 branch_visible,
230
                 branch_parents,
231
                 branch_children) = self.branch_lines[branch_id]
230.1.9 by Gary van der Merwe
Make the code able to hide a branch.
232
                if branch_visible:
230.1.14 by Gary van der Merwe
qlog: Refactor linegraphdata structure to remove dup data.
233
                    for rev_msri in branch_rev_msri:
230.1.38 by Gary van der Merwe
qlog: Fix specific_fileid.
234
                        if self.visible_msri is None or rev_msri in self.visible_msri:
235
                            self.linegraphdata.append([rev_msri,
236
                                                       None,
237
                                                       [],
238
                                                       None,
239
                                                       [],
240
                                                      ])
230.1.14 by Gary van der Merwe
qlog: Refactor linegraphdata structure to remove dup data.
241
            self.linegraphdata.sort(lambda x, y: cmp(x[0], y[0]))
242
            
230.1.27 by Gary van der Merwe
qlog: Simplfy twisties
243
            for rev_index, (msri, node, lines,
244
                            twisty_state, twisty_branch_ids) in \
230.1.14 by Gary van der Merwe
qlog: Refactor linegraphdata structure to remove dup data.
245
                    enumerate(self.linegraphdata):
230.1.41 by Gary van der Merwe
qlog: Display revisions before calculating graph.
246
                self.msri_index[msri] = rev_index        
247
        finally:
248
            self.emit(QtCore.SIGNAL("layoutChanged()"))
249
        
250
        if update_before_lines:
251
            QtCore.QCoreApplication.processEvents()
252
        
253
        # This will hold a tuple of (child_index, parent_index, col_index
254
        # for each line that needs to be drawn. If col_index is not none,
255
        # then the line is drawn along that column, else the the line can be
256
        # drawn directly between the child and parent because either the
257
        # child and parent are in the same branch line, or the child and
258
        # parent are 1 row apart.
259
        lines = []
260
        empty_column = [False for i in range(len(self.linegraphdata))]
261
        # This will hold a bit map for each cell. If the cell is true, then
262
        # the cell allready contains a node or line. This use when deciding
263
        # what column to place a branch line or line in, without it
264
        # overlaping something else.
265
        columns = [list(empty_column)]
266
        
267
        
268
        for branch_id in self.branch_ids:
269
            (branch_rev_msri,
270
             branch_visible,
271
             branch_parents,
272
             branch_children) = self.branch_lines[branch_id]
273
            
274
            if branch_visible:
275
                color = reduce(lambda x, y: x+y, branch_id, 0)
230.1.6 by Gary van der Merwe
Split the graph loading and sorting and the line computing into 2 methods.
276
                
230.1.41 by Gary van der Merwe
qlog: Display revisions before calculating graph.
277
                # Find columns for lines for each parent of each revision in
278
                # the branch that are long and need to go between the parent
279
                # branch and the child branch. Also add branch_ids to
280
                # twisty_branch_ids.
281
                parents_with_lines = []
282
                for rev_msri in branch_rev_msri:
283
                    rev_index = self.msri_index[rev_msri]
284
                    (sequence_number,
285
                         revid,
286
                         merge_depth,
287
                         revno_sequence,
288
                         end_of_merge) = self.merge_sorted_revisions[rev_msri]
289
                    
290
                    for parent_revid in self.graph_parents[revid]:
291
                        parent_msri = self.revid_msri[parent_revid]
292
                        parent_branch_id = \
293
                            self.merge_sorted_revisions[parent_msri][3][0:-1]
294
                        
295
                        if not parent_branch_id == branch_id and \
296
                           not parent_branch_id == ():
297
                            self.linegraphdata[rev_index][4].append (parent_branch_id)
298
                        
299
                        if revno_sequence[-1] == 1 or \
300
                                parent_branch_id == branch_id or\
301
                                branch_id == ():
302
                            continue
303
                        
230.1.21 by Gary van der Merwe
qlog: Fix regression where you get an error when clicking on a qlog-revid link.
304
                        if parent_msri in self.msri_index:
230.1.41 by Gary van der Merwe
qlog: Display revisions before calculating graph.
305
                            parent_index = self.msri_index[parent_msri]                            
230.1.5 by Gary van der Merwe
Move linegraph.py code to logmodel.py.
306
                            parent_node = self.linegraphdata[parent_index][1]
307
                            if parent_node:
308
                                parent_col_index = parent_node[0]
309
                            else:
230.1.41 by Gary van der Merwe
qlog: Display revisions before calculating graph.
310
                                parent_col_index = 0
311
                            col_search_order = \
312
                                _branch_line_col_search_order(columns, parent_col_index)
313
                                
314
                            
315
                            if not (len(branch_id) > 0 and \
316
                                    broken_line_length and \
317
                                    parent_index - rev_index > broken_line_length):
318
                                line_col_index = parent_col_index
319
                                if parent_index - rev_index >1:
320
                                    line_range = range(rev_index + 1, parent_index)
321
                                    line_col_index = \
322
                                        _find_free_column(columns,
323
                                                          empty_column,
324
                                                          col_search_order,
325
                                                          line_range)
326
                                    _mark_column_as_used(columns,
327
                                                         line_col_index,
328
                                                         line_range)
329
                                    lines.append((rev_index,
330
                                                  parent_index,
331
                                                  (line_col_index,),
332
                                                  ))
333
                                    parents_with_lines.append(parent_revid)
230.1.9 by Gary van der Merwe
Make the code able to hide a branch.
334
                    
230.1.41 by Gary van der Merwe
qlog: Display revisions before calculating graph.
335
                    # Work out if the twisty needs to show a + or -. If all
336
                    # twisty_branch_ids are visible, show - else +.
337
                    if len (self.linegraphdata[rev_index][4])>0:
338
                        twisty_state = True
339
                        for twisty_branch_id in self.linegraphdata[rev_index][4]:
340
                            if not self.branch_lines[twisty_branch_id][1]:
341
                                twisty_state = False
342
                                break
343
                        self.linegraphdata[rev_index][3] = twisty_state
344
                
345
                # Find a column for this branch.
346
                #
347
                # Find the col_index for the direct parent branch. This will
348
                # be the starting point when looking for a free column.
349
                parent_col_index = 0
350
                parent_index = None
351
                if len(branch_id) > 1:
352
                    parent_revno = branch_id[0:-1]
353
                    parent_msri = self.revno_msri[parent_revno]
354
                    if parent_msri in self.msri_index:
355
                        parent_index = self.msri_index[parent_msri]
356
                        parent_node = self.linegraphdata[parent_index][1]
357
                        if parent_node:
358
                            parent_col_index = parent_node[0]
359
                
360
                col_search_order = _branch_line_col_search_order(columns,
361
                                                                 parent_col_index)
362
                cur_cont_line = []
363
                
364
                # Work out what rows this branch spans
365
                line_range = []
366
                last_rev_index = None
367
                for rev_msri in branch_rev_msri:
368
                    rev_index = self.msri_index[rev_msri]
369
                    if last_rev_index:
230.1.9 by Gary van der Merwe
Make the code able to hide a branch.
370
                        if broken_line_length and \
230.1.41 by Gary van der Merwe
qlog: Display revisions before calculating graph.
371
                           rev_index - last_rev_index > broken_line_length:
230.1.14 by Gary van der Merwe
qlog: Refactor linegraphdata structure to remove dup data.
372
                            line_range.append(last_rev_index+1)
230.1.41 by Gary van der Merwe
qlog: Display revisions before calculating graph.
373
                            line_range.append(rev_index-1)
230.1.9 by Gary van der Merwe
Make the code able to hide a branch.
374
                        else:
230.1.41 by Gary van der Merwe
qlog: Display revisions before calculating graph.
375
                            line_range.extend(range(last_rev_index+1, rev_index))
376
                    
377
                    line_range.append(rev_index)
378
                    last_rev_index = rev_index
379
                
380
                if parent_index:
381
                    if broken_line_length and \
382
                       parent_index - last_rev_index > broken_line_length:
383
                        line_range.append(last_rev_index+1)
384
                    else:
385
                        line_range.extend(range(last_rev_index+1, parent_index))
386
                
387
                col_index = _find_free_column(columns,
388
                                              empty_column,
389
                                              col_search_order,
390
                                              line_range)
391
                node = (col_index, color)
392
                # Free column for this branch found. Set node for all
393
                # revision in this branch.
394
                for rev_msri in branch_rev_msri:
395
                    rev_index = self.msri_index[rev_msri]
396
                    self.linegraphdata[rev_index][1] = node
397
                    columns[col_index][rev_index] = True
398
                
399
                # Find columns for lines for each parent of each
400
                # revision in the branch.
401
                for rev_msri in branch_rev_msri:
402
                    rev_index = self.msri_index[rev_msri]
403
                    (sequence_number,
404
                         revid,
405
                         merge_depth,
406
                         revno_sequence,
407
                         end_of_merge) = self.merge_sorted_revisions[rev_msri]
408
                    
409
                    col_index = self.linegraphdata[rev_index][1][0]
410
                    
411
                    for parent_revid in self.graph_parents[revid]:
412
                        parent_msri = self.revid_msri[parent_revid]
413
                        
414
                        if parent_revid in parents_with_lines:
415
                            continue
416
                        
417
                        if parent_msri in self.msri_index:
418
                            parent_index = self.msri_index[parent_msri]                            
419
                            parent_node = self.linegraphdata[parent_index][1]
420
                            if parent_node:
421
                                parent_col_index = parent_node[0]
422
                            else:
423
                                parent_col_index = None
424
                            col_search_order = \
425
                                    _line_col_search_order(columns,
426
                                                           parent_col_index,
427
                                                           col_index)
428
                                
429
                            # If this line is really long, break it.
430
                            if len(branch_id) > 0 and \
431
                               broken_line_length and \
432
                               parent_index - rev_index > broken_line_length:
433
                                child_line_col_index = \
434
                                    _find_free_column(columns,
435
                                                      empty_column,
436
                                                      col_search_order,
437
                                                      (rev_index + 1,))
438
                                _mark_column_as_used(columns,
439
                                                     child_line_col_index,
440
                                                     (rev_index + 1,))
441
                                
442
                                # Recall _line_col_search_order to reset it back to
443
                                # the start.
230.1.5 by Gary van der Merwe
Move linegraph.py code to logmodel.py.
444
                                col_search_order = \
445
                                        _line_col_search_order(columns,
446
                                                               parent_col_index,
447
                                                               col_index)
230.1.41 by Gary van der Merwe
qlog: Display revisions before calculating graph.
448
                                parent_col_line_index = \
449
                                    _find_free_column(columns,
450
                                                      empty_column,
451
                                                      col_search_order,
452
                                                      (parent_index - 1,))
453
                                _mark_column_as_used(columns,
454
                                                     parent_col_line_index,
455
                                                     (parent_index - 1,))
456
                                lines.append((rev_index,
457
                                              parent_index,
458
                                              (child_line_col_index,
459
                                               parent_col_line_index),
460
                                              ))
461
                            else :
462
                                line_col_index = col_index
463
                                if parent_index - rev_index >1:
464
                                    line_range = range(rev_index + 1, parent_index)
465
                                    line_col_index = \
466
                                        _find_free_column(columns,
467
                                                          empty_column,
468
                                                          col_search_order,
469
                                                          line_range)
470
                                    _mark_column_as_used(columns,
471
                                                         line_col_index,
472
                                                         line_range)
473
                                lines.append((rev_index,
474
                                              parent_index,
475
                                              (line_col_index,),
476
                                              ))
477
        
478
        # It has now been calculated which column a line must go into. Now
479
        # copy the lines in to linegraphdata.
480
        for (child_index,
481
             parent_index,
482
             line_col_indexes,
483
             ) in lines:
484
            
485
            (child_col_index, child_color) = self.linegraphdata[child_index][1]
486
            if parent_index is not None:
487
                (parent_col_index, parent_color) = self.linegraphdata[parent_index][1]
488
            else:
489
                (parent_col_index, parent_color) = (child_col_index, parent_color)
490
            
491
            if len(line_col_indexes) == 1:
492
                assert parent_index is not None
493
                if parent_index - child_index == 1:
494
                    self.linegraphdata[child_index][2].append(
495
                        (child_col_index,
496
                         parent_col_index,
497
                         parent_color))
498
                else:
230.1.6 by Gary van der Merwe
Split the graph loading and sorting and the line computing into 2 methods.
499
                    # line from the child's column to the lines column
500
                    self.linegraphdata[child_index][2].append(
501
                        (child_col_index,
502
                         line_col_indexes[0],
503
                         parent_color))
230.1.41 by Gary van der Merwe
qlog: Display revisions before calculating graph.
504
                    # lines down the line's column
505
                    for line_part_index in range(child_index+1, parent_index-1):
506
                        self.linegraphdata[line_part_index][2].append(
507
                            (line_col_indexes[0],   
508
                             line_col_indexes[0],
509
                             parent_color))
510
                    # line from the line's column to the parent's column
511
                    self.linegraphdata[parent_index-1][2].append(
230.1.11 by Gary van der Merwe
Calculate the parent color if it is hidden.
512
                        (line_col_indexes[0],
230.1.41 by Gary van der Merwe
qlog: Display revisions before calculating graph.
513
                         parent_col_index,
514
                         parent_color))
515
            else:
516
                # Broken line
517
                # line from the child's column to the lines column
518
                self.linegraphdata[child_index][2].append(
519
                    (child_col_index,
520
                     line_col_indexes[0],
521
                     parent_color))
522
                # Broken line end
523
                self.linegraphdata[child_index+1][2].append(
524
                    (line_col_indexes[0],
525
                     None,
526
                     parent_color))
527
                
528
                if line_col_indexes[1] is not None:
529
                    # Broken line end 
530
                    self.linegraphdata[parent_index-2][2].append(
531
                        (None,
532
                         line_col_indexes[1],
533
                         parent_color))
534
                    # line from the line's column to the parent's column
535
                    self.linegraphdata[parent_index-1][2].append(
536
                        (line_col_indexes[1],
537
                         parent_col_index,
538
                         parent_color))
539
        self.emit(QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"),
540
                  self.createIndex (0, COL_MESSAGE, QtCore.QModelIndex()),
541
                  self.createIndex (len(self.linegraphdata), COL_MESSAGE, QtCore.QModelIndex()))
230.1.24 by Gary van der Merwe
qlog: Get search working again.
542
    
230.1.31 by Gary van der Merwe
qlog: When a revison link is clicked, make sure it is visible.
543
    def _set_branch_visible(self, branch_id, visible, has_change):
544
        if not self.branch_lines[branch_id][1] == visible:
545
            has_change = True
546
        self.branch_lines[branch_id][1] = visible
547
        return has_change
548
    
549
    def _has_visible_child(self, branch_id):
550
        for child_branch_id in self.branch_lines[branch_id][3]:
230.1.35 by Gary van der Merwe
qlog: Fix bug in code colapseing child branches.
551
            if self.branch_lines[child_branch_id][1]:
230.1.31 by Gary van der Merwe
qlog: When a revison link is clicked, make sure it is visible.
552
                return True
553
        return False
554
    
230.1.34 by Gary van der Merwe
qlog: Add keyboard navigation for graph.
555
    def colapse_expand_rev(self, index, visible):
230.1.27 by Gary van der Merwe
qlog: Simplfy twisties
556
        if not index.isValid():
557
            return
558
        if self.searchMode:
559
            return
230.1.34 by Gary van der Merwe
qlog: Add keyboard navigation for graph.
560
        twisty_branch_ids = self.linegraphdata[index.row()][4]
230.1.28 by Gary van der Merwe
qlog: Only colapse_expand_rev if clicking on the node, and only if somthing is changing.
561
        has_change = False
230.1.27 by Gary van der Merwe
qlog: Simplfy twisties
562
        for branch_id in twisty_branch_ids:
230.1.34 by Gary van der Merwe
qlog: Add keyboard navigation for graph.
563
            has_change = self._set_branch_visible(branch_id, visible, has_change)
564
            if not visible:
230.1.29 by Gary van der Merwe
qlog: Colapse parent branchs that are widdowed.
565
                for parent_branch_id in self.branch_lines[branch_id][2]:
230.1.35 by Gary van der Merwe
qlog: Fix bug in code colapseing child branches.
566
                    if not parent_branch_id==() and not self._has_visible_child(parent_branch_id):
230.1.34 by Gary van der Merwe
qlog: Add keyboard navigation for graph.
567
                        has_change = self._set_branch_visible(parent_branch_id, visible, has_change)
230.1.31 by Gary van der Merwe
qlog: When a revison link is clicked, make sure it is visible.
568
        if has_change:
569
            self.compute_lines()
570
    
571
    def ensure_rev_visible(self, revid):
572
        if self.searchMode:
573
            return
574
        rev_msri = self.revid_msri[revid]
575
        branch_id = self.merge_sorted_revisions[rev_msri][3][0:-1]
576
        has_change = self._set_branch_visible(branch_id, True, False)
577
        while not self._has_visible_child(branch_id):
578
            branch_id = self.branch_lines[branch_id][3][0]
579
            has_change = self._set_branch_visible(branch_id, True, False)
230.1.28 by Gary van der Merwe
qlog: Only colapse_expand_rev if clicking on the node, and only if somthing is changing.
580
        if has_change:
581
            self.compute_lines()
230.1.27 by Gary van der Merwe
qlog: Simplfy twisties
582
    
230.1.24 by Gary van der Merwe
qlog: Get search working again.
583
    def compute_search(self):
584
        self.emit(QtCore.SIGNAL("layoutAboutToBeChanged()"))
585
        try:
586
            self.linegraphdata = []
587
            
588
            self.msri_index = {}
589
            
230.1.42 by Gary van der Merwe
qlog: Intelligent background revision loading.
590
            rev_index = 0
591
            for (rev_msri, (sequence_number,
230.1.24 by Gary van der Merwe
qlog: Get search working again.
592
                             revid,
593
                             merge_depth,
594
                             revno_sequence,
595
                             end_of_merge)) in enumerate(self.merge_sorted_revisions):
279 by Gary van der Merwe
qlog: Fix bugs with using bzr qlog FILE.
596
                if (self.visible_msri is None or rev_msri in self.visible_msri) :
272.1.3 by Gary van der Merwe
qlog: Improve background revision loading.
597
                        #and revid in self.revisions:
279 by Gary van der Merwe
qlog: Fix bugs with using bzr qlog FILE.
598
                    self.linegraphdata.append([rev_msri,
230.1.38 by Gary van der Merwe
qlog: Fix specific_fileid.
599
                                               None,
600
                                               [],
601
                                               None,
602
                                               [],
603
                                              ])
230.1.42 by Gary van der Merwe
qlog: Intelligent background revision loading.
604
                    self.msri_index [rev_msri]=rev_index
605
                    rev_index += 1
230.1.24 by Gary van der Merwe
qlog: Get search working again.
606
        finally:
607
            self.emit(QtCore.SIGNAL("layoutChanged()"))
608
    
609
    def set_search_mode(self, searchMode):
610
        if not searchMode == self.searchMode:
611
            if searchMode:
612
                self.compute_search()
272.1.3 by Gary van der Merwe
qlog: Improve background revision loading.
613
                self._nextRevisionToLoadGen = self._nextRevisionToLoad()
230.1.24 by Gary van der Merwe
qlog: Get search working again.
614
            else:
615
                self.compute_lines()
272.1.3 by Gary van der Merwe
qlog: Improve background revision loading.
616
                self._nextRevisionToLoadGen = self._nextRevisionToLoad()
230.1.24 by Gary van der Merwe
qlog: Get search working again.
617
            self.searchMode = searchMode
618
    
230.1.4 by Gary van der Merwe
qlog: Move Model to separate file. I'm going to intergrate linegraph into the model, so it' going to get big.
619
    def columnCount(self, parent):
620
        if parent.isValid():
621
            return 0
622
        return len(self.horizontalHeaderLabels)
623
624
    def rowCount(self, parent):
625
        if parent.isValid():
626
            return 0
627
        return len(self.linegraphdata)
628
    
629
    def data(self, index, role):
630
        if not index.isValid():
631
            return QtCore.QVariant()
230.1.27 by Gary van der Merwe
qlog: Simplfy twisties
632
        (msri, node, lines, twisty_state, twisty_branch_ids) = self.linegraphdata[index.row()]
230.1.4 by Gary van der Merwe
qlog: Move Model to separate file. I'm going to intergrate linegraph into the model, so it' going to get big.
633
        
634
        if role == GraphNodeRole:
230.1.24 by Gary van der Merwe
qlog: Get search working again.
635
            if node is None:
636
                return QtCore.QVariant()
230.1.4 by Gary van der Merwe
qlog: Move Model to separate file. I'm going to intergrate linegraph into the model, so it' going to get big.
637
            return QtCore.QVariant([QtCore.QVariant(nodei) for nodei in node])
230.1.12 by Gary van der Merwe
qLog: Expandable / Colapseable branchs
638
        if role == GraphLinesRole:
639
            qlines = []
640
            for start, end, color in lines:
641
                if start is None: start = -1
642
                if end is None: end = -1
643
                qlines.append(QtCore.QVariant([QtCore.QVariant(start),
644
                                               QtCore.QVariant(end),
645
                                               QtCore.QVariant(color)]))
646
            return QtCore.QVariant(qlines)
230.1.27 by Gary van der Merwe
qlog: Simplfy twisties
647
        if role == GraphTwistyStateRole:
648
            if twisty_state is None:
649
                return QtCore.QVariant()
650
            return QtCore.QVariant(twisty_state)
230.1.4 by Gary van der Merwe
qlog: Move Model to separate file. I'm going to intergrate linegraph into the model, so it' going to get big.
651
        
230.1.14 by Gary van der Merwe
qlog: Refactor linegraphdata structure to remove dup data.
652
        (sequence_number, revid, merge_depth, revno_sequence, end_of_merge) = \
653
            self.merge_sorted_revisions[msri]
654
        
230.1.24 by Gary van der Merwe
qlog: Get search working again.
655
        if (role == QtCore.Qt.DisplayRole and index.column() == COL_REV) or \
656
                role == FilterRevnoRole:
230.1.14 by Gary van der Merwe
qlog: Refactor linegraphdata structure to remove dup data.
657
            revnos = ".".join(["%d" % (revno)
658
                                      for revno in revno_sequence])
659
            return QtCore.QVariant(revnos)
660
        
661
        if role == TagsRole:
662
            tags = []
663
            if revid in self.tags:
664
                tags = self.tags[revid]
665
            return QtCore.QVariant(tags)
230.1.24 by Gary van der Merwe
qlog: Get search working again.
666
        if role == RevIdRole or role == FilterIdRole:
230.1.20 by Gary van der Merwe
qlog: Get revisions from logmodel.
667
            return QtCore.QVariant(revid)
230.1.14 by Gary van der Merwe
qlog: Refactor linegraphdata structure to remove dup data.
668
        
272.1.3 by Gary van der Merwe
qlog: Improve background revision loading.
669
        if role == FilterMessageRole or role == FilterAuthorRole:
670
            if revid not in self.revisions:
671
                return QtCore.QVariant()
672
        
230.1.12 by Gary van der Merwe
qLog: Expandable / Colapseable branchs
673
        #Everything from here foward will need to have the revision loaded.
230.1.4 by Gary van der Merwe
qlog: Move Model to separate file. I'm going to intergrate linegraph into the model, so it' going to get big.
674
        if not revid or revid == NULL_REVISION:
675
            return QtCore.QVariant()
230.1.20 by Gary van der Merwe
qlog: Get revisions from logmodel.
676
        revision = self._revision(revid)
230.1.4 by Gary van der Merwe
qlog: Move Model to separate file. I'm going to intergrate linegraph into the model, so it' going to get big.
677
        
678
        if role == QtCore.Qt.DisplayRole and index.column() == COL_DATE:
679
            return QtCore.QVariant(strftime("%Y-%m-%d %H:%M",
680
                                            localtime(revision.timestamp)))
681
        if role == QtCore.Qt.DisplayRole and index.column() == COL_AUTHOR:
230.1.49 by Lukáš Lalinský
qlog: Use revision.get_apparent_author() instead of rev.committer
682
            return QtCore.QVariant(extract_name(revision.get_apparent_author()))
230.1.4 by Gary van der Merwe
qlog: Move Model to separate file. I'm going to intergrate linegraph into the model, so it' going to get big.
683
        if role == QtCore.Qt.DisplayRole and index.column() == COL_MESSAGE:
684
            return QtCore.QVariant(revision.get_summary())
685
        if role == BugIdsRole:
686
            bugtext = gettext("bug #%s")
687
            bugs = []
688
            for bug in revision.properties.get('bugs', '').split('\n'):
689
                if bug:
690
                    url, status = bug.split(' ')
691
                    bug_id = get_bug_id(self.branch, url)
692
                    if bug_id:
693
                        bugs.append(bugtext % bug_id)
694
            return QtCore.QVariant(bugs)
230.1.24 by Gary van der Merwe
qlog: Get search working again.
695
        
696
        if role == FilterMessageRole:
697
            return QtCore.QVariant(revision.message)
698
        if role == FilterAuthorRole:
230.1.49 by Lukáš Lalinský
qlog: Use revision.get_apparent_author() instead of rev.committer
699
            return QtCore.QVariant(revision.get_apparent_author())
230.1.4 by Gary van der Merwe
qlog: Move Model to separate file. I'm going to intergrate linegraph into the model, so it' going to get big.
700
        
701
        #return QtCore.QVariant(item.data(index.column()))
702
        return QtCore.QVariant()
703
704
    def flags(self, index):
705
        if not index.isValid():
706
            return QtCore.Qt.ItemIsEnabled
707
708
        return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
709
710
    def headerData(self, section, orientation, role):
711
        if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
712
            return QtCore.QVariant(self.horizontalHeaderLabels[section])
230.1.5 by Gary van der Merwe
Move linegraph.py code to logmodel.py.
713
        return QtCore.QVariant()
230.1.20 by Gary van der Merwe
qlog: Get revisions from logmodel.
714
    
715
    def _revision(self, revid):
716
        if revid not in self.revisions:
717
            revision = self.branch.repository.get_revisions([revid])[0]
718
            self.revisions[revid] = revision
719
            revno_sequence = self.merge_sorted_revisions[self.revid_msri[revid]][3]
720
            revision.revno = ".".join(["%d" % (revno)
721
                                      for revno in revno_sequence])
722
        else:
723
            revision = self.revisions[revid]
724
        return revision
725
    
726
    def revision(self, revid):
727
        revision = self._revision(revid)
728
        if not hasattr(revision, 'parents'):
729
            revision.parents = [self._revision(i) for i in self.graph_parents[revid]]
730
        if not hasattr(revision, 'children'):
731
            revision.children = [self._revision(i) for i in self.graph_children[revid]]
732
        return revision
733
    
272.1.3 by Gary van der Merwe
qlog: Improve background revision loading.
734
    def _loadNextRevision(self):
735
        try:
736
            if self.searchMode:
737
                revisionsChanged = []
738
                while self.searchMode:
739
                    try:
740
                        nextRevId = self._nextRevisionToLoadGen.next()
741
                        self._revision(nextRevId)
742
                        revisionsChanged.append(nextRevId)
743
                        QtCore.QCoreApplication.processEvents()
744
                    finally:
745
                        if len(revisionsChanged) == 100:
746
                            for revid in revisionsChanged:
747
                                index = self.indexFromRevId(revid)
748
                                self.emit(QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"),
749
                                          index,index)
750
                            revisionsChanged = []
751
                            QtCore.QCoreApplication.processEvents()
752
            else:
753
                nextRevId = self._nextRevisionToLoadGen.next()
754
                self._revision(nextRevId)
755
            QtCore.QTimer.singleShot(5, self._loadNextRevision)
756
        except StopIteration:
757
            # All revisions are loaded.
758
            pass
230.1.42 by Gary van der Merwe
qlog: Intelligent background revision loading.
759
    
272.1.3 by Gary van der Merwe
qlog: Improve background revision loading.
760
    def _nextRevisionToLoad(self):
279 by Gary van der Merwe
qlog: Fix bugs with using bzr qlog FILE.
761
        if self.visible_msri is not None:
762
            for msri in self.visible_msri :
763
                revid = self.merge_sorted_revisions[msri][1]
764
                if revid not in self.revisions:
765
                    yield revid
766
            return
767
        
272.1.3 by Gary van der Merwe
qlog: Improve background revision loading.
768
        if not self.searchMode:
769
            for (msri, node, lines,
770
                 twisty_state, twisty_branch_ids) in self.linegraphdata:
771
                revid = self.merge_sorted_revisions[msri][1]
772
                if revid not in self.revisions:
773
                    yield revid
774
        for (sequence_number,
775
             revid,
776
             merge_depth,
777
             revno_sequence,
778
             end_of_merge) in self.merge_sorted_revisions:
779
            if revid not in self.revisions:
780
                yield revid
781
230.1.21 by Gary van der Merwe
qlog: Fix regression where you get an error when clicking on a qlog-revid link.
782
    def indexFromRevId(self, revid):
783
        revindex = self.msri_index[self.revid_msri[revid]]
784
        return self.createIndex (revindex, 0, QtCore.QModelIndex())
785
    
230.1.34 by Gary van der Merwe
qlog: Add keyboard navigation for graph.
786
    def findChildBranchMergeRevision (self, revid):
787
        branch_id = self.merge_sorted_revisions[self.revid_msri[revid]][3][0:-1]
788
        current_revid = revid
789
        result_revid = None
790
        while current_revid is not None and result_revid is None:
791
            child_revids = self.graph_children[current_revid]
792
            current_revid = None
793
            for child_revid in child_revids:
794
                child_branch_id = self.merge_sorted_revisions[self.revid_msri[child_revid]][3][0:-1]
795
                if child_branch_id == branch_id:
796
                    current_revid = child_revid
797
                else:
798
                    if self.branch_lines[child_branch_id][1]:
799
                        result_revid = child_revid
800
                        break
801
        return result_revid
802
    
230.1.5 by Gary van der Merwe
Move linegraph.py code to logmodel.py.
803
def _branch_line_col_search_order(columns, parent_col_index):
804
    for col_index in range(parent_col_index, len(columns)):
805
        yield col_index
806
    for col_index in range(parent_col_index-1, -1, -1):
807
        yield col_index
808
809
def _line_col_search_order(columns, parent_col_index, child_col_index):
810
    if parent_col_index is not None:
811
        max_index = max(parent_col_index, child_col_index)
812
        min_index = min(parent_col_index, child_col_index)
813
        for col_index in range(max_index, min_index -1, -1):
814
            yield col_index
815
    else:
816
        max_index = child_col_index
817
        min_index = child_col_index
818
        yield child_col_index
819
    i = 1
820
    while max_index + i < len(columns) or \
821
          min_index - i > -1:
822
        if max_index + i < len(columns):
823
            yield max_index + i
824
        if min_index - i > -1:
825
            yield min_index - i
826
        i += 1
827
828
def _find_free_column(columns, empty_column, col_search_order, line_range):
829
    for col_index in col_search_order:
830
        column = columns[col_index]
831
        has_overlaping_line = False
832
        for row_index in line_range:
833
            if column[row_index]:
834
                has_overlaping_line = True
835
                break
836
        if not has_overlaping_line:
837
            break
838
    else:
839
        col_index = len(columns)
840
        column = list(empty_column)
841
        columns.append(column)
842
    return col_index
843
844
def _mark_column_as_used(columns, col_index, line_range):
845
    column = columns[col_index]
846
    for row_index in line_range:
230.1.29 by Gary van der Merwe
qlog: Colapse parent branchs that are widdowed.
847
        column[row_index] = True