/b-gtk/fix-viz

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/b-gtk/fix-viz

« back to all changes in this revision

Viewing changes to annotate/gannotate.py

  • Committer: Jelmer Vernooij
  • Date: 2006-09-30 10:21:43 UTC
  • Revision ID: jelmer@samba.org-20060930102143-c0ef64d6ca860c21
Merge some files from Olive and bzr-gtk.

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
import time
18
18
 
19
 
from gi.repository import GObject
20
 
from gi.repository import Gdk
21
 
from gi.repository import Gtk
22
 
from gi.repository import Pango
23
 
import re
 
19
import pygtk
 
20
pygtk.require("2.0")
 
21
import gobject
 
22
import gtk
 
23
import pango
24
24
 
25
 
from bzrlib import patiencediff
26
25
from bzrlib.errors import NoSuchRevision
27
 
from bzrlib.revision import NULL_REVISION, CURRENT_REVISION
 
26
from bzrlib.revision import NULL_REVISION
28
27
 
29
 
from bzrlib.plugins.gtk.annotate.colormap import AnnotateColorSaturation
30
 
from bzrlib.plugins.gtk.revisionview import RevisionView
31
 
from bzrlib.plugins.gtk.window import Window
 
28
from colormap import AnnotateColorMap, AnnotateColorSaturation
 
29
from logview import LogView
 
30
from spanselector import SpanSelector
32
31
 
33
32
 
34
33
(
41
40
) = range(6)
42
41
 
43
42
 
44
 
class GAnnotateWindow(Window):
 
43
class GAnnotateWindow(gtk.Window):
45
44
    """Annotate window."""
46
45
 
47
 
    def __init__(self, all=False, plain=False, parent=None, branch=None):
 
46
    def __init__(self, all=False, plain=False):
48
47
        self.all = all
49
48
        self.plain = plain
50
 
        self._branch = branch
51
 
 
52
 
        super(GAnnotateWindow, self).__init__(parent=parent)
53
 
 
54
 
        self.set_icon(
55
 
            self.render_icon_pixbuf(Gtk.STOCK_FIND, Gtk.IconSize.BUTTON))
 
49
        
 
50
        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
 
51
        
 
52
        self.set_icon(self.render_icon(gtk.STOCK_FIND, gtk.ICON_SIZE_BUTTON))
56
53
        self.annotate_colormap = AnnotateColorSaturation()
57
54
 
58
55
        self._create()
 
56
 
 
57
        if self.plain:
 
58
            self.span_selector.hide()
 
59
 
 
60
    def annotate(self, branch, file_id):
59
61
        self.revisions = {}
60
 
        self.history = []
61
 
        self._no_back = set()
62
 
 
63
 
    def annotate(self, tree, branch, file_id):
64
62
        self.annotations = []
65
63
        self.branch = branch
66
 
        self.tree = tree
67
64
        self.file_id = file_id
68
 
        self.revisionview.set_file_id(file_id)
69
 
        self.revision_id = getattr(tree, 'get_revision_id', 
70
 
                                   lambda: CURRENT_REVISION)()
71
 
 
72
 
        # [revision id, line number, author, revno, highlight color, line]
73
 
        self.annomodel = Gtk.ListStore(GObject.TYPE_STRING,
74
 
                                       GObject.TYPE_INT,
75
 
                                       GObject.TYPE_STRING,
76
 
                                       GObject.TYPE_STRING,
77
 
                                       GObject.TYPE_STRING,
78
 
                                       GObject.TYPE_STRING)
79
 
 
 
65
        
 
66
        # [revision id, line number, committer, revno, highlight color, line]
 
67
        self.annomodel = gtk.ListStore(gobject.TYPE_STRING,
 
68
                                       gobject.TYPE_STRING,
 
69
                                       gobject.TYPE_STRING,
 
70
                                       gobject.TYPE_STRING,
 
71
                                       gobject.TYPE_STRING,
 
72
                                       gobject.TYPE_STRING)
 
73
        
80
74
        last_seen = None
81
75
        try:
