/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
66.6.6 by Aaron Bentley
Support scrolling based on an offset
26
from bzrlib import patiencediff, tsort
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
0.1.18 by Aaron Bentley
Switched to using pink backgrounds
30
from colormap import AnnotateColorMap, AnnotateColorSaturation
146 by Jelmer Vernooij
Move more code to top-level directory.
31
from bzrlib.plugins.gtk.logview import LogView
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
0.1.2 by Dan Loda
Rename plugin: xannotate => gannotate
44
class GAnnotateWindow(gtk.Window):
0.1.1 by Dan Loda
First working version of xannotate.
45
    """Annotate window."""
46
275.1.6 by Daniel Schierbeck
Made Ctrl-W call gtk.main_quit if the window has no parent.
47
    def __init__(self, all=False, plain=False, parent=None):
0.2.6 by Dan Loda
--plain option to disable highlighting. And update README
48
        self.all = all
49
        self.plain = plain
275.1.6 by Daniel Schierbeck
Made Ctrl-W call gtk.main_quit if the window has no parent.
50
        self._parent = parent
0.2.6 by Dan Loda
--plain option to disable highlighting. And update README
51
        
0.1.1 by Dan Loda
First working version of xannotate.
52
        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
275.1.2 by Daniel Schierbeck
Close annotation window when 'destroy' signal is received.
53
275.1.5 by Daniel Schierbeck
Made windows close correctly on Ctrl-W and made the application quit on Ctrl-Q.
54
        self.connect("key-press-event", self._on_key_pressed)
0.2.6 by Dan Loda
--plain option to disable highlighting. And update README
55
        
