19
from gi.repository import GObject
20
from gi.repository import Gdk
21
from gi.repository import Gtk
22
from gi.repository import Pango
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
30
from colormap import AnnotateColorMap, AnnotateColorSaturation
29
from bzrlib.plugins.gtk.annotate.colormap import AnnotateColorSaturation
30
from bzrlib.plugins.gtk.i18n import _i18n
31
31
from bzrlib.plugins.gtk.revisionview import RevisionView
32
32
from bzrlib.plugins.gtk.window import Window
86
87
for revision_id, revno in revno_map.iteritems():
87
88
self.dotted[revision_id] = '.'.join(str(num) for num in revno)
88
89
for line_no, (revision, revno, line)\
89
in enumerate(self._annotate(tree, file_id)):
90
in enumerate(self._annotate(tree, file_id)):
90
91
if revision.revision_id == last_seen and not self.all:
91
92
revno = author = ""
93
94
last_seen = revision.revision_id
94
author = revision.get_apparent_author()
95
author = ", ".join(revision.get_apparent_authors())
96
97
if revision.revision_id not in self.revisions:
97
98
self.revisions[revision.revision_id] = revision
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)
173
175
def _selected_revision(self):
174
176
(path, col) = self.annoview.get_cursor()
193
195
self.revisionview = self._create_log_view()
194
196
self.annoview = self._create_annotate_view()
196
vbox = gtk.VBox(False)
198
vbox = Gtk.VBox(homogeneous=False, spacing=0)
199
sw = gtk.ScrolledWindow()
200
sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
201
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)
202
204
sw.add(self.annoview)
203
205
self.annoview.gwindow = self
209
swbox.pack_start(sw, True, True, 0)
210
hbox = gtk.HBox(False, 6)
212
hbox = Gtk.HBox(homogeneous=False, spacing=6)
211
213
self.back_button = self._create_back_button()
212
hbox.pack_start(self.back_button, expand=False, fill=True)
214
hbox.pack_start(self.back_button, False, True, 0)
213
215
self.forward_button = self._create_forward_button()
214
hbox.pack_start(self.forward_button, expand=False, fill=True)
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)
216
vbox.pack_start(hbox, expand=False, fill=True)
218
self.pane = pane = gtk.VPaned()
222
vbox.pack_start(hbox, False, True, 0)
224
self.pane = pane = Gtk.Paned.new(Gtk.Orientation.VERTICAL)
220
226
pane.add2(self.revisionview)
222
vbox.pack_start(pane, expand=True, fill=True)
228
vbox.pack_start(pane, True, True, 0)
224
230
self._search = SearchBox()
225
swbox.pack_start(self._search, expand=False, fill=True)
226
accels = gtk.AccelGroup()
227
accels.connect_group(gtk.keysyms.f, gtk.gdk.CONTROL_MASK,
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,
229
235
self._search_by_text)
230
accels.connect_group(gtk.keysyms.g, gtk.gdk.CONTROL_MASK,
236
accels.connect(Gdk.KEY_g, Gdk.ModifierType.CONTROL_MASK,
237
Gtk.AccelFlags.LOCKED,
232
238
self._search_by_line)
233
239
self.add_accel_group(accels)
237
def _search_by_text(self, accel_group, window, key, modifiers):
243
def _search_by_text(self, *ignored): # (accel_group, window, key, modifiers):
238
244
self._search.show_for('text')
239
245
self._search.set_target(self.annoview, TEXT_LINE_COL)
241
def _search_by_line(self, accel_group, window, key, modifiers):
247
def _search_by_line(self, *ignored): # accel_group, window, key, modifiers):
242
248
self._search.show_for('line')
243
249
self._search.set_target(self.annoview, LINE_NUM_COL)
245
251
def line_diff(self, tv, path, tvc):
252
row = path.get_indices()[0]
247
253
revision = self.annotations[row]
248
254
repository = self.branch.repository
249
255
if revision.revision_id == CURRENT_REVISION:
257
263
tree2 = repository.revision_tree(NULL_REVISION)
258
264
from bzrlib.plugins.gtk.diff import DiffWindow
259
window = DiffWindow()
265
window = DiffWindow(self)
260
266
window.set_diff("Diff for line %d" % (row+1), tree1, tree2)
261
267
window.set_file(tree1.id2path(self.file_id))
265
271
def _create_annotate_view(self):
267
273
tv.set_rules_hint(False)
268
274
tv.connect("cursor-changed", self._activate_selected_revision)
270
276
tv.connect("row-activated", self.line_diff)
272
cell = gtk.CellRendererText()
278
cell = Gtk.CellRendererText()
273
279
cell.set_property("xalign", 1.0)
274
280
cell.set_property("ypad", 0)
275
281
cell.set_property("family", "Monospace")
276
cell.set_property("cell-background-gdk",
277
tv.get_style().bg[gtk.STATE_NORMAL])
278
col = gtk.TreeViewColumn()
283
"cell-background-rgba",
284
tv.get_style_context().get_background_color(Gtk.StateType.NORMAL))
285
col = Gtk.TreeViewColumn()
279
286
col.set_resizable(False)
280
col.pack_start(cell, expand=True)
287
col.pack_start(cell, True)
281
288
col.add_attribute(cell, "text", LINE_NUM_COL)
282
289
tv.append_column(col)
284
cell = gtk.CellRendererText()
291
style_context = self.get_style_context()
293
cell = Gtk.CellRendererText()
285
294
cell.set_property("ypad", 0)
286
cell.set_property("ellipsize", pango.ELLIPSIZE_END)
287
cell.set_property("cell-background-gdk",
288
self.get_style().bg[gtk.STATE_NORMAL])
289
col = gtk.TreeViewColumn("Committer")
295
cell.set_property("ellipsize", Pango.EllipsizeMode.END)
297
"cell-background-rgba",
298
style_context.get_background_color(Gtk.StateType.NORMAL))
299
col = Gtk.TreeViewColumn("Committer")
290
300
col.set_resizable(True)
291
col.pack_start(cell, expand=True)
301
col.pack_start(cell, True)
292
302
col.add_attribute(cell, "text", COMMITTER_COL)
293
303
tv.append_column(col)
295
cell = gtk.CellRendererText()
305
cell = Gtk.CellRendererText()
296
306
cell.set_property("xalign", 1.0)
297
307
cell.set_property("ypad", 0)
298
cell.set_property("cell-background-gdk",
299
self.get_style().bg[gtk.STATE_NORMAL])
300
col = gtk.TreeViewColumn("Revno")
309
"cell-background-rgba",
310
style_context.get_background_color(Gtk.StateType.NORMAL))
311
col = Gtk.TreeViewColumn("Revno")
301
312
col.set_resizable(False)
302
col.pack_start(cell, expand=True)
313
col.pack_start(cell, True)
303
314
col.add_attribute(cell, "markup", REVNO_COL)
304
315
tv.append_column(col)
306
cell = gtk.CellRendererText()
317
cell = Gtk.CellRendererText()
307
318
cell.set_property("ypad", 0)
308
319
cell.set_property("family", "Monospace")
309
col = gtk.TreeViewColumn()
320
col = Gtk.TreeViewColumn()
310
321
col.set_resizable(False)
311
col.pack_start(cell, expand=True)
322
col.pack_start(cell, True)
312
323
# col.add_attribute(cell, "foreground", HIGHLIGHT_COLOR_COL)
313
324
col.add_attribute(cell, "background", HIGHLIGHT_COLOR_COL)
314
325
col.add_attribute(cell, "text", TEXT_LINE_COL)
315
326
tv.append_column(col)
317
# FIXME: Now that C-f is now used for search by text we
318
# may as well disable the auto search.
319
tv.set_search_column(LINE_NUM_COL)
328
# interactive substring search
329
def search_equal_func(model, column, key, iter):
330
return model.get_value(iter, TEXT_LINE_COL).lower().find(key.lower()) == -1
332
tv.set_enable_search(True)
333
tv.set_search_equal_func(search_equal_func, None)
328
342
def _create_back_button(self):
329
button = gtk.Button()
343
button = Gtk.Button()
330
344
button.set_use_stock(True)
331
345
button.set_label("gtk-go-back")
332
346
button.connect("clicked", lambda w: self.go_back())
333
button.set_relief(gtk.RELIEF_NONE)
347
button.set_relief(Gtk.ReliefStyle.NONE)
337
351
def _create_forward_button(self):
338
button = gtk.Button()
352
button = Gtk.Button()
339
353
button.set_use_stock(True)
340
354
button.set_label("gtk-go-forward")
341
355
button.connect("clicked", lambda w: self.go_forward())
342
button.set_relief(gtk.RELIEF_NONE)
356
button.set_relief(Gtk.ReliefStyle.NONE)
344
358
button.set_sensitive(False)
361
def _create_find_button(self):
362
button = Gtk.Button()
363
button.set_use_stock(True)
364
button.set_label("gtk-find")
365
button.set_tooltip_text("Search for text (Ctrl+F)")
366
button.connect("clicked", self._search_by_text)
367
button.set_relief(Gtk.ReliefStyle.NONE)
369
button.set_sensitive(True)
372
def _create_goto_button(self):
373
button = Gtk.Button()
374
button.set_label("Goto Line")
375
button.set_tooltip_text("Scroll to a line by entering its number (Ctrl+G)")
376
button.connect("clicked", self._search_by_line)
377
button.set_relief(Gtk.ReliefStyle.NONE)
379
button.set_sensitive(True)
347
382
def go_back(self):
348
383
last_tree = self.tree
349
384
rev_id = self._selected_revision()
423
462
self.__cache[revision_id] = revision
424
463
return self.__cache[revision_id]
426
class SearchBox(gtk.HBox):
466
class SearchBox(Gtk.HBox):
427
467
"""A button box for searching in text or lines of annotations"""
428
468
def __init__(self):
429
gtk.HBox.__init__(self, False, 6)
469
super(SearchBox, self).__init__(homogeneous=False, spacing=6)
432
button = gtk.Button()
434
image.set_from_stock('gtk-stop', gtk.ICON_SIZE_BUTTON)
472
button = Gtk.Button()
474
image.set_from_stock('gtk-stop', Gtk.IconSize.BUTTON)
435
475
button.set_image(image)
436
button.set_relief(gtk.RELIEF_NONE)
437
button.connect("clicked", lambda w: self.hide_all())
438
self.pack_start(button, expand=False, fill=False)
476
button.set_relief(Gtk.ReliefStyle.NONE)
477
button.connect("clicked", lambda w: self.hide())
478
self.pack_start(button, False, False, 0)
442
482
self._label = label
443
self.pack_start(label, expand=False, fill=False)
483
self.pack_start(label, False, False, 0)
446
486
self._entry = entry
447
487
entry.connect("activate", lambda w, d: self._do_search(d),
449
self.pack_start(entry, expand=False, fill=False)
489
self.pack_start(entry, False, False, 0)
451
491
# Next/previous buttons
452
button = gtk.Button('_Next')
454
image.set_from_stock('gtk-go-forward', gtk.ICON_SIZE_BUTTON)
492
button = Gtk.Button(_i18n('_Next'), use_underline=True)
494
image.set_from_stock('gtk-go-forward', Gtk.IconSize.BUTTON)
455
495
button.set_image(image)
456
496
button.connect("clicked", lambda w, d: self._do_search(d),
458
self.pack_start(button, expand=False, fill=False)
498
self.pack_start(button, False, False, 0)
460
button = gtk.Button('_Previous')
462
image.set_from_stock('gtk-go-back', gtk.ICON_SIZE_BUTTON)
500
button = Gtk.Button(_i18n('_Previous'), use_underline=True)
502
image.set_from_stock('gtk-go-back', Gtk.IconSize.BUTTON)
463
503
button.set_image(image)
464
504
button.connect("clicked", lambda w, d: self._do_search(d),
466
self.pack_start(button, expand=False, fill=False)
506
self.pack_start(button, False, False, 0)
469
check = gtk.CheckButton('Match case')
509
check = Gtk.CheckButton('Match case')
470
510
self._match_case = check
471
self.pack_start(check, expand=False, fill=False)
511
self.pack_start(check, False, False, 0)
473
check = gtk.CheckButton('Regexp')
513
check = Gtk.CheckButton('Regexp')
474
514
check.connect("toggled", lambda w: self._set_label())
475
515
self._regexp = check
476
self.pack_start(check, expand=False, fill=False)
516
self.pack_start(check, False, False, 0)
478
518
self._view = None
479
519
self._column = None
507
547
def _match(self, model, iterator, column):
508
548
matching_case = self._match_case.get_active()
509
string, = model.get(iterator, column)
549
cell_value, = model.get(iterator, column)
510
550
key = self._entry.get_text()
511
if self._regexp.get_active():
551
if column == LINE_NUM_COL:
552
# FIXME: For goto-line there are faster algorithms than searching
553
# every line til we find the right one! -- mbp 2011-01-27
554
return key.strip() == str(cell_value)
555
elif self._regexp.get_active():
512
556
if matching_case:
513
match = re.compile(key).search(string, 1)
557
match = re.compile(key).search(cell_value, 1)
515
match = re.compile(key, re.I).search(string, 1)
559
match = re.compile(key, re.I).search(cell_value, 1)
517
561
if not matching_case:
518
string = string.lower()
562
cell_value = cell_value.lower()
519
563
key = key.lower()
520
match = string.find(key) != -1
564
match = cell_value.find(key) != -1