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