/b-gtk/fix-viz

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/b-gtk/fix-viz
0.1.1 by Dan Loda
First working version of xannotate.
1
# Copyright (C) 2005 Dan Loda <danloda@gmail.com>
2
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
0.2.1 by Dan Loda
first go at emacs vc-annotate like highlighting
17
import time
18
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
19
from gi.repository import GObject
734.1.6 by Curtis Hovey
Added a very rough fix to see gannontation.
20
from gi.repository import Gdk
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
21
from gi.repository import Gtk
22
from gi.repository import Pango
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
23
import re
0.1.1 by Dan Loda
First working version of xannotate.
24
713 by Jelmer Vernooij
Remove some unused imports, fix some formatting.
25
from bzrlib import patiencediff
0.1.1 by Dan Loda
First working version of xannotate.
26
from bzrlib.errors import NoSuchRevision
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
27
from bzrlib.revision import NULL_REVISION, CURRENT_REVISION
0.1.1 by Dan Loda
First working version of xannotate.
28
724 by Jelmer Vernooij
Fix formatting, imports.
29
from bzrlib.plugins.gtk.annotate.colormap import AnnotateColorSaturation
330.3.1 by Daniel Schierbeck
Renamed logview 'revisionview'.
30
from bzrlib.plugins.gtk.revisionview import RevisionView
298.2.1 by Daniel Schierbeck
Refactored the GTK window code, creating a single base window class that handles keyboard events.
31
from bzrlib.plugins.gtk.window import Window
0.1.1 by Dan Loda
First working version of xannotate.
32
33
34
(
35
    REVISION_ID_COL,
36
    LINE_NUM_COL,
37
    COMMITTER_COL,
38
    REVNO_COL,
0.2.1 by Dan Loda
first go at emacs vc-annotate like highlighting
39
    HIGHLIGHT_COLOR_COL,
0.1.1 by Dan Loda
First working version of xannotate.
40
    TEXT_LINE_COL
0.2.1 by Dan Loda
first go at emacs vc-annotate like highlighting
41
) = range(6)
0.1.1 by Dan Loda
First working version of xannotate.
42
43
298.2.1 by Daniel Schierbeck
Refactored the GTK window code, creating a single base window class that handles keyboard events.
44
class GAnnotateWindow(Window):
0.1.1 by Dan Loda
First working version of xannotate.
45
    """Annotate window."""
46
473.1.1 by Andrew Bennetts
Simple hack to fix gannotate.
47
    def __init__(self, all=False, plain=False, parent=None, branch=None):
0.2.6 by Dan Loda
--plain option to disable highlighting. And update README
48
        self.all = all
49
        self.plain = plain
473.1.1 by Andrew Bennetts
Simple hack to fix gannotate.
50
        self._branch = branch
706 by Martin Pool
Store line numbers as ints in the gannotate ListStore.
51
734.1.51 by Curtis Hovey
Fix the initializer for many classes.
52
        super(GAnnotateWindow, self).__init__(parent=parent)
706 by Martin Pool
Store line numbers as ints in the gannotate ListStore.
53
734.1.6 by Curtis Hovey
Added a very rough fix to see gannontation.
54
        self.set_icon(
55
            self.render_icon_pixbuf(Gtk.STOCK_FIND, Gtk.IconSize.BUTTON))
0.1.18 by Aaron Bentley
Switched to using pink backgrounds
56
        self.annotate_colormap = AnnotateColorSaturation()
0.1.1 by Dan Loda
First working version of xannotate.
57
58
        self._create()
59
        self.revisions = {}
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
60
        self.history = []
173.1.1 by Aaron Bentley
Better behavior when unable to go back
61
        self._no_back = set()
0.1.17 by Dan Loda
A little refactoring.
62
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
63
    def annotate(self, tree, branch, file_id):
59.2.2 by Aaron Bentley
Annotate launches a diff for the particular revision
64
        self.annotations = []
65
        self.branch = branch
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
66
        self.tree = tree
59.2.3 by Aaron Bentley
Gannotate-launched diffs now jump to correct file
67
        self.file_id = file_id
330.3.6 by Daniel Schierbeck
Fixed bug in gannotate where logview was used instead of revisionview.
68
        self.revisionview.set_file_id(file_id)
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
69
        self.revision_id = getattr(tree, 'get_revision_id', 
70
                                   lambda: CURRENT_REVISION)()
706 by Martin Pool
Store line numbers as ints in the gannotate ListStore.
71
259 by Aaron Bentley
Add author support to gannotate and log viewer
72
        # [revision id, line number, author, revno, highlight color, line]
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
73
        self.annomodel = Gtk.ListStore(GObject.TYPE_STRING,
74
                                       GObject.TYPE_INT,
75
                                       GObject.TYPE_STRING,
76
                                       GObject.TYPE_STRING,
77
                                       GObject.TYPE_STRING,
78
                                       GObject.TYPE_STRING)