82
76
            branch.lock_read()
83
77
            branch.repository.lock_read()
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)
88
78
            for line_no, (revision, revno, line)\
89
 
                in enumerate(self._annotate(tree, file_id)):
 
79
                    in enumerate(self._annotate(branch, file_id)):
90
80
                if revision.revision_id == last_seen and not self.all:
91
 
                    revno = author = ""
 
81
                    revno = committer = ""
92
82
                else:
93
83
                    last_seen = revision.revision_id
94
 
                    author = ", ".join(revision.get_apparent_authors())
 
84
                    committer = revision.committer
95
85
 
96
86
                if revision.revision_id not in self.revisions:
97
87
                    self.revisions[revision.revision_id] = revision
98
88
 
99
89
                self.annomodel.append([revision.revision_id,
100
90
                                       line_no + 1,
101
 
                                       author,
 
91
                                       committer,
102
92
                                       revno,
103
93
                                       None,
104
94
                                       line.rstrip("\r\n")
105
 
                                       ])
 
95
                                      ])
106
96
                self.annotations.append(revision)
107
97
 
108
98
            if not self.plain:
109
 
                now = time.time()
110
 
                self.annomodel.foreach(self._highlight_annotation, now)
 
99
                self._set_oldest_newest()
 
100
                # Recall that calling activate_default will emit "span-changed",
 
101
                # so self._span_changed_cb will take care of initial highlighting
 
102
                self.span_selector.activate_default()
111
103
        finally:
112
104
            branch.repository.unlock()
113
105
            branch.unlock()
114
106
 
115
107
        self.annoview.set_model(self.annomodel)
116
108
        self.annoview.grab_focus()
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)
120
109
 
121
110
    def jump_to_line(self, lineno):
122
111
        if lineno > len(self.annomodel) or lineno < 1:
125
114
            # bar?
126
115
            print("gannotate: Line number %d does't exist. Defaulting to "
127
116
                  "line 1." % lineno)
128
 
            return
 
117
            return
129
118
        else:
130
119
            row = lineno - 1
131
120
 
132
 
        tree_path = Gtk.TreePath(path=row)
133
 
        self.annoview.set_cursor(tree_path, None, False)
134
 
        self.annoview.scroll_to_cell(tree_path, use_align=True)
 
121
        self.annoview.set_cursor(row)
 
122
        self.annoview.scroll_to_cell(row, use_align=True)
135
123
 
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]'
141
 
        current_revision.parent_ids = tree.get_parent_ids()
142
 
        current_revision.properties['branch-nick'] = self.branch._get_nick(local=True)
143
 
        current_revno = '%d?' % (self.branch.revno() + 1)
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
149
 
        revision_cache = RevisionCache(repository, self.revisions)
150
 
        for origin, text in tree.annotate_iter(file_id):
 
124
    def _annotate(self, branch, file_id):
 
125
        rev_hist = branch.revision_history()
 
126
        repository = branch.repository
 
127
        rev_tree = repository.revision_tree(branch.last_revision())
 
128
        rev_id = rev_tree.inventory[file_id].revision
 
129
        weave = repository.weave_store.get_weave(file_id,
 
130
                                                 branch.get_transaction())
 
131
        
 
132
        revision_cache = RevisionCache(repository)
 
133
        for origin, text in weave.annotate_iter(rev_id):
151
134
            rev_id = origin
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)
158
 
                    revno = self.dotted.get(rev_id, 'merge')
159
 
                    if len(revno) > 15:
160
 
                        revno = 'merge'
161
 
                except NoSuchRevision:
162
 
                    revision = FakeRevision(rev_id)
163
 
                    revno = "?"
 
135
            try:
 
136
                revision = revision_cache.get_revision(rev_id)
 
137
                if rev_id in rev_hist:
 
138
                    revno = branch.revision_id_to_revno(rev_id)
 
139
                else:
 
140
                    revno = "merge"
 
141
            except NoSuchRevision:
 
142
                revision = NoneRevision(rev_id)
 
143
                revno = "?"
164
144
 
