/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: 2012-07-09 15:23:26 UTC
  • mto: This revision was merged to the branch mainline in revision 794.
  • Revision ID: jelmer@samba.org-20120709152326-dzxb8zoz0btull7n
Remove bzr-notify.

Show diffs side-by-side

added added

removed removed

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