706 by Martin Pool
Store line numbers as ints in the gannotate ListStore.
79
0.1.1 by Dan Loda
First working version of xannotate.
80
        last_seen = None
0.4.1 by Aaron Bentley
Updated performance, API use
81
        try:
82
            branch.lock_read()
83
            branch.repository.lock_read()
436 by Aaron Bentley
Use Branch.get_revision_id_to_revno_map in gannotate
84
            self.dotted = {}
85
            revno_map = self.branch.get_revision_id_to_revno_map()
86
            for revision_id, revno in revno_map.iteritems():
87
                self.dotted[revision_id] = '.'.join(str(num) for num in revno)
0.4.1 by Aaron Bentley
Updated performance, API use
88
            for line_no, (revision, revno, line)\
706 by Martin Pool
Store line numbers as ints in the gannotate ListStore.
89
                in enumerate(self._annotate(tree, file_id)):
0.4.1 by Aaron Bentley
Updated performance, API use
90
                if revision.revision_id == last_seen and not self.all:
273 by Aaron Bentley
Use get_apparent_author, rename variables to 'author'
91
                    revno = author = ""
0.4.1 by Aaron Bentley
Updated performance, API use
92
                else:
93
                    last_seen = revision.revision_id
642 by Jelmer Vernooij
Use get_apparent_authors() rather than deprecated get_apparent_author().
94
                    author = ", ".join(revision.get_apparent_authors())
0.4.1 by Aaron Bentley
Updated performance, API use
95
96
                if revision.revision_id not in self.revisions:
97
                    self.revisions[revision.revision_id] = revision
98
99
                self.annomodel.append([revision.revision_id,
100
                                       line_no + 1,
273 by Aaron Bentley
Use get_apparent_author, rename variables to 'author'
101
                                       author,
0.4.1 by Aaron Bentley
Updated performance, API use
102
                                       revno,
103
                                       None,
104
                                       line.rstrip("\r\n")
706 by Martin Pool
Store line numbers as ints in the gannotate ListStore.
105
                                       ])
59.2.2 by Aaron Bentley
Annotate launches a diff for the particular revision
106
                self.annotations.append(revision)
0.4.1 by Aaron Bentley
Updated performance, API use
107
108
            if not self.plain:
66.6.1 by Aaron Bentley
Remove usused span selector
109
                now = time.time()
110
                self.annomodel.foreach(self._highlight_annotation, now)
0.4.1 by Aaron Bentley
Updated performance, API use
111
        finally:
112
            branch.repository.unlock()
113
            branch.unlock()
0.2.6 by Dan Loda
--plain option to disable highlighting. And update README
114
0.1.17 by Dan Loda
A little refactoring.
115
        self.annoview.set_model(self.annomodel)
116
        self.annoview.grab_focus()
438 by Aaron Bentley
Update window titles
117
        my_revno = self.dotted.get(self.revision_id, 'current')
118
        title = '%s (%s) - gannotate' % (self.tree.id2path(file_id), my_revno)
119
        self.set_title(title)
0.1.1 by Dan Loda
First working version of xannotate.
120
0.1.12 by Dan Loda
New --line option. Can now jump to a specific line number.
121
    def jump_to_line(self, lineno):
0.1.17 by Dan Loda
A little refactoring.
122
        if lineno > len(self.annomodel) or lineno < 1:
0.1.12 by Dan Loda
New --line option. Can now jump to a specific line number.
123
            row = 0
124
            # FIXME:should really deal with this in the gui. Perhaps a status
125
            # bar?
126
            print("gannotate: Line number %d does't exist. Defaulting to "
127
                  "line 1." % lineno)
706 by Martin Pool
Store line numbers as ints in the gannotate ListStore.
128
            return
0.1.12 by Dan Loda
New --line option. Can now jump to a specific line number.
129
        else:
130
            row = lineno - 1
131
734.1.23 by Curtis Hovey
Always pass a Gtk.TreePath instead of an int or tuple.
132
        tree_path = Gtk.TreePath(path=row)
734.1.22 by Curtis Hovey
GtkTreeView.set_cursor(tree_path, None, False) is the default args.
133
        self.annoview.set_cursor(tree_path, None, False)
734.1.6 by Curtis Hovey
Added a very rough fix to see gannontation.
134
        self.annoview.scroll_to_cell(tree_path, use_align=True)
0.1.12 by Dan Loda
New --line option. Can now jump to a specific line number.
135
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
136
    def _annotate(self, tree, file_id):
137
        current_revision = FakeRevision(CURRENT_REVISION)
138
        current_revision.committer = self.branch.get_config().username()
139
        current_revision.timestamp = time.time()
140
        current_revision.message = '[Not yet committed]'
66.6.6 by Aaron Bentley
Support scrolling based on an offset
141
        current_revision.parent_ids = tree.get_parent_ids()