165
145
            yield revision, revno, text
166
146
 
 
147
    def _set_oldest_newest(self):
 
148
        rev_dates = map(lambda i: self.revisions[i].timestamp, self.revisions)
 
149
        if len(rev_dates) == 0:
 
150
            return
 
151
        oldest = min(rev_dates)
 
152
        newest = max(rev_dates)
 
153
 
 
154
        span = self._span_from_seconds(time.time() - oldest)
 
155
        self.span_selector.set_to_oldest_span(span)
 
156
        
 
157
        span = self._span_from_seconds(newest - oldest)
 
158
        self.span_selector.set_newest_to_oldest_span(span)
 
159
 
 
160
    def _span_from_seconds(self, seconds):
 
161
        return (seconds / (24 * 60 * 60))
 
162
    
 
163
    def _span_changed_cb(self, w, span):
 
164
        self.annotate_colormap.set_span(span)
 
165
        now = time.time()
 
166
        self.annomodel.foreach(self._highlight_annotation, now)
 
167
 
167
168
    def _highlight_annotation(self, model, path, iter, now):
168
169
        revision_id, = model.get(iter, REVISION_ID_COL)
169
170
        revision = self.revisions[revision_id]
170
 
        # XXX sinzui 2011-08-12: What does get_color return?
171
 
        color = self.annotate_colormap.get_color(revision, now)
172
 
        model.set_value(iter, HIGHLIGHT_COLOR_COL, color)
 
171
        model.set(iter, HIGHLIGHT_COLOR_COL,
 
172
                  self.annotate_colormap.get_color(revision, now))
173
173
 
174
 
    def _selected_revision(self):
 
174
    def _show_log(self, w):
175
175
        (path, col) = self.annoview.get_cursor()
176
176
        if path is None:
177
 
            return None
178
 
        return self.annomodel[path][REVISION_ID_COL]
179
 
 
180
 
    def _activate_selected_revision(self, w):
181
 
        rev_id = self._selected_revision()
182
 
        if not rev_id or rev_id == NULL_REVISION:
183
177
            return
184
 
        selected = self.revisions[rev_id]
185
 
        self.revisionview.set_revision(selected)
186
 
        if (len(selected.parent_ids) != 0 and selected.parent_ids[0] not in
187
 
            self._no_back):
188
 
            enable_back = True
189
 
        else:
190
 
            enable_back = False
191
 
        self.back_button.set_sensitive(enable_back)
 
178
        rev_id = self.annomodel[path][REVISION_ID_COL]
 
179
        self.logview.set_revision(self.revisions[rev_id])
192
180
 
193
181
    def _create(self):
194
 
        self.revisionview = self._create_log_view()
 
182
        self.logview = self._create_log_view()
195
183
        self.annoview = self._create_annotate_view()
 
184
        self.span_selector = self._create_span_selector()
196
185
 
197
 
        vbox = Gtk.VBox(homogeneous=False, spacing=0)
 
186
        vbox = gtk.VBox(False, 12)
 
187
        vbox.set_border_width(12)
198
188
        vbox.show()
199
189
 
200
 
        sw = Gtk.ScrolledWindow()
201
 
        sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
202
 
        sw.set_shadow_type(Gtk.ShadowType.IN)
 
190
        sw = gtk.ScrolledWindow()
 
191
        sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 
192
        sw.set_shadow_type(gtk.SHADOW_IN)
203
193
        sw.add(self.annoview)
204
194
        self.annoview.gwindow = self
205
195
        sw.show()
206
 
 
207
 
        swbox = Gtk.VBox()
208
 
        swbox.pack_start(sw, True, True, 0)
209
 
        swbox.show()
210
 
 
211
 
        hbox = Gtk.HBox(homogeneous=False, spacing=6)
212
 
        self.back_button = self._create_back_button()
213
 
        hbox.pack_start(self.back_button, False, True, 0)
214
 
        self.forward_button = self._create_forward_button()
215
 
        hbox.pack_start(self.forward_button, False, True, 0)
216
 
        self.find_button = self._create_find_button()
