/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
0.1.1 by Dan Loda
First working version of xannotate.
19
import pygtk
20
pygtk.require("2.0")
0.2.1 by Dan Loda
first go at emacs vc-annotate like highlighting
21
import gobject
0.1.1 by Dan Loda
First working version of xannotate.
22
import gtk
23
import pango
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
24
import re
0.1.1 by Dan Loda
First working version of xannotate.
25
713 by Jelmer Vernooij
Remove some unused imports, fix some formatting.
26
from bzrlib import patiencediff
0.1.1 by Dan Loda
First working version of xannotate.
27
from bzrlib.errors import NoSuchRevision
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
28
from bzrlib.revision import NULL_REVISION, CURRENT_REVISION
0.1.1 by Dan Loda
First working version of xannotate.
29
724 by Jelmer Vernooij
Fix formatting, imports.
30
from bzrlib.plugins.gtk.annotate.colormap import AnnotateColorSaturation
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
298.2.1 by Daniel Schierbeck
Refactored the GTK window code, creating a single base window class that handles keyboard events.
53
        Window.__init__(self, parent)
706 by Martin Pool
Store line numbers as ints in the gannotate ListStore.
54
0.1.1 by Dan Loda
First working version of xannotate.
55
        self.set_icon(self.render_icon(gtk.STOCK_FIND, gtk.ICON_SIZE_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]
0.1.17 by Dan Loda
A little refactoring.
73
        self.annomodel = gtk.ListStore(gobject.TYPE_STRING,
706 by Martin Pool
Store line numbers as ints in the gannotate ListStore.
74
                                       gobject.TYPE_INT,
0.1.17 by Dan Loda
A little refactoring.
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
0.1.17 by Dan Loda
A little refactoring.
132
        self.annoview.set_cursor(row)
59.2.1 by Aaron Bentley
Gannotate takes a line number
133
        self.annoview.scroll_to_cell(row, use_align=True)
0.1.12 by Dan Loda
New --line option. Can now jump to a specific line number.
134
66.2.4 by Aaron Bentley
Use dotted revnos instead of 'merge' where possible
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]
0.2.5 by Dan Loda
Use granny-like colors as default and rename ColorMap => AnnotateColorMap.
170
        model.set(iter, HIGHLIGHT_COLOR_COL,
0.1.19 by Aaron Bentley
Different colours for different committers
171
                  self.annotate_colormap.get_color(revision, now))
0.2.1 by Dan Loda
first go at emacs vc-annotate like highlighting
172
66.6.4 by Aaron Bentley
Add back button to see older versions
173
    def _selected_revision(self):
0.1.17 by Dan Loda
A little refactoring.
174
        (path, col) = self.annoview.get_cursor()
79 by Jelmer Vernooij
Handle empty files more gracefully. Fixes #58951.
175
        if path is None:
66.6.4 by Aaron Bentley
Add back button to see older versions
176
            return None
177
        return self.annomodel[path][REVISION_ID_COL]
178
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
179
    def _activate_selected_revision(self, w):
66.6.4 by Aaron Bentley
Add back button to see older versions
180
        rev_id = self._selected_revision()
464.2.1 by Adrian Wilkins
Detect the reserved null: revision in appropriate places.
181
        if not rev_id or rev_id == NULL_REVISION:
79 by Jelmer Vernooij
Handle empty files more gracefully. Fixes #58951.
182
            return
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
183
        selected = self.revisions[rev_id]
330.3.1 by Daniel Schierbeck
Renamed logview 'revisionview'.
184
        self.revisionview.set_revision(selected)
173.1.1 by Aaron Bentley
Better behavior when unable to go back
185
        if (len(selected.parent_ids) != 0 and selected.parent_ids[0] not in
186
            self._no_back):
187
            enable_back = True
188
        else:
189
            enable_back = False
190
        self.back_button.set_sensitive(enable_back)
0.1.1 by Dan Loda
First working version of xannotate.
191
192
    def _create(self):
330.3.1 by Daniel Schierbeck
Renamed logview 'revisionview'.
193
        self.revisionview = self._create_log_view()
0.1.17 by Dan Loda
A little refactoring.
194
        self.annoview = self._create_annotate_view()
195
170.1.6 by Aaron Bentley
Move buttons to top, tweak layout
196
        vbox = gtk.VBox(False)
0.2.1 by Dan Loda
first go at emacs vc-annotate like highlighting
197
        vbox.show()
0.1.17 by Dan Loda
A little refactoring.
198
199
        sw = gtk.ScrolledWindow()
200
        sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
201
        sw.set_shadow_type(gtk.SHADOW_IN)
202
        sw.add(self.annoview)
59.2.2 by Aaron Bentley
Annotate launches a diff for the particular revision
203
        self.annoview.gwindow = self
0.1.17 by Dan Loda
A little refactoring.
204
        sw.show()
170.1.4 by Aaron Bentley
Move search fields directly below source window
205
206
        swbox = gtk.VBox()
207
        swbox.pack_start(sw)
208
        swbox.show()
170.1.6 by Aaron Bentley
Move buttons to top, tweak layout
209
210
        hbox = gtk.HBox(False, 6)
211
        self.back_button = self._create_back_button()
212
        hbox.pack_start(self.back_button, expand=False, fill=True)
213
        self.forward_button = self._create_forward_button()
214
        hbox.pack_start(self.forward_button, expand=False, fill=True)
688.1.3 by Martin Pool
Add 'Find' button to start text search
215
        self.find_button = self._create_find_button()
216
        hbox.pack_start(self.find_button, expand=False, fill=True)
688.1.4 by Martin Pool
Add button to goto-line too
217
        self.goto_button = self._create_goto_button()
218
        hbox.pack_start(self.goto_button, expand=False, fill=True)
170.1.6 by Aaron Bentley
Move buttons to top, tweak layout
219
        hbox.show()
220
        vbox.pack_start(hbox, expand=False, fill=True)
706 by Martin Pool
Store line numbers as ints in the gannotate ListStore.
221
0.1.8 by Dan Loda
Remember window state. This introduces the gannotate.conf configuration file,
222
        self.pane = pane = gtk.VPaned()
170.1.4 by Aaron Bentley
Move search fields directly below source window
223
        pane.add1(swbox)
330.3.1 by Daniel Schierbeck
Renamed logview 'revisionview'.
224
        pane.add2(self.revisionview)
0.1.1 by Dan Loda
First working version of xannotate.
225
        pane.show()
226
        vbox.pack_start(pane, expand=True, fill=True)
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
227
228
        self._search = SearchBox()
170.1.4 by Aaron Bentley
Move search fields directly below source window
229
        swbox.pack_start(self._search, expand=False, fill=True)
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
230
        accels = gtk.AccelGroup()
231
        accels.connect_group(gtk.keysyms.f, gtk.gdk.CONTROL_MASK,
232
                             gtk.ACCEL_LOCKED,
233
                             self._search_by_text)
234
        accels.connect_group(gtk.keysyms.g, gtk.gdk.CONTROL_MASK,
235
                             gtk.ACCEL_LOCKED,
236
                             self._search_by_line)
237
        self.add_accel_group(accels)
238
0.1.1 by Dan Loda
First working version of xannotate.
239
        self.add(vbox)
240
688.1.3 by Martin Pool
Add 'Find' button to start text search
241
    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.
242
        self._search.show_for('text')
66.5.3 by v.ladeuil+lp at free
Realbetter fix for bug #73965.
243
        self._search.set_target(self.annoview, TEXT_LINE_COL)
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
244
688.1.4 by Martin Pool
Add button to goto-line too
245
    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.
246
        self._search.show_for('line')
66.5.3 by v.ladeuil+lp at free
Realbetter fix for bug #73965.
247
        self._search.set_target(self.annoview, LINE_NUM_COL)
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
248
438 by Aaron Bentley
Update window titles
249
    def line_diff(self, tv, path, tvc):
59.2.2 by Aaron Bentley
Annotate launches a diff for the particular revision
250
        row = path[0]
251
        revision = self.annotations[row]
65 by Aaron Bentley
Handle first revision properly
252
        repository = self.branch.repository
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
253
        if revision.revision_id == CURRENT_REVISION:
254
            tree1 = self.tree
255
            tree2 = self.tree.basis_tree()
65 by Aaron Bentley
Handle first revision properly
256
        else:
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
257
            tree1 = repository.revision_tree(revision.revision_id)
258
            if len(revision.parent_ids) > 0:
259
                tree2 = repository.revision_tree(revision.parent_ids[0])
260
            else:
261
                tree2 = repository.revision_tree(NULL_REVISION)
150 by Jelmer Vernooij
Fix handling showing diffs of working tree changes.
262
        from bzrlib.plugins.gtk.diff import DiffWindow
707.1.1 by Sergei Golubchik
* make sure that gdiff opened from gannotate knows its parent
263
        window = DiffWindow(self)
438 by Aaron Bentley
Update window titles
264
        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
265
        window.set_file(tree1.id2path(self.file_id))
59.2.2 by Aaron Bentley
Annotate launches a diff for the particular revision
266
        window.show()
267
268
0.1.1 by Dan Loda
First working version of xannotate.
269
    def _create_annotate_view(self):
0.1.17 by Dan Loda
A little refactoring.
270
        tv = gtk.TreeView()
271
        tv.set_rules_hint(False)
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
272
        tv.connect("cursor-changed", self._activate_selected_revision)
0.1.17 by Dan Loda
A little refactoring.
273
        tv.show()
438 by Aaron Bentley
Update window titles
274
        tv.connect("row-activated", self.line_diff)
0.1.1 by Dan Loda
First working version of xannotate.
275
276
        cell = gtk.CellRendererText()
277
        cell.set_property("xalign", 1.0)
278
        cell.set_property("ypad", 0)
279
        cell.set_property("family", "Monospace")
280
        cell.set_property("cell-background-gdk",
0.1.17 by Dan Loda
A little refactoring.
281
                          tv.get_style().bg[gtk.STATE_NORMAL])
0.1.1 by Dan Loda
First working version of xannotate.
282
        col = gtk.TreeViewColumn()
283
        col.set_resizable(False)
284
        col.pack_start(cell, expand=True)
285
        col.add_attribute(cell, "text", LINE_NUM_COL)
0.1.17 by Dan Loda
A little refactoring.
286
        tv.append_column(col)
0.1.1 by Dan Loda
First working version of xannotate.
287
288
        cell = gtk.CellRendererText()
289
        cell.set_property("ypad", 0)
290
        cell.set_property("ellipsize", pango.ELLIPSIZE_END)
291
        cell.set_property("cell-background-gdk",
292
                          self.get_style().bg[gtk.STATE_NORMAL])
293
        col = gtk.TreeViewColumn("Committer")
294
        col.set_resizable(True)
295
        col.pack_start(cell, expand=True)
296
        col.add_attribute(cell, "text", COMMITTER_COL)
0.1.17 by Dan Loda
A little refactoring.
297
        tv.append_column(col)
0.1.1 by Dan Loda
First working version of xannotate.
298
299
        cell = gtk.CellRendererText()
300
        cell.set_property("xalign", 1.0)
301
        cell.set_property("ypad", 0)
302
        cell.set_property("cell-background-gdk",
303
                          self.get_style().bg[gtk.STATE_NORMAL])
304
        col = gtk.TreeViewColumn("Revno")
305
        col.set_resizable(False)
306
        col.pack_start(cell, expand=True)
307
        col.add_attribute(cell, "markup", REVNO_COL)
0.1.17 by Dan Loda
A little refactoring.
308
        tv.append_column(col)
0.1.1 by Dan Loda
First working version of xannotate.
309
310
        cell = gtk.CellRendererText()
311
        cell.set_property("ypad", 0)
312
        cell.set_property("family", "Monospace")
313
        col = gtk.TreeViewColumn()
314
        col.set_resizable(False)
315
        col.pack_start(cell, expand=True)
0.1.18 by Aaron Bentley
Switched to using pink backgrounds
316
#        col.add_attribute(cell, "foreground", HIGHLIGHT_COLOR_COL)
317
        col.add_attribute(cell, "background", HIGHLIGHT_COLOR_COL)
0.1.1 by Dan Loda
First working version of xannotate.
318
        col.add_attribute(cell, "text", TEXT_LINE_COL)
0.1.17 by Dan Loda
A little refactoring.
319
        tv.append_column(col)
0.1.1 by Dan Loda
First working version of xannotate.
320
723.1.1 by Sergei Golubchik
interactive substring search in gannotate
321
        # interactive substring search
322
        def search_equal_func(model, column, key, iter):
323
            return model.get_value(iter, TEXT_LINE_COL).lower().find(key.lower()) == -1
324
325
        tv.set_enable_search(True)
326
        tv.set_search_equal_func(search_equal_func)
66.5.1 by v.ladeuil+lp at free
Minimal fix for bug #73965.
327
0.1.17 by Dan Loda
A little refactoring.
328
        return tv
0.1.1 by Dan Loda
First working version of xannotate.
329
330
    def _create_log_view(self):
473.1.1 by Andrew Bennetts
Simple hack to fix gannotate.
331
        lv = RevisionView(self._branch)
0.1.17 by Dan Loda
A little refactoring.
332
        lv.show()
333
        return lv
0.1.1 by Dan Loda
First working version of xannotate.
334
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
335
    def _create_back_button(self):
66.6.4 by Aaron Bentley
Add back button to see older versions
336
        button = gtk.Button()
337
        button.set_use_stock(True)
338
        button.set_label("gtk-go-back")
339
        button.connect("clicked", lambda w: self.go_back())
170.1.6 by Aaron Bentley
Move buttons to top, tweak layout
340
        button.set_relief(gtk.RELIEF_NONE)
66.6.4 by Aaron Bentley
Add back button to see older versions
341
        button.show()
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
342
        return button
343
344
    def _create_forward_button(self):
345
        button = gtk.Button()
346
        button.set_use_stock(True)
347
        button.set_label("gtk-go-forward")
348
        button.connect("clicked", lambda w: self.go_forward())
170.1.6 by Aaron Bentley
Move buttons to top, tweak layout
349
        button.set_relief(gtk.RELIEF_NONE)
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
350
        button.show()
351
        button.set_sensitive(False)
352
        return button
66.6.4 by Aaron Bentley
Add back button to see older versions
353
688.1.3 by Martin Pool
Add 'Find' button to start text search
354
    def _create_find_button(self):
355
        button = gtk.Button()
356
        button.set_use_stock(True)
357
        button.set_label("gtk-find")
688.1.5 by Martin Pool
Also show tooltips on find/goto buttons
358
        button.set_tooltip_text("Search for text (Ctrl+F)")
688.1.3 by Martin Pool
Add 'Find' button to start text search
359
        button.connect("clicked", self._search_by_text)
360
        button.set_relief(gtk.RELIEF_NONE)
361
        button.show()
362
        button.set_sensitive(True)
363
        return button
364
688.1.4 by Martin Pool
Add button to goto-line too
365
    def _create_goto_button(self):
366
        button = gtk.Button()
367
        button.set_label("Goto Line")
688.1.5 by Martin Pool
Also show tooltips on find/goto buttons
368
        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
369
        button.connect("clicked", self._search_by_line)
370
        button.set_relief(gtk.RELIEF_NONE)
371
        button.show()
372
        button.set_sensitive(True)
373
        return button
374
66.6.4 by Aaron Bentley
Add back button to see older versions
375
    def go_back(self):
173.1.1 by Aaron Bentley
Better behavior when unable to go back
376
        last_tree = self.tree
66.6.4 by Aaron Bentley
Add back button to see older versions
377
        rev_id = self._selected_revision()
378
        parent_id = self.revisions[rev_id].parent_ids[0]
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
379
        target_tree = self.branch.repository.revision_tree(parent_id)
173.1.1 by Aaron Bentley
Better behavior when unable to go back
380
        if self._go(target_tree):
381
            self.history.append(last_tree)
382
            self.forward_button.set_sensitive(True)
383
        else:
384
            self._no_back.add(parent_id)
385
            self.back_button.set_sensitive(False)
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
386
387
    def go_forward(self):
388
        if len(self.history) == 0:
389
            return
390
        target_tree = self.history.pop()
391
        if len(self.history) == 0:
392
            self.forward_button.set_sensitive(False)
393
        self._go(target_tree)
394
395
    def _go(self, target_tree):
396
        rev_id = self._selected_revision()
397
        if self.file_id in target_tree:
398
            offset = self.get_scroll_offset(target_tree)
66.6.6 by Aaron Bentley
Support scrolling based on an offset
399
            (row,), col = self.annoview.get_cursor()
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
400
            self.annotate(target_tree, self.branch, self.file_id)
401
            new_row = row+offset
402
            if new_row < 0:
403
                new_row = 0
404
            self.annoview.set_cursor(new_row)
173.1.1 by Aaron Bentley
Better behavior when unable to go back
405
            return True
406
        else:
407
            return False
66.6.6 by Aaron Bentley
Support scrolling based on an offset
408
409
    def get_scroll_offset(self, tree):
410
        old = self.tree.get_file(self.file_id)
411
        new = tree.get_file(self.file_id)
412
        (row,), col = self.annoview.get_cursor()
413
        matcher = patiencediff.PatienceSequenceMatcher(None, old.readlines(),
414
                                                       new.readlines())
415
        for i, j, n in matcher.get_matching_blocks():
416
            if i + n >= row:
417
                return j - i
418
0.1.1 by Dan Loda
First working version of xannotate.
419
642 by Jelmer Vernooij
Use get_apparent_authors() rather than deprecated get_apparent_author().
420
class FakeRevision(object):
0.1.1 by Dan Loda
First working version of xannotate.
421
    """ A fake revision.
422
423
    For when a revision is referenced but not present.
424
    """
425
157.1.7 by Aaron Bentley
Fix branch-nick handling
426
    def __init__(self, revision_id, committer='?', nick=None):
0.1.1 by Dan Loda
First working version of xannotate.
427
        self.revision_id = revision_id
428
        self.parent_ids = []
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
429
        self.committer = committer
0.1.1 by Dan Loda
First working version of xannotate.
430
        self.message = "?"
431
        self.timestamp = 0.0
432
        self.timezone = 0
157.1.7 by Aaron Bentley
Fix branch-nick handling
433
        self.properties = {}
0.1.1 by Dan Loda
First working version of xannotate.
434
642 by Jelmer Vernooij
Use get_apparent_authors() rather than deprecated get_apparent_author().
435
    def get_apparent_authors(self):
436
        return [self.committer]
273 by Aaron Bentley
Use get_apparent_author, rename variables to 'author'
437
0.1.23 by Aaron Bentley
Added revision caching to initial annotation
438
439
class RevisionCache(object):
440
    """A caching revision source"""
642 by Jelmer Vernooij
Use get_apparent_authors() rather than deprecated get_apparent_author().
441
66.6.5 by Aaron Bentley
Speed up the 'back' operation
442
    def __init__(self, real_source, seed_cache=None):
0.1.23 by Aaron Bentley
Added revision caching to initial annotation
443
        self.__real_source = real_source
66.6.5 by Aaron Bentley
Speed up the 'back' operation
444
        if seed_cache is None:
445
            self.__cache = {}
446
        else:
447
            self.__cache = dict(seed_cache)
0.1.23 by Aaron Bentley
Added revision caching to initial annotation
448
449
    def get_revision(self, revision_id):
450
        if revision_id not in self.__cache:
451
            revision = self.__real_source.get_revision(revision_id)
452
            self.__cache[revision_id] = revision
453
        return self.__cache[revision_id]
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
454
455
class SearchBox(gtk.HBox):
456
    """A button box for searching in text or lines of annotations"""
457
    def __init__(self):
458
        gtk.HBox.__init__(self, False, 6)
459
460
        # Close button
461
        button = gtk.Button()
462
        image = gtk.Image()
463
        image.set_from_stock('gtk-stop', gtk.ICON_SIZE_BUTTON)
464
        button.set_image(image)
465
        button.set_relief(gtk.RELIEF_NONE)
466
        button.connect("clicked", lambda w: self.hide_all())
467
        self.pack_start(button, expand=False, fill=False)
468
469
        # Search entry
470
        label = gtk.Label()
471
        self._label = label
472
        self.pack_start(label, expand=False, fill=False)
473
474
        entry = gtk.Entry()
475
        self._entry = entry
476
        entry.connect("activate", lambda w, d: self._do_search(d),
477
                      'forward')
478
        self.pack_start(entry, expand=False, fill=False)
479
480
        # Next/previous buttons
481
        button = gtk.Button('_Next')
482
        image = gtk.Image()
483
        image.set_from_stock('gtk-go-forward', gtk.ICON_SIZE_BUTTON)
484
        button.set_image(image)
485
        button.connect("clicked", lambda w, d: self._do_search(d),
486
                       'forward')
487
        self.pack_start(button, expand=False, fill=False)
488
489
        button = gtk.Button('_Previous')
490
        image = gtk.Image()
491
        image.set_from_stock('gtk-go-back', gtk.ICON_SIZE_BUTTON)
492
        button.set_image(image)
493
        button.connect("clicked", lambda w, d: self._do_search(d),
494
                       'backward')
495
        self.pack_start(button, expand=False, fill=False)
496
497
        # Search options
498
        check = gtk.CheckButton('Match case')
499
        self._match_case = check
500
        self.pack_start(check, expand=False, fill=False)
501
502
        check = gtk.CheckButton('Regexp')
503
        check.connect("toggled", lambda w: self._set_label())
504
        self._regexp = check
505
        self.pack_start(check, expand=False, fill=False)
506
507
        self._view = None
508
        self._column = None
509
        # Note that we stay hidden (we do not call self.show_all())
510
511
512
    def show_for(self, kind):
513
        self._kind = kind
514
        self.show_all()
515
        self._set_label()
516
        # Hide unrelated buttons
517
        if kind == 'line':
518
            self._match_case.hide()
519
            self._regexp.hide()
520
        # Be ready
521
        self._entry.grab_focus()
522
523
    def _set_label(self):
524
        if self._kind == 'line':
525
            self._label.set_text('Find Line: ')
526
        else:
527
            if self._regexp.get_active():
528
                self._label.set_text('Find Regexp: ')
529
            else:
530
                self._label.set_text('Find Text: ')
531
532
    def set_target(self, view,column):
533
        self._view = view
534
        self._column = column
535
536
    def _match(self, model, iterator, column):
537
        matching_case = self._match_case.get_active()
706 by Martin Pool
Store line numbers as ints in the gannotate ListStore.
538
        cell_value, = model.get(iterator, column)
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
539
        key = self._entry.get_text()
706 by Martin Pool
Store line numbers as ints in the gannotate ListStore.
540
        if column == LINE_NUM_COL:
541
            # FIXME: For goto-line there are faster algorithms than searching 
542
            # every line til we find the right one! -- mbp 2011-01-27
543
            return key.strip() == str(cell_value)
544
        elif self._regexp.get_active():
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
545
            if matching_case:
706 by Martin Pool
Store line numbers as ints in the gannotate ListStore.
546
                match = re.compile(key).search(cell_value, 1)
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
547
            else:
706 by Martin Pool
Store line numbers as ints in the gannotate ListStore.
548
                match = re.compile(key, re.I).search(cell_value, 1)
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
549
        else:
550
            if not matching_case:
706 by Martin Pool
Store line numbers as ints in the gannotate ListStore.
551
                cell_value = cell_value.lower()
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
552
                key = key.lower()
706 by Martin Pool
Store line numbers as ints in the gannotate ListStore.
553
            match = cell_value.find(key) != -1
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
554
555
        return match
556
557
    def _iterate_rows_forward(self, model, start):
558
        model_size = len(model)
559
        current = start + 1
560
        while model_size != 0:
561
            if current >= model_size: current =  0
562
            yield model.get_iter_from_string('%d' % current)
563
            if current == start: raise StopIteration
564
            current += 1
565
566
    def _iterate_rows_backward(self, model, start):
567
        model_size = len(model)
568
        current = start - 1
569
        while model_size != 0:
570
            if current < 0: current = model_size - 1
571
            yield model.get_iter_from_string('%d' % current)
572
            if current == start: raise StopIteration
573
            current -= 1
574
575
    def _do_search(self, direction):
576
        if direction == 'forward':
577
            iterate = self._iterate_rows_forward
578
        else:
579
            iterate = self._iterate_rows_backward
580
581
        model, sel = self._view.get_selection().get_selected()
582
        if sel is None:
583
            start = 0
584
        else:
585
            path = model.get_string_from_iter(sel)
586
            start = int(path)
587
588
        for row in iterate(model, start):
589
            if self._match(model, row, self._column):
590
                path = model.get_path(row)
591
                self._view.set_cursor(path)
592
                self._view.scroll_to_cell(path, use_align=True)
713 by Jelmer Vernooij
Remove some unused imports, fix some formatting.
593
                break