630 by Jelmer Vernooij
Use _get_nick(local=True) rather than .nick to get at a branches' nick, since
142
        current_revision.properties['branch-nick'] = self.branch._get_nick(local=True)
66.2.15 by Aaron Bentley
fix future revno
143
        current_revno = '%d?' % (self.branch.revno() + 1)
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
144
        repository = self.branch.repository
145
        if self.revision_id == CURRENT_REVISION:
146
            revision_id = self.branch.last_revision()
147
        else:
148
            revision_id = self.revision_id
66.6.5 by Aaron Bentley
Speed up the 'back' operation
149
        revision_cache = RevisionCache(repository, self.revisions)
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
150
        for origin, text in tree.annotate_iter(file_id):
0.4.1 by Aaron Bentley
Updated performance, API use
151
            rev_id = origin
66.6.7 by Aaron Bentley
Handle current revision better
152
            if rev_id == CURRENT_REVISION:
153
                revision = current_revision
154
                revno = current_revno
155
            else:
156
                try:
157
                    revision = revision_cache.get_revision(rev_id)
436 by Aaron Bentley
Use Branch.get_revision_id_to_revno_map in gannotate
158
                    revno = self.dotted.get(rev_id, 'merge')
66.6.7 by Aaron Bentley
Handle current revision better
159
                    if len(revno) > 15:
160
                        revno = 'merge'
161
                except NoSuchRevision:
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
162
                    revision = FakeRevision(rev_id)
163
                    revno = "?"
0.1.1 by Dan Loda
First working version of xannotate.
164
165
            yield revision, revno, text
166
0.2.2 by Dan Loda
Add file's age as default span
167
    def _highlight_annotation(self, model, path, iter, now):
168
        revision_id, = model.get(iter, REVISION_ID_COL)
169
        revision = self.revisions[revision_id]
734.1.12 by Curtis Hovey
Fixed row/path issues in gannoate.
170
        # XXX sinzui 2011-08-12: What does get_color return?
171
        color = self.annotate_colormap.get_color(revision, now)
172
        model.set_value(iter, HIGHLIGHT_COLOR_COL, color)
0.2.1 by Dan Loda
first go at emacs vc-annotate like highlighting
173
66.6.4 by Aaron Bentley
Add back button to see older versions
174
    def _selected_revision(self):
0.1.17 by Dan Loda
A little refactoring.
175
        (path, col) = self.annoview.get_cursor()
79 by Jelmer Vernooij
Handle empty files more gracefully. Fixes #58951.
176
        if path is None:
66.6.4 by Aaron Bentley
Add back button to see older versions
177
            return None
178
        return self.annomodel[path][REVISION_ID_COL]
179
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
180
    def _activate_selected_revision(self, w):
66.6.4 by Aaron Bentley
Add back button to see older versions
181
        rev_id = self._selected_revision()
464.2.1 by Adrian Wilkins
Detect the reserved null: revision in appropriate places.
182
        if not rev_id or rev_id == NULL_REVISION:
79 by Jelmer Vernooij
Handle empty files more gracefully. Fixes #58951.
183
            return
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
184
        selected = self.revisions[rev_id]
330.3.1 by Daniel Schierbeck
Renamed logview 'revisionview'.
185
        self.revisionview.set_revision(selected)
173.1.1 by Aaron Bentley
Better behavior when unable to go back
186
        if (len(selected.parent_ids) != 0 and selected.parent_ids[0] not in
187
            self._no_back):
188
            enable_back = True
189
        else:
190
            enable_back = False
191
        self.back_button.set_sensitive(enable_back)
0.1.1 by Dan Loda
First working version of xannotate.
192
193
    def _create(self):
330.3.1 by Daniel Schierbeck
Renamed logview 'revisionview'.
194
        self.revisionview = self._create_log_view()
0.1.17 by Dan Loda
A little refactoring.
195
        self.annoview = self._create_annotate_view()
196
734.1.6 by Curtis Hovey
Added a very rough fix to see gannontation.
197
        vbox = Gtk.VBox(homogeneous=False, spacing=0)
0.2.1 by Dan Loda
first go at emacs vc-annotate like highlighting
198
        vbox.show()
0.1.17 by Dan Loda
A little refactoring.
199
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
200
        sw = Gtk.ScrolledWindow()
201
        sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
202
        sw.set_shadow_type(Gtk.ShadowType.IN)
0.1.17 by Dan Loda
A little refactoring.
203
        sw.add(self.annoview)
59.2.2 by Aaron Bentley
Annotate launches a diff for the particular revision
204
        self.annoview.gwindow = self
0.1.17 by Dan Loda
A little refactoring.
205
        sw.show()
170.1.4 by Aaron Bentley
Move search fields directly below source window
206
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
207
        swbox = Gtk.VBox()
208
        swbox.pack_start(sw, True, True, 0)
170.1.4 by Aaron Bentley
Move search fields directly below source window
209
        swbox.show()