217
 
        hbox.pack_start(self.find_button, False, True, 0)
218
 
        self.goto_button = self._create_goto_button()
219
 
        hbox.pack_start(self.goto_button, False, True, 0)
 
196
        
 
197
        self.pane = pane = gtk.VPaned()
 
198
        pane.add1(sw)
 
199
        pane.add2(self.logview)
 
200
        pane.show()
 
201
        vbox.pack_start(pane, expand=True, fill=True)
 
202
        
 
203
        hbox = gtk.HBox(True, 6)
 
204
        hbox.pack_start(self.span_selector, expand=False, fill=True)
 
205
        hbox.pack_start(self._create_button_box(), expand=False, fill=True)
220
206
        hbox.show()
221
 
        vbox.pack_start(hbox, False, True, 0)
222
 
 
223
 
        self.pane = pane = Gtk.VPaned()
224
 
        pane.add1(swbox)
225
 
        pane.add2(self.revisionview)
226
 
        pane.show()
227
 
        vbox.pack_start(pane, True, True, 0)
228
 
 
229
 
        self._search = SearchBox()
230
 
        swbox.pack_start(self._search, False, True, 0)
231
 
        accels = Gtk.AccelGroup()
232
 
        accels.connect(Gdk.KEY_f, Gdk.ModifierType.CONTROL_MASK,
233
 
                             Gtk.AccelFlags.LOCKED,
234
 
                             self._search_by_text)
235
 
        accels.connect(Gdk.KEY_g, Gdk.ModifierType.CONTROL_MASK,
236
 
                             Gtk.AccelFlags.LOCKED,
237
 
                             self._search_by_line)
238
 
        self.add_accel_group(accels)
 
207
        vbox.pack_start(hbox, expand=False, fill=True)
239
208
 
240
209
        self.add(vbox)
241
210
 
242
 
    def _search_by_text(self, *ignored): # (accel_group, window, key, modifiers):
243
 
        self._search.show_for('text')
244
 
        self._search.set_target(self.annoview, TEXT_LINE_COL)
245
 
 
246
 
    def _search_by_line(self, *ignored): # accel_group, window, key, modifiers):
247
 
        self._search.show_for('line')
248
 
        self._search.set_target(self.annoview, LINE_NUM_COL)
249
 
 
250
 
    def line_diff(self, tv, path, tvc):
251
 
        row = path.get_indices()[0]
 
211
    def row_diff(self, tv, path, tvc):
 
212
        row = path[0]
252
213
        revision = self.annotations[row]
253
214
        repository = self.branch.repository
254
 
        if revision.revision_id == CURRENT_REVISION:
255
 
            tree1 = self.tree
256
 
            tree2 = self.tree.basis_tree()
 
215
        tree1 = repository.revision_tree(revision.revision_id)
 
216
        if len(revision.parent_ids) > 0:
 
217
            tree2 = repository.revision_tree(revision.parent_ids[0])
257
218
        else:
258
 
            tree1 = repository.revision_tree(revision.revision_id)
259
 
            if len(revision.parent_ids) > 0:
260
 
                tree2 = repository.revision_tree(revision.parent_ids[0])
261
 
            else:
262
 
                tree2 = repository.revision_tree(NULL_REVISION)
263
 
        from bzrlib.plugins.gtk.diff import DiffWindow
264
 
        window = DiffWindow(self)
265
 
        window.set_diff("Diff for line %d" % (row+1), tree1, tree2)
 
219
            tree2 = repository.revision_tree(NULL_REVISION)
 
220
        from bzrlib.plugins.gtk.viz.diffwin import DiffWindow
 
221
        window = DiffWindow()
 
222
        window.set_diff("Diff for row %d" % (row+1), tree1, tree2)
266
223
        window.set_file(tree1.id2path(self.file_id))
267
224
        window.show()
268
225
 
269
226
 
270
227
    def _create_annotate_view(self):
271
 
        tv = Gtk.TreeView()
 
228
        tv = gtk.TreeView()
272
229
        tv.set_rules_hint(False)
