Staging
v0.8.1
v0.8.1
https://foss.heptapod.net/mercurial/hgview
Tip revision: 69320a156e41cedece5d3a490a125da9aca84c03 authored by David Douard on 24 September 2009, 15:06:33 UTC
debian: changed package description a bit (be a bit more humble)
debian: changed package description a bit (be a bit more humble)
Tip revision: 69320a1
blockmatcher.py
# -*- coding: utf-8 -*-
"""
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_()