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
282
cell.set_property("cell-background-gdk",
277
tv.get_style().bg[gtk.STATE_NORMAL])
278
col = gtk.TreeViewColumn()
283
tv.get_style().bg[Gtk.StateType.NORMAL])
284
col = Gtk.TreeViewColumn()
279
285
col.set_resizable(False)
280
col.pack_start(cell, expand=True)
286
col.pack_start(cell, True)
281
287
col.add_attribute(cell, "text", LINE_NUM_COL)
282
288
tv.append_column(col)
284
cell = gtk.CellRendererText()
290
cell = Gtk.CellRendererText()
285
291
cell.set_property("ypad", 0)
286
cell.set_property("ellipsize", pango.ELLIPSIZE_END)
292
cell.set_property("ellipsize", Pango.EllipsizeMode.END)
287
293
cell.set_property("cell-background-gdk",
288
self.get_style().bg[gtk.STATE_NORMAL])
289
col = gtk.TreeViewColumn("Committer")
294
self.get_style().bg[Gtk.StateType.NORMAL])
295
col = Gtk.TreeViewColumn("Committer")
290
296
col.set_resizable(True)
291
col.pack_start(cell, expand=True)
297
col.pack_start(cell, True)
292
298
col.add_attribute(cell, "text", COMMITTER_COL)
293
299
tv.append_column(col)
295
cell = gtk.CellRendererText()
301
cell = Gtk.CellRendererText()
296
302
cell.set_property("xalign", 1.0)
297
303
cell.set_property("ypad", 0)
298
304
cell.set_property("cell-background-gdk",
299
self.get_style().bg[gtk.STATE_NORMAL])
300
col = gtk.TreeViewColumn("Revno")
305
self.get_style().bg[Gtk.StateType.NORMAL])
306
col = Gtk.TreeViewColumn("Revno")
301
307
col.set_resizable(False)
302
col.pack_start(cell, expand=True)
308
col.pack_start(cell, True)
303
309
col.add_attribute(cell, "markup", REVNO_COL)
304
310
tv.append_column(col)
306
cell = gtk.CellRendererText()
312
cell = Gtk.CellRendererText()
307
313
cell.set_property("ypad", 0)
308
314
cell.set_property("family", "Monospace")
309
col = gtk.TreeViewColumn()
315
col = Gtk.TreeViewColumn()
310
316
col.set_resizable(False)
311
col.pack_start(cell, expand=True)
317
col.pack_start(cell, True)
312
318
# col.add_attribute(cell, "foreground", HIGHLIGHT_COLOR_COL)
313
319
col.add_attribute(cell, "background", HIGHLIGHT_COLOR_COL)
314
320
col.add_attribute(cell, "text", TEXT_LINE_COL)
315
321
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)
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)
328
337
def _create_back_button(self):
329
button = gtk.Button()
338
button = Gtk.Button()
330
339
button.set_use_stock(True)
331
340
button.set_label("gtk-go-back")
332
341
button.connect("clicked", lambda w: self.go_back())
333
button.set_relief(gtk.RELIEF_NONE)
342
button.set_relief(Gtk.ReliefStyle.NONE)
337
346
def _create_forward_button(self):
338
button = gtk.Button()
347
button = Gtk.Button()
339
348
button.set_use_stock(True)
340
349
button.set_label("gtk-go-forward")
341
350
button.connect("clicked", lambda w: self.go_forward())
342
button.set_relief(gtk.RELIEF_NONE)
351
button.set_relief(Gtk.ReliefStyle.NONE)
344
353
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)
347
377
def go_back(self):
348
378
last_tree = self.tree
349
379
rev_id = self._selected_revision()
368
398
rev_id = self._selected_revision()
369
399
if self.file_id in target_tree:
370
400
offset = self.get_scroll_offset(target_tree)
371
(row,), col = self.annoview.get_cursor()
401
path, col = self.annoview.get_cursor()
402
(row,) = path.get_indices()
372
403
self.annotate(target_tree, self.branch, self.file_id)
373
404
new_row = row+offset
376
self.annoview.set_cursor(new_row)
407
new_path = Gtk.TreePath(path=new_row)
408
self.annoview.set_cursor(new_path, None, False)
423
457
self.__cache[revision_id] = revision
424
458
return self.__cache[revision_id]
426
class SearchBox(gtk.HBox):
461
class SearchBox(Gtk.HBox):
427
462
"""A button box for searching in text or lines of annotations"""
428
463
def __init__(self):
429
gtk.HBox.__init__(self, False, 6)
464
super(SearchBox, self).__init__(homogeneous=False, spacing=6)
432
button = gtk.Button()
434
image.set_from_stock('gtk-stop', gtk.ICON_SIZE_BUTTON)
467
button = Gtk.Button()
469
image.set_from_stock('gtk-stop', Gtk.IconSize.BUTTON)
435
470
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)
471
button.set_relief(Gtk.ReliefStyle.NONE)
472
button.connect("clicked", lambda w: self.hide())
473
self.pack_start(button, False, False, 0)
442
477
self._label = label
443
self.pack_start(label, expand=False, fill=False)
478
self.pack_start(label, False, False, 0)
446
481
self._entry = entry
447
482
entry.connect("activate", lambda w, d: self._do_search(d),
449
self.pack_start(entry, expand=False, fill=False)
484
self.pack_start(entry, False, False, 0)
451
486
# Next/previous buttons
452
button = gtk.Button('_Next')
454
image.set_from_stock('gtk-go-forward', gtk.ICON_SIZE_BUTTON)
487
button = Gtk.Button(_i18n('_Next'), use_underline=True)
489
image.set_from_stock('gtk-go-forward', Gtk.IconSize.BUTTON)
455
490
button.set_image(image)
456
491
button.connect("clicked", lambda w, d: self._do_search(d),
458
self.pack_start(button, expand=False, fill=False)
493
self.pack_start(button, False, False, 0)
460
button = gtk.Button('_Previous')
462
image.set_from_stock('gtk-go-back', gtk.ICON_SIZE_BUTTON)
495
button = Gtk.Button(_i18n('_Previous'), use_underline=True)
497
image.set_from_stock('gtk-go-back', Gtk.IconSize.BUTTON)
463
498
button.set_image(image)
464
499
button.connect("clicked", lambda w, d: self._do_search(d),
466
self.pack_start(button, expand=False, fill=False)
501
self.pack_start(button, False, False, 0)
469
check = gtk.CheckButton('Match case')
504
check = Gtk.CheckButton('Match case')
470
505
self._match_case = check
471
self.pack_start(check, expand=False, fill=False)
506
self.pack_start(check, False, False, 0)
473
check = gtk.CheckButton('Regexp')
508
check = Gtk.CheckButton('Regexp')
474
509
check.connect("toggled", lambda w: self._set_label())
475
510
self._regexp = check
476
self.pack_start(check, expand=False, fill=False)
511
self.pack_start(check, False, False, 0)
478
513
self._view = None
479
514
self._column = None
507
542
def _match(self, model, iterator, column):
508
543
matching_case = self._match_case.get_active()
509
string, = model.get(iterator, column)
544
cell_value, = model.get(iterator, column)
510
545
key = self._entry.get_text()
511
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():
512
551
if matching_case:
513
match = re.compile(key).search(string, 1)
552
match = re.compile(key).search(cell_value, 1)
515
match = re.compile(key, re.I).search(string, 1)
554
match = re.compile(key, re.I).search(cell_value, 1)
517
556
if not matching_case:
518
string = string.lower()
557
cell_value = cell_value.lower()
519
558
key = key.lower()
520
match = string.find(key) != -1
559
match = cell_value.find(key) != -1