273
 
        tv.connect("cursor-changed", self._activate_selected_revision)
 
230
        tv.connect("cursor-changed", self._show_log)
274
231
        tv.show()
275
 
        tv.connect("row-activated", self.line_diff)
 
232
        tv.connect("row-activated", self.row_diff)
276
233
 
277
 
        cell = Gtk.CellRendererText()
 
234
        cell = gtk.CellRendererText()
278
235
        cell.set_property("xalign", 1.0)
279
236
        cell.set_property("ypad", 0)
280
237
        cell.set_property("family", "Monospace")
281
238
        cell.set_property("cell-background-gdk",
282
 
                          tv.get_style().bg[Gtk.StateType.NORMAL])
283
 
        col = Gtk.TreeViewColumn()
 
239
                          tv.get_style().bg[gtk.STATE_NORMAL])
 
240
        col = gtk.TreeViewColumn()
284
241
        col.set_resizable(False)
285
 
        col.pack_start(cell, True)
 
242
        col.pack_start(cell, expand=True)
286
243
        col.add_attribute(cell, "text", LINE_NUM_COL)
287
244
        tv.append_column(col)
288
245
 
289
 
        cell = Gtk.CellRendererText()
 
246
        cell = gtk.CellRendererText()
290
247
        cell.set_property("ypad", 0)
291
 
        cell.set_property("ellipsize", Pango.EllipsizeMode.END)
 
248
        cell.set_property("ellipsize", pango.ELLIPSIZE_END)
292
249
        cell.set_property("cell-background-gdk",
293
 
                          self.get_style().bg[Gtk.StateType.NORMAL])
294
 
        col = Gtk.TreeViewColumn("Committer")
 
250
                          self.get_style().bg[gtk.STATE_NORMAL])
 
251
        col = gtk.TreeViewColumn("Committer")
295
252
        col.set_resizable(True)
296
 
        col.pack_start(cell, True)
 
253
        col.pack_start(cell, expand=True)
297
254
        col.add_attribute(cell, "text", COMMITTER_COL)
298
255
        tv.append_column(col)
299
256
 
300
 
        cell = Gtk.CellRendererText()
 
257
        cell = gtk.CellRendererText()
301
258
        cell.set_property("xalign", 1.0)
302
259
        cell.set_property("ypad", 0)
303
260
        cell.set_property("cell-background-gdk",
304
 
                          self.get_style().bg[Gtk.StateType.NORMAL])
305
 
        col = Gtk.TreeViewColumn("Revno")
 
261
                          self.get_style().bg[gtk.STATE_NORMAL])
 
262
        col = gtk.TreeViewColumn("Revno")
306
263
        col.set_resizable(False)
307
 
        col.pack_start(cell, True)
 
264
        col.pack_start(cell, expand=True)
308
265
        col.add_attribute(cell, "markup", REVNO_COL)
309
266
        tv.append_column(col)
310
267
 
311
 
        cell = Gtk.CellRendererText()
 
268
        cell = gtk.CellRendererText()
312
269
        cell.set_property("ypad", 0)
313
270
        cell.set_property("family", "Monospace")
314
 
        col = Gtk.TreeViewColumn()
 
271
        col = gtk.TreeViewColumn()
315
272
        col.set_resizable(False)
316
 
        col.pack_start(cell, True)
 
273
        col.pack_start(cell, expand=True)
317
274
#        col.add_attribute(cell, "foreground", HIGHLIGHT_COLOR_COL)
318
275
        col.add_attribute(cell, "background", HIGHLIGHT_COLOR_COL)
319
276
        col.add_attribute(cell, "text", TEXT_LINE_COL)
320
277
        tv.append_column(col)
321
278
 
322
 
        # interactive substring search
323
 
        def search_equal_func(model, column, key, iter):
324
 
            return model.get_value(iter, TEXT_LINE_COL).lower().find(key.lower()) == -1
325
 
 
326
 
        tv.set_enable_search(True)
327
 
        tv.set_search_equal_func(search_equal_func, None)
328
 
 
 