0.1.1 by Dan Loda
First working version of xannotate.
56
        self.set_icon(self.render_icon(gtk.STOCK_FIND, gtk.ICON_SIZE_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
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
69
        self.revision_id = getattr(tree, 'get_revision_id', 
70
                                   lambda: CURRENT_REVISION)()
0.1.1 by Dan Loda
First working version of xannotate.
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,
74
                                       gobject.TYPE_STRING,
75
                                       gobject.TYPE_STRING,
76
                                       gobject.TYPE_STRING,
77
                                       gobject.TYPE_STRING,
78
                                       gobject.TYPE_STRING)
0.1.1 by Dan Loda
First working version of xannotate.
79
        
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()
84
            for line_no, (revision, revno, line)\
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
85
                    in enumerate(self._annotate(tree, file_id)):
0.4.1 by Aaron Bentley
Updated performance, API use
86
                if revision.revision_id == last_seen and not self.all:
273 by Aaron Bentley
Use get_apparent_author, rename variables to 'author'
87
                    revno = author = ""
0.4.1 by Aaron Bentley
Updated performance, API use
88
                else:
89
                    last_seen = revision.revision_id
273 by Aaron Bentley
Use get_apparent_author, rename variables to 'author'
90
                    author = revision.get_apparent_author()
0.4.1 by Aaron Bentley
Updated performance, API use
91
92
                if revision.revision_id not in self.revisions:
93
                    self.revisions[revision.revision_id] = revision
94
95
                self.annomodel.append([revision.revision_id,
96
                                       line_no + 1,
273 by Aaron Bentley
Use get_apparent_author, rename variables to 'author'
97
                                       author,
0.4.1 by Aaron Bentley
Updated performance, API use
98
                                       revno,
99
                                       None,
100
                                       line.rstrip("\r\n")
101
                                      ])
59.2.2 by Aaron Bentley
Annotate launches a diff for the particular revision
102
                self.annotations.append(revision)
0.4.1 by Aaron Bentley
Updated performance, API use
103
104
            if not self.plain:
66.6.1 by Aaron Bentley
Remove usused span selector
105
                now = time.time()
106
                self.annomodel.foreach(self._highlight_annotation, now)
0.4.1 by Aaron Bentley
Updated performance, API use
107
        finally:
108
            branch.repository.unlock()
109
            branch.unlock()
0.2.6 by Dan Loda
--plain option to disable highlighting. And update README
110
0.1.17 by Dan Loda
A little refactoring.
111
        self.annoview.set_model(self.annomodel)
112
        self.annoview.grab_focus()
0.1.1 by Dan Loda
First working version of xannotate.
113
0.1.12 by Dan Loda
New --line option. Can now jump to a specific line number.
114
    def jump_to_line(self, lineno):
0.1.17 by Dan Loda
A little refactoring.
115
        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.
116
            row = 0
117
            # FIXME:should really deal with this in the gui. Perhaps a status
118
            # bar?
119
            print("gannotate: Line number %d does't exist. Defaulting to "
120
                  "line 1." % lineno)
79 by Jelmer Vernooij
Handle empty files more gracefully. Fixes #58951.
121
	    return
0.1.12 by Dan Loda
New --line option. Can now jump to a specific line number.
122
        else:
123
            row = lineno - 1
124
0.1.17 by Dan Loda
A little refactoring.
125
        self.annoview.set_cursor(row)
59.2.1 by Aaron Bentley
Gannotate takes a line number
126
        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.
127
66.2.4 by Aaron Bentley
Use dotted revnos instead of 'merge' where possible
128
    def _dotted_revnos(self, repository, revision_id):
129
        """Return a dict of revision_id -> dotted revno
130
        
131
        :param repository: The repository to get the graph from
132
        :param revision_id: The last revision for which this info is needed
133
        """
134
        graph = repository.get_revision_graph(revision_id)
135
        dotted = {}
136
        for n, revision_id, d, revno, e in tsort.merge_sort(graph, 
137
            revision_id, generate_revno=True):
138
            dotted[revision_id] = '.'.join(str(num) for num in revno)
139
        return dotted
140
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
141
    def _annotate(self, tree, file_id):
142
        current_revision = FakeRevision(CURRENT_REVISION)
143
        current_revision.committer = self.branch.get_config().username()
144
        current_revision.timestamp = time.time()
145
        current_revision.message = '[Not yet committed]'
66.6.6 by Aaron Bentley
Support scrolling based on an offset
146
        current_revision.parent_ids = tree.get_parent_ids()
157.1.7 by Aaron Bentley
Fix branch-nick handling
147
        current_revision.properties['branch-nick'] = self.branch.nick
66.2.15 by Aaron Bentley
fix future revno
148
        current_revno = '%d?' % (self.branch.revno() + 1)
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
149
        repository = self.branch.repository
150
        if self.revision_id == CURRENT_REVISION:
151
            revision_id = self.branch.last_revision()
152
        else:
153
            revision_id = self.revision_id
66.2.4 by Aaron Bentley
Use dotted revnos instead of 'merge' where possible
154
        dotted = self._dotted_revnos(repository, revision_id)
66.6.5 by Aaron Bentley
Speed up the 'back' operation
155
        revision_cache = RevisionCache(repository, self.revisions)
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
156
        for origin, text in tree.annotate_iter(file_id):
0.4.1 by Aaron Bentley
Updated performance, API use
157
            rev_id = origin
66.6.7 by Aaron Bentley
Handle current revision better
158
            if rev_id == CURRENT_REVISION:
159
                revision = current_revision
160
                revno = current_revno
161
            else:
162
                try:
163
                    revision = revision_cache.get_revision(rev_id)
164
                    revno = dotted.get(rev_id, 'merge')
165
                    if len(revno) > 15:
166
                        revno = 'merge'
167
                except NoSuchRevision:
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
168
                    revision = FakeRevision(rev_id)
169
                    revno = "?"
0.1.1 by Dan Loda
First working version of xannotate.
170
171
            yield revision, revno, text
172
0.2.2 by Dan Loda
Add file's age as default span
173
    def _highlight_annotation(self, model, path, iter, now):
174
        revision_id, = model.get(iter, REVISION_ID_COL)
175
        revision = self.revisions[revision_id]
0.2.5 by Dan Loda
Use granny-like colors as default and rename ColorMap => AnnotateColorMap.
176
        model.set(iter, HIGHLIGHT_COLOR_COL,
0.1.19 by Aaron Bentley
Different colours for different committers
177
                  self.annotate_colormap.get_color(revision, now))
0.2.1 by Dan Loda
first go at emacs vc-annotate like highlighting
178
66.6.4 by Aaron Bentley
Add back button to see older versions
179
    def _selected_revision(self):
0.1.17 by Dan Loda
A little refactoring.
180
        (path, col) = self.annoview.get_cursor()
79 by Jelmer Vernooij
Handle empty files more gracefully. Fixes #58951.
181
        if path is None:
66.6.4 by Aaron Bentley
Add back button to see older versions
182
            return None
183
        return self.annomodel[path][REVISION_ID_COL]
184
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
185
    def _activate_selected_revision(self, w):
66.6.4 by Aaron Bentley
Add back button to see older versions
186
        rev_id = self._selected_revision()
187
        if rev_id is None:
79 by Jelmer Vernooij
Handle empty files more gracefully. Fixes #58951.
188
            return
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
189
        selected = self.revisions[rev_id]
190
        self.logview.set_revision(selected)
173.1.1 by Aaron Bentley
Better behavior when unable to go back
191
        if (len(selected.parent_ids) != 0 and selected.parent_ids[0] not in
192
            self._no_back):
193
            enable_back = True
194
        else:
195
            enable_back = False
196
        self.back_button.set_sensitive(enable_back)
0.1.1 by Dan Loda
First working version of xannotate.
197
198
    def _create(self):
0.1.17 by Dan Loda
A little refactoring.
199
        self.logview = self._create_log_view()
200
        self.annoview = self._create_annotate_view()
201
170.1.6 by Aaron Bentley
Move buttons to top, tweak layout
202
        vbox = gtk.VBox(False)
0.2.1 by Dan Loda
first go at emacs vc-annotate like highlighting
203
        vbox.show()
0.1.17 by Dan Loda
A little refactoring.
204
205
        sw = gtk.ScrolledWindow()
206
        sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
207
        sw.set_shadow_type(gtk.SHADOW_IN)
208
        sw.add(self.annoview)
59.2.2 by Aaron Bentley
Annotate launches a diff for the particular revision
209
        self.annoview.gwindow = self
0.1.17 by Dan Loda
A little refactoring.
210
        sw.show()
170.1.4 by Aaron Bentley
Move search fields directly below source window
211
212
        swbox = gtk.VBox()
213
        swbox.pack_start(sw)
214
        swbox.show()
170.1.6 by Aaron Bentley
Move buttons to top, tweak layout
215
216
        hbox = gtk.HBox(False, 6)
217
        self.back_button = self._create_back_button()
218
        hbox.pack_start(self.back_button, expand=False, fill=True)
219
        self.forward_button = self._create_forward_button()
220
        hbox.pack_start(self.forward_button, expand=False, fill=True)
221
        hbox.show()
222
        vbox.pack_start(hbox, expand=False, fill=True)
0.2.1 by Dan Loda
first go at emacs vc-annotate like highlighting
223
        
0.1.8 by Dan Loda
Remember window state. This introduces the gannotate.conf configuration file,
224
        self.pane = pane = gtk.VPaned()
170.1.4 by Aaron Bentley
Move search fields directly below source window
225
        pane.add1(swbox)
0.1.17 by Dan Loda
A little refactoring.
226
        pane.add2(self.logview)
0.1.1 by Dan Loda
First working version of xannotate.
227
        pane.show()
228
        vbox.pack_start(pane, expand=True, fill=True)
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
229
230
        self._search = SearchBox()
170.1.4 by Aaron Bentley
Move search fields directly below source window
231
        swbox.pack_start(self._search, expand=False, fill=True)
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
232
        accels = gtk.AccelGroup()
233
        accels.connect_group(gtk.keysyms.f, gtk.gdk.CONTROL_MASK,
234
                             gtk.ACCEL_LOCKED,
235
                             self._search_by_text)
236
        accels.connect_group(gtk.keysyms.g, gtk.gdk.CONTROL_MASK,
237
                             gtk.ACCEL_LOCKED,
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
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
243
    def _search_by_text(self, accel_group, window, key, modifiers):
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
247
    def _search_by_line(self, accel_group, window, key, modifiers):
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
59.2.2 by Aaron Bentley
Annotate launches a diff for the particular revision
251
    def row_diff(self, tv, path, tvc):
252
        row = path[0]
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
59.2.2 by Aaron Bentley
Annotate launches a diff for the particular revision
265
        window = DiffWindow()
66 by Aaron Bentley
Fix annotate diff window title
266
        window.set_diff("Diff for row %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):
0.1.17 by Dan Loda
A little refactoring.
272
        tv = gtk.TreeView()
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()
59.2.2 by Aaron Bentley
Annotate launches a diff for the particular revision
276
        tv.connect("row-activated", self.row_diff)
0.1.1 by Dan Loda
First working version of xannotate.
277
278
        cell = gtk.CellRendererText()
279
        cell.set_property("xalign", 1.0)
280
        cell.set_property("ypad", 0)
281
        cell.set_property("family", "Monospace")
282
        cell.set_property("cell-background-gdk",
0.1.17 by Dan Loda
A little refactoring.
283
                          tv.get_style().bg[gtk.STATE_NORMAL])
0.1.1 by Dan Loda
First working version of xannotate.
284
        col = gtk.TreeViewColumn()
285
        col.set_resizable(False)
286
        col.pack_start(cell, expand=True)
287
        col.add_attribute(cell, "text", LINE_NUM_COL)
0.1.17 by Dan Loda
A little refactoring.
288
        tv.append_column(col)
0.1.1 by Dan Loda
First working version of xannotate.
289
290
        cell = gtk.CellRendererText()
291
        cell.set_property("ypad", 0)
292
        cell.set_property("ellipsize", pango.ELLIPSIZE_END)
293
        cell.set_property("cell-background-gdk",
294
                          self.get_style().bg[gtk.STATE_NORMAL])
295
        col = gtk.TreeViewColumn("Committer")
296
        col.set_resizable(True)
297
        col.pack_start(cell, expand=True)
298
        col.add_attribute(cell, "text", COMMITTER_COL)
0.1.17 by Dan Loda
A little refactoring.
299
        tv.append_column(col)
0.1.1 by Dan Loda
First working version of xannotate.
300
301
        cell = gtk.CellRendererText()
302
        cell.set_property("xalign", 1.0)
303
        cell.set_property("ypad", 0)
304
        cell.set_property("cell-background-gdk",
305
                          self.get_style().bg[gtk.STATE_NORMAL])
306
        col = gtk.TreeViewColumn("Revno")
307
        col.set_resizable(False)
308
        col.pack_start(cell, expand=True)
309
        col.add_attribute(cell, "markup", REVNO_COL)
0.1.17 by Dan Loda
A little refactoring.
310
        tv.append_column(col)
0.1.1 by Dan Loda
First working version of xannotate.
311
312
        cell = gtk.CellRendererText()
313
        cell.set_property("ypad", 0)
314
        cell.set_property("family", "Monospace")
315
        col = gtk.TreeViewColumn()
316
        col.set_resizable(False)
317
        col.pack_start(cell, expand=True)
0.1.18 by Aaron Bentley
Switched to using pink backgrounds
318
#        col.add_attribute(cell, "foreground", HIGHLIGHT_COLOR_COL)
319
        col.add_attribute(cell, "background", HIGHLIGHT_COLOR_COL)
0.1.1 by Dan Loda
First working version of xannotate.
320
        col.add_attribute(cell, "text", TEXT_LINE_COL)
0.1.17 by Dan Loda
A little refactoring.
321
        tv.append_column(col)
0.1.1 by Dan Loda
First working version of xannotate.
322
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
323
        # FIXME: Now that C-f is now used for search by text we
324
        # may as well disable the auto search.
325
        tv.set_search_column(LINE_NUM_COL)
66.5.1 by v.ladeuil+lp at free
Minimal fix for bug #73965.
326
0.1.17 by Dan Loda
A little refactoring.
327
        return tv
0.1.1 by Dan Loda
First working version of xannotate.
328
329
    def _create_log_view(self):
0.1.17 by Dan Loda
A little refactoring.
330
        lv = LogView()
331
        lv.show()
332
        return lv
0.1.1 by Dan Loda
First working version of xannotate.
333
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
334
    def _create_back_button(self):
66.6.4 by Aaron Bentley
Add back button to see older versions
335
        button = gtk.Button()
336
        button.set_use_stock(True)
337
        button.set_label("gtk-go-back")
338
        button.connect("clicked", lambda w: self.go_back())
170.1.6 by Aaron Bentley
Move buttons to top, tweak layout
339
        button.set_relief(gtk.RELIEF_NONE)
66.6.4 by Aaron Bentley
Add back button to see older versions
340
        button.show()
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
341
        return button
342
343
    def _create_forward_button(self):
344
        button = gtk.Button()
345
        button.set_use_stock(True)
346
        button.set_label("gtk-go-forward")
347
        button.connect("clicked", lambda w: self.go_forward())
170.1.6 by Aaron Bentley
Move buttons to top, tweak layout
348
        button.set_relief(gtk.RELIEF_NONE)
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
349
        button.show()
350
        button.set_sensitive(False)
351
        return button
66.6.4 by Aaron Bentley
Add back button to see older versions
352
353
    def go_back(self):
173.1.1 by Aaron Bentley
Better behavior when unable to go back
354
        last_tree = self.tree
66.6.4 by Aaron Bentley
Add back button to see older versions
355
        rev_id = self._selected_revision()
356
        parent_id = self.revisions[rev_id].parent_ids[0]
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
357
        target_tree = self.branch.repository.revision_tree(parent_id)
173.1.1 by Aaron Bentley
Better behavior when unable to go back
358
        if self._go(target_tree):
359
            self.history.append(last_tree)
360
            self.forward_button.set_sensitive(True)
361
        else:
362
            self._no_back.add(parent_id)
363
            self.back_button.set_sensitive(False)
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
364
365
    def go_forward(self):
366
        if len(self.history) == 0:
367
            return
368
        target_tree = self.history.pop()
369
        if len(self.history) == 0:
370
            self.forward_button.set_sensitive(False)
371
        self._go(target_tree)
372
373
    def _go(self, target_tree):
374
        rev_id = self._selected_revision()
375
        if self.file_id in target_tree:
376
            offset = self.get_scroll_offset(target_tree)
66.6.6 by Aaron Bentley
Support scrolling based on an offset
377
            (row,), col = self.annoview.get_cursor()
170.1.5 by Aaron Bentley
Add 'forward' button, much button cleanup
378
            self.annotate(target_tree, self.branch, self.file_id)
379
            new_row = row+offset
380
            if new_row < 0:
381
                new_row = 0
382
            self.annoview.set_cursor(new_row)
173.1.1 by Aaron Bentley
Better behavior when unable to go back
383
            return True
384
        else:
385
            return False
66.6.6 by Aaron Bentley
Support scrolling based on an offset
386
387
    def get_scroll_offset(self, tree):
388
        old = self.tree.get_file(self.file_id)
389
        new = tree.get_file(self.file_id)
390
        (row,), col = self.annoview.get_cursor()
391
        matcher = patiencediff.PatienceSequenceMatcher(None, old.readlines(),
392
                                                       new.readlines())
393
        for i, j, n in matcher.get_matching_blocks():
394
            if i + n >= row:
395
                return j - i
396
275.1.5 by Daniel Schierbeck
Made windows close correctly on Ctrl-W and made the application quit on Ctrl-Q.
397
    def _on_key_pressed(self, widget, event):
398
        """ Key press event handler. """
399
        keyname = gtk.gdk.keyval_name(event.keyval)
400
        func = getattr(self, '_on_key_press_' + keyname, None)
401
        if func:
402
            return func(event)
403
404
    def _on_key_press_w(self, event):
405
        if event.state & gtk.gdk.CONTROL_MASK:
406
            self.destroy()
275.1.6 by Daniel Schierbeck
Made Ctrl-W call gtk.main_quit if the window has no parent.
407
            if self._parent is None:
408
                gtk.main_quit()
275.1.5 by Daniel Schierbeck
Made windows close correctly on Ctrl-W and made the application quit on Ctrl-Q.
409
410
    def _on_key_press_q(self, event):
411
        if event.state & gtk.gdk.CONTROL_MASK:
412
            gtk.main_quit()
413
    
414
66.6.4 by Aaron Bentley
Add back button to see older versions
415
0.1.1 by Dan Loda
First working version of xannotate.
416
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
417
class FakeRevision:
0.1.1 by Dan Loda
First working version of xannotate.
418
    """ A fake revision.
419
420
    For when a revision is referenced but not present.
421
    """
422
157.1.7 by Aaron Bentley
Fix branch-nick handling
423
    def __init__(self, revision_id, committer='?', nick=None):
0.1.1 by Dan Loda
First working version of xannotate.
424
        self.revision_id = revision_id
425
        self.parent_ids = []
66.2.14 by Aaron Bentley
Annotate showing uncommitted changes
426
        self.committer = committer
0.1.1 by Dan Loda
First working version of xannotate.
427
        self.message = "?"
428
        self.timestamp = 0.0
429
        self.timezone = 0
157.1.7 by Aaron Bentley
Fix branch-nick handling
430
        self.properties = {}
0.1.1 by Dan Loda
First working version of xannotate.
431
273 by Aaron Bentley
Use get_apparent_author, rename variables to 'author'
432
    def get_apparent_author(self):
433
        return self.committer
434
0.1.23 by Aaron Bentley
Added revision caching to initial annotation
435
436
class RevisionCache(object):
437
    """A caching revision source"""
66.6.5 by Aaron Bentley
Speed up the 'back' operation
438
    def __init__(self, real_source, seed_cache=None):
0.1.23 by Aaron Bentley
Added revision caching to initial annotation
439
        self.__real_source = real_source
66.6.5 by Aaron Bentley
Speed up the 'back' operation
440
        if seed_cache is None:
441
            self.__cache = {}
442
        else:
443
            self.__cache = dict(seed_cache)
0.1.23 by Aaron Bentley
Added revision caching to initial annotation
444
445
    def get_revision(self, revision_id):
446
        if revision_id not in self.__cache:
447
            revision = self.__real_source.get_revision(revision_id)
448
            self.__cache[revision_id] = revision
449
        return self.__cache[revision_id]
66.5.2 by v.ladeuil+lp at free
Better fix for bug #73965.
450
451
class SearchBox(gtk.HBox):
452
    """A button box for searching in text or lines of annotations"""
453
    def __init__(self):
454
        gtk.HBox.__init__(self, False, 6)
455
456
        # Close button
457
        button = gtk.Button()
458
        image = gtk.Image()
459
        image.set_from_stock('gtk-stop', gtk.ICON_SIZE_BUTTON)
460
        button.set_image(image)
461
        button.set_relief(gtk.RELIEF_NONE)
462
        button.connect("clicked", lambda w: self.hide_all())
463
        self.pack_start(button, expand=False, fill=False)
464
465
        # Search entry
466
        label = gtk.Label()
467
        self._label = label
468
        self.pack_start(label, expand=False, fill=False)
469
470
        entry = gtk.Entry()
471
        self._entry = entry
472
        entry.connect("activate", lambda w, d: self._do_search(d),
473
                      'forward')
474
        self.pack_start(entry, expand=False, fill=False)
475
476
        # Next/previous buttons
477
        button = gtk.Button('_Next')
478
        image = gtk.Image()
479
        image.set_from_stock('gtk-go-forward', gtk.ICON_SIZE_BUTTON)
480
        button.set_image(image)
481
        button.connect("clicked", lambda w, d: self._do_search(d),
482
                       'forward')
483
        self.pack_start(button, expand=False, fill=False)
484
485
        button = gtk.Button('_Previous')
486
        image = gtk.Image()
487
        image.set_from_stock('gtk-go-back', gtk.ICON_SIZE_BUTTON)
488
        button.set_image(image)
489
        button.connect("clicked", lambda w, d: self._do_search(d),
490
                       'backward')
491
        self.pack_start(button, expand=False, fill=False)
492
493
        # Search options
494
        check = gtk.CheckButton('Match case')
495
        self._match_case = check
496
        self.pack_start(check, expand=False, fill=False)
497
498
        check = gtk.CheckButton('Regexp')
499
        check.connect("toggled", lambda w: self._set_label())
500
        self._regexp = check
501
        self.pack_start(check, expand=False, fill=False)
502
503
        self._view = None
504
        self._column = None
505
        # Note that we stay hidden (we do not call self.show_all())
506
507
508
    def show_for(self, kind):
509
        self._kind = kind
510
        self.show_all()
511
        self._set_label()
512
        # Hide unrelated buttons
513
        if kind == 'line':
514
            self._match_case.hide()
515
            self._regexp.hide()
516
        # Be ready
517
        self._entry.grab_focus()
518
519
    def _set_label(self):
520
        if self._kind == 'line':
521
            self._label.set_text('Find Line: ')
522
        else:
523
            if self._regexp.get_active():
524
                self._label.set_text('Find Regexp: ')
525
            else:
526
                self._label.set_text('Find Text: ')
527
528
    def set_target(self, view,column):
529
        self._view = view
530
        self._column = column
531
532
    def _match(self, model, iterator, column):
533
        matching_case = self._match_case.get_active()
534
        string, = model.get(iterator, column)
535
        key = self._entry.get_text()
536
        if self._regexp.get_active():
537
            if matching_case:
538
                match = re.compile(key).search(string, 1)
539
            else:
540
                match = re.compile(key, re.I).search(string, 1)
541
        else:
542
            if not matching_case:
543
                string = string.lower()
544
                key = key.lower()
545
            match = string.find(key) != -1
546
547
        return match
548
549
    def _iterate_rows_forward(self, model, start):
550
        model_size = len(model)
551
        current = start + 1
552
        while model_size != 0:
553
            if current >= model_size: current =  0
554
            yield model.get_iter_from_string('%d' % current)
555
            if current == start: raise StopIteration
556
            current += 1
557
558
    def _iterate_rows_backward(self, model, start):
559
        model_size = len(model)
560
        current = start - 1
561
        while model_size != 0:
562
            if current < 0: current = model_size - 1
563
            yield model.get_iter_from_string('%d' % current)
564
            if current == start: raise StopIteration
565
            current -= 1
566
567
    def _do_search(self, direction):
568
        if direction == 'forward':
569
            iterate = self._iterate_rows_forward
570
        else:
571
            iterate = self._iterate_rows_backward
572
573
        model, sel = self._view.get_selection().get_selected()
574
        if sel is None:
575
            start = 0
576
        else:
577
            path = model.get_string_from_iter(sel)
578
            start = int(path)
579
580
        for row in iterate(model, start):
581
            if self._match(model, row, self._column):
582
                path = model.get_path(row)
583
                self._view.set_cursor(path)
584
                self._view.scroll_to_cell(path, use_align=True)
585
                break