# Copyright (c) 2009-2012 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, see . """ Qt4 high level widgets for hg repo changelogs and filelogs """ from collections import defaultdict from mercurial.node import short as short_hex from mercurial.error import RepoError from PyQt4 import QtCore, QtGui from PyQt4.QtCore import Qt, pyqtSignal from hgviewlib.config import HgConfig from hgviewlib.util import format_desc, xml_escape, tounicode from hgviewlib.util import first_known_precursors, first_known_successors from hgviewlib.qt4.mixins import ActionsMixin # Re-Structured Text support raw2html = lambda x: u'
%s
' % xml_escape(x) try: from docutils.core import publish_string import docutils.utils def rst2html(text, stylesheet_path=None): if stylesheet_path is None: stylesheet_path = 'qrc:/resources/description.css' try: # halt_level allows the parser to raise errors # report_level cleans the standard output out = publish_string(text, writer_name='html', settings_overrides={ 'halt_level':docutils.utils.Reporter.WARNING_LEVEL, 'report_level':docutils.utils.Reporter.SEVERE_LEVEL + 1, 'stylesheet_path': stylesheet_path, 'embed_stylesheet': False, }) except: # docutils is not always reliable (or reliably packaged) out = raw2html(text) if not isinstance(out, unicode): # if the docutils call did not fail, we likely got an str ... out = tounicode(out) return out except ImportError: rst2html = None TROUBLE_EXPLANATIONS = defaultdict(lambda:u'unknown trouble') TROUBLE_EXPLANATIONS['unstable'] = "Based on obsolete ancestor" TROUBLE_EXPLANATIONS['bumped'] = "Hopeless successors of a public changeset" TROUBLE_EXPLANATIONS['divergent'] = "Another changeset are also a successors "\ "of one of your precursor" # temporary compat with older evolve version TROUBLE_EXPLANATIONS['latecomer'] = TROUBLE_EXPLANATIONS['bumped'] TROUBLE_EXPLANATIONS['conflicting'] = TROUBLE_EXPLANATIONS['divergent'] class RevisionDescriptionView(ActionsMixin, QtGui.QTextBrowser): """ Display metadata for one revision (rev, author, description, etc.) using a TextBrowser. """ parent_revision_selected = pyqtSignal([], [int]) revision_selected = pyqtSignal([], [int]) def __init__(self, parent=None): super(RevisionDescriptionView, self).__init__(parent) self.excluded = () self.descwidth = 60 # number of chars displayed for parent/child descriptions self.add_action( 'rst', self.tr("Fancy description"), menu=self.tr("Display"), tip=self.tr('Interpret ReST comments'), callback=self.refreshDisplay, checked=bool(rst2html), enabled=bool(rst2html), ) self.anchorClicked.connect(self.on_anchor_clicked) def on_anchor_clicked(self, qurl): """ Callback called when a link is clicked in the text browser """ rev = qurl.toString() if hasattr(rev, 'toUtf8'): # a PyQt4.QtCore.QString(u'...') ... but we won't the encoded string (preferably with the original encoding ...) rev = rev.toUtf8().data() diff = False if rev.startswith('diff_'): rev = int(rev[5:]) diff = True elif rev.isdigit(): rev = int(rev) try: rev = self.ctx._repo[rev].rev() except RepoError: QtGui.QDesktopServices.openUrl(qurl) self.refreshDisplay() if diff: self.diffrev = rev self.refreshDisplay() # TODO: emit a signal to recompute the diff if self.diffrev is None: self.parent_revision_selected.emit() else: self.parent_revision_selected[int].emit(self.diffrev) else: if rev is None: self.revision_selected.emit() else: self.revision_selected[int].emit(rev) def setDiffRevision(self, rev): if rev != self.diffrev: self.diffrev = rev self.refreshDisplay() def displayRevision(self, ctx): self.ctx = ctx self.diffrev = ctx.parents()[0].rev() if hasattr(self.ctx._repo, "mq"): self.mqseries = self.ctx._repo.mq.series[:] self.mqunapplied = [x[1] for x in self.ctx._repo.mq.unapplied(self.ctx._repo)] mqpatch = set(self.ctx.tags()).intersection(self.mqseries) if mqpatch: self.mqpatch = mqpatch.pop() else: self.mqpatch = None else: self.mqseries = [] self.mqunapplied = [] self.mqpatch = None self.refreshDisplay() def selectNone(self): cursor = self.textCursor() cursor.clearSelection() cursor.setPosition(0) self.setTextCursor(cursor) self.setExtraSelections([]) def searchString(self, text): self.selectNone() if text in self.toPlainText(): clist = [] while self.find(text): eselect = self.ExtraSelection() eselect.cursor = self.textCursor() eselect.format.setBackground(QtGui.QColor('#ffffbb')) clist.append(eselect) self.selectNone() self.setExtraSelections(clist) def finditer(self, text): if text: while True: if self.find(text): yield self.ctx.rev(), None else: break return finditer(self, text) def refreshDisplay(self): ctx = self.ctx rev = ctx.rev() cfg = HgConfig(ctx._repo.ui) buf = u"\n" if self.mqpatch: buf += u'' % cfg.getMQFGColor() buf += u'\n' buf += u'' if rev is None: buf += u"\n" else: buf += u'\n' % (ctx.rev(), short_hex(ctx.node())) user = tounicode(ctx.user()) if ctx.node() else u'' buf += '\n' % user buf += '\n' % tounicode(ctx.branch()) buf += '\n' % tounicode(ctx.phasestr()) buf += '' buf += "
Patch queue: ' for p in self.mqseries: if p in self.mqunapplied: p = u"%s" % tounicode(p) elif p == self.mqpatch: p = u"%s" % tounicode(p) buf += u' %s ' % tounicode(p) buf += u'
Working Directory'\ u'%s:'\ u'%s'\ u'%s%s%s
\n" buf += "\n" parents = [p for p in ctx.parents() if p] for p in parents: if p.rev() > -1: buf += self._html_ctx_info(p, 'Parent', 'Direct ancestor of this changeset') if len(parents) == 2: p = parents[0].ancestor(parents[1]) buf += self._html_ctx_info(p, 'Ancestor', 'Direct ancestor of this changeset') for p in ctx.children(): r = p.rev() if r > -1 and r not in self.excluded: buf += self._html_ctx_info(p, 'Child', 'Direct descendant of this changeset') for prec in first_known_precursors(ctx, self.excluded): buf += self._html_ctx_info(prec, 'Precursor', 'Previous version obsolete by this changeset') for suc in first_known_successors(ctx, self.excluded): buf += self._html_ctx_info(suc, 'Successors', 'Updated version that make this changeset obsolete') bookmarks = ', '.join(tounicode(bookmark) for bookmark in ctx.bookmarks()) if bookmarks: buf += ''\ ''\ '\n' % bookmarks troubles = ctx.instabilities() if troubles: span = u'%s' content = u', '.join([span % (TROUBLE_EXPLANATIONS[troub], troub) for troub in troubles]) buf += ''\ ''\ '\n' % ''.join(content) buf += u"
Bookmarks: '\ '%s
Troubles: '\ '%s
\n" desc = tounicode(ctx.description()) if self.get_action('rst').isChecked(): replace = cfg.getFancyReplace() if replace: desc = replace(desc) desc = rst2html(desc, cfg.getDescriptionStylePath()) else: desc = raw2html(desc) buf += u'
%s
\n' % desc self.setHtml(buf) def _html_ctx_info(self, ctx, title, tooltip=None): isdiffrev = ctx.rev() == self.diffrev if not tooltip: tooltip = title short = short_hex(ctx.node()) if getattr(ctx, 'applied', True) else ctx.node() descr = format_desc(ctx.description(), self.descwidth) rev = ctx.rev() out = ''\ '%(title)s:'\ '' % locals() if isdiffrev: out += '' out += ''\ '%(rev)s'\ ':'\ '%(short)s '\ '%(descr)s' % locals() if isdiffrev: out += '' out += '\n' return out