279
        tv.set_search_column(LINE_NUM_COL)
 
280
        
329
281
        return tv
330
282
 
 
283
    def _create_span_selector(self):
 
284
        ss = SpanSelector()
 
285
        ss.connect("span-changed", self._span_changed_cb)
 
286
        ss.show()
 
287
 
 
288
        return ss
 
289
 
331
290
    def _create_log_view(self):
332
 
        lv = RevisionView(self._branch)
 
291
        lv = LogView()
333
292
        lv.show()
 
293
 
334
294
        return lv
335
295
 
336
 
    def _create_back_button(self):
337
 
        button = Gtk.Button()
338
 
        button.set_use_stock(True)
339
 
        button.set_label("gtk-go-back")
340
 
        button.connect("clicked", lambda w: self.go_back())
341
 
        button.set_relief(Gtk.ReliefStyle.NONE)
342
 
        button.show()
343
 
        return button
344
 
 
345
 
    def _create_forward_button(self):
346
 
        button = Gtk.Button()
347
 
        button.set_use_stock(True)
348
 
        button.set_label("gtk-go-forward")
349
 
        button.connect("clicked", lambda w: self.go_forward())
350
 
        button.set_relief(Gtk.ReliefStyle.NONE)
351
 
        button.show()
352
 
        button.set_sensitive(False)
353
 
        return button
354
 
 
355
 
    def _create_find_button(self):
356
 
        button = Gtk.Button()
357
 
        button.set_use_stock(True)
358
 
        button.set_label("gtk-find")
359
 
        button.set_tooltip_text("Search for text (Ctrl+F)")
360
 
        button.connect("clicked", self._search_by_text)
361
 
        button.set_relief(Gtk.ReliefStyle.NONE)
362
 
        button.show()
363
 
        button.set_sensitive(True)
364
 
        return button
365
 
 
366
 
    def _create_goto_button(self):
367
 
        button = Gtk.Button()
368
 
        button.set_label("Goto Line")
369
 
        button.set_tooltip_text("Scroll to a line by entering its number (Ctrl+G)")
370
 
        button.connect("clicked", self._search_by_line)
371
 
        button.set_relief(Gtk.ReliefStyle.NONE)
372
 
        button.show()
373
 
        button.set_sensitive(True)
374
 
        return button
375
 
 
376
 
    def go_back(self):
377
 
        last_tree = self.tree
378
 
        rev_id = self._selected_revision()
379
 
        parent_id = self.revisions[rev_id].parent_ids[0]
380
 
        target_tree = self.branch.repository.revision_tree(parent_id)
381
 
        if self._go(target_tree):
382
 
            self.history.append(last_tree)
383
 
            self.forward_button.set_sensitive(True)
384
 
        else:
385
 
            self._no_back.add(parent_id)
386
 
            self.back_button.set_sensitive(False)
387
 
 
388
 
    def go_forward(self):
389
 
        if len(self.history) == 0:
390
 
            return
391
 
        target_tree = self.history.pop()
392
 
        if len(self.history) == 0:
393
 
            self.forward_button.set_sensitive(False)
394
 
        self._go(target_tree)
395
 
 
396
 
    def _go(self, target_tree):
397
 
        rev_id = self._selected_revision()
398
 
        if self.file_id in target_tree:
399
 
            offset = self.get_scroll_offset(target_tree)
400
 
            path, col = self.annoview.get_cursor()
401
 
            (row,) = path.get_indices()
402
 
            self.annotate(target_tree, self.branch, self.file_id)
403
 
            new_row = row+offset
404
 
            if new_row < 0:
405
 
                new_row = 0
406
 
            new_path = Gtk.TreePath(path=new_row)
407
 
            self.annoview.set_cursor(new_path, None, False)
408
 
            return True
409
 
        else:
410
 
            return False
411
 
 
412
 
    def get_scroll_offset(self, tree):
413
 
        old = self.tree.get_file(self.file_id)
414
 
        new = tree.get_file(self.file_id)
415
 
        path, col = self.annoview.get_cursor()
