19
from gi.repository import GObject
20
from gi.repository import Gdk
21
from gi.repository import Gtk
22
from gi.repository import Pango
25
from bzrlib import patiencediff
26
from bzrlib import patiencediff, tsort
26
27
from bzrlib.errors import NoSuchRevision
27
28
from bzrlib.revision import NULL_REVISION, CURRENT_REVISION
29
from bzrlib.plugins.gtk.annotate.colormap import AnnotateColorSaturation
30
from colormap import AnnotateColorMap, AnnotateColorSaturation
30
31
from bzrlib.plugins.gtk.revisionview import RevisionView
31
32
from bzrlib.plugins.gtk.window import Window
86
86
for revision_id, revno in revno_map.iteritems():
87
87
self.dotted[revision_id] = '.'.join(str(num) for num in revno)
88
88
for line_no, (revision, revno, line)\
89
in enumerate(self._annotate(tree, file_id)):
89
in enumerate(self._annotate(tree, file_id)):
90
90
if revision.revision_id == last_seen and not self.all:
91
91
revno = author = ""
93
93
last_seen = revision.revision_id
94
author = ", ".join(revision.get_apparent_authors())
94
author = revision.get_apparent_author()
96
96
if revision.revision_id not in self.revisions:
97
97
self.revisions[revision.revision_id] = revision
167
167
def _highlight_annotation(self, model, path, iter, now):
168
168
revision_id, = model.get(iter, REVISION_ID_COL)
169
169
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)
170
model.set(iter, HIGHLIGHT_COLOR_COL,
171
self.annotate_colormap.get_color(revision, now))
174
173
def _selected_revision(self):
175
174
(path, col) = self.annoview.get_cursor()
194
193
self.revisionview = self._create_log_view()
195
194
self.annoview = self._create_annotate_view()
197
vbox = Gtk.VBox(homogeneous=False, spacing=0)
196
vbox = gtk.VBox(False)
200
sw = Gtk.ScrolledWindow()
201
sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
202
sw.set_shadow_type(Gtk.ShadowType.IN)
199
sw = gtk.ScrolledWindow()
200
sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
201
sw.set_shadow_type(gtk.SHADOW_IN)
203
202
sw.add(self.annoview)
204
203
self.annoview.gwindow = self
208
swbox.pack_start(sw, True, True, 0)
211
hbox = Gtk.HBox(homogeneous=False, spacing=6)
210
hbox = gtk.HBox(False, 6)
212
211
self.back_button = self._create_back_button()
213
hbox.pack_start(self.back_button, False, True, 0)
212
hbox.pack_start(self.back_button, expand=False, fill=True)
214
213
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)
214
hbox.pack_start(self.forward_button, expand=False, fill=True)
221
vbox.pack_start(hbox, False, True, 0)
223
self.pane = pane = Gtk.Paned.new(Gtk.Orientation.VERTICAL)
216
vbox.pack_start(hbox, expand=False, fill=True)
218
self.pane = pane = gtk.VPaned()
225
220
pane.add2(self.revisionview)
227
vbox.pack_start(pane, True, True, 0)
222
vbox.pack_start(pane, expand=True, fill=True)
229
224
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,
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,
234
229
self._search_by_text)
235
accels.connect(Gdk.KEY_g, Gdk.ModifierType.CONTROL_MASK,
236
Gtk.AccelFlags.LOCKED,
230
accels.connect_group(gtk.keysyms.g, gtk.gdk.CONTROL_MASK,
237
232
self._search_by_line)
238
233
self.add_accel_group(accels)
242
def _search_by_text(self, *ignored): # (accel_group, window, key, modifiers):
237
def _search_by_text(self, accel_group, window, key, modifiers):
243
238
self._search.show_for('text')
244
239
self._search.set_target(self.annoview, TEXT_LINE_COL)
246
def _search_by_line(self, *ignored): # accel_group, window, key, modifiers):
241
def _search_by_line(self, accel_group, window, key, modifiers):
247
242
self._search.show_for('line')
248
243
self._search.set_target(self.annoview, LINE_NUM_COL)
250
245
def line_diff(self, tv, path, tvc):
251
row = path.get_indices()[0]
252
247
revision = self.annotations[row]
253
248
repository = self.branch.repository
254
249
if revision.revision_id == CURRENT_REVISION:
262
257
tree2 = repository.revision_tree(NULL_REVISION)
263
258
from bzrlib.plugins.gtk.diff import DiffWindow
264
window = DiffWindow(self)
259
window = DiffWindow()
265
260
window.set_diff("Diff for line %d" % (row+1), tree1, tree2)
266
261
window.set_file(tree1.id2path(self.file_id))
270
265
def _create_annotate_view(self):
272
267
tv.set_rules_hint(False)
273
268
tv.connect("cursor-changed", self._activate_selected_revision)
275
270
tv.connect("row-activated", self.line_diff)
277
cell = Gtk.CellRendererText()
272
cell = gtk.CellRendererText()
278
273
cell.set_property("xalign", 1.0)
279
274
cell.set_property("ypad", 0)
280
275
cell.set_property("family", "Monospace")
281
276
cell.set_property("cell-background-gdk",
282
tv.get_style().bg[Gtk.StateType.NORMAL])
283
col = Gtk.TreeViewColumn()
277
tv.get_style().bg[gtk.STATE_NORMAL])
278
col = gtk.TreeViewColumn()
284
279
col.set_resizable(False)
285
col.pack_start(cell, True)
280
col.pack_start(cell, expand=True)
286
281
col.add_attribute(cell, "text", LINE_NUM_COL)
287
282
tv.append_column(col)
289
cell = Gtk.CellRendererText()
284
cell = gtk.CellRendererText()
290
285
cell.set_property("ypad", 0)
291
cell.set_property("ellipsize", Pango.EllipsizeMode.END)
286
cell.set_property("ellipsize", pango.ELLIPSIZE_END)
292
287
cell.set_property("cell-background-gdk",
293
self.get_style().bg[Gtk.StateType.NORMAL])
294
col = Gtk.TreeViewColumn("Committer")
288
self.get_style().bg[gtk.STATE_NORMAL])
289
col = gtk.TreeViewColumn("Committer")
295
290
col.set_resizable(True)
296
col.pack_start(cell, True)
291
col.pack_start(cell, expand=True)
297
292
col.add_attribute(cell, "text", COMMITTER_COL)
298
293
tv.append_column(col)
300
cell = Gtk.CellRendererText()
295
cell = gtk.CellRendererText()
301
296
cell.set_property("xalign", 1.0)
302
297
cell.set_property("ypad", 0)
303
298
cell.set_property("cell-background-gdk",
304
self.get_style().bg[Gtk.StateType.NORMAL])
305
col = Gtk.TreeViewColumn("Revno")
299
self.get_style().bg[gtk.STATE_NORMAL])
300
col = gtk.TreeViewColumn("Revno")
306
301
col.set_resizable(False)
307
col.pack_start(cell, True)
302
col.pack_start(cell, expand=True)
308
303
col.add_attribute(cell, "markup", REVNO_COL)
309
304
tv.append_column(col)
311
cell = Gtk.CellRendererText()
306
cell = gtk.CellRendererText()
312
307
cell.set_property("ypad", 0)
313
308
cell.set_property("family", "Monospace")
314
col = Gtk.TreeViewColumn()
309
col = gtk.TreeViewColumn()
315
310
col.set_resizable(False)
316
col.pack_start(cell, True)
311
col.pack_start(cell, expand=True)
317
312
# col.add_attribute(cell, "foreground", HIGHLIGHT_COLOR_COL)
318
313
col.add_attribute(cell, "background", HIGHLIGHT_COLOR_COL)
319
314
col.add_attribute(cell, "text", TEXT_LINE_COL)
320
315
tv.append_column(col)
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
326
tv.set_enable_search(True)
327
tv.set_search_equal_func(search_equal_func, None)
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)
336
328
def _create_back_button(self):
337
button = Gtk.Button()
329
button = gtk.Button()
338
330
button.set_use_stock(True)
339
331
button.set_label("gtk-go-back")
340
332
button.connect("clicked", lambda w: self.go_back())
341
button.set_relief(Gtk.ReliefStyle.NONE)
333
button.set_relief(gtk.RELIEF_NONE)
345
337
def _create_forward_button(self):
346
button = Gtk.Button()
338
button = gtk.Button()
347
339
button.set_use_stock(True)
348
340
button.set_label("gtk-go-forward")
349
341
button.connect("clicked", lambda w: self.go_forward())
350
button.set_relief(Gtk.ReliefStyle.NONE)
342
button.set_relief(gtk.RELIEF_NONE)
352
344
button.set_sensitive(False)
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)
363
button.set_sensitive(True)
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)
373
button.set_sensitive(True)
376
347
def go_back(self):
377
348
last_tree = self.tree
378
349
rev_id = self._selected_revision()
397
368
rev_id = self._selected_revision()
398
369
if self.file_id in target_tree:
399
370
offset = self.get_scroll_offset(target_tree)
400
path, col = self.annoview.get_cursor()
401
(row,) = path.get_indices()
371
(row,), col = self.annoview.get_cursor()
402
372
self.annotate(target_tree, self.branch, self.file_id)
403
373
new_row = row+offset
406
new_path = Gtk.TreePath(path=new_row)
407
self.annoview.set_cursor(new_path, None, False)
376
self.annoview.set_cursor(new_row)
456
423
self.__cache[revision_id] = revision
457
424
return self.__cache[revision_id]
460
class SearchBox(Gtk.HBox):
426
class SearchBox(gtk.HBox):
461
427
"""A button box for searching in text or lines of annotations"""
462
428
def __init__(self):
463
super(SearchBox, self).__init__(homogeneous=False, spacing=6)
429
gtk.HBox.__init__(self, False, 6)
466
button = Gtk.Button()
468
image.set_from_stock('gtk-stop', Gtk.IconSize.BUTTON)
432
button = gtk.Button()
434
image.set_from_stock('gtk-stop', gtk.ICON_SIZE_BUTTON)
469
435
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)
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
442
self._label = label
477
self.pack_start(label, False, False, 0)
443
self.pack_start(label, expand=False, fill=False)
480
446
self._entry = entry
481
447
entry.connect("activate", lambda w, d: self._do_search(d),
483
self.pack_start(entry, False, False, 0)
449
self.pack_start(entry, expand=False, fill=False)
485
451
# Next/previous buttons
486
button = Gtk.Button('_Next')
488
image.set_from_stock('gtk-go-forward', Gtk.IconSize.BUTTON)
452
button = gtk.Button('_Next')
454
image.set_from_stock('gtk-go-forward', gtk.ICON_SIZE_BUTTON)
489
455
button.set_image(image)
490
456
button.connect("clicked", lambda w, d: self._do_search(d),
492
self.pack_start(button, False, False, 0)
458
self.pack_start(button, expand=False, fill=False)
494
button = Gtk.Button('_Previous')
496
image.set_from_stock('gtk-go-back', Gtk.IconSize.BUTTON)
460
button = gtk.Button('_Previous')
462
image.set_from_stock('gtk-go-back', gtk.ICON_SIZE_BUTTON)
497
463
button.set_image(image)
498
464
button.connect("clicked", lambda w, d: self._do_search(d),
500
self.pack_start(button, False, False, 0)
466
self.pack_start(button, expand=False, fill=False)
503
check = Gtk.CheckButton('Match case')
469
check = gtk.CheckButton('Match case')
504
470
self._match_case = check
505
self.pack_start(check, False, False, 0)
471
self.pack_start(check, expand=False, fill=False)
507
check = Gtk.CheckButton('Regexp')
473
check = gtk.CheckButton('Regexp')
508
474
check.connect("toggled", lambda w: self._set_label())
509
475
self._regexp = check
510
self.pack_start(check, False, False, 0)
476
self.pack_start(check, expand=False, fill=False)
512
478
self._view = None
513
479
self._column = None
541
507
def _match(self, model, iterator, column):
542
508
matching_case = self._match_case.get_active()
543
cell_value, = model.get(iterator, column)
509
string, = model.get(iterator, column)
544
510
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():
511
if self._regexp.get_active():
550
512
if matching_case:
551
match = re.compile(key).search(cell_value, 1)
513
match = re.compile(key).search(string, 1)
553
match = re.compile(key, re.I).search(cell_value, 1)
515
match = re.compile(key, re.I).search(string, 1)
555
517
if not matching_case:
556
cell_value = cell_value.lower()
518
string = string.lower()
557
519
key = key.lower()
558
match = cell_value.find(key) != -1
520
match = string.find(key) != -1