Staging
v0.5.0
v0.5.0
https://foss.heptapod.net/mercurial/hgview
Tip revision: 6b70062505a2d3001f5208161f8a68694534505c authored by David Douard on 30 May 2012, 10:09:40 UTC
close 0.x
close 0.x
Tip revision: 6b70062
hgview_gtk.py
# -*- coding: iso-8859-1 -*-
#!/usr/bin/env python
# hgview.py - gtk-based hgk
#
# Copyright (C) 2007 Logilab. All rights reserved.
#
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.
"""
Main gtk application for hgview
"""
import sys, os
import time
import re
from optparse import OptionParser
from os.path import dirname, join, isfile
import gtk
import gtk.glade
import gobject
import pango
import hgview.fixes
from graphrenderer import RevGraphRenderer
from diffstatrenderer import DiffStatRenderer
from hgview.hgrepo import HgHLRepo, short_hex, short_bin
GLADE_FILE_NAME = "hgview.glade"
def watch_displayed(meth):
"""
Method to be used as a decorator. The decorated method will
have automatically a watch displayed until they return.
Works less reliably than watch_displayed, but can returns something...
"""
assert callable(meth)
def watched(self, *args, **kw):
watch = gtk.gdk.Cursor(gtk.gdk.WATCH)
w = self.xml.get_widget('window_main')
w.window.set_cursor(watch)
# Give the mainloop a chance to change the cursor
# This might be dangerous!
gtk.main_iteration(block=False)
try:
ret = meth(self, *args, **kw)
return ret
finally:
w.window.set_cursor(None)
return watched
def load_glade(root=""):
"""Try several paths in which the glade file might be found"""
for _path in [dirname(__file__),
join(sys.exec_prefix, 'share/hgview'),
os.path.expanduser('~/share/hgview'),
join(dirname(__file__), "../../../../share/hgview"),
join(dirname(__file__), "../../../../../share/hgview"),
]:
glade_file = join(_path, GLADE_FILE_NAME)
if isfile(glade_file):
break
else:
raise ValueError("Unable to find hgview.glade."
"Check your installation.")
return gtk.glade.XML(glade_file, root)
#import hotshot
#PROF = hotshot.Profile("/tmp/hgview.prof")
DIFFHDR = "=== %s ===\n"
M_ID = 0
M_NODE = 1
M_NODEX = 2
M_EDGES = 3
M_DATE = 4
M_GRAPHORDER = 5
M_AUTH = 6
M_AUTHCOLOR = 7
M_REV = 8
def make_texttag( name, **kwargs ):
"""Helper function generating a TextTag"""
tag = gtk.TextTag(name)
for key, value in kwargs.items():
key = key.replace("_","-")
try:
tag.set_property( key, value )
except TypeError:
print "Warning the property %s is unsupported in this version of pygtk" % key
return tag
class SearchState(object):
__slots__ = ('rev_iter', 'text_iter', 'last_grep_string', 'last_find_string')
def __init__(self, **kwargs):
for k, v in kwargs.iteritems():
setattr(self, k, v)
class HgViewApp(object):
"""Main hg view application"""
def __init__(self, repo, filerex = None ):
self.xml = load_glade("window_main")
self.xml.signal_autoconnect( self )
statusbar = self.xml.get_widget("statusbar1")
self.progressbar = gtk.ProgressBar()
self.progressbar.hide()
statusbar.pack_start( self.progressbar )
self.repo = repo
self.filerex = filerex
if filerex:
self.filter_files = re.compile( filerex )
else:
self.filter_files = None
self.filter_noderange = None
self.branch_selected = None
self._search_state = None
self.SHOW_GRAPH = True
# The strings are stored as PYOBJECT when they contain zeros and also
# to save memory when they are used by the custom renderer
self.revisions = gtk.ListStore( gobject.TYPE_PYOBJECT, # node id
gobject.TYPE_PYOBJECT, # node
gobject.TYPE_PYOBJECT, # x for the node
gobject.TYPE_PYOBJECT, # lines to draw
gobject.TYPE_FLOAT, # date
gobject.TYPE_INT64, # natural order
gobject.TYPE_STRING, # author
gobject.TYPE_STRING, # author color
gobject.TYPE_INT64, # node id
)
self.filelist = gtk.ListStore( gobject.TYPE_STRING, # filename
gobject.TYPE_STRING, # markname
gobject.TYPE_PYOBJECT, # diffstat
)
self.graph = None
self.find_text = None
self._idletask_id = None
self.setup_tags()
self.setup_tree()
self.init_filter()
self.refresh_tree()
self.create_revision_popup()
self.setup_combo('branch_highlight_combo')
def create_revision_popup(self):
self.revpopup_path = None, None
tree = self.xml.get_widget( "treeview_revisions" )
self.revpopup = gtk.Menu()
self.revpopup.attach_to_widget( tree, None)
m1 = gtk.MenuItem("Add tag...")
m1.show()
m1.connect("activate", self.revpopup_add_tag)
self.revpopup.attach(m1, 0, 1, 0, 1)
# not yet implemented, disactivate
m1.set_sensitive(False)
m2 = gtk.MenuItem("Update")
m2.show()
m2.connect("activate", self.revpopup_update)
self.revpopup.attach(m2, 0, 1, 1, 2)
# not yet implemented, disactivate
m2.set_sensitive(False)
def revpopup_add_tag(self, item):
raise NotImplementedError()
def revpopup_update(self, item):
raise NotImplementedError()
def setup_combo(self, combo_name):
combo_filter = self.xml.get_widget(combo_name)
self.branch_store = gtk.ListStore( gobject.TYPE_STRING )
combo_filter.set_model(self.branch_store)
if combo_name == 'branch_highlight_combo':
self.branch_store.append( ('All',) )
for branch_name in self.repo.get_branch().keys():
self.branch_store.append( (branch_name,) )
combo_filter.set_active(0)
def get_node_branches(self):
branch_nodeRev = {}
dic_branch, _unknown, _tip_rev = self.repo.get_branches_heads()
for branch, node in dic_branch.items():
node_info = self.repo.read_node(node)
rev = node_info.rev
branch_nodeRev[rev] = branch
return branch_nodeRev
def on_refresh_activate(self, arg):
self.repo.refresh()
self.refresh_tree()
def filter_nodes(self):
"""Filter the nodes according to filter_files and filter_nodes"""
if not self.filter_files and not self.filter_noderange:
return self.repo.nodes
keepnodes = []
nodes = self.repo.nodes
frex = self.filter_files
noderange = self.filter_noderange or set(range(len(nodes)))
hide_otherbranches = self.xml.get_widget("branch_checkbox").get_active()
branch_selected = self.branch_selected
for n in nodes:
node = self.repo.read_node(n)
if node.rev in noderange:
if hide_otherbranches:
if node.branches['branch'] != branch_selected and branch_selected != 'All':
continue
if not node.files:
keepnodes.append(n)
else:
for f in node.files:
if frex.search(f):
keepnodes.append( n )
break
tree = self.xml.get_widget( "treeview_revisions" )
file_filter = self.xml.get_widget("entry_file_filter").get_text()
if file_filter:
col = tree.get_column(0)
self.revisions.set_sort_column_id(M_REV, gtk.SORT_ASCENDING)
self.SHOW_GRAPH = False
else:
col = tree.get_column(1)
self.revisions.set_sort_column_id(M_GRAPHORDER, gtk.SORT_ASCENDING)
self.SHOW_GRAPH = True
return keepnodes
def on_window_main_delete_event( self, win, evt ):
"""Bye"""
gtk.main_quit()
def on_quit1_activate( self, *args ):
"""Bye"""
gtk.main_quit()
def on_about_activate(self, *args):
""" Display about dialog """
dlg=gtk.AboutDialog()
dlg.set_authors([u'Ludovic Aubry',
u'Aur幨ien Camp嶧s',
u'David Douard',
u'Graziella Toutoungis',
])
try:
from __pkginfo__ import short_desc, long_desc, version, modname
except ImportError:
short_desc = "mercurial interactive history viewer"
long_desc = """
Its purpose is similar to the hgk tool of mercurial, and it has been
written with efficiency in mind when dealing with big repositories
(it can happily be used to browse Linux kernel source code
repository).
"""
version ='unknown'
modname ='hgview'
dlg.set_comments(short_desc)
dlg.set_name(modname)
dlg.set_version(version)
#dlg.set_logo(pixbuf)
dlg.run()
dlg.destroy()
def setup_tags(self):
"""Creates the tags to be used inside the TextView"""
textwidget = self.xml.get_widget( "textview_status" )
text_buffer = textwidget.get_buffer()
tag_table = text_buffer.get_tag_table()
tag_table.add( make_texttag( "mono", family="Monospace" ))
tag_table.add( make_texttag( "blue", foreground='blue' ))
tag_table.add( make_texttag( "red", foreground='red' ))
tag_table.add( make_texttag( "green", foreground='darkgreen' ))
tag_table.add( make_texttag( "black", foreground='black' ))
tag_table.add( make_texttag( "greybg",
paragraph_background='grey',
weight=pango.WEIGHT_BOLD ))
tag_table.add( make_texttag( "yellowbg", background='yellow' ))
link_tag = make_texttag( "link", foreground="blue",
underline=pango.UNDERLINE_SINGLE )
link_tag.connect("event", self.link_event )
tag_table.add( link_tag )
textwidget.connect("motion-notify-event", self.text_widget_move )
def text_widget_move(self, widget, event):
cursor = gtk.gdk.Cursor(gtk.gdk.XTERM)
widget.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(cursor)
def link_event( self, tag, widget, event, iter_ ):
"""Handle a click on a 'link' tag in the main TextView"""
if event.type == gtk.gdk.MOTION_NOTIFY and not \
(event.state & (gtk.gdk.BUTTON1_MASK|gtk.gdk.BUTTON2_MASK|gtk.gdk.BUTTON3_MASK)):
# we change cursor only if no button is pressed
cursor = gtk.gdk.Cursor(gtk.gdk.HAND2)
widget.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(cursor)
# return true so the event is not passed through to text_widget_move
return True
if event.type != gtk.gdk.BUTTON_RELEASE:
return
text_buffer = widget.get_buffer()
beg = iter_.copy()
while not beg.begins_tag(tag):
beg.backward_char()
end = iter_.copy()
while not end.ends_tag(tag):
end.forward_char()
text = text_buffer.get_text( beg, end )
it = self.revisions.get_iter_first()
while it:
node = self.revisions.get_value( it, M_ID )
hhex = short_hex(node)
if hhex == text:
break
it = self.revisions.iter_next( it )
if not it:
return
tree = self.xml.get_widget("treeview_revisions")
sel = tree.get_selection()
sel.select_iter(it)
path = self.revisions.get_path(it)
tree.scroll_to_cell( path )
def author_data_func( self, column, cell, model, iter_, user_data=None ):
"""A Cell datafunction used to provide the author's name and
foreground color"""
node = model.get_value( iter_, M_NODE )
branch = node.branches['branch']
cell.set_property( "text", self.repo.authors[node.author_id] )
cell.set_property( "foreground", self.repo.colors[node.author_id] )
def rev_data_func( self, column, cell, model, iter_, user_data=None ):
"""A Cell datafunction used to provide the revnode's text"""
node = model.get_value( iter_, M_NODE )
cell.set_property( "text", str(node.rev) )
row = model[iter_]
def date_data_func( self, column, cell, model, iter_, user_data=None ):
"""A Cell datafunction used to provide the date"""
node = model.get_value( iter_, M_NODE )
cell.set_property( "text", node.date )
reversed = column.get_sort_order() == gtk.SORT_DESCENDING
row = model[iter_]
def files_data_func( self, column, cell, model, iter_, user_data=None ):
"""A Cell datafunction used to provide the files"""
node = model.get_value( iter_, 0 )
txt = self._get_grep_string()
str_node = str(node).replace('&', '&').replace('<', '<')
if txt:
try:
rexp = re.compile( '(%s)' % txt.replace('&', '&').replace('<', '<') )
markup = rexp.sub('<span background="yellow">\\1</span>', str_node)
except:
# regexp incorrect
markup = str_node
else:
markup = str_node
cell.set_property("markup", '<span>%s</span>' % markup)
def setup_tree(self ):
"""Configure the 2 gtk.TreeView"""
# Setup the revisions treeview
tree = self.xml.get_widget( "treeview_revisions" )
tree.get_selection().connect("changed", self.selection_changed )
rend = gtk.CellRendererText()
col = gtk.TreeViewColumn("ID", rend, text=M_REV)
col.set_sort_column_id(M_REV)
col.set_resizable(True)
col.connect("clicked", self.sort_on_id)
tree.append_column( col )
rend = RevGraphRenderer(self)
rend.connect( "activated", self.cell_activated )
self.graph_rend = rend
col = gtk.TreeViewColumn("Log", rend, nodex=M_NODEX, edges=M_EDGES,
node=M_NODE)
col.set_resizable(True)
col.set_sizing( gtk.TREE_VIEW_COLUMN_FIXED )
col.set_fixed_width(400)
col.set_sort_column_id(M_GRAPHORDER)
col.connect("clicked", self.sort_on_id)
tree.append_column(col)
rend = gtk.CellRendererText()
col = gtk.TreeViewColumn("Author", rend, text=M_AUTH, foreground=M_AUTHCOLOR)
#col.set_cell_data_func( rend, self.author_data_func )
col.set_resizable(True)
col.set_sort_column_id(M_AUTH)
col.connect("clicked", self.sort_on_id)
tree.append_column(col)
rend = gtk.CellRendererText()
col = gtk.TreeViewColumn("Date", rend )
col.set_cell_data_func( rend, self.date_data_func )
col.set_resizable(True)
col.set_sort_column_id(M_DATE)
col.connect("clicked", self.sort_on_id)
tree.append_column( col )
tree.set_model(self.revisions )
##Setup the filelist treeview
tree_files = self.xml.get_widget( "treeview_filelist" )
#tree_files.set_rules_hint( 1 )
tree_files.get_selection().connect("changed", self.fileselection_changed )
rend = gtk.CellRendererText()
col = gtk.TreeViewColumn("Files", rend, text=0)
col.set_cell_data_func( rend, self.files_data_func )
col.set_reorderable(True)
col.set_resizable(True)
tree_files.append_column( col )
tree_files.set_model( self.filelist )
rend = DiffStatRenderer()
col = gtk.TreeViewColumn("Diff Stat", rend, stats=2 )
col.set_reorderable(True)
tree_files.append_column( col )
tree_files.set_model( self.filelist )
def sort_on_id(self, *args):
sorted_column = args[0].get_sort_column_id()
sort_type = args[0].get_sort_order().value_nick
if sort_type == 'ascending':
if sorted_column == 5:
self.SHOW_GRAPH = True
else:
self.SHOW_GRAPH = False
else:
self.SHOW_GRAPH = False
return self.SHOW_GRAPH
def cell_activated(self, *args):
print "nudge"
def refresh_tree(self):
"""Starts the process of filling the ListStore model"""
if self._idletask_id is not None:
self._idletask_id = None
gobject.main_context_default().iteration(True)
self.repo.read_nodes()
todo_nodes = self.filter_nodes()
graph = self.repo.graph( todo_nodes )
self.graph_rend.set_colors( graph.colors )
self.revisions.clear()
self.progressbar.show()
self.last_node = 0
self.graph = graph
self._idletask_id = gobject.idle_add( self.idle_fill_model )
return
def on_treeview_revisions_button_press_event(self, treeview, event):
if event.button==3:
x = int(event.x)
y = int(event.y)
time = event.time
pthinfo = treeview.get_path_at_pos(x, y)
if pthinfo is not None:
path, col, cellx, celly = pthinfo
treeview.grab_focus()
treeview.set_cursor( path, col, 0)
self.revpopup_path = path, col
self.revpopup.popup( None, None, None, event.button, time)
return 1
def idle_fill_model(self):
"""Idle task filling the ListStore model chunks by chunks"""
if self._idletask_id is None:
# self._idletask_id has been set to None, which means we
# want to cancel the background task of filling the tree
# model
self.graph = None
self.rowselected = None
self.progressbar.hide()
return False
NMAX = 300 # Max number of entries we process each time
graph = self.graph
N = self.last_node
graph.build(NMAX)
rowselected = self.graph.rows
add_rev = self.revisions.append
tree = self.xml.get_widget( "treeview_revisions" )
tree.freeze_notify()
# compute the final bound of this run
last_node = min(len(rowselected), N + NMAX)
for n in xrange(N, last_node ):
node = rowselected[n]
if node is None:
continue
rnode = self.repo.read_node( node )
lines = graph.rowlines[n]
auth = self.repo.authors[rnode.author_id]
authcolor = self.repo.colors[rnode.author_id]
rev = int(rnode.rev)
add_rev( (node, rnode, graph.x[node], (lines,n), time.mktime(rnode.localtime), n, auth, authcolor, rev) )
self.last_node = last_node
tree.thaw_notify()
self.progressbar.set_fraction( float(self.last_node) / len(rowselected) )
if self.last_node == len(rowselected):
self.graph = None
self.rowselected = None
self.progressbar.hide()
self._idletask_id = None
return False
return True
def set_revlog_header( self, buf, node, rnode ):
"""Put the revision log header in the TextBuffer"""
repo = self.repo
eob = buf.get_end_iter()
buf.insert( eob, "Revision: %d:" % rnode.rev )
buf.insert_with_tags_by_name( eob, short_hex(node), "link" )
buf.insert( eob, "\n" )
buf.insert( eob, "Branch: %s\n" % self.repo.read_node(node).branches['branch'])
buf.insert( eob, "Author: %s\n" % repo.authors[rnode.author_id] )
buf.create_mark( "begdesc", buf.get_start_iter() )
for p in repo.parents(node):
pnode = repo.read_node(p)
short = short_hex(p)
buf.insert( eob, "Parent: %d:" % pnode.rev )
buf.insert_with_tags_by_name( eob, short, "link" )
buf.insert(eob, "(%s)\n" % pnode.short)
for p in repo.children(node):
pnode = repo.read_node(p)
short = short_hex(p)
buf.insert( eob, "Child: %d:" % pnode.rev )
buf.insert_with_tags_by_name( eob, short, "link" )
buf.insert(eob, "(%s)\n" % pnode.short)
buf.insert( eob, "\n" )
def prepare_diff(self, difflines, offset, diffmsg, changes):
idx = 0
outlines = []
tags = []
filespos = []
def addtag( name, offset, length ):
if tags and tags[-1][0] == name and tags[-1][2]==offset:
tags[-1][2] += length
else:
tags.append( [name, offset, offset+length] )
stats = [0,0]
statmax = 0
if diffmsg:
DIFFHDR = ""
for i, key in enumerate(diffmsg.split("\n")):
if i < len(diffmsg.split("\n"))-1:
msg = key.split(':')[0]
DIFFHDR += "===%s===\n" % msg
txt = DIFFHDR
addtag("greybg", offset, len(txt))
outlines.append(txt)
markname = "file%d" % idx
statmax = max( statmax, stats[0]+stats[1] )
stats = [0,0]
for file_added in changes[1]:
idx += 1
markname = "file%d" % idx
statmax = max( statmax, stats[0]+stats[1] )
stats = [0,0]
filespos.append(( file_added , markname, offset, stats ))
offset += len(txt)
else:
for i,l in enumerate(difflines):
if l.startswith("diff"):
f = l.split(' ', 5)[-1]
DIFFHDR = "=== %s ===\n"
txt = DIFFHDR % f
addtag( "greybg", offset, len(txt) )
outlines.append(txt)
markname = "file%d" % idx
idx += 1
statmax = max( statmax, stats[0]+stats[1] )
stats = [0,0]
filespos.append(( f, markname, offset, stats ))
offset += len(txt)
continue
elif l.startswith("+++"):
continue
elif l.startswith("---"):
continue
elif l.startswith("+"):
tag = "green"
stats[0] += 1
elif l.startswith("-"):
stats[1] += 1
tag = "red"
elif l.startswith("@@"):
tag = "blue"
else:
tag = "black"
l = l+"\n"
length = len(l)
addtag( tag, offset, length )
outlines.append( l )
offset += length
statmax = max( statmax, stats[0]+stats[1] )
return filespos, tags, outlines, statmax
def selection_changed( self, selection ):
model, it = selection.get_selected()
if it is None:
return
node, rnode = model.get(
it, M_ID, M_NODE )
textwidget = self.xml.get_widget( "textview_status" )
text_buffer = textwidget.get_buffer()
textwidget.freeze_child_notify()
text_buffer.set_text( "" )
try:
self.set_revlog_header( text_buffer, node, rnode )
eob = text_buffer.get_end_iter()
text_buffer.insert( eob, rnode.desc+"\n\n" )
self.filelist.clear()
enddesc = text_buffer.get_end_iter()
enddesc.backward_line()
text_buffer.create_mark( "enddesc", enddesc )
self.filelist.append( ("Content", "begdesc", None ) )
if len(self.repo.parents(node)) > 1:
node_files=[]
nodes_parents = self.repo.parents(node)
for node_value in nodes_parents:
parent_files = self.repo.read_node(node_value).files
[node_files.append(x) for x in parent_files]
rnode.files = tuple(node_files)
diffmsg, buff, changes = self.repo.diff(self.repo.parents(node), node, rnode.files)
try:
buff = unicode( buff, "utf-8" )
except UnicodeError:
# XXX use a default encoding from config
buff = unicode( buff, "iso-8859-1", 'ignore' )
difflines = buff.splitlines()
del buff
eob = text_buffer.get_end_iter()
offset = eob.get_offset()
fileoffsets, tags, lines, statmax = self.prepare_diff( difflines, offset, diffmsg, changes)
txt = u"".join(lines)
# XXX debug : sometime gtk complains it's not valid utf-8 !!!
text_buffer.insert( eob, txt.encode('utf-8') )
# inserts the tags
for name, p0, p1 in tags:
i0 = text_buffer.get_iter_at_offset( p0 )
i1 = text_buffer.get_iter_at_offset( p1 )
txt = text_buffer.get_text( i0, i1 )
text_buffer.apply_tag_by_name( name, i0, i1 )
# inserts the marks
for f, mark,offset, stats in fileoffsets:
pos = text_buffer.get_iter_at_offset( offset )
text_buffer.create_mark( mark, pos )
self.filelist.append( (f, mark, (stats[0],stats[1],statmax) ) )
finally:
textwidget.thaw_child_notify()
sob, eob = text_buffer.get_bounds()
text_buffer.apply_tag_by_name( "mono", sob, eob )
def find_entry(self):
# text to find
find_entry = self.xml.get_widget( "entry_find" )
return find_entry
def fileselection_changed( self, selection ):
model, it = selection.get_selected()
if it is None:
return
markname = model.get_value( it, 1 )
tw = self.xml.get_widget("textview_status" )
mark = tw.get_buffer().get_mark( markname )
tw.scroll_to_mark( mark, .2, use_align=True, xalign=1., yalign=0. )
def find_next_row( self, iter, stop_iter=None ):
"""Find the next revision row matching grep & find entries"""
grep = self._get_grep_string()
find = self._get_find_string()
grexp = re.compile( grep )
frexp = re.compile( find )
while iter != stop_iter and iter != None:
revnode = self.revisions.get( iter, M_NODE ) [0]
# look in author_id, log
author = self.repo.authors[revnode.author_id]
if ( grexp.search( author ) or
grexp.search( revnode.desc ) ):
break
# look in diffs
node = self.revisions.get_value(iter, M_ID)
rnode = self.repo.read_node(node)
buff = self.repo.single_diff(self.repo.parents(node)[0], node, rnode.files)
if grexp.search(buff):
break
for fname in revnode.files:
if frexp.search( fname ):
break
iter = self.revisions.iter_next( iter )
if iter == stop_iter or iter is None:
return None
self.select_row( iter )
return iter
def select_row( self, itr ):
if itr is None:
self.find_text = None
return
else:
self.find_text = self.xml.get_widget( "entry_find" ).get_text()
tree = self.xml.get_widget( "treeview_revisions" )
sel = tree.get_selection()
sel.select_iter( itr )
path = self.revisions.get_path( itr )
tree.scroll_to_cell( path, use_align=True, row_align=0.2 )
def get_selected_rev(self):
sel = self.xml.get_widget("treeview_revisions").get_selection()
model, it = sel.get_selected()
if it is None:
it = model.get_iter_first()
return model, it
def _get_grep_string(self):
return self.xml.get_widget( "entry_find" ).get_text().replace('(', '\(').replace(')', '\)')
def _get_find_string(self):
return self.xml.get_widget( "entry_file_filter" ).get_text()
def _get_buffer_text(self):
textwidget = self.xml.get_widget( "textview_status" )
text_buffer = textwidget.get_buffer()
sob, eob = text_buffer.get_bounds()
return unicode(text_buffer.get_slice(sob, eob), 'UTF-8')
def notify_end_of_search(self):
self.update_status("end of search for %r, %r" % (self._get_grep_string(),
self._get_find_string))
@watch_displayed
def on_button_find_clicked( self, *args ):
"""callback: clicking on the find button
makes the search start at the row after the
current row
"""
st = self._search_state
grep_string = self._get_grep_string()
find_string = self._get_find_string()
if st is None or st.last_grep_string != grep_string or st.last_find_string != find_string:
_, it = self.get_selected_rev()
st = self._search_state = SearchState(last_grep_string=grep_string,
last_find_string=find_string,
rev_iter=self.revisions.iter_next( it ))
if st.rev_iter is None: # there was nothing at all
self._search_state = None
self.notify_end_of_search()
return
st.text_iter = re.finditer(re.compile('(%s)' % st.last_grep_string.replace('&', '&').replace('<', '<')),
self._get_buffer_text())
next_match = None
try:
next_match = st.text_iter.next()
except StopIteration:
try:
st.rev_iter = self.find_next_row( self.revisions.iter_next( st.rev_iter ))
except StopIteration:
st.rev_iter = None
if st.rev_iter is None:
self._search_state = None
self.notify_end_of_search()
return
st.text_iter = re.finditer(re.compile('(%s)' % st.last_grep_string.replace('&', '&').replace('<', '<')),
self._get_buffer_text())
try:
next_match = st.text_iter.next()
except StopIteration:
return
# Search State is OK
self.highlight_search_string(next_match)
def highlight_search_string(self, match):
textwidget = self.xml.get_widget( "textview_status" )
text_buffer = textwidget.get_buffer()
_b = text_buffer.get_iter_at_offset( match.start() )
_e = text_buffer.get_iter_at_offset( match.end() )
mark = text_buffer.create_mark('mark_search', _b, True)
textwidget.scroll_to_mark(mark, .1, use_align=True, xalign=1., yalign=0.1)
text_buffer.apply_tag_by_name("yellowbg", _b, _e )
def update_status(self, msg):
statusbar = self.xml.get_widget("statusbar1")
statusbar.push(True, msg)
def on_entry_find_changed( self, *args ):
"""callback: each keypress triggers verification of the search string
regular expression syntax"""
try:
st = self._get_grep_string()
re.compile(st)
except:
self.update_status('Wrong regular expression : %s' % st)
else:
self.update_status('Your regular expression : %s' % st)
def on_entry_find_activate( self, *args ):
"""Pressing enter in the entry_find field does the
same as clicking on the Find button"""
self.on_button_find_clicked()
def on_filter1_activate( self, *args ):
self.filter_dialog.show()
def init_filter(self):
file_filter = self.xml.get_widget("entry_file_filter")
node_low = self.xml.get_widget("spinbutton_rev_low")
node_high = self.xml.get_widget("spinbutton_rev_high")
cnt = self.repo.count()
if self.filter_files:
file_filter.set_text( self.filerex )
node_low.set_range(0, cnt+1 )
node_high.set_range(0, cnt+1 )
node_low.set_value( 0 )
node_high.set_value( cnt )
self.branch_selected = 'All'
def on_button_filter_apply_clicked( self, *args ):
file_filter = self.xml.get_widget("entry_file_filter")
node_low = self.xml.get_widget("spinbutton_rev_low")
node_high = self.xml.get_widget("spinbutton_rev_high")
self.filter_files = re.compile(file_filter.get_text())
self.filter_noderange = set(range( node_low.get_value_as_int(), node_high.get_value_as_int() ))
self.refresh_tree()
def on_branch_checkbox_toggled( self, *args ):
self.on_button_filter_apply_clicked()
def on_branch_highlight_combo_changed( self, *args ):
self.branch_selected = self.get_selected_named_branch()
if self.get_value_branch_checkbox():
# if "hide others" is checked in, we really need to
# recompute the tree
return self.on_button_filter_apply_clicked()
# if not, we just need to refresh to displayed part of the
# graph to update bold lines
tree = self.xml.get_widget( "treeview_revisions" )
tree.queue_draw()
def get_selected_named_branch(self):
"""
Returns the name of the currently selected named branch in the combo
"""
combo_filter = self.xml.get_widget("branch_highlight_combo")
active_branch = combo_filter.get_active_text()
return active_branch
def get_value_branch_checkbox(self):
"""
return True or False
"""
return self.xml.get_widget("branch_checkbox").get_active()
def main():
try:
import __pkginfo__
VERSION=str(__pkginfo__.numversion)
except ImportError:
VERSION='unknown'
parser = OptionParser(usage="%prog [-R]", version="%%prog %s" %(VERSION))
parser.add_option( '-R', '--repository', dest='repo',
help='location of the repository to explore' )
parser.add_option( '-f', '--file', dest='filename',
help='filter revisions which touch FILE', metavar="FILE")
parser.add_option( '-g', '--regexp', dest='filerex',
help='filter revisions which touch FILE matching regexp')
opt, args = parser.parse_args()
dir_ = None
if opt.repo:
dir_ = opt.repo
else:
dir_ = os.getcwd()
filerex = None
if opt.filename:
filerex = "^" + re.escape( opt.filename ) + "$"
elif opt.filerex:
filerex = opt.filerex
try:
repo = HgHLRepo( dir_ )
except:
print "You are not in a repo, are you ?"
sys.exit(1)
app = HgViewApp( repo, filerex )
gtk.main()
if __name__ == "__main__":
# remove current dir from sys.path
if sys.path.count('.'):
sys.path.remove('.')
main()