416
 
        (row,) = path.get_indices()
417
 
        matcher = patiencediff.PatienceSequenceMatcher(None, old.readlines(),
418
 
                                                       new.readlines())
419
 
        for i, j, n in matcher.get_matching_blocks():
420
 
            if i + n >= row:
421
 
                return j - i
422
 
 
423
 
 
424
 
class FakeRevision(object):
 
296
    def _create_button_box(self):
 
297
        box = gtk.HButtonBox()
 
298
        box.set_layout(gtk.BUTTONBOX_END)
 
299
        box.show()
 
300
        
 
301
        button = gtk.Button()
 
302
        button.set_use_stock(True)
 
303
        button.set_label("gtk-close")
 
304
        button.connect("clicked", lambda w: self.destroy())
 
305
        button.show()
 
306
        
 
307
        box.pack_start(button, expand=False, fill=False)
 
308
 
 
309
        return box
 
310
 
 
311
 
 
312
class NoneRevision:
425
313
    """ A fake revision.
426
314
 
427
315
    For when a revision is referenced but not present.
428
316
    """
429
317
 
430
 
    def __init__(self, revision_id, committer='?', nick=None):
 
318
    def __init__(self, revision_id):
431
319
        self.revision_id = revision_id
432
320
        self.parent_ids = []
433
 
        self.committer = committer
 
321
        self.committer = "?"
434
322
        self.message = "?"
435
323
        self.timestamp = 0.0
436
324
        self.timezone = 0
437
 
        self.properties = {}
438
 
 
439
 
    def get_apparent_authors(self):
440
 
        return [self.committer]
441
325
 
442
326
 
443
327
class RevisionCache(object):
444
328
    """A caching revision source"""
445
 
 
446
 
    def __init__(self, real_source, seed_cache=None):
 
329
    def __init__(self, real_source):
447
330
        self.__real_source = real_source
448
 
        if seed_cache is None:
449
 
            self.__cache = {}
450
 
        else:
451
 
            self.__cache = dict(seed_cache)
 
331
        self.__cache = {}
452
332
 
453
333
    def get_revision(self, revision_id):
454
334
        if revision_id not in self.__cache:
455
335
            revision = self.__real_source.get_revision(revision_id)
456
336
            self.__cache[revision_id] = revision
457
337
        return self.__cache[revision_id]
458
 
 
459
 
 
460
 
class SearchBox(Gtk.HBox):
461
 
    """A button box for searching in text or lines of annotations"""
462
 
    def __init__(self):
463
 
        super(SearchBox, self).__init__(homogeneous=False, spacing=6)
464
 
 
465
 
        # Close button
466
 
        button = Gtk.Button()
467
 
        image = Gtk.Image()
468
 
        image.set_from_stock('gtk-stop', Gtk.IconSize.BUTTON)
469
 
        button.set_image(image)
470
 
        button.set_relief(Gtk.ReliefStyle.NONE)
471
 
        button.connect("clicked", lambda w: self.hide())
472
 
        self.pack_start(button, False, False, 0)
473
 
 
474
 
        # Search entry
475
 
        label = Gtk.Label()
476
 
        self._label = label
477
 
        self.pack_start(label, False, False, 0)
478
 
 
479
 
        entry = Gtk.Entry()
480
 
        self._entry = entry
481
 
        entry.connect("activate", lambda w, d: self._do_search(d),
482
 
                      'forward')
483
 
        self.pack_start(entry, False, False, 0)
484
 
 
485
 
        # Next/previous buttons
486
 
        button = Gtk.Button('_Next')
487
 
        image = Gtk.Image()
488
 
        image.set_from_stock('gtk-go-forward', Gtk.IconSize.BUTTON)
489
 
        button.set_image(image)
490
 
        button.connect("clicked", lambda w, d: self._do_search(d),
491
 
                       'forward')
492
 
        self.pack_start(button, False, False, 0)
493
 
 
494
 
        button = Gtk.Button('_Previous')
495
 
        image = Gtk.Image()
