/****************************************************************************
**
** Copyright (C) 2001-2006 Frank Hemer <frank@hemer.org>,
** Tilo Riemer <riemer@crossvc.com>,
** Jose Hernandez <joseh@tesco.net>
**
**
**----------------------------------------------------------------------------
**
**----------------------------------------------------------------------------
**
** CrossVC is available under two different licenses:
**
** If CrossVC is linked against the GPLed version of Qt
** CrossVC is released under the terms of GPL also.
**
** If CrossVC is linked against a nonGPLed version of Qt
** CrossVC is released under the terms of the
** CrossVC License for non-Unix platforms (CLNU)
**
**
** CrossVC License for non-Unix platforms (CLNU):
**
** Redistribution and use in binary form, without modification,
** are permitted provided that the following conditions are met:
**
** 1. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 2. It is not permitted to distribute the binary package under a name
** different than CrossVC.
** 3. The name of the authors may not be used to endorse or promote
** products derived from this software without specific prior written
** permission.
** 4. The source code is the creative property of the authors.
** Extensions and development under the terms of the Gnu Public License
** are limited to the Unix platform. Any distribution or compilation of
** the source code against libraries licensed other than gpl requires
** the written permission of the authors.
**
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
** GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
**
**
** CrossVC License for Unix platforms:
**
** 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, version 2 of the License.
** 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 version 2 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.
**
*****************************************************************************/
#include "config.h"
#include <stdio.h>
#include <qlayout.h>
#include <qlabel.h>
#include <qfont.h>
#include <qpushbutton.h>
#include <qtimer.h>
#include <qpainter.h>
#include <qregexp.h>
#include <qwhatsthis.h>
#include <qsplitter.h>
#include <qsizegrip.h>
#include <assert.h>
#include "DiffDialogImpl.h"
#include "globals.h"
#include "pixmapcache.h"
//The width of the splitter
const unsigned int DiffDialogImpl::SplitterStyle::SPLITTER_WIDTH = 4;
int containsWChar(const QString s) {
int count = 0;
char * c = const_cast<char *>(s.ascii());
if (c) {
unsigned int i;
unsigned int len = strlen(c);
for (i=0; i<len; ++i) {
if (c[i] < 0) ++count;
}
}
return count;
}
/**
* Constructs a DiffDialogImpl which is a child of 'parent', with the
* name 'name' and widget flags set to 'f'
*
* The dialog will by default be modeless, unless you set 'modal' to
* TRUE to construct a modal dialog.
*/
DiffDialogImpl::DiffDialogImpl(const QIconSet &whatsThisIconSet,
QWidget* parent, const char* name, WFlags fl)
: DiffDialog(LookAndFeel::g_b0AsParent ? 0 : parent, name, fl)
{
Splitter->setStyle(new SplitterStyle(this));
Splitter->setOpaqueResize(true);
m_pWhatsThis->setIconSet(whatsThisIconSet);
#ifdef Q_WS_MAC
m_pWhatsThis->setMaximumWidth(m_pWhatsThis->height() * 2);
#else
m_pWhatsThis->setMaximumWidth(m_pWhatsThis->height());
#endif
ButtonLayout->addWidget(new QSizeGrip(this),0,Qt::AlignRight|Qt::AlignBottom);
m_pDiffA->setPartner(m_pDiffB);
m_pDiffA->setLineNumbers(true);
m_pDiffA->setMarkers(true);
m_pDiffB->setPartner(m_pDiffA);
m_pDiffB->setLineNumbers(true);
m_pDiffB->setMarkers(true);
if (!Font::g_diff.isEmpty()) {
QFont f;
if (f.fromString(Font::g_diff)) {
m_pDiffA->setFont(f);
m_pDiffB->setFont(f);
} else Font::g_diff = QString::null;
}
QFontMetrics fm (m_pDiffA->fontMetrics ());
int dialogWidth = QMIN (QApplication::desktop ()->width (), fm.width ("0123456789")*12);
setMinimumSize (dialogWidth, fm.lineSpacing ()*30);
}
/**
* Destroys the object and frees any allocated resources
*/
DiffDialogImpl::~DiffDialogImpl()
{
// no need to delete child widgets, Qt does it all for us
}
/**
* Toggles scrollbar synchronisation on and off.
*/
void DiffDialogImpl::toggleSynchronize(bool b)
{
m_pDiffA->setPartner(b? m_pDiffB : 0);
m_pDiffB->setPartner(b? m_pDiffA : 0);
}
#define COLUMNWIDTH ((SIDEWIDTH-3)>>1)
#define DELIMITERWIDTH (SIDEWIDTH-(COLUMNWIDTH<<1))
void DiffDialogImpl::parseCvsDiff(CvsBuffer *pCvsBuffer, QString name,
QString revA, QString revB)
{
setCaption ("CVS Diff - " + name);
revlabel1->setText(revA.isEmpty() ? QString(tr("Repository"))
: tr("Revision ") + revA);
revlabel2->setText(revB.isEmpty() ? QString(tr("Sandbox"))
: tr("Revision ") + revB);
int lineno1, lineno2;
int skiplines = outputLineOffset + 5;
int blockChanges = 0;
int lineChanges = 0;
int blockStart = -1;
DiffView::DiffType type = DiffView::Unchanged;
m_linesTotal = 0;
DiffView::DiffType prevType = DiffView::Unchanged;
lineno1 = lineno2 = 0;
QString line;
unsigned int len = (*pCvsBuffer).numLines();
// must skip some lines
bool skip = true;
unsigned int i;
for (i = skiplines; i < len; i++)
{
line = (*pCvsBuffer).textLine(i);
if (skip) {
if (line.startsWith("diff ")) skip = false;
continue;
}
//ugly hack to work around cvs-server ignoring the client-locale
int colWidth = COLUMNWIDTH;
// int wcCount = line.left(colWidth).contains(195);
int wcCount = containsWChar(line.left(colWidth));
// qDebug("line ->"+line+", wcCount: "+QString::number(wcCount)+", wc: "+QString::number(containsWChar(line)));
int offset = colWidth;
int deltaCount = wcCount;
int tCount;
// while ( (tCount=line.mid(offset,deltaCount).contains(195)) > 0 ) {
while ( (tCount=containsWChar(line.mid(offset,deltaCount))) > 0 ) {
offset += deltaCount;
wcCount += tCount;
deltaCount = tCount;
}
// colWidth += (2*wcCount);
colWidth += wcCount;
QString line1 = line.left(COLUMNWIDTH);
QString line2 = line.mid(colWidth + DELIMITERWIDTH, COLUMNWIDTH);
QChar marker = line.at(colWidth+1);
type =
(marker == '|') ? DiffView::Change :
(marker == '<') ? DiffView::Delete :
(marker == '>') ? DiffView::Insert :
DiffView::Unchanged;
if (type != DiffView::Unchanged) {
lineChanges++;
}
if (type != prevType) {//start of changed block
if ( type != DiffView::Unchanged) {
if (blockStart==-1) {//new changed block
blockStart = m_linesTotal;
} else {//next changed block
m_changes[blockStart] = ChangedBlock(m_linesTotal-blockStart,prevType);
blockStart = m_linesTotal;
}
blockChanges++;
} else if (blockStart!=-1){//end of changed block
m_changes[blockStart] = ChangedBlock(m_linesTotal-blockStart,prevType);
blockStart = -1;
}
prevType = type;
}
//remove trailing space
line1.truncate(line1.findRev(QRegExp("\\S"))+1);
line2.truncate(line2.findRev(QRegExp("\\S"))+1);
DiffView::DiffList diffList1;
DiffView::DiffList diffList2;
if (type == DiffView::Change) {
analyzeChangeDiff(line1,diffList1,line2,diffList2);
} else {
DiffView::Diff diff1;
diff1.pos = 0;
diff1.len = line1.length();
diff1.type = DiffView::Unchanged;//type; -->don't change char color on add or remove
DiffView::Diff diff2;
diff2.pos = 0;
diff2.len = line2.length();
diff2.type = DiffView::Unchanged;//type; -->don't change char color on add or remove
diffList1.append(diff1);
diffList2.append(diff2);
}
m_pDiffA->addLine(line1, diffList1,
(type != DiffView::Unchanged ) ? DiffView::Neutral : type,
(type!=DiffView::Insert) ? ++lineno1 : -1);
m_pDiffB->addLine(line2, diffList2, type,
(type!=DiffView::Delete)? ++lineno2 : -1);
LineChangeBox->setNum(lineChanges);
BlockChangeBox->setNum(blockChanges);
m_linesTotal++;
}
if (blockStart!=-1){//end of changed block
m_changes[blockStart] = ChangedBlock(m_linesTotal-blockStart,type);
}
}
void DiffDialogImpl::analyzeChangeDiff(QString &line1, DiffView::DiffList &diffList1, QString &line2, DiffView::DiffList &diffList2) {
// static int test = 0;if (test++ != 2) return;
QMap<int,MatchMark> matchMarks;
int offset1 = 0;
int offset2 = 0;
while (line1.at(offset1).isSpace()) offset1++;
while (line2.at(offset2).isSpace()) offset2++;
markBestMatch(line1,offset1,line1.length()-1,line2,offset2,line2.length()-1,matchMarks);
if (matchMarks.empty()) {//no match found
DiffView::Diff diff1;
DiffView::Diff diff2;
diff1.pos = 0;
diff1.len = line1.length();
diff1.type = DiffView::Change;
diff2.pos = 0;
diff2.len = line2.length();
diff2.type = DiffView::Change;
diffList1.append(diff1);
diffList2.append(diff2);
} else {//analyze added/removed/changed
QMap<int,MatchMark>::iterator it = matchMarks.begin();
DiffView::Diff diff1;
DiffView::Diff diff2;
//insert offset (spaces)
diff1.pos = 0;
diff1.len = offset1;
diff1.type = DiffView::Unchanged;
diff2.pos = 0;
diff2.len = offset2;
diff2.type = DiffView::Unchanged;
diffList1.append(diff1);
diffList2.append(diff2);
// qDebug("appending offset(blank): "+line1.mid(diff1.pos,diff1.len)+", line2: "+line2.mid(diff2.pos,diff2.len));
int lastSPos = offset1;
int lastTPos = offset2;
while (it != matchMarks.end()) {
int sLen = it.key()-lastSPos;
int tLen = it.data().pos-lastTPos;
if (sLen == 0 && tLen == 0) {//caused by the algorithm
;
} else if ( sLen == 0) {//added
diff2.pos = lastTPos;
diff2.len = tLen;
diff2.type = DiffView::Insert;
diffList2.append(diff2);
// qDebug("added: "+line2.mid(lastTPos,tLen));
} else if ( tLen == 0) {//removed
diff1.pos = lastSPos;
diff1.len = sLen;
diff1.type = DiffView::Delete;
diffList1.append(diff1);
// qDebug("removed: "+line1.mid(lastSPos,sLen));
} else {//changed
diff1.pos = lastSPos;
diff1.len = sLen;
diff1.type = DiffView::Change;
diff2.pos = lastTPos;
diff2.len = tLen;
diff2.type = DiffView::Change;
diffList1.append(diff1);
diffList2.append(diff2);
// qDebug("changed, line1: "+line1.mid(lastSPos,sLen)+", line2: "+line2.mid(lastTPos,tLen));
}
//append the matching area
diff1.pos = it.key();
diff1.len = it.data().len;
diff1.type = DiffView::Unchanged;
diff2.pos = it.data().pos;
diff2.len = it.data().len;
diff2.type = DiffView::Unchanged;
diffList1.append(diff1);
diffList2.append(diff2);
// qDebug("appending unchanged line1: "+line1.mid(diff1.pos,diff1.len)+", line2: "+line2.mid(diff2.pos,diff2.len));
lastSPos = it.key()+it.data().len;
lastTPos = it.data().pos+it.data().len;
++it;
}
int rest1 = line1.length()-lastSPos;
int rest2 = line2.length()-lastTPos;
if ( (rest1==0) && (rest2==0)) return;
if (rest1==0) {//added
diff2.pos = lastTPos;
diff2.len = rest2;
diff2.type = DiffView::Insert;
diffList2.append(diff2);
// qDebug("rest added: "+line2.mid(lastTPos,rest2));
} else if (rest2==0) {//removed
diff1.pos = lastSPos;
diff1.len = rest1;
diff1.type = DiffView::Delete;
diffList1.append(diff1);
// qDebug("rest removed: "+line1.mid(lastSPos,rest1));
} else {//changed
diff1.pos = lastSPos;
diff1.len = rest1;
diff1.type = DiffView::Change;
diff2.pos = lastTPos;
diff2.len = rest2;
diff2.type = DiffView::Change;
diffList1.append(diff1);
diffList2.append(diff2);
// qDebug("rest changed, line1: "+line1.mid(lastSPos,rest1)+", line2: "+line2.mid(lastTPos,rest2));
}
}
}
void DiffDialogImpl::markBestMatch(QString &line1, int offset1, int end1,
QString &line2, int offset2, int end2,
QMap<int,MatchMark> &matchMarks) {
end1 = end1-offset1;
end2 = end2-offset2;
QString str1 = line1.mid(offset1,end1+1);
QString str2 = line2.mid(offset2,end2+1);
// qDebug("\nanalyzeChangeDiff(->"+str1+"<-,->"+str2+"<-)\n");
// qDebug("end1: "+QString::number(end1)+", end2: "+QString::number(end2));
int tokenStart = 0;
int tokenLen = 0;
int matchStart = 0;
//set minimum match size
int maxMatchLen = CvsOptions::g_precision-1;
int maxMatchSStart = -1;
int maxMatchTStart = -1;
QChar c;
QChar cn;
bool isLetterToken = FALSE;
int pos = 0;
while ( (pos <= end1) ) {
// qDebug("outer loop pos: "+QString::number(pos));
//get token ('letters,numbers,_' in any length or single char)
c = str1.at(pos);
tokenLen = 1;
if ( (c.isLetterOrNumber() || c=='_') && (isLetterToken = TRUE) && (pos+tokenLen <= end1) ) {
do {
c = str1.at(pos+tokenLen);
} while ( ( c.isLetterOrNumber() || c=='_') && (pos+(++tokenLen) <= end1) );
} else isLetterToken = FALSE;
tokenStart = pos;
int firstTokenLen = tokenLen;
QString token = str1.mid(tokenStart,firstTokenLen);
// qDebug("use token: ->"+token+"<-, len: "+QString::number(tokenLen));
matchStart = str2.find(token);
//check if char after token (in target str) belongs to the token, i.e. the compared part is longer, and so doesn't match
if (isLetterToken && (matchStart+tokenLen<=end2) ) {
c = str2.at(matchStart+tokenLen);
if ( c.isLetterOrNumber() || c=='_') matchStart = -1;
}
if (matchStart>-1) {
// qDebug("found token at pos: "+QString::number(matchStart));
int afterMatchEnd = matchStart + tokenLen;
int tmpTokenLen = 0;
pos += tokenLen;
while ( (pos <= end1) && (afterMatchEnd <= end2) ) {
// qDebug("inner loop pos: "+QString::number(pos)+", afterMatchEnd: "+QString::number(afterMatchEnd) );
//get next token ('letters,numbers,_' in any length or single char
c = str1.at(pos);
tmpTokenLen = 1;
if ( (c.isLetterOrNumber() || c=='_') && (isLetterToken = TRUE) && (pos+tmpTokenLen <= end1) ) {
do {
c = str1.at(pos+tmpTokenLen);
} while ( ( c.isLetterOrNumber() || c=='_') && (pos+(++tmpTokenLen) <= end1) );
} else isLetterToken = FALSE;
QString tmpToken = str1.mid(pos,tmpTokenLen);
// qDebug("intern use token: ->"+tmpToken+"<-, len: "+QString::number(tmpTokenLen));
if ( str2.find(tmpToken,afterMatchEnd) == afterMatchEnd) {
if (isLetterToken && (afterMatchEnd+tmpTokenLen<=end2) ) {
c = str2.at(afterMatchEnd+tmpTokenLen);
if ( c.isLetterOrNumber() || c=='_') {//compared part is longer, and so doesn't match
// qDebug("intern didn't find token: ->"+tmpToken+"<-, val: "+QString::number(afterMatchEnd));
break;
}
}
// qDebug("intern found token: ->"+tmpToken+"<- at: "+QString::number(afterMatchEnd));
tokenLen += tmpTokenLen;
afterMatchEnd += tmpTokenLen;
pos += tmpTokenLen;
} else {
// qDebug("intern didn't find token: ->"+token+"<-, val: "+QString::number(afterMatchEnd));
break;
}
}
if ( tokenLen > maxMatchLen ) {
maxMatchSStart = tokenStart;
maxMatchTStart = matchStart;
maxMatchLen = tokenLen;
// qDebug("inner found max match: ->"+str1.mid(maxMatchSStart,maxMatchLen)
// +"<- at: "+QString::number(maxMatchTStart)+", len: "+QString::number(tokenLen));
}
}
pos = tokenStart+firstTokenLen;
}
if (maxMatchSStart > -1) {
// qDebug("found max match: ->"+str1.mid(maxMatchSStart,maxMatchLen)
// +"<- at: "+QString::number(maxMatchTStart)+", len: "+QString::number(maxMatchLen)+"\n");
MatchMark mark;
mark.pos = offset2+maxMatchTStart;
mark.len = maxMatchLen;
matchMarks[offset1+maxMatchSStart] = mark;
} else {
// qDebug("\n********** no max match found, returning **********\n");
return;
}
//right half comes first!!
if ( (maxMatchSStart+maxMatchLen<=end1) && (maxMatchTStart+maxMatchLen<=end2) ) {
// qDebug("process right half: ");
markBestMatch(line1,offset1+maxMatchSStart+maxMatchLen,offset1+end1,line2,offset2+maxMatchTStart+maxMatchLen,offset2+end2,matchMarks);
}
//left half is next!!
if (maxMatchSStart>0 && maxMatchTStart>0) {
// qDebug("process left half: ");
markBestMatch(line1,offset1,offset1+maxMatchSStart-1,line2,offset2,offset2+maxMatchTStart-1,matchMarks);
}
// qDebug("\n********** end of func, line1: ->"+line1+"<- line2: ->"+line2+"<- **********\n");
}
void DiffDialogImpl::enterWhatsThisMode()
{
QWhatsThis::enterWhatsThisMode();
}
void DiffDialogImpl::SplitterStyle::drawPrimitive(PrimitiveElement pe, QPainter *p,
const QRect &r, const QColorGroup &cg, SFlags flags /*= Style_Default*/,
const QStyleOption &StyleOption /*= QStyleOption::Default*/) const
{
if (pe != PE_Splitter) {
QApplication::style().drawPrimitive(pe, p, r, cg, flags, StyleOption);
} else {
int top = dlg->revlabel1->height();
int h = 0;
dlg->m_pDiffA->adjustScrollBarOffsets(top,h);
double ppl = (double)h/(double)dlg->m_linesTotal;
ChangesMap::Iterator it;
for ( it = dlg->m_changes.begin(); it != dlg->m_changes.end(); ++it ) {
QColor c;
switch(it.data().type()) {
case DiffView::Change: {//red
c.setRgb(255, 0, 0);
break;
}
case DiffView::Insert: {//blue
c.setRgb(0, 65, 255);
break;
}
case DiffView::Delete: {//green
c.setRgb(43, 190, 65);
break;
}
default: {
continue;
}
}
int y = (int)(it.key()*ppl);
int height = (int)(it.data().lines()*ppl);
if (height<1) {
height = 1;
}
p->setPen(c);
p->setBrush(c);
p->drawRect(0,top+y,r.width(),height);
}
}
}
syntax highlighted by Code2HTML, v. 0.9.1