Staging
v0.5.1
https://foss.heptapod.net/mercurial/hgview
Raw File
Tip revision: 091c74a5d7931cf7dd70af2962f707060f7dca7d authored by David Douard on 05 June 2009, 15:21:46 UTC
do not display error message ig .hgusers does not exist
Tip revision: 091c74a
hgfileview.py
# Copyright (c) 2009 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 high level widgets for hg repo changelogs and filelogs
"""
import sys

from mercurial.node import hex, short as short_hex, bin as short_bin
from mercurial import util

from PyQt4 import QtCore, QtGui, Qsci
Qt = QtCore.Qt
connect = QtCore.QObject.connect
SIGNAL = QtCore.SIGNAL
nullvariant = QtCore.QVariant()

from hgviewlib.decorators import timeit
from hgviewlib.qt4 import icon as geticon
from hgviewlib.qt4.hgfileviewer import FileViewer, FileDiffViewer, ManifestViewer
from hgviewlib.qt4.quickbar import QuickBar
from hgviewlib.qt4.lexers import get_lexer

class HgFileView(Qsci.QsciScintilla):
    def __init__(self, parent=None):
        Qsci.QsciScintilla.__init__(self, parent)
        self.setMarginLineNumbers(1, True)
        self.setMarginWidth(1, '000')
        self.setReadOnly(True)
        #self.setFont(self.font)

        self.SendScintilla(self.SCI_INDICSETSTYLE, 8, self.INDIC_ROUNDBOX)
        self.SendScintilla(self.SCI_INDICSETUNDER, 8, True)
        self.SendScintilla(self.SCI_INDICSETFORE, 8, 0xBBFFFF)
        self.SendScintilla(self.SCI_INDICSETSTYLE, 9, self.INDIC_PLAIN)
        self.SendScintilla(self.SCI_INDICSETUNDER, 9, False)
        self.SendScintilla(self.SCI_INDICSETFORE, 9, 0x0000FF)

        self.SendScintilla(self.SCI_SETSELEOLFILLED, True)

        self._model = None
        self._ctx = None
        self._filename = None
        self._find_text = None
        self._mode = "diff" # can be 'diff' or 'file' 

    def setMode(self, mode):
        assert mode in ('diff', 'file')
        if mode != self._mode:
            self._mode = mode
            self.displayFile(self._filename)
        
    def setModel(self, model):
        # XXX we really need only the "Graph" instance 
        self._model = model
        self.clear()
        
    def setContext(self, ctx):
        self._ctx = ctx
        self.clear()

    def rev(self):
        return self._ctx.rev()

    def filename(self):
        return self._filename
    
    def displayFile(self, filename):
        self._filename = filename
        self.clear()
        if filename is None:
            return
        if self._mode == 'file':
            flag = "+"            
            data = self._ctx.filectx(filename).data()
            if util.binary(data):
                data = "binary file"
        else:
            flag, data = self._model.graph.filedata(filename, self._ctx.rev())
        lexer = None
        if flag == "+":
            lexer = get_lexer(filename, data)
            nlines = data.count('\n')
            self.setMarginWidth(1, str(nlines)+'0')            
        elif flag == "=":
            lexer = Qsci.QsciLexerDiff()
            self.setMarginWidth(1, 0)
        if lexer:
            lexer.setDefaultFont(self.font())
            lexer.setFont(self.font())
        self.setLexer(lexer)
        self._cur_lexer = lexer

        self.setText(data)
        if self._find_text:
            self.highlightSearchString(self._find_text)
        
    def searchString(self, text):
        self._find_text = text
        self.clearHighlights()
        if self._find_text:
            for pos in self.highlightSearchString(self._find_text):
                if not self._find_text: # XXX is this required to handle "cancellation"?
                    break                
                self.highlightCurrentSearchString(pos, self._find_text)
                yield self._ctx.rev(), self._filename, pos
                
    def clearHighlights(self):
        n = self.length()
        self.SendScintilla(self.SCI_SETINDICATORCURRENT, 8) # highlight
        self.SendScintilla(self.SCI_INDICATORCLEARRANGE, 0, n)
        self.SendScintilla(self.SCI_SETINDICATORCURRENT, 9) # current found occurrence
        self.SendScintilla(self.SCI_INDICATORCLEARRANGE, 0, n)

    def highlightSearchString(self, text):
        data = unicode(self.text())
        self.SendScintilla(self.SCI_SETINDICATORCURRENT, 8)
        pos = [data.find(text)]
        n = len(text)
        while pos[-1] > -1:
            self.SendScintilla(self.SCI_INDICATORFILLRANGE, pos[-1], n)
            pos.append(data.find(text, pos[-1]+1))
        pos = [x for x in pos if x > -1]
        return pos
        
    def highlightCurrentSearchString(self, pos, text):
        line = self.SendScintilla(self.SCI_LINEFROMPOSITION, pos)
        #line, idx = w.lineIndexFromPosition(nextpos)
        self.ensureLineVisible(line)
        self.SendScintilla(self.SCI_SETINDICATORCURRENT, 9)
        self.SendScintilla(self.SCI_INDICATORCLEARRANGE, 0, pos)
        self.SendScintilla(self.SCI_INDICATORFILLRANGE, pos, len(text))

        
class HgFileListView(QtGui.QTableView):
    """
    A QTableView for displaying a HgFileListModel
    """
    def __init__(self, parent=None):
        QtGui.QTableView.__init__(self, parent)
        self.setShowGrid(False)
        self.verticalHeader().hide()
        self.verticalHeader().setDefaultSectionSize(20)
        self.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
        self.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
        self.setAlternatingRowColors(True)
        self.setTextElideMode(Qt.ElideLeft)
        self.horizontalHeader().setToolTip('Double click to toggle merge mode')
        
        self.createActions()
        
        connect(self.horizontalHeader(), SIGNAL('sectionDoubleClicked(int)'),
                self.toggleFullFileList)
        connect(self,
                SIGNAL('doubleClicked (const QModelIndex &)'),
                self.fileActivated)
        
        connect(self.horizontalHeader(),
                SIGNAL('sectionResized(int, int, int)'),
                self.sectionResized)        

    def setModel(self, model):
        QtGui.QTableView.setModel(self, model)
        connect(model, SIGNAL('layoutChanged()'),
                self.fileSelected)
        connect(self.selectionModel(),
                SIGNAL('currentRowChanged (const QModelIndex & , const QModelIndex & )'),
                self.fileSelected)

    def currentFile(self):
        index = self.currentIndex()
        return self.model().fileFromIndex(index)
        
    def fileSelected(self, index=None, *args):
        if index is None:
            index = self.currentIndex()
        sel_file = self.model().fileFromIndex(index)
        self.emit(SIGNAL('fileSelected'), sel_file)

    def selectFile(self, filename):
        self.setCurrentIndex(self.model().indexFromFile(filename))

    def fileActivated(self, index):
        sel_file = self.model().fileFromIndex(index)
        self.diffNavigate(sel_file)
        
    def toggleFullFileList(self, *args):
        self.model().toggleFullFileList()

    def navigate(self, filename=None):
        if filename is None:
            filename = self.currentFile()
        if  len(self.model().repo.file(filename))>1:
            dlg = FileViewer(self.model().repo, filename)
            dlg.setWindowTitle('Hg file log viewer')
            dlg.show()
            self._dlg = dlg # keep a reference on the dlg

    def diffNavigate(self, filename=None):
        if filename is None:
            filename = self.currentFile()
        if  len(self.model().repo.file(filename))>1:
            dlg = FileDiffViewer(self.model().repo, filename)
            dlg.setWindowTitle('Hg file log viewer')
            dlg.show()
            self._dlg = dlg # keep a reference on the dlg
    
    def _action_defs(self):
        a = [("navigate", self.tr("Navigate"), None , self.tr('Navigate the revision tree of this file'), None, self.navigate),
             ("diffnavigate", self.tr("Diff-mode navigate"), None , self.tr('Navigate the revision tree of this file in diff mode'), None, self.diffNavigate),
             ]
        return a

    def createActions(self):
        self._actions = {}
        for name, desc, icon, tip, key, cb in self._action_defs():
            act = QtGui.QAction(desc, self)
            if icon:
                act.setIcon(geticon(icon))
            if tip:
                act.setStatusTip(tip)
            if key:
                act.setShortcut(key)
            if cb:
                connect(act, SIGNAL('triggered()'), cb)
            self._actions[name] = act
            self.addAction(act)
        
    def contextMenuEvent(self, event):
        menu = QtGui.QMenu(self)
        for act in ['navigate', 'diffnavigate']:
            if act:
                menu.addAction(self._actions[act])
            else:
                menu.addSeparator()
        menu.exec_(event.globalPos())
        
    def resizeEvent(self, event):
        QtGui.QTableView.resizeEvent(self, event)
        vp_width = self.viewport().width()
        col_widths = [self.columnWidth(i) \
                      for i in range(1, self.model().columnCount())]
        col_width = vp_width - sum(col_widths)
        col_width = max(col_width, 50)
        self.setColumnWidth(0, col_width)

    def sectionResized(self, idx, oldsize, newsize):
        if idx == 1:
            self.model().setDiffWidth(newsize)
back to top