496
 
        image.set_from_stock('gtk-go-back', Gtk.IconSize.BUTTON)
497
 
        button.set_image(image)
498
 
        button.connect("clicked", lambda w, d: self._do_search(d),
499
 
                       'backward')
500
 
        self.pack_start(button, False, False, 0)
501
 
 
502
 
        # Search options
503
 
        check = Gtk.CheckButton('Match case')
504
 
        self._match_case = check
505
 
        self.pack_start(check, False, False, 0)
506
 
 
507
 
        check = Gtk.CheckButton('Regexp')
508
 
        check.connect("toggled", lambda w: self._set_label())
509
 
        self._regexp = check
510
 
        self.pack_start(check, False, False, 0)
511
 
 
512
 
        self._view = None
513
 
        self._column = None
514
 
        # Note that we stay hidden (we do not call self.show_all())
515
 
 
516
 
 
517
 
    def show_for(self, kind):
518
 
        self._kind = kind
519
 
        self.show_all()
520
 
        self._set_label()
521
 
        # Hide unrelated buttons
522
 
        if kind == 'line':
523
 
            self._match_case.hide()
524
 
            self._regexp.hide()
525
 
        # Be ready
526
 
        self._entry.grab_focus()
527
 
 
528
 
    def _set_label(self):
529
 
        if self._kind == 'line':
530
 
            self._label.set_text('Find Line: ')
531
 
        else:
532
 
            if self._regexp.get_active():
533
 
                self._label.set_text('Find Regexp: ')
534
 
            else:
535
 
                self._label.set_text('Find Text: ')
536
 
 
537
 
    def set_target(self, view,column):
538
 
        self._view = view
539
 
        self._column = column
540
 
 
541
 
    def _match(self, model, iterator, column):
542
 
        matching_case = self._match_case.get_active()
543
 
        cell_value, = model.get(iterator, column)
544
 
        key = self._entry.get_text()
545
 
        if column == LINE_NUM_COL:
546
 
            # FIXME: For goto-line there are faster algorithms than searching 
547
 
            # every line til we find the right one! -- mbp 2011-01-27
548
 
            return key.strip() == str(cell_value)
549
 
        elif self._regexp.get_active():
550
 
            if matching_case:
551
 
                match = re.compile(key).search(cell_value, 1)
552
 
            else:
553
 
                match = re.compile(key, re.I).search(cell_value, 1)
554
 
        else:
555
 
            if not matching_case:
556
 
                cell_value = cell_value.lower()
557
 
                key = key.lower()
558
 
            match = cell_value.find(key) != -1
559
 
 
560
 
        return match
561
 
 
562
 
    def _iterate_rows_forward(self, model, start):
563
 
        model_size = len(model)
564
 
        current = start + 1
565
 
        while model_size != 0:
566
 
            if current >= model_size: current =  0
567
 
            yield model.get_iter_from_string('%d' % current)
568
 
            if current == start: raise StopIteration
569
 
            current += 1
570
 
 
571
 
    def _iterate_rows_backward(self, model, start):
572
 
        model_size = len(model)
573
 
        current = start - 1
574
 
        while model_size != 0:
575
 
            if current < 0: current = model_size - 1
576
 
            yield model.get_iter_from_string('%d' % current)
577
 
            if current == start: raise StopIteration
578
 
            current -= 1
579
 
 
580
 
    def _do_search(self, direction):
581
 
        if direction == 'forward':
582
 
            iterate = self._iterate_rows_forward
583
 
        else:
584
 
            iterate = self._iterate_rows_backward
585
 
 
586
 
        model, sel = self._view.get_selection().get_selected()
587
 
        if sel is None:
588
 
            start = 0
589
 
        else:
590
 
            path = model.get_string_from_iter(sel)
591
 
            start = int(path)
592
 
 
593
 
        for row in iterate(model, start):
594
 
            if self._match(model, row, self._column):
595
 
                path = model.get_path(row)
596
 
                self._view.set_cursor(path, None, False)
597
 
                self._view.scroll_to_cell(path, use_align=True)
598
 
                break