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 bzrlib.plugins.gtk.i18n import _i18n
30
from colormap import AnnotateColorMap, AnnotateColorSaturation
31
31
from bzrlib.plugins.gtk.revisionview import RevisionView
32
32
from bzrlib.plugins.gtk.window import Window
168
167
def _highlight_annotation(self, model, path, iter, now):
169
168
revision_id, = model.get(iter, REVISION_ID_COL)
170
169
revision = self.revisions[revision_id]
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)
170
model.set(iter, HIGHLIGHT_COLOR_COL,
171
self.annotate_colormap.get_color(revision, now))
175
173
def _selected_revision(self):
176
174
(path, col) = self.annoview.get_cursor()
195
193
self.revisionview = self._create_log_view()
196
194
self.annoview = self._create_annotate_view()
198
vbox = Gtk.VBox(homogeneous=False, spacing=0)
196
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)
199
sw = gtk.ScrolledWindow()
200
sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
201
sw.set_shadow_type(gtk.SHADOW_IN)
204
202
sw.add(self.annoview)
205
203
self.annoview.gwindow = self
209
swbox.pack_start(sw, True, True, 0)
212
hbox = Gtk.HBox(homogeneous=False, spacing=6)
210
hbox = gtk.HBox(False, 6)
213
211
self.back_button = self._create_back_button()
214
hbox.pack_start(self.back_button, False, True, 0)
212
hbox.pack_start(self.back_button, expand=False, fill=True)
215
213
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)
214
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)
216
vbox.pack_start(hbox, expand=False, fill=True)
218
self.pane = pane = gtk.VPaned()
226
220
pane.add2(self.revisionview)
228
vbox.pack_start(pane, True, True, 0)
222
vbox.pack_start(pane, expand=True, fill=True)
230
224
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,
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,
235
229
self._search_by_text)
236
accels.connect(Gdk.KEY_g, Gdk.ModifierType.CONTROL_MASK,
237
Gtk.AccelFlags.LOCKED,
230
accels.connect_group(gtk.keysyms.g, gtk.gdk.CONTROL_MASK,
238
232
self._search_by_line)
239
233
self.add_accel_group(accels)
243
def _search_by_text(self, *ignored): # (accel_group, window, key, modifiers):
237
def _search_by_text(self, accel_group, window, key, modifiers):
244
238
self._search.show_for('text')
245
239
self._search.set_target(self.annoview, TEXT_LINE_COL)
247
def _search_by_line(self, *ignored): # accel_group, window, key, modifiers):
241
def _search_by_line(self, accel_group, window, key, modifiers):
248
242
self._search.show_for('line')
249
243
self._search.set_target(self.annoview, LINE_NUM_COL)
251
245
def line_diff(self, tv, path, tvc):
252
row = path.get_indices()[0]
253
247
revision = self.annotations[row]
254
248
repository = self.branch.repository
255
249
if revision.revision_id == CURRENT_REVISION:
263
257
tree2 = repository.revision_tree(NULL_REVISION)
264
258
from bzrlib.plugins.gtk.diff import DiffWindow
265
window = DiffWindow(self)
259
window = DiffWindow()
266
260
window.set_diff("Diff for line %d" % (row+1), tree1, tree2)
267
261
window.set_file(tree1.id2path(self.file_id))
271
265
def _create_annotate_view(self):
273
267
tv.set_rules_hint(False)
274
268
tv.connect("cursor-changed", self._activate_selected_revision)
276
270
tv.connect("row-activated", self.line_diff)
278
cell = Gtk.CellRendererText()
272
cell = gtk.CellRendererText()
279
273
cell.set_property("xalign", 1.0)
280
274
cell.set_property("ypad", 0)
281
275
cell.set_property("family", "Monospace")
282
276
cell.set_property("cell-background-gdk",
283
tv.get_style().bg[Gtk.StateType.NORMAL])
284
col = Gtk.TreeViewColumn()
277
tv.get_style().bg[gtk.STATE_NORMAL])
278
col = gtk.TreeViewColumn()
285
279
col.set_resizable(False)
286
col.pack_start(cell, True)
280
col.pack_start(cell, expand=True)
287
281
col.add_attribute(cell, "text", LINE_NUM_COL)
288
282
tv.append_column(col)
290
cell = Gtk.CellRendererText()
284
cell = gtk.CellRendererText()
291
285
cell.set_property("ypad", 0)
292
cell.set_property("ellipsize", Pango.EllipsizeMode.END)
286
cell.set_property("ellipsize", pango.ELLIPSIZE_END)
293
287
cell.set_property("cell-background-gdk",
294
self.get_style().bg[Gtk.StateType.NORMAL])
295
col = Gtk.TreeViewColumn("Committer")
288
self.get_style().bg[gtk.STATE_NORMAL])
289
col = gtk.TreeViewColumn("Committer")
296
290
col.set_resizable(True)
297
col.pack_start(cell, True)
291
col.pack_start(cell, expand=True)
298
292
col.add_attribute(cell, "text", COMMITTER_COL)
299
293
tv.append_column(col)
301
cell = Gtk.CellRendererText()
295
cell = gtk.CellRendererText()
302
296
cell.set_property("xalign", 1.0)
303
297
cell.set_property("ypad", 0)
304
298
cell.set_property("cell-background-gdk",
305
self.get_style().bg[Gtk.StateType.NORMAL])
306
col = Gtk.TreeViewColumn("Revno")
299
self.get_style().bg[gtk.STATE_NORMAL])
300
col = gtk.TreeViewColumn("Revno")
307
301
col.set_resizable(False)
308
col.pack_start(cell, True)
302
col.pack_start(cell, expand=True)
309
303
col.add_attribute(cell, "markup", REVNO_COL)
310
304
tv.append_column(col)
312
cell = Gtk.CellRendererText()
306
cell = gtk.CellRendererText()
313
307
cell.set_property("ypad", 0)
314
308
cell.set_property("family", "Monospace")
315
col = Gtk.TreeViewColumn()
309
col = gtk.TreeViewColumn()
316
310
col.set_resizable(False)
317
col.pack_start(cell, True)
311
col.pack_start(cell, expand=True)
318
312
# col.add_attribute(cell, "foreground", HIGHLIGHT_COLOR_COL)
319
313
col.add_attribute(cell, "background", HIGHLIGHT_COLOR_COL)
320
314
col.add_attribute(cell, "text", TEXT_LINE_COL)
321
315
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)
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)
337
328
def _create_back_button(self):
338
button = Gtk.Button()
329
button = gtk.Button()
339
330
button.set_use_stock(True)
340
331
button.set_label("gtk-go-back")
341
332
button.connect("clicked", lambda w: self.go_back())
342
button.set_relief(Gtk.ReliefStyle.NONE)
333
button.set_relief(gtk.RELIEF_NONE)
346
337
def _create_forward_button(self):
347
button = Gtk.Button()
338
button = gtk.Button()
348
339
button.set_use_stock(True)
349
340
button.set_label("gtk-go-forward")
350
341
button.connect("clicked", lambda w: self.go_forward())
351
button.set_relief(Gtk.ReliefStyle.NONE)
342
button.set_relief(gtk.RELIEF_NONE)
353
344
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
347
def go_back(self):
378
348
last_tree = self.tree
379
349
rev_id = self._selected_revision()
398
368
rev_id = self._selected_revision()
399
369
if self.file_id in target_tree:
400
370
offset = self.get_scroll_offset(target_tree)
401
path, col = self.annoview.get_cursor()
402
(row,) = path.get_indices()
371
(row,), col = self.annoview.get_cursor()
403
372
self.annotate(target_tree, self.branch, self.file_id)
404
373
new_row = row+offset
407
new_path = Gtk.TreePath(path=new_row)
408
self.annoview.set_cursor(new_path, None, False)
376
self.annoview.set_cursor(new_row)
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