Changeset 4f3155fd9442…
Parent aa5eebcf2f3c…
by
Changes to 46 files · Browse files at 4f3155fd9442 Showing diff from parent aa5eebcf2f3c Diff from another changeset...
|
@@ -0,0 +1,29 @@ + # Copyright (c) 2003-2010 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""mercurial interactive history viewer
+
+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).
+"""
+
+# monkey patch to support older hg versions
+from mercurial import changelog, filelog
+if not hasattr(changelog.changelog, '__len__'):
+ changelog.changelog.__len__ = changelog.changelog.count
+if not hasattr(filelog.filelog, '__len__'):
+ filelog.filelog.__len__ = filelog.filelog.count
|
|
|
@@ -0,0 +1,278 @@ + # -*- coding: utf-8 -*-
+# Copyright (c) 2003-2010 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+# pylint: disable-msg=C0103
+
+"""
+Module for managing configuration parameters of hgview using Hg's
+configuration system
+"""
+import os
+import re
+
+def cached(meth):
+ """
+ decorator to cache config values once they are read
+ """
+ name = meth.func_name
+ def wrapper(self, *args, **kw):
+ if name in self._cache:
+ return self._cache[name]
+ res = meth(self, *args, **kw)
+ self._cache[name] = res
+ return res
+ wrapper.__doc__ = meth.__doc__
+ return wrapper
+
+class HgConfig(object):
+ """
+ Class managing user configuration from hg standard configuration system (.hgrc)
+ """
+ def __init__(self, ui, section="hgview"):
+ self.ui = ui
+ self.section = section
+ self._cache = {}
+
+ @cached
+ def getFont(self):
+ """
+ font: default font used to display diffs and files. Use Qt4 format.
+ """
+ return self.ui.config(self.section, 'font', 'Monospace')
+
+ @cached
+ def getFontSize(self, default=9):
+ """
+ fontsize: text size in file content viewer
+ """
+ return int(self.ui.config(self.section, 'fontsize', default))
+
+ @cached
+ def getDotRadius(self, default=8):
+ """
+ dotradius: radius (in pixels) of the dot in the revision graph
+ """
+ r = self.ui.config(self.section, 'dotradius', default)
+ return int(r)
+
+ @cached
+ def getUsers(self):
+ """
+ users: path of the file holding users configurations
+ """
+ users = {}
+ aliases = {}
+ usersfile = self.ui.config(self.section, 'users',
+ os.path.join('~', ".hgusers"))
+ cfgfile = None
+ if usersfile:
+ try:
+ cfgfile = open(os.path.expanduser(usersfile))
+ except IOError:
+ cfgfile = None
+
+ if cfgfile:
+ currid = None
+ for line in cfgfile:
+ line = line.strip()
+ if not line or line.startswith('#'):
+ continue
+ cmd, val = line.split('=', 1)
+ if cmd == 'id':
+ currid = val
+ if currid in users:
+ print "W: user %s is defined several times" % currid
+ users[currid] = {'aliases': set()}
+ elif cmd == "alias":
+ users[currid]['aliases'].add(val)
+ if val in aliases:
+ print ("W: alias %s is used in several "
+ "user definitions" % val)
+ aliases[val] = currid
+ else:
+ users[currid][cmd] = val
+ return users, aliases
+
+ @cached
+ def getFileModifiedColor(self, default='blue'):
+ """
+ filemodifiedcolor: display color of a modified file
+ """
+ return self.ui.config(self.section, 'filemodifiedcolor', default)
+ @cached
+ def getFileRemovedColor(self, default='red'):
+ """
+ fileremovedcolor: display color of a removed file
+ """
+ return self.ui.config(self.section, 'fileremovededcolor', default)
+ @cached
+ def getFileDeletedColor(self, default='darkred'):
+ """
+ filedeletedcolor: display color of a deleted file
+ """
+ return self.ui.config(self.section, 'filedeletedcolor', default)
+ @cached
+ def getFileAddedColor(self, default='green'):
+ """
+ fileaddedcolor: display color of an added file
+ """
+ return self.ui.config(self.section, 'fileaddedcolor', default)
+
+ @cached
+ def getRowHeight(self, default=20):
+ """
+ rowheight: height (in pixels) on a row of the revision table
+ """
+ return int(self.ui.config(self.section, 'rowheight', default))
+
+ @cached
+ def getHideFindDelay(self, default=10000):
+ """
+ hidefinddelay: delay (in ms) after which the find bar will disappear
+ """
+ return int(self.ui.config(self.section, 'hidefindddelay', default))
+
+ @cached
+ def getFillingStep(self, default=300):
+ """
+ fillingstep: number of nodes 'loaded' at a time when updating repo graph log
+ """
+ return int(self.ui.config(self.section, 'fillingstep', default))
+
+ @cached
+ def getChangelogColumns(self, default=None):
+ """
+ changelogcolumns: ordered list of displayed columns in changelog views;
+ defaults to ID, Branch, Log, Author, Date, Tags
+ """
+ cols = self.ui.config(self.section, 'changelogcolumns', default)
+ if cols is None:
+ return None
+ return [col.strip() for col in cols.split(',') if col.strip()]
+
+ @cached
+ def getFilelogColumns(self, default=None):
+ """
+ filelogcolumns: ordered list of displayed columns in filelog views;
+ defaults to ID, Log, Author, Date
+ """
+ cols = self.ui.config(self.section, 'filelogcolumns', default)
+ if cols is None:
+ return None
+ return [col.strip() for col in cols.split(',') if col.strip()]
+
+ @cached
+ def getDisplayDiffStats(self, default="no"):
+ """
+ displaydiffstats: flag controllong the appearance of the
+ 'Diff' column in a revision's file list
+ """
+ val = str(self.ui.config(self.section, 'displaydiffstats', default))
+ return val.lower() in ['true', 'yes', '1', 'on']
+
+ @cached
+ def getMaxFileSize(self, default=100000):
+ """
+ maxfilesize: max size of a file (for diff computations, display content, etc.)
+ """
+ return int(self.ui.config(self.section, 'maxfilesize', default))
+
+ @cached
+ def getDiffBGColor(self, default='white'):
+ """
+ diffbgcolor: background color of diffs
+ """
+ return self.ui.config(self.section, 'diffbgcolor', default)
+
+ @cached
+ def getDiffFGColor(self, default='black'):
+ """
+ difffgcolor: text color of diffs
+ """
+ return self.ui.config(self.section, 'difffgcolor', default)
+
+ @cached
+ def getDiffPlusColor(self, default='green'):
+ """
+ diffpluscolor: text color of added lines in diffs
+ """
+ return self.ui.config(self.section, 'diffpluscolor', default)
+
+ @cached
+ def getDiffMinusColor(self, default='red'):
+ """
+ diffminuscolor: text color of removed lines in diffs
+ """
+ return self.ui.config(self.section, 'diffminuscolor', default)
+
+ @cached
+ def getDiffSectionColor(self, default='magenta'):
+ """
+ diffsectioncolor: text color of new section in diffs
+ """
+ return self.ui.config(self.section, 'diffsectioncolor', default)
+
+ @cached
+ def getMQFGColor(self, default='#ff8183'):
+ """
+ mqfgcolor: bg color to highlight mq patches
+ """
+ return self.ui.config(self.section, 'mqfgcolor', default)
+
+ @cached
+ def getMQHideTags(self, default=False):
+ """
+ mqhidetags: hide mq tags
+ """
+ return self.ui.config(self.section, 'mqhidetags', default)
+
+
+
+_HgConfig = HgConfig
+# HgConfig is instanciated only once (singleton)
+#
+# this 'factory' is used to manage this (not using heavy guns of
+# metaclass or so)
+_hgconfig = None
+def HgConfig(ui):
+ """Factory to instanciate HgConfig class as a singleton
+ """
+ # pylint: disable-msg=E0102
+ global _hgconfig
+ if _hgconfig is None:
+ _hgconfig = _HgConfig(ui)
+ return _hgconfig
+
+
+def get_option_descriptions(rest=False):
+ """
+ Extract options descriptions (docstrings of HgConfig methods)
+ """
+ options = []
+ for attr in dir(_HgConfig):
+ if attr.startswith('get'):
+ meth = getattr(_HgConfig, attr)
+ if callable(meth):
+ doc = meth.__doc__
+ if doc and doc.strip():
+ doc = doc.strip()
+ if rest:
+ doc = re.sub(r' *(?P<arg>.*) *: *(?P<desc>.*)', r'``\1`` \2', doc.strip())
+ doc = ' '.join(doc.split()) # remove \n and other multiple whitespaces
+ options.append(doc)
+ return options
+
|
|
@@ -0,0 +1,19 @@ + # -*- coding: utf-8 -*-
+"""
+Some useful decorator functions
+"""
+import time
+
+def timeit(func):
+ """Decorator used to time the execution of a function"""
+ def timefunc(*args, **kwargs):
+ """wrapper"""
+ t_1 = time.time()
+ t_2 = time.clock()
+ res = func(*args, **kwargs)
+ t_3 = time.clock()
+ t_4 = time.time()
+ print "%s: %.2fms (time) %.2fms (clock)" % \
+ (func.func_name, 1000*(t_3 - t_2), 1000*(t_4 - t_1))
+ return res
+ return timefunc
|
|
|
@@ -0,0 +1,477 @@ + # Copyright (c) 2003-2010 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+"""helper functions and classes to ease hg revision graph building
+
+Based on graphlog's algorithm, with insipration stolen to TortoiseHg
+revision grapher.
+"""
+
+from cStringIO import StringIO
+import difflib
+
+from mercurial.node import nullrev
+from mercurial import patch, util, match
+
+import hgviewlib # force apply monkeypatches
+from hgviewlib.util import tounicode, isbfile
+
+def diff(repo, ctx1, ctx2=None, files=None):
+ """
+ Compute the diff of files between 2 changectx
+ """
+ if ctx2 is None:
+ ctx2 = ctx1.parents()[0]
+ if files is None:
+ m = match.always(repo.root, repo.getcwd())
+ else:
+ m = match.exact(repo.root, repo.getcwd(), files)
+ # try/except for the sake of hg compatibility (API changes between
+ # 1.0 and 1.1)
+ try:
+ out = StringIO()
+ patch.diff(repo, ctx2.node(), ctx1.node(), match=m, fp=out)
+ diffdata = out.getvalue()
+ except:
+ diffdata = '\n'.join(patch.diff(repo, ctx2.node(), ctx1.node(),
+ match=m))
+ # XXX how to deal diff encodings?
+ try:
+ diffdata = unicode(diffdata, "utf-8")
+ except UnicodeError:
+ # XXX use a default encoding from config?
+ diffdata = unicode(diffdata, "iso-8859-15", 'ignore')
+ return diffdata
+
+
+def __get_parents(repo, rev, branch=None):
+ """
+ Return non-null parents of `rev`. If branch is given, only return
+ parents that belongs to names branch `branch` (beware that this is
+ much slower).
+ """
+ if not branch:
+ if rev is None:
+ return [x.rev() for x in repo.changectx(None).parents() if x]
+ return [x for x in repo.changelog.parentrevs(rev) if x != nullrev]
+ if rev is None:
+ return [x.rev() for x in repo.changectx(None).parents() \
+ if x and repo.changectx(rev).branch() == branch]
+ return [x for x in repo.changelog.parentrevs(rev) \
+ if (x != nullrev and repo.changectx(rev).branch() == branch)]
+
+
+def ismerge(ctx):
+ """
+ Return True if the changecontext ctx is a merge mode (should work
+ with hg 1.0 and 1.2)
+ """
+ if ctx:
+ return len(ctx.parents()) == 2 and ctx.parents()[1]
+ return False
+
+def revision_grapher(repo, start_rev=None, stop_rev=0, branch=None, follow=False):
+ """incremental revision grapher
+
+ This generator function walks through the revision history from
+ revision start_rev to revision stop_rev (which must be less than
+ or equal to start_rev) and for each revision emits tuples with the
+ following elements:
+
+ - current revision
+ - column of the current node in the set of ongoing edges
+ - color of the node (?)
+ - lines; a list of (col, next_col, color) indicating the edges between
+ the current row and the next row
+ - parent revisions of current revision
+
+ If follow is True, only generated the subtree from the start_rev head.
+
+ If branch is set, only generated the subtree for the given named branch.
+ """
+ if start_rev is None and repo.status() == ([],)*7:
+ start_rev = len(repo.changelog)
+ assert start_rev is None or start_rev >= stop_rev
+ curr_rev = start_rev
+ revs = []
+ rev_color = {}
+ nextcolor = 0
+ while curr_rev is None or curr_rev >= stop_rev:
+ # Compute revs and next_revs.
+ if curr_rev not in revs:
+ if branch:
+ ctx = repo.changectx(curr_rev)
+ if ctx.branch() != branch:
+ if curr_rev is None:
+ curr_rev = len(repo.changelog)
+ else:
+ curr_rev -= 1
+ yield None
+ continue
+
+ # New head.
+ if start_rev and follow and curr_rev != start_rev:
+ curr_rev -= 1
+ continue
+ revs.append(curr_rev)
+ rev_color[curr_rev] = curcolor = nextcolor
+ nextcolor += 1
+ p_revs = __get_parents(repo, curr_rev, branch)
+ while p_revs:
+ rev0 = p_revs[0]
+ if rev0 < stop_rev or rev0 in rev_color:
+ break
+ rev_color[rev0] = curcolor
+ p_revs = __get_parents(repo, rev0, branch)
+ curcolor = rev_color[curr_rev]
+ rev_index = revs.index(curr_rev)
+ next_revs = revs[:]
+
+ # Add parents to next_revs.
+ parents = __get_parents(repo, curr_rev, branch)
+ parents_to_add = []
+ if len(parents) > 1:
+ preferred_color = None
+ else:
+ preferred_color = curcolor
+ for parent in parents:
+ if parent not in next_revs:
+ parents_to_add.append(parent)
+ if parent not in rev_color:
+ if preferred_color:
+ rev_color[parent] = preferred_color
+ preferred_color = None
+ else:
+ rev_color[parent] = nextcolor
+ nextcolor += 1
+ preferred_color = None
+
+ # parents_to_add.sort()
+ next_revs[rev_index:rev_index + 1] = parents_to_add
+
+ lines = []
+ for i, rev in enumerate(revs):
+ if rev in next_revs:
+ color = rev_color[rev]
+ lines.append( (i, next_revs.index(rev), color) )
+ elif rev == curr_rev:
+ for parent in parents:
+ color = rev_color[parent]
+ lines.append( (i, next_revs.index(parent), color) )
+
+ yield (curr_rev, rev_index, curcolor, lines, parents)
+ revs = next_revs
+ if curr_rev is None:
+ curr_rev = len(repo.changelog)
+ else:
+ curr_rev -= 1
+
+
+def filelog_grapher(repo, path):
+ '''
+ Graph the ancestry of a single file (log). Deletions show
+ up as breaks in the graph.
+ '''
+ filerev = len(repo.file(path)) - 1
+ fctx = repo.filectx(path, fileid=filerev)
+ rev = fctx.rev()
+
+ flog = fctx.filelog()
+ heads = [repo.filectx(path, fileid=flog.rev(x)).rev() for x in flog.heads()]
+ assert rev in heads
+ heads.remove(rev)
+
+ revs = []
+ rev_color = {}
+ nextcolor = 0
+ _paths = {}
+
+ while rev >= 0:
+ # Compute revs and next_revs
+ if rev not in revs:
+ revs.append(rev)
+ rev_color[rev] = nextcolor ; nextcolor += 1
+ curcolor = rev_color[rev]
+ index = revs.index(rev)
+ next_revs = revs[:]
+
+ # Add parents to next_revs
+ fctx = repo.filectx(_paths.get(rev, path), changeid=rev)
+ for pfctx in fctx.parents():
+ _paths[pfctx.rev()] = pfctx.path()
+ parents = [pfctx.rev() for pfctx in fctx.parents()]# if f.path() == path]
+ parents_to_add = []
+ for parent in parents:
+ if parent not in next_revs:
+ parents_to_add.append(parent)
+ if len(parents) > 1:
+ rev_color[parent] = nextcolor ; nextcolor += 1
+ else:
+ rev_color[parent] = curcolor
+ parents_to_add.sort()
+ next_revs[index:index + 1] = parents_to_add
+
+ lines = []
+ for i, nrev in enumerate(revs):
+ if nrev in next_revs:
+ color = rev_color[nrev]
+ lines.append( (i, next_revs.index(nrev), color) )
+ elif nrev == rev:
+ for parent in parents:
+ color = rev_color[parent]
+ lines.append( (i, next_revs.index(parent), color) )
+
+ pcrevs = [pfc.rev() for pfc in fctx.parents()]
+ yield (fctx.rev(), index, curcolor, lines, pcrevs,
+ _paths.get(fctx.rev(), path))
+ revs = next_revs
+
+ if revs:
+ rev = max(revs)
+ else:
+ rev = -1
+ if heads and rev <= heads[-1]:
+ rev = heads.pop()
+
+class GraphNode(object):
+ """
+ Simple class to encapsulate e hg node in the revision graph. Does
+ nothing but declaring attributes.
+ """
+ def __init__(self, rev, xposition, color, lines, parents, ncols=None,
+ extra=None):
+ self.rev = rev
+ self.x = xposition
+ self.color = color
+ if ncols is None:
+ ncols = len(lines)
+ self.cols = ncols
+ self.parents = parents
+ self.bottomlines = lines
+ self.toplines = []
+ self.extra = extra
+
+class Graph(object):
+ """
+ Graph object to ease hg repo navigation. The Graph object
+ instanciate a `revision_grapher` generator, and provide a `fill`
+ method to build the graph progressively.
+ """
+ #@timeit
+ def __init__(self, repo, grapher, maxfilesize=100000):
+ self.maxfilesize = maxfilesize
+ self.repo = repo
+ self.maxlog = len(self.repo.changelog)
+ self.grapher = grapher
+ self.nodes = []
+ self.nodesdict = {}
+ self.max_cols = 0
+
+ def build_nodes(self, nnodes=None, rev=None):
+ """
+ Build up to `nnodes` more nodes in our graph, or build as many
+ nodes required to reach `rev`.
+
+ If both rev and nnodes are set, build as many nodes as
+ required to reach rev plus nnodes more.
+ """
+ if self.grapher is None:
+ return False
+ stopped = False
+ mcol = [self.max_cols]
+ for vnext in self.grapher:
+ if vnext is None:
+ continue
+ nrev, xpos, color, lines, parents = vnext[:5]
+ if nrev >= self.maxlog:
+ continue
+ gnode = GraphNode(nrev, xpos, color, lines, parents,
+ extra=vnext[5:])
+ if self.nodes:
+ gnode.toplines = self.nodes[-1].bottomlines
+ self.nodes.append(gnode)
+ self.nodesdict[nrev] = gnode
+ mcol.append(gnode.cols)
+ if rev is not None and nrev <= rev:
+ rev = None # we reached rev, switching to nnode counter
+ if rev is None:
+ if nnodes is not None:
+ nnodes -= 1
+ if not nnodes:
+ break
+ else:
+ break
+ else:
+ self.grapher = None
+ stopped = True
+
+ self.max_cols = max(mcol)
+ return not stopped
+
+ def isfilled(self):
+ return self.grapher is None
+
+ def fill(self, step=100):
+ """
+ Return a generator that fills the graph by bursts of `step`
+ more nodes at each iteration.
+ """
+ while self.build_nodes(step):
+ yield len(self)
+ yield len(self)
+
+ def __getitem__(self, idx):
+ if isinstance(idx, slice):
+ # XXX TODO: ensure nodes are built
+ return self.nodes.__getitem__(idx)
+ if idx >= len(self.nodes):
+ # build as many graph nodes as required to answer the
+ # requested idx
+ self.build_nodes(idx)
+ if idx > len(self):
+ return self.nodes[-1]
+ return self.nodes[idx]
+
+ def __len__(self):
+ # len(graph) is the number of actually built graph nodes
+ return len(self.nodes)
+
+ def index(self, rev):
+ if len(self) == 0: # graph is empty, let's build some nodes
+ self.build_nodes(10)
+ if rev is not None and rev < self.nodes[-1].rev:
+ self.build_nodes(self.nodes[-1].rev - rev)
+ if rev in self.nodesdict:
+ return self.nodes.index(self.nodesdict[rev])
+ return -1
+
+ def fileflags(self, filename, rev):
+ """
+ Return a couple of flags ('=', '+', '-' or '?') depending on the nature
+ of the diff for filename between rev and its parents.
+ """
+ ctx = self.repo.changectx(rev)
+ flags = []
+ for p in ctx.parents():
+ changes = self.repo.status(p.node(), ctx.node())[:5]
+ # changes = modified, added, removed, deleted, unknown
+ for flag, lst in zip(["=", "+", "-", "-", "?"], changes):
+ if filename in lst:
+ if flag == "+":
+ renamed = ctx.filectx(filename).renamed()
+ if renamed:
+ flags.append(renamed)
+ break
+ flags.append(flag)
+ break
+ else:
+ flags.append('')
+ return flags
+
+ def fileflag(self, filename, rev):
+ """
+ Return a flag (see fileflags) between rev and its first parent
+ """
+ return self.fileflags(filename, rev)[0]
+
+ def filename(self, rev):
+ return self.nodesdict[rev].extra[0]
+
+ def filedata(self, filename, rev, mode='diff'):
+ """XXX written under dubious encoding assumptions
+ """
+ # XXX This really begins to be a dirty mess...
+ data = ""
+ flag = self.fileflag(filename, rev)
+ ctx = self.repo.changectx(rev)
+ try:
+ fctx = ctx.filectx(filename)
+ except LookupError:
+ fctx = None # may happen for renamed files?
+
+ if isbfile(filename):
+ data = "[bfile]\n"
+ if fctx:
+ data = fctx.data()
+ data += "footprint: %s\n" % data
+ return "+", data
+ if flag not in ('-', '?'):
+ if fctx is None:# or fctx.node() is None:
+ return '', None
+ if fctx.size() > self.maxfilesize:
+ data = "file too big"
+ return flag, data
+ if flag == "+" or mode == 'file':
+ flag = '+'
+ # return the whole file
+ data = fctx.data()
+ if util.binary(data):
+ data = "binary file"
+ else: # tries to convert to unicode
+ data = tounicode(data)
+ elif flag == "=" or isinstance(mode, int):
+ flag = "="
+ if isinstance(mode, int):
+ parentctx = self.repo.changectx(mode)
+ else:
+ parent = self.fileparent(filename, rev)
+ parentctx = self.repo.changectx(parent)
+ # return the diff but the 3 first lines
+ data = diff(self.repo, ctx, parentctx, files=[filename])
+ data = u'\n'.join(data.splitlines()[3:])
+ elif flag == '':
+ data = ''
+ else: # file renamed
+ oldname, node = flag
+ newdata = fctx.data().splitlines()
+ olddata = self.repo.filectx(oldname, fileid=node)
+ olddata = olddata.data().splitlines()
+ data = list(difflib.unified_diff(olddata, newdata, oldname,
+ filename))[2:]
+ if data:
+ flag = "="
+ else:
+ data = newdata
+ flag = "+"
+ data = u'\n'.join(tounicode(elt) for elt in data)
+ return flag, data
+
+ def fileparent(self, filename, rev):
+ if rev is not None:
+ node = self.repo.changelog.node(rev)
+ else:
+ node = self.repo.changectx(rev).node()
+ for parent in self.nodesdict[rev].parents:
+ pnode = self.repo.changelog.node(parent)
+ changes = self.repo.status(pnode, node)[:5]
+ allchanges = []
+ [allchanges.extend(e) for e in changes]
+ if filename in allchanges:
+ return parent
+ return None
+
+if __name__ == "__main__":
+ # pylint: disable-msg=C0103
+ import sys
+ from mercurial import ui, hg
+ u = ui.ui()
+ r = hg.repository(u, sys.argv[1])
+ if len(sys.argv) == 3:
+ rg = filelog_grapher(r, sys.argv[2])
+ else:
+ rg = revision_grapher(r)
+ g = Graph(r, rg)
+
|
|
|
@@ -0,0 +1,223 @@ + # Copyright (c) 2003-2010 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""
+help messages for hgview
+"""
+
+help_msg = """
+hgview: a visual hg log viewer
+==============================
+
+This command will launch the hgview log navigator, allowing to
+visually browse in the hg graph log, search in logs, and display diff
+between arbitrary revisions of a file, with simple support for mq and
+bigfile extensions.
+
+If a filename is given, launch the filelog diff viewer for this file,
+and with the '-n' option, launch the filelog navigator for the file.
+
+With the '-r' option, launch the manifest viewer for the given revision.
+
+Revlog graph
+------------
+
+The main revision graph display the repository history as a graph,
+sorted by revision number.
+
+The color of the node of each revision depends on the named branch the
+revision belongs to.
+
+The color of the links (between nodes) is randomly chosen.
+
+The position of the working directory is marked on the graph using a
+small sunny icon as node marker. If the working directory has local
+modifications, a *virtual* is added in the graph with a special sign
+icon (with no revision number). Modified, added and removed files are
+listed and browsable as a normal changeset node.
+
+Note that if the working directoy is in merge state, there will be 2
+revisions marked as modified in the graph (since the working directory
+is then a son of both the merged nodes).
+
+mq support
+~~~~~~~~~~
+
+There is a simple support for the mq extension. Applied patches are
+seen in the revlog graph with a special arrow icon. Unapplied patches
+are *not* in the revlog graph (since they are not mercurial
+changesets).
+
+When the currently selected revision is an applied patch, the revision
+metadata display (see below) area point this by showing an additional
+line with coloured background listing all available patches (applied
+or not, so if you cannot see the content of an unapplied patch, you
+are aware there are unapplied patches, as long as there is at leat one
+applied patch). Current patch is displayed using bold font; unapplied
+patches are displayed in italic.
+
+
+Revision metadata display
+-------------------------
+
+The area where current revision's metadata is displayed
+(description, parents revisions, etc.) may contain two kinds of hyperlink:
+
+- when the hyperlink is the **changeset ID**, it allows you to
+ directly go to the given revision,
+
+- when the hyperlink is the **revision number** (on merge nodes only),
+ it means that you can change the other revision used to comput
+ the diff. This allows you to compare the merged node with each
+ of its parents, or even with the common ancestor of these 2
+ nodes.
+
+
+Revision's modified file list
+-----------------------------
+
+The file list diplay the list of modified files. The diff
+displayed is the one of the selected file, between the selected
+revision and its parent revision.
+
+On a merge node, by default, only files which are different from
+both its parents are listed here. However, you can display the
+list of all modified files by double-clickig the file list column
+header.
+
+
+Quickbars
+---------
+
+Quickbars are tollbar that appear when asked for by hitting it's
+keybord shortcut. Only one quickbar can be displayed at a time.
+
+When a quickbar is visible, hitting the Esc key make it disappear.
+
+The goto quickbar
+~~~~~~~~~~~~~~~~~
+
+This toolbar appears when hitting Ctrl+G. It allows you to jump to a
+given revision. The destination revision can be entered by:
+
+- it's revision number (negative values allowed, count from tip)
+- it's changeset ID (short or long)
+- a tag name (with completion)
+- a branch name
+- an empty string; means "goto current working directory"
+
+The search quickbar
+~~~~~~~~~~~~~~~~~~~
+
+This toolbar appears when hitting Ctrl+F or / (if not in goto toolbar).
+
+It allows you to type a string to be searched for:
+
+- in the currently displayed revision commit message (with highlight-as-you-type)
+- in the currently displayed file or diff (with highlight-as-you-type)
+
+Hitting the "Search next" button starts a background task for searching among the whole
+revision log, starting from the current position (selected revision
+and file).
+
+
+Keyboard shortcuts
+------------------
+
+**Up/Down**
+ go to next/previous revision
+
+**MidButton**
+ go to the common ancestor of the clicked revision and the currently selected one
+
+
+**Left/Right**
+ display previous/next files of the current changeset
+
+**Ctrl+F** or **/**
+ display the search 'quickbar'
+
+**Ctrl+G**
+ display the goto 'quickbar'
+
+**Esc**
+ exit or hide the visible 'quickbar'
+
+**Enter**
+ run the diff viewer for the currently selected file (display diff
+ between revisions)
+
+**Alt+Enter**
+ run the filelog navigator
+
+**Shift+Enter**
+ run the manifest viewer for the displayed revision
+
+**Ctrl+R**
+ reread repo; note that by default, repo will be automatically
+ reloaded if it is modified (due to a commit, a pull, etc.)
+
+**Alt+Up/Down**
+ display previous/next diff block
+
+**Alt+Left/Right**
+ go to previous/next visited revision (in navigation history)
+
+**Backspace**
+ set current revision the current start revision (hide any revision above it)
+
+**Shit+Backspace**
+ clear the start revision value
+
+
+ """
+
+def get_options_helpmsg(rest=False):
+ """display hgview full list of configuration options
+ """
+ from config import get_option_descriptions
+ options = get_option_descriptions(rest)
+ msg = """
+Configuration options
+=====================
+
+These should be set under the [hgview] section of the hgrc config file.
+
+"""
+ msg += '\n'.join(["- " + v for v in options]) + '\n'
+ msg += """
+The 'users' config statement should be the path of a file
+describing users, like::
+
+ --8<-------------------------------------------
+ # file ~/.hgusers
+ id=david
+ alias=david.douard@logilab.fr
+ alias=david@logilab.fr
+ alias=David Douard <david.douard@logilab.fr>
+ color=#FF0000
+ id=ludal
+ alias=ludovic.aubry@logilab.fr
+ alias=ludal@logilab.fr
+ alias=Ludovic Aubry <ludovic.aubry@logilab.fr>
+ color=#00FF00
+ --8<-------------------------------------------
+
+This allow to make several 'authors' under the same name, with the
+same color, in the graphlog browser.
+ """
+ return msg
+
+long_help_msg = help_msg + get_options_helpmsg()
|
|
@@ -0,0 +1,84 @@ + # -*- coding: utf-8 -*-
+# Copyright (c) 2003-2010 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+#
+# make sure the Qt rc files are converted into python modules, then load them
+# this must be done BEFORE other hgqv qt4 modules are loaded.
+import os
+import os.path as osp
+import sys
+import mx.DateTime as dt
+
+def should_rebuild(srcfile, pyfile):
+ return not osp.isfile(pyfile) or osp.isfile(srcfile) and \
+ osp.getmtime(pyfile) < osp.getmtime(srcfile)
+
+# automatically load resource module, creating it on the fly if
+# required
+curdir = osp.dirname(__file__)
+pyfile = osp.join(curdir, "hgqv_rc.py")
+rcfile = osp.join(curdir, "hgqv.qrc")
+if should_rebuild(rcfile, pyfile):
+ if os.system('pyrcc4 %s -o %s' % (rcfile, pyfile)):
+ print "ERROR: Cannot convert the resource file '%s' into a python module."
+ print "Please check the PyQt 'pyrcc4' tool is installed, or do it by hand running:"
+ print "pyrcc4 %s -o %s" % (rcfile, pyfile)
+
+# load icons from resource and store them in a dict, no matter their
+# extension (.svg or .png)
+from PyQt4 import QtCore
+from PyQt4 import QtGui, uic
+connect = QtCore.QObject.connect
+SIGNAL = QtCore.SIGNAL
+Qt = QtCore.Qt
+import hgqv_rc
+
+
+_icons = {}
+def _load_icons():
+ t = dt.today()
+ x = t.month == 12 and t.day in (24,25)
+ d = QtCore.QDir(':/icons')
+ for icn in d.entryList():
+ name, ext = osp.splitext(str(icn))
+ if name not in _icons or ext == ".svg":
+ _icons[name] = QtGui.QIcon(':/icons/%s' % icn)
+ if x:
+ for name in _icons:
+ if name.endswith('_x'):
+ _icons[name[:-2]] = _icons[name]
+
+def icon(name):
+ """
+ Return a QIcon for the resource named 'name.(svg|png)' (the given
+ 'name' parameter must *not* provide the extension).
+ """
+ if not _icons:
+ _load_icons()
+ return _icons.get(name)
+
+
+# dirty hack to please PyQt4 uic
+import hgrepoview, hgfileview
+sys.modules['hgrepoview'] = hgrepoview
+sys.modules['hgfileview'] = hgfileview
+sys.modules['hgqv_rc'] = hgqv_rc
+
+def setup_font_substitutions():
+ # be sure monospace default font for diffs have a decent substitution
+ # on MacOS
+ QtGui.QFont.insertSubstitutions('monospace', ['monaco', 'courier new'])
|
|
|
@@ -0,0 +1,362 @@ + # -*- coding: utf-8 -*-
+# Copyright (c) 2003-2010 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+"""
+Qt4 widgets to display diffs as blocks
+"""
+import sys, os
+
+from PyQt4 import QtGui, QtCore
+from PyQt4.QtCore import Qt, SIGNAL
+
+class BlockList(QtGui.QWidget):
+ """
+ A simple widget to be 'linked' to the scrollbar of a diff text
+ view.
+
+ It represents diff blocks with coloured rectangles, showing
+ currently viewed area by a semi-transparant rectangle sliding
+ above them.
+ """
+ def __init__(self, *args):
+ QtGui.QWidget.__init__(self, *args)
+ self._blocks = set()
+ self._minimum = 0
+ self._maximum = 100
+ self.blockTypes = {'+': QtGui.QColor(0xA0, 0xFF, 0xB0, ),#0xa5),
+ '-': QtGui.QColor(0xFF, 0xA0, 0xA0, ),#0xa5),
+ 'x': QtGui.QColor(0xA0, 0xA0, 0xFF, ),#0xa5),
+ }
+ self._sbar = None
+ self._value = 0
+ self._pagestep = 10
+ self._vrectcolor = QtGui.QColor(0x00, 0x00, 0x55, 0x25)
+ self._vrectbordercolor = self._vrectcolor.darker()
+ self.sizePolicy().setControlType(QtGui.QSizePolicy.Slider)
+ self.setMinimumWidth(20)
+
+ def clear(self):
+ self._blocks = set()
+
+ def addBlock(self, typ, alo, ahi):
+ self._blocks.add((typ, alo, ahi))
+
+ def setMaximum(self, maximum):
+ self._maximum = maximum
+ self.update()
+ self.emit(SIGNAL('rangeChanged(int, int)'),
+ self._minimum, self._maximum)
+
+ def setMinimum(self, minimum):
+ self._minimum = minimum
+ self.update()
+ self.emit(SIGNAL('rangeChanged(int, int)'),
+ self._minimum, self._maximum)
+
+ def setRange(self, minimum, maximum):
+ self._minimum = minimum
+ self._maximum = maximum
+ self.update()
+ self.emit(SIGNAL('rangeChanged(int, int)'),
+ self._minimum, self._maximum)
+
+ def setValue(self, val):
+ if val != self._value:
+ self._value = val
+ self.update()
+ self.emit(SIGNAL('valueChanged(int)'), val)
+
+ def setPageStep(self, pagestep):
+ if pagestep != self._pagestep:
+ self._pagestep = pagestep
+ self.update()
+ self.emit(SIGNAL('pageStepChanged(int)'), pagestep)
+
+ def linkScrollBar(self, sbar):
+ """
+ Make the block list displayer be linked to the scrollbar
+ """
+ self._sbar = sbar
+ self.setUpdatesEnabled(False)
+ self.setMaximum(sbar.maximum())
+ self.setMinimum(sbar.minimum())
+ self.setPageStep(sbar.pageStep())
+ self.setValue(sbar.value())
+ self.setUpdatesEnabled(True)
+ self.connect(sbar, SIGNAL('valueChanged(int)'), self.setValue)
+ self.connect(sbar, SIGNAL('rangeChanged(int, int)'), self.setRange)
+ self.connect(self, SIGNAL('valueChanged(int)'), sbar.setValue)
+ self.connect(self, SIGNAL('rangeChanged(int, int)'), sbar.setRange)
+ self.connect(self, SIGNAL('pageStepChanged(int)'), sbar.setPageStep)
+
+ def syncPageStep(self):
+ self.setPageStep(self._sbar.pageStep())
+
+ def paintEvent(self, event):
+ w = self.width() - 1
+ h = self.height()
+ p = QtGui.QPainter(self)
+ p.scale(1.0, float(h)/(self._maximum - self._minimum + self._pagestep))
+ p.setPen(Qt.NoPen)
+ for typ, alo, ahi in self._blocks:
+ p.save()
+ p.setBrush(self.blockTypes[typ])
+ p.drawRect(1, alo, w-1, ahi-alo)
+ p.restore()
+
+ p.save()
+ p.setPen(self._vrectbordercolor)
+ p.setBrush(self._vrectcolor)
+ p.drawRect(0, self._value, w, self._pagestep)
+ p.restore()
+
+class BlockMatch(BlockList):
+ """
+ A simpe widget to be linked to 2 file views (text areas),
+ displaying 2 versions of a same file (diff).
+
+ It will show graphically matching diff blocks between the 2 text
+ areas.
+ """
+ def __init__(self, *args):
+ QtGui.QWidget.__init__(self, *args)
+ self._blocks = set()
+ self._minimum = {'left': 0, 'right': 0}
+ self._maximum = {'left': 100, 'right': 100}
+ self.blockTypes = {'+': QtGui.QColor(0xA0, 0xFF, 0xB0, ),#0xa5),
+ '-': QtGui.QColor(0xFF, 0xA0, 0xA0, ),#0xa5),
+ 'x': QtGui.QColor(0xA0, 0xA0, 0xFF, ),#0xa5),
+ }
+ self._sbar = {}
+ self._value = {'left': 0, 'right': 0}
+ self._pagestep = {'left': 10, 'right': 10}
+ self._vrectcolor = QtGui.QColor(0x00, 0x00, 0x55, 0x25)
+ self._vrectbordercolor = self._vrectcolor.darker()
+ self.sizePolicy().setControlType(QtGui.QSizePolicy.Slider)
+ self.setMinimumWidth(20)
+
+ def nDiffs(self):
+ return len(self._blocks)
+
+ def showDiff(self, delta):
+ ps_l = float(self._pagestep['left'])
+ ps_r = float(self._pagestep['right'])
+ mv_l = self._value['left']
+ mv_r = self._value['right']
+ Mv_l = mv_l + ps_l
+ Mv_r = mv_r + ps_r
+
+ vblocks = []
+ blocks = sorted(self._blocks, key=lambda x:(x[1],x[3],x[2],x[4]))
+ for i, (typ, alo, ahi, blo, bhi) in enumerate(blocks):
+ if (mv_l<=alo<=Mv_l or mv_l<=ahi<=Mv_l or
+ mv_r<=blo<=Mv_r or mv_r<=bhi<=Mv_r):
+ break
+ else:
+ i = -1
+ i += delta
+
+ if i < 0:
+ return -1
+ if i >= len(blocks):
+ return 1
+ typ, alo, ahi, blo, bhi = blocks[i]
+ self.setValue(alo, "left")
+ self.setValue(blo, "right")
+ if i == 0:
+ return -1
+ if i == len(blocks)-1:
+ return 1
+ return 0
+
+ def nextDiff(self):
+ return self.showDiff(+1)
+
+ def prevDiff(self):
+ return self.showDiff(-1)
+
+ def addBlock(self, typ, alo, ahi, blo=None, bhi=None):
+ if bhi is None:
+ bhi = ahi
+ if blo is None:
+ blo = alo
+ self._blocks.add((typ, alo, ahi, blo, bhi))
+
+ def paintEvent(self, event):
+ w = self.width()
+ h = self.height()
+ p = QtGui.QPainter(self)
+ p.setRenderHint(p.Antialiasing)
+
+ ps_l = float(self._pagestep['left'])
+ ps_r = float(self._pagestep['right'])
+ v_l = self._value['left']
+ v_r = self._value['right']
+
+ # we do integer divisions here cause the pagestep is the
+ # integer number of fully displayed text lines
+ scalel = self._sbar['left'].height()//ps_l
+ scaler = self._sbar['right'].height()//ps_r
+
+ ml = v_l
+ Ml = v_l + ps_l
+ mr = v_r
+ Mr = v_r + ps_r
+
+ p.setPen(Qt.NoPen)
+ for typ, alo, ahi, blo, bhi in self._blocks:
+ if not (ml<=alo<=Ml or ml<=ahi<=Ml or mr<=blo<=Mr or mr<=bhi<=Mr):
+ continue
+ p.save()
+ p.setBrush(self.blockTypes[typ])
+
+ path = QtGui.QPainterPath()
+ path.moveTo(0, scalel * (alo - ml))
+ path.cubicTo(w/3.0, scalel * (alo - ml),
+ 2*w/3.0, scaler * (blo - mr),
+ w, scaler * (blo - mr))
+ path.lineTo(w, scaler * (bhi - mr) + 2)
+ path.cubicTo(2*w/3.0, scaler * (bhi - mr) + 2,
+ w/3.0, scalel * (ahi - ml) + 2,
+ 0, scalel * (ahi - ml) + 2)
+ path.closeSubpath()
+ p.drawPath(path)
+
+ p.restore()
+
+ def setMaximum(self, maximum, side):
+ self._maximum[side] = maximum
+ self.update()
+ self.emit(SIGNAL('rangeChanged(int, int, const QString &)'),
+ self._minimum[side], self._maximum[side], side)
+
+ def setMinimum(self, minimum, side):
+ self._minimum[side] = minimum
+ self.update()
+ self.emit(SIGNAL('rangeChanged(int, int, const QString &)'),
+ self._minimum[side], self._maximum[side], side)
+
+ def setRange(self, minimum, maximum, side=None):
+ if side is None:
+ if self.sender() == self._sbar['left']:
+ side = 'left'
+ else:
+ side = 'right'
+ self._minimum[side] = minimum
+ self._maximum[side] = maximum
+ self.update()
+ self.emit(SIGNAL('rangeChanged(int, int, const QString &)'),
+ self._minimum[side], self._maximum[side], side)
+
+ def setValue(self, val, side=None):
+ if side is None:
+ if self.sender() == self._sbar['left']:
+ side = 'left'
+ else:
+ side = 'right'
+ if val != self._value[side]:
+ self._value[side] = val
+ self.update()
+ self.emit(SIGNAL('valueChanged(int, const QString &)'), val, side)
+
+ def setPageStep(self, pagestep, side):
+ if pagestep != self._pagestep[side]:
+ self._pagestep[side] = pagestep
+ self.update()
+ self.emit(SIGNAL('pageStepChanged(int, const QString &)'),
+ pagestep, side)
+
+ def syncPageStep(self):
+ for side in ['left', 'right']:
+ self.setPageStep(self._sbar[side].pageStep(), side)
+
+ def resizeEvent(self, event):
+ self.syncPageStep()
+
+ def linkScrollBar(self, sb, side):
+ """
+ Make the block list displayer be linked to the scrollbar
+ """
+ if self._sbar is None:
+ self._sbar = {}
+ self._sbar[side] = sb
+ self.setUpdatesEnabled(False)
+ self.setMaximum(sb.maximum(), side)
+ self.setMinimum(sb.minimum(), side)
+ self.setPageStep(sb.pageStep(), side)
+ self.setValue(sb.value(), side)
+ self.setUpdatesEnabled(True)
+ self.connect(sb, SIGNAL('valueChanged(int)'), self.setValue)
+ self.connect(sb, SIGNAL('rangeChanged(int, int)'), self.setRange)
+
+ self.connect(self, SIGNAL('valueChanged(int, const QString &)'),
+ lambda v, s: side==s and sb.setValue(v))
+ self.connect(self, SIGNAL('rangeChanged(int, int, const QString )'),
+ lambda v1, v2, s: side==s and sb.setRange(v1, v2))
+ self.connect(self, SIGNAL('pageStepChanged(int, const QString )'),
+ lambda v, s: side==s and sb.setPageStep(v))
+
+if __name__ == '__main__':
+ a = QtGui.QApplication([])
+ f = QtGui.QFrame()
+ l = QtGui.QHBoxLayout(f)
+
+ sb1 = QtGui.QScrollBar()
+ sb2 = QtGui.QScrollBar()
+
+ w0 = BlockList()
+ w0.addBlock('-', 200, 300)
+ w0.addBlock('-', 450, 460)
+ w0.addBlock('x', 500, 501)
+ w0.linkScrollBar(sb1)
+
+ w1 = BlockMatch()
+ w1.addBlock('+', 12, 42)
+ w1.addBlock('+', 55, 142)
+ w1.addBlock('-', 200, 300)
+ w1.addBlock('-', 330, 400, 450, 460)
+ w1.addBlock('x', 420, 450, 500, 501)
+ w1.linkScrollBar(sb1, 'left')
+ w1.linkScrollBar(sb2, 'right')
+
+ w2 = BlockList()
+ w2.addBlock('+', 12, 42)
+ w2.addBlock('+', 55, 142)
+ w2.addBlock('x', 420, 450)
+ w2.linkScrollBar(sb2)
+
+ l.addWidget(sb1)
+ l.addWidget(w0)
+ l.addWidget(w1)
+ l.addWidget(w2)
+ l.addWidget(sb2)
+
+ w0.setRange(0, 1200)
+ w0.setPageStep(100)
+ w1.setRange(0, 1200, 'left')
+ w1.setRange(0, 1200, 'right')
+ w1.setPageStep(100, 'left')
+ w1.setPageStep(100, 'right')
+ w2.setRange(0, 1200)
+ w2.setPageStep(100)
+
+ print "sb1=", sb1.minimum(), sb1.maximum(), sb1.pageStep()
+ print "sb2=", sb2.minimum(), sb2.maximum(), sb2.pageStep()
+
+ f.show()
+ a.exec_()
+
|
|
|
@@ -0,0 +1,163 @@ + <ui version="4.0" >
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>620</width>
+ <height>546</height>
+ </rect>
+ </property>
+ <property name="windowTitle" >
+ <string>MainWindow</string>
+ </property>
+ <widget class="QWidget" name="centralwidget" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>33</y>
+ <width>620</width>
+ <height>513</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QSplitter" name="splitter" >
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ <widget class="QWidget" name="layoutWidget" >
+ <layout class="QHBoxLayout" name="horizontalLayout" >
+ <property name="spacing" >
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="HgRepoView" name="tableView_revisions_left" >
+ <property name="alternatingRowColors" >
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode" >
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior" >
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="showGrid" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="HgRepoView" name="tableView_revisions_right" >
+ <property name="alternatingRowColors" >
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode" >
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior" >
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="showGrid" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="layoutWidget" >
+ <layout class="QHBoxLayout" name="horizontalLayout_2" >
+ <property name="spacing" >
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QFrame" name="frame" >
+ <property name="frameShape" >
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="frameShadow" >
+ <enum>QFrame::Raised</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QToolBar" name="toolBar" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>121</width>
+ <height>33</height>
+ </rect>
+ </property>
+ <property name="windowTitle" >
+ <string>toolBar</string>
+ </property>
+ <attribute name="toolBarArea" >
+ <enum>TopToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak" >
+ <bool>true</bool>
+ </attribute>
+ <addaction name="actionClose" />
+ <addaction name="actionReload" />
+ </widget>
+ <widget class="QToolBar" name="toolBar_edit" >
+ <property name="geometry" >
+ <rect>
+ <x>121</x>
+ <y>0</y>
+ <width>499</width>
+ <height>33</height>
+ </rect>
+ </property>
+ <property name="windowTitle" >
+ <string>toolBar_2</string>
+ </property>
+ <attribute name="toolBarArea" >
+ <enum>TopToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak" >
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ <action name="actionClose" >
+ <property name="text" >
+ <string>Close</string>
+ </property>
+ <property name="shortcut" >
+ <string>Ctrl+Q</string>
+ </property>
+ </action>
+ <action name="actionReload" >
+ <property name="text" >
+ <string>Reload</string>
+ </property>
+ <property name="shortcut" >
+ <string>Ctrl+R</string>
+ </property>
+ </action>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>HgRepoView</class>
+ <extends>QTableView</extends>
+ <header>hgrepoview.h</header>
+ </customwidget>
+ </customwidgets>
+ <tabstops>
+ <tabstop>tableView_revisions_left</tabstop>
+ <tabstop>tableView_revisions_right</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
|
|
|
@@ -0,0 +1,108 @@ + <ui version="4.0" >
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>481</width>
+ <height>438</height>
+ </rect>
+ </property>
+ <property name="windowTitle" >
+ <string>hg FileViewer</string>
+ </property>
+ <widget class="QWidget" name="centralwidget" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>33</y>
+ <width>481</width>
+ <height>405</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout" >
+ <property name="margin" >
+ <number>2</number>
+ </property>
+ <item>
+ <widget class="QSplitter" name="splitter" >
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ <widget class="HgRepoView" name="tableView_revisions" >
+ <property name="alternatingRowColors" >
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode" >
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior" >
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="showGrid" >
+ <bool>false</bool>
+ </property>
+ <property name="gridStyle" >
+ <enum>Qt::NoPen</enum>
+ </property>
+ </widget>
+ <widget class="HgFileView" native="1" name="textView" />
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QToolBar" name="toolBar_edit" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>481</width>
+ <height>33</height>
+ </rect>
+ </property>
+ <property name="windowTitle" >
+ <string>toolBar</string>
+ </property>
+ <attribute name="toolBarArea" >
+ <enum>TopToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak" >
+ <bool>false</bool>
+ </attribute>
+ <addaction name="actionClose" />
+ <addaction name="actionReload" />
+ </widget>
+ <action name="actionClose" >
+ <property name="text" >
+ <string>Close</string>
+ </property>
+ <property name="shortcut" >
+ <string>Ctrl+Q</string>
+ </property>
+ </action>
+ <action name="actionReload" >
+ <property name="text" >
+ <string>Reload</string>
+ </property>
+ <property name="shortcut" >
+ <string>Ctrl+R</string>
+ </property>
+ </action>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>HgRepoView</class>
+ <extends>QTableView</extends>
+ <header>hgrepoview.h</header>
+ </customwidget>
+ <customwidget>
+ <class>HgFileView</class>
+ <extends>QWidget</extends>
+ <header>hgfileview.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
|
|
@@ -0,0 +1,47 @@ + # -*- coding: iso-8859-1 -*-
+#!/usr/bin/env python
+# main.py - qt4-based hg rev log browser
+#
+# Copyright (C) 2007-2010 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.
+"""
+Help window for hgview
+"""
+import sys, os
+import re
+
+from PyQt4 import QtCore, QtGui, Qsci
+
+from hgviewlib.qt4 import icon as geticon
+from hgviewlib.qt4.hgdialogmixin import HgDialogMixin
+from hgviewlib.hgviewhelp import help_msg, get_options_helpmsg
+
+Qt = QtCore.Qt
+bold = QtGui.QFont.Bold
+connect = QtCore.QObject.connect
+SIGNAL = QtCore.SIGNAL
+
+from docutils.core import publish_string
+
+class HelpViewer(QtGui.QDialog, HgDialogMixin):
+ """hgview simple help viewer"""
+ _uifile = 'helpviewer.ui'
+ def __init__(self, repo, parent=None):
+ self.repo = repo
+ QtGui.QDialog.__init__(self, parent)
+ HgDialogMixin.__init__(self)
+ data = help_msg + get_options_helpmsg(rest=True)
+
+ html = publish_string(data, writer_name='html')
+ self.textBrowser.setText(html)
+
+ # must be redefined cause it's a QDialog
+ def accept(self):
+ QtGui.QDialog.accept(self)
+
+ def reject(self):
+ QtGui.QDialog.reject(self)
+
+
|
|
@@ -0,0 +1,66 @@ + <ui version="4.0" >
+ <class>Dialog</class>
+ <widget class="QDialog" name="Dialog" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle" >
+ <string>Dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout" >
+ <item>
+ <widget class="QTextBrowser" name="textBrowser" />
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox" >
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons" >
+ <set>QDialogButtonBox::Close</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>Dialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>Dialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
|
|
|
@@ -0,0 +1,134 @@ + # -*- coding: utf-8 -*-
+# Copyright (c) 2003-2010 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+#
+# make sur the Qt rc files are converted into python modules, then load them
+# this must be done BEFORE other hgview qt4 modules are loaded.
+import os
+import os.path as osp
+import sys
+
+from PyQt4 import QtCore
+from PyQt4 import QtGui, uic
+connect = QtCore.QObject.connect
+SIGNAL = QtCore.SIGNAL
+Qt = QtCore.Qt
+
+from hgviewlib.config import HgConfig
+from hgviewlib.qt4 import should_rebuild
+
+class HgDialogMixin(object):
+ """
+ Mixin for QDialogs defined from a .ui file, wich automates the
+ setup of the UI from the ui file, and the loading of user
+ preferences.
+ The main class must define a '_ui_file' class attribute.
+ """
+ def __init__(self):
+ # self.repo must be defined in actual class before calling __init__
+ assert self.repo is not None
+ self.load_config()
+
+ _path = osp.dirname(__file__)
+ uifile = osp.join(_path, self._uifile)
+ pyfile = uifile.replace(".ui", "_ui.py")
+ if should_rebuild(uifile, pyfile):
+ os.system('pyuic4 %s -o %s' % (uifile, pyfile))
+ try:
+ modname = osp.splitext(osp.basename(uifile))[0] + "_ui"
+ modname = "hgviewlib.qt4.%s" % modname
+ mod = __import__(modname, fromlist=['*'])
+ classnames = [x for x in dir(mod) if x.startswith('Ui_')]
+ if len(classnames) == 1:
+ ui_class = getattr(mod, classnames[0])
+ elif 'Ui_MainWindow' in classnames:
+ ui_class = getattr(mod, 'Ui_MainWindow')
+ else:
+ raise ValueError("Can't determine which main class to use in %s" % modname)
+ except ImportError:
+ ui_class, base_class = uic.loadUiType(uifile)
+
+ if ui_class not in self.__class__.__bases__:
+ # hacking by adding the form class from ui file or pyuic4
+ # generated module because we cannot use metaclass here,
+ # due to "QObject" not being a subclass of "object"
+ self.__class__.__bases__ = self.__class__.__bases__ + (ui_class,)
+ self.setupUi(self)
+ self.load_ui()
+ self.disab_shortcuts = []
+
+ def load_ui(self):
+ # we explicitely create a QShortcut so we can disable it
+ # when a "helper context toolbar" is activated (which can be
+ # closed hitting the Esc shortcut)
+ self.esc_shortcut = QtGui.QShortcut(self)
+ self.esc_shortcut.setKey(Qt.Key_Escape)
+ connect(self.esc_shortcut, SIGNAL('activated()'),
+ self.maybeClose)
+ self._quickbars = []
+
+ def attachQuickBar(self, qbar):
+ qbar.setParent(self)
+ self._quickbars.append(qbar)
+ connect(qbar, SIGNAL('escShortcutDisabled(bool)'),
+ self.setShortcutsEnabled)
+ self.addToolBar(Qt.BottomToolBarArea, qbar)
+ connect(qbar, SIGNAL('visible'),
+ self.ensureOneQuickBar)
+
+ def setShortcutsEnabled(self, enabled=True):
+ for sh in self.disab_shortcuts:
+ sh.setEnabled(enabled)
+
+ def ensureOneQuickBar(self):
+ tb = self.sender()
+ for w in self._quickbars:
+ if w is not tb:
+ w.hide()
+
+ def maybeClose(self):
+ for w in self._quickbars:
+ if w.isVisible():
+ w.cancel()
+ break
+ else:
+ self.close()
+
+ def load_config(self):
+ cfg = HgConfig(self.repo.ui)
+ fontstr = cfg.getFont()
+ font = QtGui.QFont()
+ try:
+ if not font.fromString(fontstr):
+ raise Exception
+ except:
+ print "bad font name '%s'" % fontstr
+ font.setFamily("Monospace")
+ font.setFixedPitch(True)
+ font.setPointSize(10)
+ self._font = font
+
+ self.rowheight = cfg.getRowHeight()
+ self.users, self.aliases = cfg.getUsers()
+ return cfg
+
+ def accept(self):
+ self.close()
+ def reject(self):
+ self.close()
+
+
|
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
|
|
@@ -0,0 +1,156 @@ + # -*- coding: utf-8 -*-
+# Copyright (c) 2003-2010 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""
+Qt4 dialogs to display hg revisions of a file
+"""
+
+import sys, os
+import os.path as osp
+
+from mercurial import ui, hg, util
+from mercurial.revlog import LookupError
+
+from PyQt4 import QtGui, QtCore, Qsci
+from PyQt4.QtCore import Qt
+
+from hgviewlib.util import tounicode
+
+from hgviewlib.qt4 import icon as geticon
+from hgviewlib.qt4.hgdialogmixin import HgDialogMixin
+from hgviewlib.qt4.hgrepomodel import ManifestModel
+from hgviewlib.qt4.lexers import get_lexer
+
+connect = QtCore.QObject.connect
+disconnect = QtCore.QObject.disconnect
+SIGNAL = QtCore.SIGNAL
+nullvariant = QtCore.QVariant()
+
+
+class ManifestViewer(QtGui.QMainWindow, HgDialogMixin):
+ """
+ Qt4 dialog to display all files of a repo at a given revision
+ """
+ _uifile = 'manifestviewer.ui'
+ def __init__(self, repo, noderev):
+ self.repo = repo
+ QtGui.QMainWindow.__init__(self)
+ HgDialogMixin.__init__(self)
+ self.setWindowTitle('Hg manifest viewer - %s:%s' % (repo.root, noderev))
+
+ # hg repo
+ self.repo = repo
+ self.rev = noderev
+ self.setupModels()
+
+ self.createActions()
+ self.setupTextview()
+
+ def load_config(self):
+ cfg = HgDialogMixin.load_config(self)
+ self.max_file_size = cfg.getMaxFileSize()
+
+ def setupModels(self):
+ self.treemodel = ManifestModel(self.repo, self.rev)
+ self.treeView.setModel(self.treemodel)
+ connect(self.treeView.selectionModel(),
+ SIGNAL('currentChanged(const QModelIndex &, const QModelIndex &)'),
+ self.fileSelected)
+
+ def createActions(self):
+ connect(self.actionClose, SIGNAL('triggered()'),
+ self.close)
+ self.actionClose.setIcon(geticon('quit'))
+
+ def setupTextview(self):
+ lay = QtGui.QHBoxLayout(self.mainFrame)
+ lay.setSpacing(0)
+ lay.setContentsMargins(0,0,0,0)
+ sci = Qsci.QsciScintilla(self.mainFrame)
+ lay.addWidget(sci)
+ sci.setMarginLineNumbers(1, True)
+ sci.setMarginWidth(1, '000')
+ sci.setReadOnly(True)
+ sci.setFont(self._font)
+
+ sci.SendScintilla(sci.SCI_SETSELEOLFILLED, True)
+ self.textView = sci
+
+ def fileSelected(self, index, *args):
+ if not index.isValid():
+ return
+ path = self.treemodel.pathFromIndex(index)
+ try:
+ fc = self.repo.changectx(self.rev).filectx(path)
+ except LookupError:
+ # may occur when a directory is selected
+ self.textView.setMarginWidth(1, '00')
+ self.textView.setText('')
+ return
+
+ if fc.size() > self.max_file_size:
+ data = "file too big"
+ else:
+ # return the whole file
+ data = fc.data()
+ if util.binary(data):
+ data = "binary file"
+ else:
+ data = tounicode(data)
+ lexer = get_lexer(path, data)
+ if lexer:
+ lexer.setFont(self._font)
+ self.textView.setLexer(lexer)
+ self._cur_lexer = lexer
+ nlines = data.count('\n')
+ self.textView.setMarginWidth(1, str(nlines)+'00')
+ self.textView.setText(data)
+
+ def setCurrentFile(self, filename):
+ index = QtCore.QModelIndex()
+ path = filename.split(osp.sep)
+ for p in path:
+ self.treeView.expand(index)
+ for row in range(self.treemodel.rowCount(index)):
+ newindex = self.treemodel.index(row, 0, index)
+ if newindex.internalPointer().data(0) == p:
+ index = newindex
+ break
+ self.treeView.setCurrentIndex(index)
+
+
+if __name__ == '__main__':
+ from mercurial import ui, hg
+ from optparse import OptionParser
+ opt = OptionParser()
+ opt.add_option('-R', '--repo',
+ dest='repo',
+ default='.',
+ help='Hg repository')
+
+ options, args = opt.parse_args()
+ if len(args) != 1:
+ opt.error('Please specify a revision number')
+ rev = args[0]
+
+ u = ui.ui()
+ repo = hg.repository(u, options.repo)
+ app = QtGui.QApplication([])
+
+ view = ManifestViewer(repo, int(rev))
+ view.show()
+ sys.exit(app.exec_())
+
|
|
@@ -0,0 +1,23 @@ + <RCC>
+ <qresource prefix="/" >
+ <file>icons/quit.svg</file>
+ <file>icons/reload.svg</file>
+ <file>icons/back.svg</file>
+ <file>icons/forward.svg</file>
+ <file>icons/left.svg</file>
+ <file>icons/right.svg</file>
+ <file>icons/up.svg</file>
+ <file>icons/down.svg</file>
+ <file>icons/leftright.svg</file>
+ <file>icons/close.png</file>
+ <file>icons/help.svg</file>
+ <file>icons/find.svg</file>
+ <file>icons/goto.svg</file>
+ <file>icons/modified.svg</file>
+ <file>icons/clean.svg</file>
+ <file>icons/mqdiff.svg</file>
+ <file>icons/mqdiff_x.svg</file>
+ <file>icons/mqpatch.svg</file>
+ <file>icons/mqpatch_x.svg</file>
+ </qresource>
+</RCC>
|
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
|
@@ -0,0 +1,1 @@ + Most of the icons used here are from the Tango Icon Theme. Some of them have been modified.
|
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
|
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
|
@@ -0,0 +1,44 @@ + <?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+ <!ENTITY ns_svg "http://www.w3.org/2000/svg">
+ <!ENTITY ns_xlink "http://www.w3.org/1999/xlink">
+]>
+<svg version="1.1" id="Layer_1" xmlns="&ns_svg;" xmlns:xlink="&ns_xlink;" width="128" height="128" viewBox="0 0 128 128"
+ overflow="visible" enable-background="new 0 0 128 128" xml:space="preserve">
+ <path opacity="0.2" d="M3,64c0,34.188,27.813,62,62,62c34.188,0,62-27.813,62-62S99.188,2,65,2C30.813,2,3,29.813,3,64z"/>
+ <path opacity="0.2" d="M4,64c0,33.636,27.364,61,61,61s61-27.364,61-61S98.636,3,65,3S4,30.364,4,64z"/>
+ <linearGradient id="XMLID_5_" gradientUnits="userSpaceOnUse" x1="53.6196" y1="-10.749" x2="79.1196" y2="154.5011">
+ <stop offset="0" style="stop-color:#295AA6"/>
+ <stop offset="0.3319" style="stop-color:#27569F"/>
+ <stop offset="0.8192" style="stop-color:#224C8B"/>
+ <stop offset="1" style="stop-color:#1F4782"/>
+ </linearGradient>
+ <path fill="url(#XMLID_5_)" d="M4,63c0,33.636,27.364,61,61,61s61-27.364,61-61S98.636,2,65,2S4,29.364,4,63z"/>
+ <linearGradient id="XMLID_6_" gradientUnits="userSpaceOnUse" x1="69.3184" y1="125.5117" x2="64.0683" y2="49.5123">
+ <stop offset="0" style="stop-color:#2C60B0"/>
+ <stop offset="0.3199" style="stop-color:#2A5CA9"/>
+ <stop offset="0.7896" style="stop-color:#245195"/>
+ <stop offset="1" style="stop-color:#214B8A"/>
+ </linearGradient>
+ <circle fill="url(#XMLID_6_)" cx="65" cy="63" r="60"/>
+ <linearGradient id="XMLID_7_" gradientUnits="userSpaceOnUse" x1="17.7852" y1="24.6035" x2="124.5362" y2="98.6042">
+ <stop offset="0" style="stop-color:#728FBA"/>
+ <stop offset="1" style="stop-color:#3C6199"/>
+ </linearGradient>
+ <path fill="url(#XMLID_7_)" d="M57.067,53.727C86,43,123.648,50.273,123.648,50.273C117.811,23.25,93.771,3,65,3
+ C31.863,3,5,29.863,5,63c0,8.284,1.679,16.176,4.715,23.354C9.715,86.355,23.271,66.256,57.067,53.727z"/>
+ <path fill="#4C7AC2" d="M65,122C32.03,122,5.283,95.406,5.013,62.5C5.011,62.668,5,62.832,5,63c0,33.137,26.863,60,60,60
+ c33.137,0,60-26.863,60-60c0-0.168-0.012-0.332-0.013-0.5C124.717,95.406,97.97,122,65,122z"/>
+ <linearGradient id="XMLID_8_" gradientUnits="userSpaceOnUse" x1="5" y1="33.3506" x2="125" y2="33.3506">
+ <stop offset="0" style="stop-color:#90A5C4"/>
+ <stop offset="0.4719" style="stop-color:#D0E0FB"/>
+ <stop offset="0.8119" style="stop-color:#ACBAD5"/>
+ <stop offset="1" style="stop-color:#9DAAC5"/>
+ </linearGradient>
+ <path fill="url(#XMLID_8_)" d="M65,4.101c32.97,0,59.717,26.595,59.987,59.5c0.001-0.167,0.013-0.333,0.013-0.5
+ c0-33.138-26.863-60-60-60c-33.137,0-60,26.862-60,60c0,0.167,0.011,0.333,0.013,0.5C5.283,30.695,32.03,4.101,65,4.101z"/>
+ <path fill="#FFFFFF" d="M40.86,25.273c7.22-2.143,14.779-3.723,22.336-3.723c12.974,0,25.721,5.754,25.721,20.194
+ c0,15.794-20.531,22.111-19.178,36.891H55.864c0-10.718,4.175-16.585,8.236-21.21c4.174-4.625,8.349-8.01,8.349-13.877
+ c0-7.444-6.431-10.266-12.974-10.266c-5.979,0-11.958,2.031-17.373,4.514L40.86,25.273z M54.962,85.855h15.794v15.793H54.962
+ V85.855z"/>
+</svg>
|
|
@@ -0,0 +1,98 @@ + <?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="345.32999"
+ height="345.32999"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docname="left.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ version="1.0">
+ <defs
+ id="defs4">
+ <marker
+ inkscape:stockid="EmptyTriangleInL"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="EmptyTriangleInL"
+ style="overflow:visible">
+ <path
+ id="path3310"
+ d="M 5.77,0 L -2.88,5 L -2.88,-5 L 5.77,0 z"
+ style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none"
+ transform="matrix(-0.8,0,0,-0.8,4.8,0)" />
+ </marker>
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 526.18109 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+ id="perspective10" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1.6292675"
+ inkscape:cx="162.63104"
+ inkscape:cy="175.19921"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:window-width="1189"
+ inkscape:window-height="767"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ showguides="true"
+ inkscape:guide-bbox="true">
+ <sodipodi:guide
+ orientation="1,0"
+ position="237.98396,631.10233"
+ id="guide3977" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Calque 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-237.99891,-394.73638)">
+ <g
+ id="g4070"
+ transform="translate(27.2422,68.587853)">
+ <g
+ id="g4023" />
+ <path
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.19999981;stroke-dasharray:none;stroke-opacity:1"
+ d="M 265.95591,479.63572 L 265.95591,517.98928 L 527.31536,517.98928 L 527.31536,479.63572 L 265.95591,479.63572 z"
+ id="path3973" />
+ <path
+ sodipodi:nodetypes="ccc"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:30;marker-start:none;stroke-miterlimit:4;stroke-dasharray:none"
+ d="M 353.78909,587.89066 L 252.99891,498.81352 L 353.78909,409.73638"
+ id="path4037" />
+ </g>
+ </g>
+</svg>
|
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
Loading...