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
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)
424
457
self.__cache[revision_id] = revision
425
458
return self.__cache[revision_id]
427
class SearchBox(gtk.HBox):
461
class SearchBox(Gtk.HBox):
428
462
"""A button box for searching in text or lines of annotations"""
429
463
def __init__(self):
430
gtk.HBox.__init__(self, False, 6)
464
super(SearchBox, self).__init__(homogeneous=False, spacing=6)
433
button = gtk.Button()
435
image.set_from_stock('gtk-stop', gtk.ICON_SIZE_BUTTON)
467
button = Gtk.Button()
469
image.set_from_stock('gtk-stop', Gtk.IconSize.BUTTON)
436
470
button.set_image(image)
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)
471
button.set_relief(Gtk.ReliefStyle.NONE)
472
button.connect("clicked", lambda w: self.hide())
473
self.pack_start(button, False, False, 0)
443
477
self._label = label
444
self.pack_start(label, expand=False, fill=False)
478
self.pack_start(label, False, False, 0)
447
481
self._entry = entry
448
482
entry.connect("activate", lambda w, d: self._do_search(d),
450
self.pack_start(entry, expand=False, fill=False)
484
self.pack_start(entry, False, False, 0)
452
486
# Next/previous buttons
453
button = gtk.Button('_Next')
455
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)
456
490
button.set_image(image)
457
491
button.connect("clicked", lambda w, d: self._do_search(d),
459
self.pack_start(button, expand=False, fill=False)
493
self.pack_start(button, False, False, 0)
461
button = gtk.Button('_Previous')
463
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)
464
498
button.set_image(image)
465
499
button.connect("clicked", lambda w, d: self._do_search(d),
467
self.pack_start(button, expand=False, fill=False)
501
self.pack_start(button, False, False, 0)
470
check = gtk.CheckButton('Match case')
504
check = Gtk.CheckButton('Match case')
471
505
self._match_case = check
472
self.pack_start(check, expand=False, fill=False)
506
self.pack_start(check, False, False, 0)
474
check = gtk.CheckButton('Regexp')
508
check = Gtk.CheckButton('Regexp')
475
509
check.connect("toggled", lambda w: self._set_label())
476
510
self._regexp = check
477
self.pack_start(check, expand=False, fill=False)
511
self.pack_start(check, False, False, 0)
479
513
self._view = None
480
514
self._column = None
508
542
def _match(self, model, iterator, column):
509
543
matching_case = self._match_case.get_active()
510
string, = model.get(iterator, column)
544
cell_value, = model.get(iterator, column)
511
545
key = self._entry.get_text()
512
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():
513
551
if matching_case:
514
match = re.compile(key).search(string, 1)
552
match = re.compile(key).search(cell_value, 1)
516
match = re.compile(key, re.I).search(string, 1)
554
match = re.compile(key, re.I).search(cell_value, 1)
518
556
if not matching_case:
519
string = string.lower()
557
cell_value = cell_value.lower()
520
558
key = key.lower()
521
match = string.find(key) != -1
559
match = cell_value.find(key) != -1