66
63
        self.branch = branch
 
68
65
        self.file_id = file_id
 
69
 
        self.revisionview.set_file_id(file_id)
 
70
66
        self.revision_id = getattr(tree, 'get_revision_id', 
 
71
67
                                   lambda: CURRENT_REVISION)()
 
73
 
        # [revision id, line number, author, revno, highlight color, line]
 
74
 
        self.annomodel = Gtk.ListStore(GObject.TYPE_STRING,
 
 
69
        # [revision id, line number, committer, revno, highlight color, line]
 
 
70
        self.annomodel = gtk.ListStore(gobject.TYPE_STRING,
 
84
80
            branch.repository.lock_read()
 
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)
 
89
81
            for line_no, (revision, revno, line)\
 
90
 
                in enumerate(self._annotate(tree, file_id)):
 
 
82
                    in enumerate(self._annotate(tree, file_id)):
 
91
83
                if revision.revision_id == last_seen and not self.all:
 
 
84
                    revno = committer = ""
 
94
86
                    last_seen = revision.revision_id
 
95
 
                    author = ", ".join(revision.get_apparent_authors())
 
 
87
                    committer = revision.committer
 
97
89
                if revision.revision_id not in self.revisions:
 
98
90
                    self.revisions[revision.revision_id] = revision
 
100
92
                self.annomodel.append([revision.revision_id,
 
105
97
                                       line.rstrip("\r\n")
 
107
99
                self.annotations.append(revision)
 
109
101
            if not self.plain:
 
 
127
116
            print("gannotate: Line number %d does't exist. Defaulting to "
 
128
117
                  "line 1." % lineno)
 
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)
 
 
122
        self.annoview.set_cursor(row)
 
 
123
        self.annoview.scroll_to_cell(row, use_align=True)
 
 
125
    def _dotted_revnos(self, repository, revision_id):
 
 
126
        """Return a dict of revision_id -> dotted revno
 
 
128
        :param repository: The repository to get the graph from
 
 
129
        :param revision_id: The last revision for which this info is needed
 
 
131
        graph = repository.get_revision_graph(revision_id)
 
 
133
        for n, revision_id, d, revno, e in tsort.merge_sort(graph, 
 
 
134
            revision_id, generate_revno=True):
 
 
135
            dotted[revision_id] = '.'.join(str(num) for num in revno)
 
137
138
    def _annotate(self, tree, file_id):
 
138
139
        current_revision = FakeRevision(CURRENT_REVISION)
 
 
192
193
        self.back_button.set_sensitive(enable_back)
 
194
195
    def _create(self):
 
195
 
        self.revisionview = self._create_log_view()
 
 
196
        self.logview = self._create_log_view()
 
196
197
        self.annoview = self._create_annotate_view()
 
198
 
        vbox = Gtk.VBox(homogeneous=False, spacing=0)
 
 
199
        vbox = gtk.VBox(False)
 
201
 
        sw = Gtk.ScrolledWindow()
 
202
 
        sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
 
203
 
        sw.set_shadow_type(Gtk.ShadowType.IN)
 
 
202
        sw = gtk.ScrolledWindow()
 
 
203
        sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 
 
204
        sw.set_shadow_type(gtk.SHADOW_IN)
 
204
205
        sw.add(self.annoview)
 
205
206
        self.annoview.gwindow = self
 
209
 
        swbox.pack_start(sw, True, True, 0)
 
212
 
        hbox = Gtk.HBox(homogeneous=False, spacing=6)
 
 
213
        hbox = gtk.HBox(False, 6)
 
213
214
        self.back_button = self._create_back_button()
 
214
 
        hbox.pack_start(self.back_button, False, True, 0)
 
 
215
        hbox.pack_start(self.back_button, expand=False, fill=True)
 
215
216
        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)
 
 
217
        hbox.pack_start(self.forward_button, expand=False, fill=True)
 
222
 
        vbox.pack_start(hbox, False, True, 0)
 
224
 
        self.pane = pane = Gtk.Paned.new(Gtk.Orientation.VERTICAL)
 
 
219
        vbox.pack_start(hbox, expand=False, fill=True)
 
 
221
        self.pane = pane = gtk.VPaned()
 
226
 
        pane.add2(self.revisionview)
 
 
223
        pane.add2(self.logview)
 
228
 
        vbox.pack_start(pane, True, True, 0)
 
 
225
        vbox.pack_start(pane, expand=True, fill=True)
 
230
227
        self._search = SearchBox()
 
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,
 
 
228
        swbox.pack_start(self._search, expand=False, fill=True)
 
 
229
        accels = gtk.AccelGroup()
 
 
230
        accels.connect_group(gtk.keysyms.f, gtk.gdk.CONTROL_MASK,
 
235
232
                             self._search_by_text)
 
236
 
        accels.connect(Gdk.KEY_g, Gdk.ModifierType.CONTROL_MASK,
 
237
 
                             Gtk.AccelFlags.LOCKED,
 
 
233
        accels.connect_group(gtk.keysyms.g, gtk.gdk.CONTROL_MASK,
 
238
235
                             self._search_by_line)
 
239
236
        self.add_accel_group(accels)
 
243
 
    def _search_by_text(self, *ignored): # (accel_group, window, key, modifiers):
 
 
240
    def _search_by_text(self, accel_group, window, key, modifiers):
 
244
241
        self._search.show_for('text')
 
245
242
        self._search.set_target(self.annoview, TEXT_LINE_COL)
 
247
 
    def _search_by_line(self, *ignored): # accel_group, window, key, modifiers):
 
 
244
    def _search_by_line(self, accel_group, window, key, modifiers):
 
248
245
        self._search.show_for('line')
 
249
246
        self._search.set_target(self.annoview, LINE_NUM_COL)
 
251
 
    def line_diff(self, tv, path, tvc):
 
252
 
        row = path.get_indices()[0]
 
 
248
    def row_diff(self, tv, path, tvc):
 
253
250
        revision = self.annotations[row]
 
254
251
        repository = self.branch.repository
 
255
252
        if revision.revision_id == CURRENT_REVISION:
 
 
263
260
                tree2 = repository.revision_tree(NULL_REVISION)
 
264
261
        from bzrlib.plugins.gtk.diff import DiffWindow
 
265
 
        window = DiffWindow(self)
 
266
 
        window.set_diff("Diff for line %d" % (row+1), tree1, tree2)
 
 
262
        window = DiffWindow()
 
 
263
        window.set_diff("Diff for row %d" % (row+1), tree1, tree2)
 
267
264
        window.set_file(tree1.id2path(self.file_id))
 
271
268
    def _create_annotate_view(self):
 
273
270
        tv.set_rules_hint(False)
 
274
271
        tv.connect("cursor-changed", self._activate_selected_revision)
 
276
 
        tv.connect("row-activated", self.line_diff)
 
 
273
        tv.connect("row-activated", self.row_diff)
 
278
 
        cell = Gtk.CellRendererText()
 
 
275
        cell = gtk.CellRendererText()
 
279
276
        cell.set_property("xalign", 1.0)
 
280
277
        cell.set_property("ypad", 0)
 
281
278
        cell.set_property("family", "Monospace")
 
282
279
        cell.set_property("cell-background-gdk",
 
283
 
                          tv.get_style().bg[Gtk.StateType.NORMAL])
 
284
 
        col = Gtk.TreeViewColumn()
 
 
280
                          tv.get_style().bg[gtk.STATE_NORMAL])
 
 
281
        col = gtk.TreeViewColumn()
 
285
282
        col.set_resizable(False)
 
286
 
        col.pack_start(cell, True)
 
 
283
        col.pack_start(cell, expand=True)
 
287
284
        col.add_attribute(cell, "text", LINE_NUM_COL)
 
288
285
        tv.append_column(col)
 
290
 
        cell = Gtk.CellRendererText()
 
 
287
        cell = gtk.CellRendererText()
 
291
288
        cell.set_property("ypad", 0)
 
292
 
        cell.set_property("ellipsize", Pango.EllipsizeMode.END)
 
 
289
        cell.set_property("ellipsize", pango.ELLIPSIZE_END)
 
293
290
        cell.set_property("cell-background-gdk",
 
294
 
                          self.get_style().bg[Gtk.StateType.NORMAL])
 
295
 
        col = Gtk.TreeViewColumn("Committer")
 
 
291
                          self.get_style().bg[gtk.STATE_NORMAL])
 
 
292
        col = gtk.TreeViewColumn("Committer")
 
296
293
        col.set_resizable(True)
 
297
 
        col.pack_start(cell, True)
 
 
294
        col.pack_start(cell, expand=True)
 
298
295
        col.add_attribute(cell, "text", COMMITTER_COL)
 
299
296
        tv.append_column(col)
 
301
 
        cell = Gtk.CellRendererText()
 
 
298
        cell = gtk.CellRendererText()
 
302
299
        cell.set_property("xalign", 1.0)
 
303
300
        cell.set_property("ypad", 0)
 
304
301
        cell.set_property("cell-background-gdk",
 
305
 
                          self.get_style().bg[Gtk.StateType.NORMAL])
 
306
 
        col = Gtk.TreeViewColumn("Revno")
 
 
302
                          self.get_style().bg[gtk.STATE_NORMAL])
 
 
303
        col = gtk.TreeViewColumn("Revno")
 
307
304
        col.set_resizable(False)
 
308
 
        col.pack_start(cell, True)
 
 
305
        col.pack_start(cell, expand=True)
 
309
306
        col.add_attribute(cell, "markup", REVNO_COL)
 
310
307
        tv.append_column(col)
 
312
 
        cell = Gtk.CellRendererText()
 
 
309
        cell = gtk.CellRendererText()
 
313
310
        cell.set_property("ypad", 0)
 
314
311
        cell.set_property("family", "Monospace")
 
315
 
        col = Gtk.TreeViewColumn()
 
 
312
        col = gtk.TreeViewColumn()
 
316
313
        col.set_resizable(False)
 
317
 
        col.pack_start(cell, True)
 
 
314
        col.pack_start(cell, expand=True)
 
318
315
#        col.add_attribute(cell, "foreground", HIGHLIGHT_COLOR_COL)
 
319
316
        col.add_attribute(cell, "background", HIGHLIGHT_COLOR_COL)
 
320
317
        col.add_attribute(cell, "text", TEXT_LINE_COL)
 
321
318
        tv.append_column(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
 
327
 
        tv.set_enable_search(True)
 
328
 
        tv.set_search_equal_func(search_equal_func, None)
 
 
320
        # FIXME: Now that C-f is now used for search by text we
 
 
321
        # may as well disable the auto search.
 
 
322
        tv.set_search_column(LINE_NUM_COL)
 
332
326
    def _create_log_view(self):
 
333
 
        lv = RevisionView(self._branch)
 
337
331
    def _create_back_button(self):
 
338
 
        button = Gtk.Button()
 
 
332
        button = gtk.Button()
 
339
333
        button.set_use_stock(True)
 
340
334
        button.set_label("gtk-go-back")
 
341
335
        button.connect("clicked", lambda w: self.go_back())
 
342
 
        button.set_relief(Gtk.ReliefStyle.NONE)
 
 
336
        button.set_relief(gtk.RELIEF_NONE)
 
346
340
    def _create_forward_button(self):
 
347
 
        button = Gtk.Button()
 
 
341
        button = gtk.Button()
 
348
342
        button.set_use_stock(True)
 
349
343
        button.set_label("gtk-go-forward")
 
350
344
        button.connect("clicked", lambda w: self.go_forward())
 
351
 
        button.set_relief(Gtk.ReliefStyle.NONE)
 
 
345
        button.set_relief(gtk.RELIEF_NONE)
 
353
347
        button.set_sensitive(False)
 
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)
 
364
 
        button.set_sensitive(True)
 
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)
 
374
 
        button.set_sensitive(True)
 
377
350
    def go_back(self):
 
378
351
        last_tree = self.tree
 
379
352
        rev_id = self._selected_revision()
 
 
457
424
            self.__cache[revision_id] = revision
 
458
425
        return self.__cache[revision_id]
 
461
 
class SearchBox(Gtk.HBox):
 
 
427
class SearchBox(gtk.HBox):
 
462
428
    """A button box for searching in text or lines of annotations"""
 
463
429
    def __init__(self):
 
464
 
        super(SearchBox, self).__init__(homogeneous=False, spacing=6)
 
 
430
        gtk.HBox.__init__(self, False, 6)
 
467
 
        button = Gtk.Button()
 
469
 
        image.set_from_stock('gtk-stop', Gtk.IconSize.BUTTON)
 
 
433
        button = gtk.Button()
 
 
435
        image.set_from_stock('gtk-stop', gtk.ICON_SIZE_BUTTON)
 
470
436
        button.set_image(image)
 
471
 
        button.set_relief(Gtk.ReliefStyle.NONE)
 
472
 
        button.connect("clicked", lambda w: self.hide())
 
473
 
        self.pack_start(button, False, False, 0)
 
 
437
        button.set_relief(gtk.RELIEF_NONE)
 
 
438
        button.connect("clicked", lambda w: self.hide_all())
 
 
439
        self.pack_start(button, expand=False, fill=False)
 
477
443
        self._label = label
 
478
 
        self.pack_start(label, False, False, 0)
 
 
444
        self.pack_start(label, expand=False, fill=False)
 
481
447
        self._entry = entry
 
482
448
        entry.connect("activate", lambda w, d: self._do_search(d),
 
484
 
        self.pack_start(entry, False, False, 0)
 
 
450
        self.pack_start(entry, expand=False, fill=False)
 
486
452
        # Next/previous buttons
 
487
 
        button = Gtk.Button(_i18n('_Next'), use_underline=True)
 
489
 
        image.set_from_stock('gtk-go-forward', Gtk.IconSize.BUTTON)
 
 
453
        button = gtk.Button('_Next')
 
 
455
        image.set_from_stock('gtk-go-forward', gtk.ICON_SIZE_BUTTON)
 
490
456
        button.set_image(image)
 
491
457
        button.connect("clicked", lambda w, d: self._do_search(d),
 
493
 
        self.pack_start(button, False, False, 0)
 
 
459
        self.pack_start(button, expand=False, fill=False)
 
495
 
        button = Gtk.Button(_i18n('_Previous'), use_underline=True)
 
497
 
        image.set_from_stock('gtk-go-back', Gtk.IconSize.BUTTON)
 
 
461
        button = gtk.Button('_Previous')
 
 
463
        image.set_from_stock('gtk-go-back', gtk.ICON_SIZE_BUTTON)
 
498
464
        button.set_image(image)
 
499
465
        button.connect("clicked", lambda w, d: self._do_search(d),
 
501
 
        self.pack_start(button, False, False, 0)
 
 
467
        self.pack_start(button, expand=False, fill=False)
 
504
 
        check = Gtk.CheckButton('Match case')
 
 
470
        check = gtk.CheckButton('Match case')
 
505
471
        self._match_case = check
 
506
 
        self.pack_start(check, False, False, 0)
 
 
472
        self.pack_start(check, expand=False, fill=False)
 
508
 
        check = Gtk.CheckButton('Regexp')
 
 
474
        check = gtk.CheckButton('Regexp')
 
509
475
        check.connect("toggled", lambda w: self._set_label())
 
510
476
        self._regexp = check
 
511
 
        self.pack_start(check, False, False, 0)
 
 
477
        self.pack_start(check, expand=False, fill=False)
 
513
479
        self._view = None
 
514
480
        self._column = None
 
 
542
508
    def _match(self, model, iterator, column):
 
543
509
        matching_case = self._match_case.get_active()
 
544
 
        cell_value, = model.get(iterator, column)
 
 
510
        string, = model.get(iterator, column)
 
545
511
        key = self._entry.get_text()
 
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():
 
 
512
        if self._regexp.get_active():
 
551
513
            if matching_case:
 
552
 
                match = re.compile(key).search(cell_value, 1)
 
 
514
                match = re.compile(key).search(string, 1)
 
554
 
                match = re.compile(key, re.I).search(cell_value, 1)
 
 
516
                match = re.compile(key, re.I).search(string, 1)
 
556
518
            if not matching_case:
 
557
 
                cell_value = cell_value.lower()
 
 
519
                string = string.lower()
 
558
520
                key = key.lower()
 
559
 
            match = cell_value.find(key) != -1
 
 
521
            match = string.find(key) != -1