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