170.1.6 by Aaron Bentley
Move buttons to top, tweak layout
210
734.1.6 by Curtis Hovey
Added a very rough fix to see gannontation.
211
        hbox = Gtk.HBox(homogeneous=False, spacing=6)
170.1.6 by Aaron Bentley
Move buttons to top, tweak layout
212
        self.back_button = self._create_back_button()
734.1.6 by Curtis Hovey
Added a very rough fix to see gannontation.
213
        hbox.pack_start(self.back_button, False, True, 0)
170.1.6 by Aaron Bentley
Move buttons to top, tweak layout
214
        self.forward_button = self._create_forward_button()
734.1.6 by Curtis Hovey
Added a very rough fix to see gannontation.
215
        hbox.pack_start(self.forward_button, False, True, 0)
688.1.3 by Martin Pool
Add 'Find' button to start text search
216
        self.find_button = self._create_find_button()
734.1.6 by Curtis Hovey
Added a very rough fix to see gannontation.
217
        hbox.pack_start(self.find_button, False, True, 0)
688.1.4 by Martin Pool
Add button to goto-line too
218
        self.goto_button = self._create_goto_button()
734.1.6 by Curtis Hovey
Added a very rough fix to see gannontation.
219
        hbox.pack_start(self.goto_button, False, True, 0)
170.1.6 by Aaron Bentley
Move buttons to top, tweak layout
220
        hbox.show()
734.1.6 by Curtis Hovey
Added a very rough fix to see gannontation.
221
        vbox.pack_start(hbox, False, True, 0)
706 by Martin Pool
Store line numbers as ints in the gannotate ListStore.
222
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
223
        self.pane = pane = Gtk.VPaned()
170.1.4 by Aaron Bentley
Move search fields directly below source window
224
        pane.add1(swbox)
330.3.1 by Daniel Schierbeck
Renamed logview 'revisionview'.
225
        pane.add2(self.revisionview)
0.1.1 by Dan Loda
First working version of xannotate.
226
        pane.show()
734.1.6 by Curtis Hovey
Added a very rough fix to see gannontation.
227
        vbox.pack_start(pane, True, True, 0)
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
228
229
        self._search = SearchBox()
734.1.6 by Curtis Hovey
Added a very rough fix to see gannontation.
230
        swbox.pack_start(self._search, False, True, 0)
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
231
        accels = Gtk.AccelGroup()
734.1.6 by Curtis Hovey
Added a very rough fix to see gannontation.
232
        accels.connect(Gdk.KEY_f, Gdk.ModifierType.CONTROL_MASK,
233
                             Gtk.AccelFlags.LOCKED,
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
234
                             self._search_by_text)
734.1.6 by Curtis Hovey
Added a very rough fix to see gannontation.
235
        accels.connect(Gdk.KEY_g, Gdk.ModifierType.CONTROL_MASK,
236
                             Gtk.AccelFlags.LOCKED,
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
237
                             self._search_by_line)
238
        self.add_accel_group(accels)
239
0.1.1 by Dan Loda
First working version of xannotate.
240
        self.add(vbox)
241
688.1.3 by Martin Pool
Add 'Find' button to start text search
242
    def _search_by_text(self, *ignored): # (accel_group, window, key, modifiers):
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
243
        self._search.show_for('text')
66.5.3 by v.ladeuil+lp at free
Realbetter fix for bug #73965.
244
        self._search.set_target(self.annoview, TEXT_LINE_COL)
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
245
688.1.4 by Martin Pool
Add button to goto-line too
246
    def _search_by_line(self, *ignored): # accel_group, window, key, modifiers):
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
247
        self._search.show_for('line')
66.5.3 by v.ladeuil+lp at free
Realbetter fix for bug #73965.
248
        self._search.set_target(self.annoview, LINE_NUM_COL)
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
249
438 by Aaron Bentley
Update window titles
250
    def line_diff(self, tv, path, tvc):
734.1.12 by Curtis Hovey
Fixed row/path issues in gannoate.
251
        row = path.get_indices()[0]
59.2.2 by Aaron Bentley
Annotate launches a diff for the particular revision
252
        revision = self.annotations[row]
65 by Aaron Bentley
Handle first revision properly
253
        repository = self.branch.repository
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
254
        if revision.revision_id == CURRENT_REVISION:
255
            tree1 = self.tree
256
            tree2 = self.tree.basis_tree()
65 by Aaron Bentley
Handle first revision properly
257
        else:
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
258
            tree1 = repository.revision_tree(revision.revision_id)
259
            if len(revision.parent_ids) > 0:
260
                tree2 = repository.revision_tree(revision.parent_ids[0])
261
            else:
262
                tree2 = repository.revision_tree(NULL_REVISION)
150 by Jelmer Vernooij
Fix handling showing diffs of working tree changes.
263
        from bzrlib.plugins.gtk.diff import DiffWindow
