|
# datamine.py - Data Mining dialog for TortoiseHg
#
# Copyright 2008 Steve Borho <steve@borho.org>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2, incorporated herein by reference.
import gtk
import gobject
import os
import pango
import Queue
import threading
import re
from mercurial import util, error
from tortoisehg.util.i18n import _
from tortoisehg.util import hglib, thread2
from tortoisehg.util.colormap import AnnotateColorMap
from tortoisehg.util.colormap import AnnotateColorSaturation
from tortoisehg.hgtk.logview.treeview import TreeView as LogTreeView
from tortoisehg.hgtk.logview import treemodel as LogTreeModelModule
from tortoisehg.hgtk import gtklib, gdialog, changeset, statusbar, csinfo
# Column indexes for grep
GCOL_REVID = 0
GCOL_LINE = 1 # matched line
GCOL_DESC = 2 # utf-8, escaped summary
GCOL_PATH = 3
# Column indexes for annotation
ACOL_REVID = 0
ACOL_LINE = 1 # file line
ACOL_DESC = 2 # utf-8, escaped summary
ACOL_PATH = 3
ACOL_COLOR = 4
ACOL_USER = 5
ACOL_LNUM = 6 # line number
class DataMineDialog(gdialog.GWindow):
def get_title(self):
return _('%s - datamine') % self.get_reponame()
def get_icon(self):
return 'menurepobrowse.ico'
def parse_opts(self):
pass
def get_tbbuttons(self):
self.stop_button = self.make_toolbutton(gtk.STOCK_STOP, _('Stop'),
self.stop_current_search,
tip=_('Stop operation on current tab'))
return [
self.make_toolbutton(gtk.STOCK_FIND, _('New Search'),
self.search_clicked,
tip=_('Open new search tab')),
self.stop_button
]
def prepare_display(self):
root = self.repo.root
cf = []
for f in self.pats:
try:
if os.path.isfile(f):
cf.append(util.canonpath(root, self.cwd, f))
elif os.path.isdir(f):
for fn in os.listdir(f):
fname = os.path.join(f, fn)
if not os.path.isfile(fname):
continue
cf.append(util.canonpath(root, self.cwd, fname))
except util.Abort:
pass
for f in cf:
self.add_annotate_page(f, '.')
if not self.notebook.get_n_pages():
self.add_search_page()
os.chdir(root)
def save_settings(self):
settings = gdialog.GWindow.save_settings(self)
return settings
def load_settings(self, settings):
gdialog.GWindow.load_settings(self, settings)
self.tabwidth = hglib.gettabwidth(self.repo.ui)
def get_body(self):
""" Initialize the Dialog. """
self.grep_cmenu = self.grep_context_menu()
self.changedesc = {}
self.newpagecount = 1
self.currev = None
vbox = gtk.VBox()
notebook = gtk.Notebook()
notebook.set_tab_pos(gtk.POS_TOP)
notebook.set_scrollable(True)
notebook.popup_enable()
notebook.show()
self.notebook = notebook
vbox.pack_start(self.notebook, True, True, 2)
self.stop_button.set_sensitive(False)
accelgroup = gtk.AccelGroup()
self.add_accel_group(accelgroup)
mod = gtklib.get_thg_modifier()
key, modifier = gtk.accelerator_parse(mod+'w')
notebook.add_accelerator('thg-close', accelgroup, key,
modifier, gtk.ACCEL_VISIBLE)
notebook.connect('thg-close', self.close_notebook)
key, modifier = gtk.accelerator_parse(mod+'n')
notebook.add_accelerator('thg-new', accelgroup, key,
modifier, gtk.ACCEL_VISIBLE)
notebook.connect('thg-new', self.new_notebook)
# status bar
hbox = gtk.HBox()
style = csinfo.labelstyle(contents=('%(shortuser)s@%(revnum)s '
'%(dateage)s', ' "%(summary)s"',), selectable=True)
self.cslabel = csinfo.create(self.repo, style=style)
hbox.pack_start(self.cslabel, False, False, 4)
self.stbar = statusbar.StatusBar()
hbox.pack_start(self.stbar)
vbox.pack_start(hbox, False, False)
return vbox
def _destroying(self, gtkobj):
self.stop_all_searches()
gdialog.GWindow._destroying(self, gtkobj)
def ann_header_context_menu(self, treeview):
m = gtklib.MenuBuilder()
m.append(_('Filename'), self.toggle_annatate_columns,
ascheck=True, args=[treeview, 2])
m.append(_('User'), self.toggle_annatate_columns,
ascheck=True, args=[treeview, 3])
menu = m.build()
menu.show_all()
return menu
def grep_context_menu(self):
m = gtklib.MenuBuilder()
m.append(_('Di_splay Change'), self.cmenu_display,
'menushowchanged.ico')
m.append(_('_Annotate File'), self.cmenu_annotate, 'menublame.ico')
m.append(_('_File History'), self.cmenu_file_log, 'menulog.ico')
m.append(_('_View File at Revision'), self.cmenu_view, gtk.STOCK_EDIT)
menu = m.build()
menu.show_all()
return menu
def annotate_context_menu(self, objs):
m = gtklib.MenuBuilder()
m.append(_('_Zoom to Change'), self.cmenu_zoom, gtk.STOCK_ZOOM_IN,
args=[objs])
m.append(_('Di_splay Change'), self.cmenu_display,
'menushowchanged.ico')
(frame, treeview, filepath, graphview) = objs
path = graphview.get_path_at_revid(int(self.currev))
filepath = graphview.get_wfile_at_path(path)
ctx = self.repo[self.currev]
fctx = ctx.filectx(filepath)
parents = fctx.parents()
if len(parents) > 0:
if len(parents) == 1:
m.append(_('_Annotate Parent'), self.cmenu_annotate_1st_parent,
'menublame.ico', args=[objs])
else:
m.append(_('_Annotate First Parent'), self.cmenu_annotate_1st_parent,
'menublame.ico', args=[objs])
m.append(_('Annotate Second Parent'), self.cmenu_annotate_2nd_parent,
'menublame.ico', args=[objs])
m.append(_('_View File at Revision'), self.cmenu_view, gtk.STOCK_EDIT)
m.append(_('_File History'), self.cmenu_file_log, 'menulog.ico')
m.append(_('_Diff to Local'), self.cmenu_local_diff)
menu = m.build()
menu.show_all()
return menu
def cmenu_zoom(self, menuitem, objs):
(frame, treeview, path, graphview) = objs
graphview.scroll_to_revision(int(self.currev))
graphview.set_revision_id(int(self.currev))
def cmenu_display(self, menuitem):
statopts = {'rev' : [self.currev] }
dlg = changeset.ChangeSet(self.ui, self.repo, self.cwd, [], statopts)
dlg.display()
def cmenu_view(self, menuitem):
self._node2 = self.currev
self._view_files([self.curpath], False)
def cmenu_annotate(self, menuitem):
self.add_annotate_page(self.curpath, self.currev)
def cmenu_annotate_1st_parent(self, menuitem, objs):
self.annotate_parent(objs, 0)
def cmenu_annotate_2nd_parent(self, menuitem, objs):
self.annotate_parent(objs, 1)
def annotate_parent(self, objs, parent_idx):
(frame, treeview, filepath, graphview) = objs
path = graphview.get_path_at_revid(int(self.currev))
filepath = graphview.get_wfile_at_path(path)
ctx = self.repo[self.currev]
fctx = ctx.filectx(filepath)
parents = fctx.parents()
parent_fctx = parents[parent_idx]
parent_revid = parent_fctx.changectx().rev()
filepath = parent_fctx.path()
# annotate file of parent rev
self.trigger_annotate(parent_revid, filepath, objs)
graphview.scroll_to_revision(int(parent_revid))
graphview.set_revision_id(int(parent_revid))
def cmenu_local_diff(self, menuitem):
opts = {'rev':[str(self.currev)], 'bundle':None}
self._do_diff([self.curpath], opts)
def cmenu_file_log(self, menuitem):
from tortoisehg.hgtk import history
dlg = history.run(self.ui, filehist=self.curpath)
dlg.display()
def grep_button_release(self, widget, event):
if event.button == 3 and not (event.state & (gtk.gdk.SHIFT_MASK |
gtk.gdk.CONTROL_MASK)):
self.grep_popup_menu(widget, event.button, event.time)
return False
def grep_popup_menu(self, treeview, button=0, time=0):
self.grep_cmenu.popup(None, None, None, button, time)
return True
def grep_thgdiff(self, treeview):
if self.currev:
self._do_diff([], {'change' : self.currev})
def grep_row_act(self, tree, path, column):
'Default action is the first entry in the context menu'
self.grep_cmenu.get_children()[0].activate()
return True
def get_rev_desc(self, rev):
if rev in self.changedesc:
return self.changedesc[rev]
ctx = self.repo[rev]
author = hglib.toutf(hglib.username(ctx.user()))
date = hglib.toutf(hglib.displaytime(ctx.date()))
text = hglib.tounicode(ctx.description()).replace(u'\0', '')
lines = text.splitlines()
summary = hglib.toutf(lines and lines[0] or '')
desc = gtklib.markup_escape_text('%s@%s %s "%s"' % \
(author, rev, date, summary))
self.changedesc[rev] = (desc, author)
return self.changedesc[rev]
def search_clicked(self, button, data):
self.add_search_page()
def create_tab_close_button(self):
button = gtk.Button()
iconBox = gtk.HBox(False, 0)
image = gtk.Image()
image.set_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)
gtk.Button.set_relief(button, gtk.RELIEF_NONE)
settings = gtk.Widget.get_settings(button)
(w,h) = gtk.icon_size_lookup_for_settings(settings, gtk.ICON_SIZE_MENU)
gtk.Widget.set_size_request(button, w + 4, h + 4)
image.show()
iconBox.pack_start(image, True, False, 0)
button.add(iconBox)
iconBox.show()
return button
def close_notebook(self, notebook):
if notebook.get_n_pages() <= 1:
gtklib.thgclose(self)
else:
self.close_current_page()
def new_notebook(self, notebook):
self.add_search_page()
def add_search_page(self):
frame = gtk.Frame()
frame.set_border_width(10)
vbox = gtk.VBox()
search_hbox = gtk.HBox()
regexp = gtk.Entry()
includes = gtk.Entry()
if self.cwd.startswith(self.repo.root):
try:
relpath = util.canonpath(self.repo.root, self.cwd, '.')
includes.set_text(relpath)
except util.Abort:
# Some paths inside root are invalid (.hg/*)
pass
excludes = gtk.Entry()
search = gtk.Button(_('Search'))
search_hbox.pack_start(gtk.Label(_('Regexp:')), False, False, 4)
search_hbox.pack_start(regexp, True, True, 4)
search_hbox.pack_start(gtk.Label(_('Includes:')), False, False, 4)
search_hbox.pack_start(includes, True, True, 4)
search_hbox.pack_start(gtk.Label(_('Excludes:')), False, False, 4)
search_hbox.pack_start(excludes, True, True, 4)
search_hbox.pack_start(search, False, False, 4)
self.tooltips.set_tip(search, _('Start this search'))
self.tooltips.set_tip(regexp, _('Regular expression search pattern'))
self.tooltips.set_tip(includes, _('Comma separated list of '
'inclusion patterns. By default, the entire repository '
'is searched.'))
self.tooltips.set_tip(excludes, _('Comma separated list of '
'exclusion patterns. Exclusion patterns are applied '
'after inclusion patterns.'))
vbox.pack_start(search_hbox, False, False, 4)
hbox = gtk.HBox()
follow = gtk.CheckButton(_('Follow copies and renames'))
ignorecase = gtk.CheckButton(_('Ignore case'))
linenum = gtk.CheckButton(_('Show line numbers'))
showall = gtk.CheckButton(_('Show all matching revisions'))
hbox.pack_start(follow, False, False, 4)
hbox.pack_start(ignorecase, False, False, 4)
hbox.pack_start(linenum, False, False, 4)
hbox.pack_start(showall, False, False, 4)
vbox.pack_start(hbox, False, False, 4)
treeview = gtk.TreeView()
treeview.get_selection().set_mode(gtk.SELECTION_SINGLE)
treeview.set_rules_hint(True)
treeview.set_property('fixed-height-mode', True)
treeview.connect("cursor-changed", self.grep_selection_changed)
treeview.connect('button-release-event', self.grep_button_release)
treeview.connect('popup-menu', self.grep_popup_menu)
treeview.connect('row-activated', self.grep_row_act)
accelgroup = gtk.AccelGroup()
self.add_accel_group(accelgroup)
mod = gtklib.get_thg_modifier()
key, modifier = gtk.accelerator_parse(mod+'d')
treeview.add_accelerator('thg-diff', accelgroup, key,
modifier, gtk.ACCEL_VISIBLE)
treeview.connect('thg-diff', self.grep_thgdiff)
results = gtk.ListStore(str, # revision id
str, # matched line (utf-8)
str, # description (utf-8, escaped)
str) # file path (utf-8)
treeview.set_model(results)
treeview.set_search_equal_func(self.search_in_grep)
for title, width, ttype, col, emode in (
(_('Rev'), 10, 'text', GCOL_REVID, pango.ELLIPSIZE_NONE),
(_('File'), 25, 'text', GCOL_PATH, pango.ELLIPSIZE_START),
(_('Matches'), 80, 'markup', GCOL_LINE, pango.ELLIPSIZE_END)):
cell = gtk.CellRendererText()
cell.set_property('width-chars', width)
cell.set_property('ellipsize', emode)
cell.set_property('family', 'Monospace')
column = gtk.TreeViewColumn(title)
column.set_resizable(True)
column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
column.set_fixed_width(cell.get_size(treeview)[2])
column.pack_start(cell, expand=True)
column.add_attribute(cell, ttype, col)
treeview.append_column(column)
if hasattr(treeview, 'set_tooltip_column'):
treeview.set_tooltip_column(GCOL_DESC)
scroller = gtk.ScrolledWindow()
scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
scroller.add(treeview)
vbox.pack_start(scroller, True, True)
frame.add(vbox)
frame.show_all()
hbox = gtk.HBox()
lbl = gtk.Label(_('Search %d') % self.newpagecount)
close = self.create_tab_close_button()
close.connect('clicked', self.close_page, frame)
hbox.pack_start(lbl, True, True, 2)
hbox.pack_start(close, False, False)
hbox.show_all()
num = self.notebook.append_page(frame, hbox)
self.newpagecount += 1
objs = (treeview.get_model(), frame, regexp, follow, ignorecase,
excludes, includes, linenum, showall, search_hbox)
# Clicking 'search' or hitting Enter in any text entry triggers search
search.connect('clicked', self.trigger_search, objs)
regexp.connect('activate', self.trigger_search, objs)
includes.connect('activate', self.trigger_search, objs)
excludes.connect('activate', self.trigger_search, objs)
# Includes/excludes must disable following copies
objs = (includes, excludes, follow)
includes.connect('changed', self.update_following_possible, objs)
excludes.connect('changed', self.update_following_possible, objs)
self.update_following_possible(includes, objs)
if hasattr(self.notebook, 'set_tab_reorderable'):
self.notebook.set_tab_reorderable(frame, True)
self.notebook.set_current_page(num)
regexp.grab_focus()
def search_in_grep(self, model, column, key, iter):
"""Searches all fields shown in the tree when the user hits crtr+f,
not just the ones that are set via tree.set_search_column.
Case insensitive
"""
key = key.lower()
for col in (GCOL_PATH, GCOL_LINE):
if key in model.get_value(iter, col).lower():
return False
return True
def trigger_search(self, button, objs):
(model, frame, regexp, follow, ignorecase,
excludes, includes, linenum, showall, search_hbox) = objs
retext = regexp.get_text()
if not retext:
gdialog.Prompt(_('No regular expression given'),
_('You must provide a search expression'), self).run()
regexp.grab_focus()
return
try:
re.compile(retext)
except re.error, e:
gdialog.Prompt(_('Invalid regular expression'),
_('Error: %s') % str(e), self).run()
regexp.grab_focus()
return
q = Queue.Queue()
args = [q, 'grep']
if follow.get_active(): args.append('--follow')
if ignorecase.get_active(): args.append('--ignore-case')
if linenum.get_active(): args.append('--line-number')
if showall.get_active(): args.append('--all')
incs = [x.strip() for x in includes.get_text().split(',')]
excs = [x.strip() for x in excludes.get_text().split(',')]
for i in incs:
if i: args.extend(['-I', i])
for x in excs:
if x: args.extend(['-X', x])
args.append(retext)
def threadfunc(q, *args):
try:
hglib.hgcmd_toq(q, True, args)
except (util.Abort, error.LookupError), e:
self.stbar.set_text(_('Abort: %s') % str(e))
thread = thread2.Thread(target=threadfunc, args=args)
thread.start()
frame._mythread = thread
self.stop_button.set_sensitive(True)
model.clear()
search_hbox.set_sensitive(False)
self.stbar.begin(msg='hg ' + ' '.join(args[2:]))
hbox = gtk.HBox()
lbl = gtk.Label(_('Search "%s"') % retext.split()[0])
close = self.create_tab_close_button()
close.connect('clicked', self.close_page, frame)
hbox.pack_start(lbl, True, True, 2)
hbox.pack_start(close, False, False)
hbox.show_all()
self.notebook.set_tab_label(frame, hbox)
gobject.timeout_add(50, self.grep_wait, thread, q, model,
search_hbox, regexp, frame)
def grep_wait(self, thread, q, model, search_hbox, regexp, frame):
"""
Handle all the messages currently in the queue (if any).
"""
text = ''
while q.qsize():
data, label = q.get(0)
data = gtklib.markup_escape_text(hglib.toutf(data))
if label == 'grep.match':
text += '<span foreground="red"><b>%s</b></span>' % data
else:
text += data
for line in text.splitlines():
try:
(path, revid, text) = line.split(':', 2)
desc, user = self.get_rev_desc(long(revid))
except ValueError:
continue
if self.tabwidth:
text = text.expandtabs(self.tabwidth)
model.append((revid, text, desc, hglib.toutf(path)))
if thread.isAlive():
return True
else:
if threading.activeCount() == 1:
self.stop_button.set_sensitive(False)
frame._mythread = None
search_hbox.set_sensitive(True)
regexp.grab_focus()
self.stbar.end()
return False
def grep_selection_changed(self, treeview):
"""
Callback for when the user selects grep output.
"""
(path, focus) = treeview.get_cursor()
model = treeview.get_model()
if path is not None and model is not None:
iter = model.get_iter(path)
self.currev = model[iter][GCOL_REVID]
self.curpath = hglib.fromutf(model[iter][GCOL_PATH])
self.cslabel.update(model[iter][GCOL_REVID])
def close_current_page(self):
num = self.notebook.get_current_page()
if num != -1 and self.notebook.get_n_pages():
self.notebook.remove_page(num)
def stop_current_search(self, button, widget):
num = self.notebook.get_current_page()
frame = self.notebook.get_nth_page(num)
self.stop_search(frame)
def stop_all_searches(self):
for num in xrange(self.notebook.get_n_pages()):
frame = self.notebook.get_nth_page(num)
self.stop_search(frame)
def stop_search(self, frame):
if getattr(frame, '_mythread', None):
if frame._mythread.isAlive():
try:
frame._mythread.terminate()
frame._mythread.join()
except (threading.ThreadError, ValueError):
pass
frame._mythread = None
def close_page(self, button, widget):
'''Close page button has been pressed'''
num = self.notebook.page_num(widget)
if num != -1:
self.notebook.remove_page(num)
if self.notebook.get_n_pages() < 1:
self.newpagecount = 1
self.add_search_page()
def add_header_context_menu(self, col, menu):
lb = gtk.Label(col.get_title())
lb.show()
col.set_widget(lb)
wgt = lb.get_parent()
while wgt:
if type(wgt) == gtk.Button:
wgt.connect("button-press-event",
self.tree_header_button_press, menu)
break
wgt = wgt.get_parent()
def tree_header_button_press(self, widget, event, menu):
if event.button == 3:
menu.popup(None, None, None, event.button, event.time)
return True
return False
def update_following_possible(self, widget, objs):
(includes, excludes, follow) = objs
allow = not includes.get_text() and not excludes.get_text()
if not allow:
follow.set_active(False)
follow.set_sensitive(allow)
def add_annotate_page(self, path, revid):
'''
Add new annotation page to notebook. Start scan of
file 'path' revision history, start annotate of supplied
revision 'revid'.
'''
if revid == '.':
ctx = self.repo.parents()[0]
try:
fctx = ctx.filectx(path)
except error.LookupError:
gdialog.Prompt(_('File is unrevisioned'),
_('Unable to annotate ') + path, self).run()
return
rev = fctx.filelog().linkrev(fctx.filerev())
revid = str(rev)
else:
rev = long(revid)
frame = gtk.Frame()
frame.set_border_width(10)
vbox = gtk.VBox()
graphopts = { 'date': None, 'no_merges':False, 'only_merges':False,
'keyword':[], 'branch':None, 'pats':[], 'revrange':[],
'revlist':[], 'noheads':False, 'orig-tip':len(self.repo),
'branch-view':False, 'rev':[], 'npreviews':0 }
graphopts['filehist'] = path
# File log revision graph
graphview = LogTreeView(self.repo, 5000)
graphview.set_property('rev-column-visible', True)
graphview.set_property('msg-column-visible', True)
graphview.set_property('user-column-visible', True)
graphview.set_property('age-column-visible', True)
graphview.set_columns(['graph', 'rev', 'msg', 'user', 'age'])
graphview.connect('revisions-loaded', self.revisions_loaded, rev)
graphview.refresh(True, [path], graphopts)
# Annotation text tree view
treeview = gtk.TreeView()
treeview.get_selection().set_mode(gtk.SELECTION_SINGLE)
treeview.set_property('fixed-height-mode', True)
treeview.set_border_width(0)
accelgroup = gtk.AccelGroup()
self.add_accel_group(accelgroup)
mod = gtklib.get_thg_modifier()
key, modifier = gtk.accelerator_parse(mod+'d')
treeview.add_accelerator('thg-diff', accelgroup, key,
modifier, gtk.ACCEL_VISIBLE)
treeview.connect('thg-diff', self.annotate_thgdiff)
results = gtk.ListStore(str, # revision id
str, # file line (utf-8)
str, # description (utf-8, escaped)
str, # file path (utf-8)
str, # color
str, # author (utf-8)
str) # line number
treeview.set_model(results)
treeview.set_search_equal_func(self.search_in_file)
context_menu = self.ann_header_context_menu(treeview)
for title, width, col, emode, visible in (
(_('Line'), 8, ACOL_LNUM, pango.ELLIPSIZE_NONE, True),
(_('Rev'), 10, ACOL_REVID, pango.ELLIPSIZE_NONE, True),
(_('File'), 15, ACOL_PATH, pango.ELLIPSIZE_START, False),
(_('User'), 15, ACOL_USER, pango.ELLIPSIZE_END, False),
(_('Source'), 80, ACOL_LINE, pango.ELLIPSIZE_END, True)):
cell = gtk.CellRendererText()
cell.set_property('width-chars', width)
cell.set_property('ellipsize', emode)
cell.set_property('family', 'Monospace')
column = gtk.TreeViewColumn(title)
column.set_resizable(True)
column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
column.set_fixed_width(cell.get_size(treeview)[2])
column.pack_start(cell, expand=True)
column.add_attribute(cell, 'text', col)
column.add_attribute(cell, 'background', ACOL_COLOR)
column.set_visible(visible)
treeview.append_column(column)
self.add_header_context_menu(column, context_menu)
treeview.set_headers_clickable(True)
if hasattr(treeview, 'set_tooltip_column'):
treeview.set_tooltip_column(ACOL_DESC)
results.path = path
results.rev = revid
scroller = gtk.ScrolledWindow()
scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
scroller.add(treeview)
vpaned = gtk.VPaned()
vpaned.pack1(graphview, True, True)
vpaned.pack2(scroller, True, True)
vbox.pack_start(vpaned, True, True)
frame.add(vbox)
frame.show_all()
hbox = gtk.HBox()
lbl = gtk.Label(hglib.toutf(os.path.basename(path) + '@' + revid))
close = self.create_tab_close_button()
close.connect('clicked', self.close_page, frame)
hbox.pack_start(lbl, True, True, 2)
hbox.pack_start(close, False, False)
hbox.show_all()
num = self.notebook.append_page_menu(frame,
hbox, gtk.Label(hglib.toutf(path + '@' + revid)))
if hasattr(self.notebook, 'set_tab_reorderable'):
self.notebook.set_tab_reorderable(frame, True)
self.notebook.set_current_page(num)
graphview.connect('revision-selected', self.log_selection_changed, path)
objs = (frame, treeview, path, graphview)
graphview.treeview.connect('row-activated', self.log_activate, objs)
graphview.treeview.connect('button-release-event',
self.ann_button_release, objs)
graphview.treeview.connect('popup-menu', self.ann_popup_menu, objs)
treeview.connect("cursor-changed", self.ann_selection_changed)
treeview.connect('button-release-event', self.ann_button_release, objs)
treeview.connect('popup-menu', self.ann_popup_menu, objs)
treeview.connect('row-activated', self.ann_row_act, objs)
self.stbar.begin(msg=_('Loading history...'))
def search_in_file(self, model, column, key, iter):
"""Searches all fields shown in the tree when the user hits crtr+f,
not just the ones that are set via tree.set_search_column.
Case insensitive
"""
key = key.lower()
for col in (ACOL_USER, ACOL_LINE):
if key in model.get_value(iter, col).lower():
return False
return True
def annotate_thgdiff(self, treeview):
self._do_diff([], {'change' : self.currev})
def toggle_annatate_columns(self, button, treeview, col):
b = button.get_active()
treeview.get_column(col).set_visible(b)
def log_selection_changed(self, graphview, path):
treeview = graphview.treeview
(model, paths) = treeview.get_selection().get_selected_rows()
if not paths:
return
revid = graphview.get_revid_at_path(paths[0])
self.currev = str(revid)
wfile = graphview.get_wfile_at_path(paths[0])
if wfile:
self.curpath = wfile
def log_activate(self, treeview, path, column, objs):
(frame, treeview, file, graphview) = objs
rev = graphview.get_revid_at_path(path)
wfile = graphview.get_wfile_at_path(path)
self.trigger_annotate(rev, wfile, objs)
def revisions_loaded(self, graphview, rev):
self.stbar.end()
graphview.set_revision_id(rev)
treeview = graphview.treeview
path, column = treeview.get_cursor()
# It's possible that the requested change was not found in the
# file's filelog history. In that case, no row will be
# selected.
if path != None and column != None:
treeview.row_activated(path, column)
def trigger_annotate(self, rev, path, objs):
'''
User has selected a file revision to annotate. Trigger a
background thread to perform the annotation. Disable the select
button until this operation is complete.
'''
def threadfunc(q, *args):
try:
hglib.hgcmd_toq(q, False, args)
except (util.Abort, error.LookupError), e:
self.stbar.set_text(_('Abort: %s') % str(e))
(frame, treeview, origpath, graphview) = objs
q = Queue.Queue()
# Use short -f here because it's meaning has changed, it used
# to be --follow but now it means --file. We want either.
# Replace -f with --file when support for hg-1.4 is dropped
args = [q, 'annotate', '-f', '--number', '--rev', str(rev),
'path:'+path]
thread = thread2.Thread(target=threadfunc, args=args)
thread.start()
frame._mythread = thread
self.stop_button.set_sensitive(True)
# date of selected revision
ctx = self.repo[long(rev)]
curdate = ctx.date()[0]
colormap = AnnotateColorSaturation()
model, rows = treeview.get_selection().get_selected_rows()
model.clear()
self.stbar.begin(msg=hglib.toutf('hg ' + ' '.join(args[2:])))
hbox = gtk.HBox()
lbl = gtk.Label(hglib.toutf(os.path.basename(path) + '@' + str(rev)))
close = self.create_tab_close_button()
close.connect('clicked', self.close_page, frame)
hbox.pack_start(lbl, True, True, 2)
hbox.pack_start(close, False, False)
hbox.show_all()
self.notebook.set_tab_label(frame, hbox)
gobject.timeout_add(50, self.annotate_wait, thread, q, treeview,
curdate, colormap, frame, rows)
def annotate_wait(self, thread, q, tview, curdate, colormap, frame, rows):
"""
Handle all the messages currently in the queue (if any).
"""
model = tview.get_model()
while q.qsize():
line = q.get(0).rstrip('\r\n')
try:
(revpath, text) = line.split(':', 1)
revid, path = revpath.lstrip().split(' ', 1)
rowrev = long(revid)
except ValueError:
continue
desc, user = self.get_rev_desc(rowrev)
ctx = self.repo[rowrev]
color = colormap.get_color(ctx, curdate)
if self.tabwidth:
text = text.expandtabs(self.tabwidth)
model.append((revid, hglib.toutf(text[:512]), desc,
hglib.toutf(path.strip()), color, user, len(model)+1))
if thread.isAlive():
return True
else:
if threading.activeCount() == 1:
self.stop_button.set_sensitive(False)
if rows:
tview.get_selection().select_path(rows[0])
tview.scroll_to_cell(rows[0], use_align=True, row_align=0.5)
tview.grab_focus()
frame._mythread = None
self.stbar.end()
return False
def ann_selection_changed(self, treeview):
"""
User selected line of annotate output, describe revision
responsible for this line in the status bar
"""
(path, focus) = treeview.get_cursor()
model = treeview.get_model()
if path is not None and model is not None:
anniter = model.get_iter(path)
self.currev = model[anniter][ACOL_REVID]
self.path = model.path
self.cslabel.update(model[anniter][ACOL_REVID])
def ann_button_release(self, widget, event, objs):
if event.button == 3 and not (event.state & (gtk.gdk.SHIFT_MASK |
gtk.gdk.CONTROL_MASK)):
self.ann_popup_menu(widget, event.button, event.time, objs)
return False
def ann_popup_menu(self, treeview, button, time, objs):
ann_cmenu = self.annotate_context_menu(objs)
ann_cmenu.popup(None, None, None, button, time)
return True
def ann_row_act(self, tree, path, column, objs):
ann_cmenu = self.annotate_context_menu(objs)
ann_cmenu.get_children()[0].activate()
def run(ui, *pats, **opts):
cmdoptions = {
'follow':False, 'follow-first':False, 'copies':False, 'keyword':[],
'limit':0, 'rev':[], 'removed':False, 'no_merges':False, 'date':None,
'only_merges':None, 'prune':[], 'git':False, 'verbose':False,
'include':[], 'exclude':[]
}
return DataMineDialog(ui, None, None, pats, cmdoptions)
|
Loading...