707.1.1 by Sergei Golubchik
* make sure that gdiff opened from gannotate knows its parent
264
        window = DiffWindow(self)
438 by Aaron Bentley
Update window titles
265
        window.set_diff("Diff for line %d" % (row+1), tree1, tree2)
59.2.3 by Aaron Bentley
Gannotate-launched diffs now jump to correct file
266
        window.set_file(tree1.id2path(self.file_id))
59.2.2 by Aaron Bentley
Annotate launches a diff for the particular revision
267
        window.show()
268
269
0.1.1 by Dan Loda
First working version of xannotate.
270
    def _create_annotate_view(self):
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
271
        tv = Gtk.TreeView()
0.1.17 by Dan Loda
A little refactoring.
272
        tv.set_rules_hint(False)
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
273
        tv.connect("cursor-changed", self._activate_selected_revision)
0.1.17 by Dan Loda
A little refactoring.
274
        tv.show()
438 by Aaron Bentley
Update window titles
275
        tv.connect("row-activated", self.line_diff)
0.1.1 by Dan Loda
First working version of xannotate.
276
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
277
        cell = Gtk.CellRendererText()
0.1.1 by Dan Loda
First working version of xannotate.
278
        cell.set_property("xalign", 1.0)
279
        cell.set_property("ypad", 0)
280
        cell.set_property("family", "Monospace")
281
        cell.set_property("cell-background-gdk",
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
282
                          tv.get_style().bg[Gtk.StateType.NORMAL])
283
        col = Gtk.TreeViewColumn()
0.1.1 by Dan Loda
First working version of xannotate.
284
        col.set_resizable(False)
734.1.6 by Curtis Hovey
Added a very rough fix to see gannontation.
285
        col.pack_start(cell, True)
0.1.1 by Dan Loda
First working version of xannotate.
286
        col.add_attribute(cell, "text", LINE_NUM_COL)
0.1.17 by Dan Loda
A little refactoring.
287
        tv.append_column(col)
0.1.1 by Dan Loda
First working version of xannotate.
288
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
289
        cell = Gtk.CellRendererText()
0.1.1 by Dan Loda
First working version of xannotate.
290
        cell.set_property("ypad", 0)
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
291
        cell.set_property("ellipsize", Pango.EllipsizeMode.END)
0.1.1 by Dan Loda
First working version of xannotate.
292
        cell.set_property("cell-background-gdk",
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
293
                          self.get_style().bg[Gtk.StateType.NORMAL])
294
        col = Gtk.TreeViewColumn("Committer")
0.1.1 by Dan Loda
First working version of xannotate.
295
        col.set_resizable(True)
734.1.6 by Curtis Hovey
Added a very rough fix to see gannontation.
296
        col.pack_start(cell, True)
0.1.1 by Dan Loda
First working version of xannotate.
297
        col.add_attribute(cell, "text", COMMITTER_COL)
0.1.17 by Dan Loda
A little refactoring.
298
        tv.append_column(col)
0.1.1 by Dan Loda
First working version of xannotate.
299
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
300
        cell = Gtk.CellRendererText()
0.1.1 by Dan Loda
First working version of xannotate.
301
        cell.set_property("xalign", 1.0)
302
        cell.set_property("ypad", 0)
303
        cell.set_property("cell-background-gdk",
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
304
                          self.get_style().bg[Gtk.StateType.NORMAL])
305
        col = Gtk.TreeViewColumn("Revno")
0.1.1 by Dan Loda
First working version of xannotate.
306
        col.set_resizable(False)
734.1.6 by Curtis Hovey
Added a very rough fix to see gannontation.
307
        col.pack_start(cell, True)
0.1.1 by Dan Loda
First working version of xannotate.
308
        col.add_attribute(cell, "markup", REVNO_COL)
0.1.17 by Dan Loda
A little refactoring.
309
        tv.append_column(col)
0.1.1 by Dan Loda
First working version of xannotate.
310
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
311
        cell = Gtk.CellRendererText()
0.1.1 by Dan Loda
First working version of xannotate.
312
        cell.set_property("ypad", 0)
313
        cell.set_property("family", "Monospace")
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
314
        col = Gtk.TreeViewColumn()
0.1.1 by Dan Loda
First working version of xannotate.
315
        col.set_resizable(False)
734.1.6 by Curtis Hovey
Added a very rough fix to see gannontation.
316
        col.pack_start(cell, True)
0.1.18 by Aaron Bentley
Switched to using pink backgrounds
317
#        col.add_attribute(cell, "foreground", HIGHLIGHT_COLOR_COL)
318
        col.add_attribute(cell, "background", HIGHLIGHT_COLOR_COL)
0.1.1 by Dan Loda
First working version of xannotate.
319
        col.add_attribute(cell, "text", TEXT_LINE_COL)
0.1.17 by Dan Loda
A little refactoring.
320
        tv.append_column(col)
0.1.1 by Dan Loda
First working version of xannotate.
321
723.1.1 by Sergei Golubchik
interactive substring search in gannotate
322
        # interactive substring search
323
        def search_equal_func(model, column, key, iter):
324
            return model.get_value(iter, TEXT_LINE_COL).lower().find(key.lower()) == -1
325
326
        tv.set_enable_search(True)
734.1.6 by Curtis Hovey
Added a very rough fix to see gannontation.
327
        tv.set_search_equal_func(search_equal_func, None)
66.5.1 by v.ladeuil+lp at free
Minimal fix for bug #73965.
328
0.1.17 by Dan Loda
A little refactoring.
329
        return tv
0.1.1 by Dan Loda
First working version of xannotate.
330
331
    def _create_log_view(self):
473.1.1 by Andrew Bennetts
Simple hack to fix gannotate.
332
        lv = RevisionView(self._branch)
0.1.17 by Dan Loda
A little refactoring.
333
        lv.show()
334
        return lv
0.1.1 by Dan Loda
First working version of xannotate.
335
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
336
    def _create_back_button(self):
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
337
        button = Gtk.Button()
66.6.4 by Aaron Bentley
Add back button to see older versions
338
        button.set_use_stock(True)
339
        button.set_label("gtk-go-back")
340
        button.connect("clicked", lambda w: self.go_back())
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
341
        button.set_relief(Gtk.ReliefStyle.NONE)
66.6.4 by Aaron Bentley
Add back button to see older versions
342
        button.show()
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
343
        return button
344
345
    def _create_forward_button(self):
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
346
        button = Gtk.Button()
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
347
        button.set_use_stock(True)
348
        button.set_label("gtk-go-forward")
349
        button.connect("clicked", lambda w: self.go_forward())
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
350
        button.set_relief(Gtk.ReliefStyle.NONE)
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
351
        button.show()
352
        button.set_sensitive(False)
353
        return button
66.6.4 by Aaron Bentley
Add back button to see older versions
354
688.1.3 by Martin Pool
Add 'Find' button to start text search
355
    def _create_find_button(self):
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
356
        button = Gtk.Button()
688.1.3 by Martin Pool
Add 'Find' button to start text search
357
        button.set_use_stock(True)
358
        button.set_label("gtk-find")
688.1.5 by Martin Pool
Also show tooltips on find/goto buttons
359
        button.set_tooltip_text("Search for text (Ctrl+F)")
688.1.3 by Martin Pool
Add 'Find' button to start text search
360
        button.connect("clicked", self._search_by_text)
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
361
        button.set_relief(Gtk.ReliefStyle.NONE)
688.1.3 by Martin Pool
Add 'Find' button to start text search
362
        button.show()
363
        button.set_sensitive(True)
364
        return button
365
688.1.4 by Martin Pool
Add button to goto-line too
366
    def _create_goto_button(self):
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
367
        button = Gtk.Button()
688.1.4 by Martin Pool
Add button to goto-line too
368
        button.set_label("Goto Line")
688.1.5 by Martin Pool
Also show tooltips on find/goto buttons
369
        button.set_tooltip_text("Scroll to a line by entering its number (Ctrl+G)")
688.1.4 by Martin Pool
Add button to goto-line too
370
        button.connect("clicked", self._search_by_line)
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
371
        button.set_relief(Gtk.ReliefStyle.NONE)
688.1.4 by Martin Pool
Add button to goto-line too
372
        button.show()
373
        button.set_sensitive(True)
374
        return button
375
66.6.4 by Aaron Bentley
Add back button to see older versions
376
    def go_back(self):
173.1.1 by Aaron Bentley
Better behavior when unable to go back
377
        last_tree = self.tree
66.6.4 by Aaron Bentley
Add back button to see older versions
378
        rev_id = self._selected_revision()
379
        parent_id = self.revisions[rev_id].parent_ids[0]
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
380
        target_tree = self.branch.repository.revision_tree(parent_id)
173.1.1 by Aaron Bentley
Better behavior when unable to go back
381
        if self._go(target_tree):
382
            self.history.append(last_tree)
383
            self.forward_button.set_sensitive(True)
384
        else:
385
            self._no_back.add(parent_id)
386
            self.back_button.set_sensitive(False)
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
387
388
    def go_forward(self):
389
        if len(self.history) == 0:
390
            return
391
        target_tree = self.history.pop()
392
        if len(self.history) == 0:
393
            self.forward_button.set_sensitive(False)
394
        self._go(target_tree)
395
396
    def _go(self, target_tree):
397
        rev_id = self._selected_revision()
398
        if self.file_id in target_tree:
399
            offset = self.get_scroll_offset(target_tree)
734.1.12 by Curtis Hovey
Fixed row/path issues in gannoate.
400
            path, col = self.annoview.get_cursor()
401
            (row,) = path.get_indices()
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
402
            self.annotate(target_tree, self.branch, self.file_id)
403
            new_row = row+offset
404
            if new_row < 0:
405
                new_row = 0
734.1.12 by Curtis Hovey
Fixed row/path issues in gannoate.
406
            new_path = Gtk.TreePath(path=new_row)
407
            self.annoview.set_cursor(new_path, None, False)
173.1.1 by Aaron Bentley
Better behavior when unable to go back
408
            return True
409
        else:
410
            return False
66.6.6 by Aaron Bentley
Support scrolling based on an offset
411
412
    def get_scroll_offset(self, tree):
413
        old = self.tree.get_file(self.file_id)
414
        new = tree.get_file(self.file_id)
734.1.12 by Curtis Hovey
Fixed row/path issues in gannoate.
415
        path, col = self.annoview.get_cursor()
416
        (row,) = path.get_indices()
66.6.6 by Aaron Bentley
Support scrolling based on an offset
417
        matcher = patiencediff.PatienceSequenceMatcher(None, old.readlines(),
418
                                                       new.readlines())
419
        for i, j, n in matcher.get_matching_blocks():
420
            if i + n >= row:
421
                return j - i
422
0.1.1 by Dan Loda
First working version of xannotate.
423
642 by Jelmer Vernooij
Use get_apparent_authors() rather than deprecated get_apparent_author().
424
class FakeRevision(object):
0.1.1 by Dan Loda
First working version of xannotate.
425
    """ A fake revision.
426
427
    For when a revision is referenced but not present.
428
    """
429
157.1.7 by Aaron Bentley
Fix branch-nick handling
430
    def __init__(self, revision_id, committer='?', nick=None):
0.1.1 by Dan Loda
First working version of xannotate.
431
        self.revision_id = revision_id
432
        self.parent_ids = []
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
433
        self.committer = committer
0.1.1 by Dan Loda
First working version of xannotate.
434
        self.message = "?"
435
        self.timestamp = 0.0
436
        self.timezone = 0
157.1.7 by Aaron Bentley
Fix branch-nick handling
437
        self.properties = {}
0.1.1 by Dan Loda
First working version of xannotate.
438
642 by Jelmer Vernooij
Use get_apparent_authors() rather than deprecated get_apparent_author().
439
    def get_apparent_authors(self):
440
        return [self.committer]
273 by Aaron Bentley
Use get_apparent_author, rename variables to 'author'
441
0.1.23 by Aaron Bentley
Added revision caching to initial annotation
442
443
class RevisionCache(object):
444
    """A caching revision source"""
642 by Jelmer Vernooij
Use get_apparent_authors() rather than deprecated get_apparent_author().
445
66.6.5 by Aaron Bentley
Speed up the 'back' operation
446
    def __init__(self, real_source, seed_cache=None):
0.1.23 by Aaron Bentley
Added revision caching to initial annotation
447
        self.__real_source = real_source
66.6.5 by Aaron Bentley
Speed up the 'back' operation
448
        if seed_cache is None:
449
            self.__cache = {}
450
        else:
451
            self.__cache = dict(seed_cache)
0.1.23 by Aaron Bentley
Added revision caching to initial annotation
452
453
    def get_revision(self, revision_id):
454
        if revision_id not in self.__cache:
455
            revision = self.__real_source.get_revision(revision_id)
456
            self.__cache[revision_id] = revision
457
        return self.__cache[revision_id]
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
458
734.1.51 by Curtis Hovey
Fix the initializer for many classes.
459
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
460
class SearchBox(Gtk.HBox):
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
461
    """A button box for searching in text or lines of annotations"""
462
    def __init__(self):
734.1.51 by Curtis Hovey
Fix the initializer for many classes.
463
        super(SearchBox, self).__init__(homogeneous=False, spacing=6)
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
464
465
        # Close button
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
466
        button = Gtk.Button()
467
        image = Gtk.Image()
468
        image.set_from_stock('gtk-stop', Gtk.IconSize.BUTTON)
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
469
        button.set_image(image)
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
470
        button.set_relief(Gtk.ReliefStyle.NONE)
734.1.13 by Curtis Hovey
Fixed search in diff.
471
        button.connect("clicked", lambda w: self.hide())
734.1.6 by Curtis Hovey
Added a very rough fix to see gannontation.
472
        self.pack_start(button, False, False, 0)
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
473
474
        # Search entry
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
475
        label = Gtk.Label()
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
476
        self._label = label
734.1.6 by Curtis Hovey
Added a very rough fix to see gannontation.
477
        self.pack_start(label, False, False, 0)
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
478
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
479
        entry = Gtk.Entry()
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
480
        self._entry = entry
481
        entry.connect("activate", lambda w, d: self._do_search(d),
482
                      'forward')
734.1.6 by Curtis Hovey
Added a very rough fix to see gannontation.
483
        self.pack_start(entry, False, False, 0)
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
484
485
        # Next/previous buttons
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
486
        button = Gtk.Button('_Next')
487
        image = Gtk.Image()
488
        image.set_from_stock('gtk-go-forward', Gtk.IconSize.BUTTON)
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
489
        button.set_image(image)
490
        button.connect("clicked", lambda w, d: self._do_search(d),
491
                       'forward')
734.1.6 by Curtis Hovey
Added a very rough fix to see gannontation.
492
        self.pack_start(button, False, False, 0)
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
493
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
494
        button = Gtk.Button('_Previous')
495
        image = Gtk.Image()
496
        image.set_from_stock('gtk-go-back', Gtk.IconSize.BUTTON)
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
497
        button.set_image(image)
498
        button.connect("clicked", lambda w, d: self._do_search(d),
499
                       'backward')
734.1.6 by Curtis Hovey
Added a very rough fix to see gannontation.
500
        self.pack_start(button, False, False, 0)
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
501
502
        # Search options
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
503
        check = Gtk.CheckButton('Match case')
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
504
        self._match_case = check
734.1.6 by Curtis Hovey
Added a very rough fix to see gannontation.
505
        self.pack_start(check, False, False, 0)
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
506
734.1.1 by Curtis Hovey
Mechanical changes made by pygi.convert.sh.
507
        check = Gtk.CheckButton('Regexp')
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
508
        check.connect("toggled", lambda w: self._set_label())
509
        self._regexp = check
734.1.6 by Curtis Hovey
Added a very rough fix to see gannontation.
510
        self.pack_start(check, False, False, 0)
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
511
512
        self._view = None
513
        self._column = None
514
        # Note that we stay hidden (we do not call self.show_all())
515
516
517
    def show_for(self, kind):
518
        self._kind = kind
519
        self.show_all()
520
        self._set_label()
521
        # Hide unrelated buttons
522
        if kind == 'line':
523
            self._match_case.hide()
524
            self._regexp.hide()
525
        # Be ready
526
        self._entry.grab_focus()
527
528
    def _set_label(self):
529
        if self._kind == 'line':
530
            self._label.set_text('Find Line: ')
531
        else:
532
            if self._regexp.get_active():
533
                self._label.set_text('Find Regexp: ')
534
            else:
535
                self._label.set_text('Find Text: ')
536
537
    def set_target(self, view,column):
538
        self._view = view
539
        self._column = column
540
541
    def _match(self, model, iterator, column):
542
        matching_case = self._match_case.get_active()
706 by Martin Pool
Store line numbers as ints in the gannotate ListStore.
543
        cell_value, = model.get(iterator, column)
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
544
        key = self._entry.get_text()
706 by Martin Pool
Store line numbers as ints in the gannotate ListStore.
545
        if column == LINE_NUM_COL:
546
            # FIXME: For goto-line there are faster algorithms than searching 
547
            # every line til we find the right one! -- mbp 2011-01-27
548
            return key.strip() == str(cell_value)
549
        elif self._regexp.get_active():
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
550
            if matching_case:
706 by Martin Pool
Store line numbers as ints in the gannotate ListStore.
551
                match = re.compile(key).search(cell_value, 1)
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
552
            else:
706 by Martin Pool
Store line numbers as ints in the gannotate ListStore.
553
                match = re.compile(key, re.I).search(cell_value, 1)
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
554
        else:
555
            if not matching_case:
706 by Martin Pool
Store line numbers as ints in the gannotate ListStore.
556
                cell_value = cell_value.lower()
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
557
                key = key.lower()
706 by Martin Pool
Store line numbers as ints in the gannotate ListStore.
558
            match = cell_value.find(key) != -1
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
559
560
        return match
561
562
    def _iterate_rows_forward(self, model, start):
563
        model_size = len(model)
564
        current = start + 1
565
        while model_size != 0:
566
            if current >= model_size: current =  0
567
            yield model.get_iter_from_string('%d' % current)
568
            if current == start: raise StopIteration
569
            current += 1
570
571
    def _iterate_rows_backward(self, model, start):
572
        model_size = len(model)
573
        current = start - 1
574
        while model_size != 0:
575
            if current < 0: current = model_size - 1
576
            yield model.get_iter_from_string('%d' % current)
577
            if current == start: raise StopIteration
578
            current -= 1
579
580
    def _do_search(self, direction):
581
        if direction == 'forward':
582
            iterate = self._iterate_rows_forward
583
        else:
584
            iterate = self._iterate_rows_backward
585
586
        model, sel = self._view.get_selection().get_selected()
587
        if sel is None:
588
            start = 0
589
        else:
590
            path = model.get_string_from_iter(sel)
591
            start = int(path)
592
593
        for row in iterate(model, start):
594
            if self._match(model, row, self._column):
595
                path = model.get_path(row)
734.1.13 by Curtis Hovey
Fixed search in diff.
596
                self._view.set_cursor(path, None, False)
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
597
                self._view.scroll_to_cell(path, use_align=True)
713 by Jelmer Vernooij
Remove some unused imports, fix some formatting.
598
                break