diff --git a/src/diff.cpp b/src/diff.cpp index 2eb4626..984d89f 100644 --- a/src/diff.cpp +++ b/src/diff.cpp @@ -1,1462 +1,1465 @@ /*************************************************************************** * Copyright (C) 2003-2007 by Joachim Eibl * * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * * * * 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. * * * ***************************************************************************/ #include "diff.h" #include "gnudiff_diff.h" +#include "merger.h" #include "options.h" #include "progress.h" #include #include #include #include #include #include int LineData::width(int tabSize) const { QString pLine = getLine(); int w = 0; int j = 0; for(int i = 0; i < size(); ++i) { if(pLine[i] == '\t') { for(j %= tabSize; j < tabSize; ++j) ++w; j = 0; } else { ++w; ++j; } } return w; } // The bStrict flag is true during the test where a nonmatching area ends. // Then the equal()-function requires that the match has more than 2 nonwhite characters. // This is to avoid matches on trivial lines (e.g. with white space only). // This choice is good for C/C++. bool LineData::equal(const LineData& l1, const LineData& l2, bool bStrict) { if(l1.getLine() == nullptr || l2.getLine() == nullptr) return false; if(bStrict && g_bIgnoreTrivialMatches) return false; // Ignore white space diff QString::const_iterator p1 = l1.getLine().begin(); QString::const_iterator p1End = l1.getLine().end(); QString::const_iterator p2 = l2.getLine().begin(); QString::const_iterator p2End = l2.getLine().end(); if(g_bIgnoreWhiteSpace) { int nonWhite = 0; for(;;) { while(isWhite(*p1) && p1 != p1End) ++p1; while(isWhite(*p2) && p2 != p2End) ++p2; if(p1 == p1End && p2 == p2End) { if(bStrict && g_bIgnoreTrivialMatches) { // Then equality is not enough return nonWhite > 2; } else // equality is enough return true; } else if(p1 == p1End || p2 == p2End) return false; if(*p1 != *p2) return false; ++p1; ++p2; ++nonWhite; } } else { return (l1.size() == l2.size() && QString::compare(l1.getLine(), l2.getLine()) == 0); } } // First step void Diff3LineList::calcDiff3LineListUsingAB(const DiffList* pDiffListAB) { // First make d3ll for AB (from pDiffListAB) DiffList::const_iterator i = pDiffListAB->begin(); LineRef::LineType lineA = 0; LineRef::LineType lineB = 0; Diff d(0, 0, 0); qCInfo(kdiffMain) << "Enter: calcDiff3LineListUsingAB" ; for(;;) { if(d.numberOfEquals() == 0 && d.diff1() == 0 && d.diff2() == 0) { if(i != pDiffListAB->end()) { d = *i; ++i; } else break; } Diff3Line d3l; if(d.numberOfEquals() > 0) { d3l.bAEqB = true; d3l.setLineA(lineA); d3l.setLineB(lineB); d.adjustNumberOfEquals(-1); ++lineA; ++lineB; } else if(d.diff1() > 0 && d.diff2() > 0) { d3l.setLineA(lineA); d3l.setLineB(lineB); d.adjustDiff1(-1); d.adjustDiff2(-1); ++lineA; ++lineB; } else if(d.diff1() > 0) { d3l.setLineA(lineA); d.adjustDiff1(-1); ++lineA; } else if(d.diff2() > 0) { d3l.setLineB(lineB); d.adjustDiff2(-1); ++lineB; } Q_ASSERT(d.numberOfEquals() >= 0); qCDebug(kdiffCore) << "lineA = " << d3l.getLineA() << ", lineB = " << d3l.getLineB() ; push_back(d3l); } qCInfo(kdiffMain) << "Leave: calcDiff3LineListUsingAB" ; } // Second step void Diff3LineList::calcDiff3LineListUsingAC(const DiffList* pDiffListAC) { //////////////// // Now insert data from C using pDiffListAC DiffList::const_iterator i = pDiffListAC->begin(); Diff3LineList::iterator i3 = begin(); LineRef::LineType lineA = 0; LineRef::LineType lineC = 0; Diff d(0, 0, 0); for(;;) { if(d.numberOfEquals() == 0 && d.diff1() == 0 && d.diff2() == 0) { if(i != pDiffListAC->end()) { d = *i; ++i; } else break; } Diff3Line d3l; if(d.numberOfEquals() > 0) { // Find the corresponding lineA while(i3->getLineA() != lineA) ++i3; i3->setLineC(lineC); i3->bAEqC = true; i3->bBEqC = i3->isEqualAB(); d.adjustNumberOfEquals(-1); ++lineA; ++lineC; ++i3; } else if(d.diff1() > 0 && d.diff2() > 0) { d3l.setLineC(lineC); insert(i3, d3l); d.adjustDiff1(-1); d.adjustDiff2(-1); ++lineA; ++lineC; } else if(d.diff1() > 0) { d.adjustDiff1(-1); ++lineA; } else if(d.diff2() > 0) { d3l.setLineC(lineC); insert(i3, d3l); d.adjustDiff2(-1); ++lineC; } } } // Third step void Diff3LineList::calcDiff3LineListUsingBC(const DiffList* pDiffListBC) { //////////////// // Now improve the position of data from C using pDiffListBC // If a line from C equals a line from A then it is in the // same Diff3Line already. // If a line from C equals a line from B but not A, this // information will be used here. DiffList::const_iterator i = pDiffListBC->begin(); Diff3LineList::iterator i3b = begin(); Diff3LineList::iterator i3c = begin(); LineRef::LineType lineB = 0; LineRef::LineType lineC = 0; Diff d(0, 0, 0); for(;;) { if(d.numberOfEquals() == 0 && d.diff1() == 0 && d.diff2() == 0) { if(i != pDiffListBC->end()) { d = *i; ++i; } else break; } Diff3Line d3l; if(d.numberOfEquals() > 0) { // Find the corresponding lineB and lineC while(i3b != end() && i3b->getLineB() != lineB) ++i3b; while(i3c != end() && i3c->getLineC() != lineC) ++i3c; Q_ASSERT(i3b != end()); Q_ASSERT(i3c != end()); if(i3b == i3c) { Q_ASSERT(i3b->getLineC() == lineC); i3b->bBEqC = true; } else { // Is it possible to move this line up? // Test if no other B's are used between i3c and i3b // First test which is before: i3c or i3b ? Diff3LineList::iterator i3c1 = i3c; Diff3LineList::iterator i3b1 = i3b; while(i3c1 != i3b && i3b1 != i3c) { Q_ASSERT(i3b1 != end() || i3c1 != end()); if(i3c1 != end()) ++i3c1; if(i3b1 != end()) ++i3b1; } if(i3c1 == i3b && !i3b->isEqualAB()) // i3c before i3b { Diff3LineList::iterator i3 = i3c; int nofDisturbingLines = 0; while(i3 != i3b && i3 != end()) { if(i3->getLineB().isValid()) ++nofDisturbingLines; ++i3; } if(nofDisturbingLines > 0) //&& nofDisturbingLines < d.nofEquals*d.nofEquals+4 ) { Diff3LineList::iterator i3_last_equal_A = end(); i3 = i3c; while(i3 != i3b) { if(i3->isEqualAB()) { i3_last_equal_A = i3; } ++i3; } /* If i3_last_equal_A isn't still set to d3ll.end(), then * we've found a line in A that is equal to one in B * somewhere between i3c and i3b */ bool before_or_on_equal_line_in_A = (i3_last_equal_A != end()); // Move the disturbing lines up, out of sight. i3 = i3c; while(i3 != i3b) { if(i3->getLineB().isValid() || (before_or_on_equal_line_in_A && i3->getLineA().isValid())) { d3l.setLineB(i3->getLineB()); i3->getLineB().invalidate(); // Move A along if it matched B if(before_or_on_equal_line_in_A) { d3l.setLineA(i3->getLineA()); d3l.bAEqB = i3->isEqualAB(); i3->getLineA().invalidate(); i3->bAEqC = false; } i3->bAEqB = false; i3->bBEqC = false; insert(i3c, d3l); } if(i3 == i3_last_equal_A) { before_or_on_equal_line_in_A = false; } ++i3; } nofDisturbingLines = 0; } if(nofDisturbingLines == 0) { // Yes, the line from B can be moved. i3b->getLineB().invalidate(); // This might leave an empty line: removed later. i3b->bAEqB = false; i3b->bBEqC = false; i3c->setLineB(lineB); i3c->bBEqC = true; i3c->bAEqB = i3c->isEqualAC(); } } else if(i3b1 == i3c && !i3c->isEqualAC()) { Diff3LineList::iterator i3 = i3b; int nofDisturbingLines = 0; while(i3 != i3c && i3 != end()) { if(i3->getLineC().isValid()) ++nofDisturbingLines; ++i3; } if(nofDisturbingLines > 0) //&& nofDisturbingLines < d.nofEquals*d.nofEquals+4 ) { Diff3LineList::iterator i3_last_equal_A = end(); i3 = i3b; while(i3 != i3c) { if(i3->isEqualAC()) { i3_last_equal_A = i3; } ++i3; } /* If i3_last_equal_A isn't still set to d3ll.end(), then * we've found a line in A that is equal to one in C * somewhere between i3b and i3c */ bool before_or_on_equal_line_in_A = (i3_last_equal_A != end()); // Move the disturbing lines up. i3 = i3b; while(i3 != i3c) { if(i3->getLineC().isValid() || (before_or_on_equal_line_in_A && i3->getLineA().isValid())) { d3l.setLineC(i3->getLineC()); i3->getLineC().invalidate(); // Move A along if it matched C if(before_or_on_equal_line_in_A) { d3l.setLineA(i3->getLineA()); d3l.bAEqC = i3->isEqualAC(); i3->getLineA().invalidate(); i3->bAEqB = false; } i3->bAEqC = false; i3->bBEqC = false; insert(i3b, d3l); } if(i3 == i3_last_equal_A) { before_or_on_equal_line_in_A = false; } ++i3; } nofDisturbingLines = 0; } if(nofDisturbingLines == 0) { // Yes, the line from C can be moved. i3c->getLineC().invalidate(); // This might leave an empty line: removed later. i3c->bAEqC = false; i3c->bBEqC = false; i3b->setLineC(lineC); i3b->bBEqC = true; i3b->bAEqC = i3b->isEqualAB(); } } } d.adjustNumberOfEquals(-1); ++lineB; ++lineC; ++i3b; ++i3c; } else if(d.diff1() > 0) { Diff3LineList::iterator i3 = i3b; while(i3->getLineB() != lineB) ++i3; if(i3 != i3b && !i3->isEqualAB()) { // Take B from this line and move it up as far as possible d3l.setLineB(lineB); insert(i3b, d3l); i3->getLineB().invalidate(); } else { i3b = i3; } d.adjustDiff1(-1); ++lineB; ++i3b; if(d.diff2() > 0) { d.adjustDiff2(-1); ++lineC; } } else if(d.diff2() > 0) { d.adjustDiff2(-1); ++lineC; } } /* Diff3LineList::iterator it = d3ll.begin(); int li=0; for( ; it!=d3ll.end(); ++it, ++li ) { printf( "%4d %4d %4d %4d A%c=B A%c=C B%c=C\n", li, it->getLineA(), it->getLineB(), it->getLineC(), it->isEqualAB() ? '=' : '!', it->isEqualAC() ? '=' : '!', it->isEqualBC() ? '=' : '!' ); } printf("\n");*/ } // Test if the move would pass a barrier. Return true if not. bool ManualDiffHelpList::isValidMove(int line1, int line2, e_SrcSelector winIdx1, e_SrcSelector winIdx2) const { if(line1 >= 0 && line2 >= 0) { ManualDiffHelpList::const_iterator i; for(i = begin(); i != end(); ++i) { const ManualDiffHelpEntry& mdhe = *i; if(!mdhe.isValidMove(line1, line2, winIdx1, winIdx2)) return false; } } return true; // no barrier passed. } bool ManualDiffHelpEntry::isValidMove(int line1, int line2, e_SrcSelector winIdx1, e_SrcSelector winIdx2) const { // Barrier int l1 = winIdx1 == A ? lineA1 : winIdx1 == B ? lineB1 : lineC1; int l2 = winIdx2 == A ? lineA1 : winIdx2 == B ? lineB1 : lineC1; if(l1 >= 0 && l2 >= 0) { if((line1 >= l1 && line2 < l2) || (line1 < l1 && line2 >= l2)) return false; l1 = winIdx1 == A ? lineA2 : winIdx1 == B ? lineB2 : lineC2; l2 = winIdx2 == A ? lineA2 : winIdx2 == B ? lineB2 : lineC2; ++l1; ++l2; if((line1 >= l1 && line2 < l2) || (line1 < l1 && line2 >= l2)) return false; } return true; } static bool runDiff(const QVector* p1, const qint32 index1, LineRef size1, const QVector* p2, const qint32 index2, LineRef size2, DiffList& diffList, const QSharedPointer &pOptions) { ProgressProxy pp; static GnuDiff gnuDiff; // All values are initialized with zeros. pp.setCurrent(0); diffList.clear(); if(p1 == nullptr || (*p1)[index1].getLine() == nullptr || p2 == nullptr || (*p2)[index2].getLine() == nullptr || size1 == 0 || size2 == 0) { if(p1 != nullptr && p2 != nullptr && (*p1)[index1].getLine() == nullptr && (*p2)[index2].getLine() == nullptr && size1 == size2) diffList.push_back(Diff(size1, 0, 0)); else { diffList.push_back(Diff(0, size1, size2)); } } else { GnuDiff::comparison comparisonInput; memset(&comparisonInput, 0, sizeof(comparisonInput)); comparisonInput.parent = nullptr; comparisonInput.file[0].buffer = (*p1)[index1].getBuffer()->unicode() + (*p1)[index1].getOffset(); //ptr to buffer comparisonInput.file[0].buffered = ((*p1)[size1].getOffset() - 1); // size of buffer comparisonInput.file[1].buffer = (*p2)[index2].getBuffer()->unicode() + (*p2)[index2].getOffset(); //ptr to buffer comparisonInput.file[1].buffered = ((*p2)[size2].getOffset() - 1); // size of buffer gnuDiff.ignore_white_space = GnuDiff::IGNORE_ALL_SPACE; // I think nobody needs anything else ... gnuDiff.bIgnoreWhiteSpace = true; gnuDiff.bIgnoreNumbers = pOptions->m_bIgnoreNumbers; gnuDiff.minimal = pOptions->m_bTryHard; gnuDiff.ignore_case = false; GnuDiff::change* script = gnuDiff.diff_2_files(&comparisonInput); LineRef equalLinesAtStart = (LineRef)comparisonInput.file[0].prefix_lines; LineRef currentLine1 = 0; LineRef currentLine2 = 0; GnuDiff::change* p = nullptr; for(GnuDiff::change* e = script; e; e = p) { Diff d((LineRef)(e->line0 - currentLine1), e->deleted, e->inserted); Q_ASSERT(d.numberOfEquals() == e->line1 - currentLine2); currentLine1 += (LineRef)(d.numberOfEquals() + d.diff1()); currentLine2 += (LineRef)(d.numberOfEquals() + d.diff2()); diffList.push_back(d); p = e->link; free(e); } if(diffList.empty()) { qint32 numofEquals = std::min(size1, size2); Diff d(numofEquals, size1 - numofEquals, size2 - numofEquals); diffList.push_back(d); } else { diffList.front().adjustNumberOfEquals(equalLinesAtStart); currentLine1 += equalLinesAtStart; currentLine2 += equalLinesAtStart; LineRef nofEquals = std::min(size1 - currentLine1, size2 - currentLine2); if(nofEquals == 0) { diffList.back().adjustDiff1(size1 - currentLine1); diffList.back().adjustDiff2(size2 - currentLine2); } else { Diff d(nofEquals, size1 - currentLine1 - nofEquals, size2 - currentLine2 - nofEquals); diffList.push_back(d); } } } // Verify difflist { LineRef::LineType l1 = 0; LineRef::LineType l2 = 0; DiffList::iterator i; for(i = diffList.begin(); i != diffList.end(); ++i) { l1 += i->numberOfEquals() + i->diff1(); l2 += i->numberOfEquals() + i->diff2(); } Q_ASSERT(l1 == size1 && l2 == size2); } pp.setCurrent(1); return true; } bool ManualDiffHelpList::runDiff(const QVector* p1, LineRef size1, const QVector* p2, LineRef size2, DiffList& diffList, e_SrcSelector winIdx1, e_SrcSelector winIdx2, const QSharedPointer &pOptions) { diffList.clear(); DiffList diffList2; int l1begin = 0; int l2begin = 0; ManualDiffHelpList::const_iterator i; for(i = begin(); i != end(); ++i) { const ManualDiffHelpEntry& mdhe = *i; int l1end = mdhe.getLine1(winIdx1); int l2end = mdhe.getLine1(winIdx2); if(l1end >= 0 && l2end >= 0) { ::runDiff(p1, l1begin, l1end - l1begin, p2, l2begin, l2end - l2begin, diffList2, pOptions); diffList.splice(diffList.end(), diffList2); l1begin = l1end; l2begin = l2end; l1end = mdhe.getLine2(winIdx1); l2end = mdhe.getLine2(winIdx2); if(l1end >= 0 && l2end >= 0) { ++l1end; // point to line after last selected line ++l2end; ::runDiff(p1, l1begin, l1end - l1begin, p2, l2begin, l2end - l2begin, diffList2, pOptions); diffList.splice(diffList.end(), diffList2); l1begin = l1end; l2begin = l2end; } } } ::runDiff(p1, l1begin, size1 - l1begin, p2, l2begin, size2 - l2begin, diffList2, pOptions); diffList.splice(diffList.end(), diffList2); return true; } void Diff3LineList::correctManualDiffAlignment(ManualDiffHelpList* pManualDiffHelpList) { if(pManualDiffHelpList->empty()) return; // If a line appears unaligned in comparison to the manual alignment, correct this. ManualDiffHelpList::iterator iMDHL; for(iMDHL = pManualDiffHelpList->begin(); iMDHL != pManualDiffHelpList->end(); ++iMDHL) { Diff3LineList::iterator i3 = begin(); e_SrcSelector missingWinIdx = None; int alignedSum = (iMDHL->getLine1(A) < 0 ? 0 : 1) + (iMDHL->getLine1(B) < 0 ? 0 : 1) + (iMDHL->getLine1(C) < 0 ? 0 : 1); if(alignedSum == 2) { // If only A & B are aligned then let C rather be aligned with A // If only A & C are aligned then let B rather be aligned with A // If only B & C are aligned then let A rather be aligned with B missingWinIdx = iMDHL->getLine1(A) < 0 ? A : (iMDHL->getLine1(B) < 0 ? B : C); } else if(alignedSum <= 1) { return; } // At the first aligned line, move up the two other lines into new d3ls until the second input is aligned // Then move up the third input until all three lines are aligned. int wi = None; for(; i3 != end(); ++i3) { for(wi = A; wi <= Max; ++wi) { if(i3->getLineInFile((e_SrcSelector)wi) >= 0 && iMDHL->firstLine((e_SrcSelector)wi) == i3->getLineInFile((e_SrcSelector)wi)) break; } if(wi <= Max) break; } if(wi >= A && wi <= Max) { // Found manual alignment for one source Diff3LineList::iterator iDest = i3; // Move lines up until the next firstLine is found. Omit wi from move and search. int wi2 = None; for(; i3 != end(); ++i3) { for(wi2 = A; wi2 <= C; ++wi2) { if(wi != wi2 && i3->getLineInFile((e_SrcSelector)wi2) >= 0 && iMDHL->firstLine((e_SrcSelector)wi2) == i3->getLineInFile((e_SrcSelector)wi2)) break; } if(wi2 > C) { // Not yet found // Move both others up Diff3Line d3l; // Move both up if(wi == A) // Move B and C up { d3l.bBEqC = i3->isEqualBC(); d3l.setLineB(i3->getLineB()); d3l.setLineC(i3->getLineC()); i3->getLineB().invalidate(); i3->getLineC().invalidate(); } if(wi == B) // Move A and C up { d3l.bAEqC = i3->isEqualAC(); d3l.setLineA(i3->getLineA()); d3l.setLineC(i3->getLineC()); i3->getLineA().invalidate(); i3->getLineC().invalidate(); } if(wi == C) // Move A and B up { d3l.bAEqB = i3->isEqualAB(); d3l.setLineA(i3->getLineA()); d3l.setLineB(i3->getLineB()); i3->getLineA().invalidate(); i3->getLineB().invalidate(); } i3->bAEqB = false; i3->bAEqC = false; i3->bBEqC = false; insert(iDest, d3l); } else { // align the found line with the line we already have here if(i3 != iDest) { if(wi2 == A) { iDest->setLineA(i3->getLineA()); i3->getLineA().invalidate(); i3->bAEqB = false; i3->bAEqC = false; } else if(wi2 == B) { iDest->setLineB(i3->getLineB()); i3->getLineB().invalidate(); i3->bAEqB = false; i3->bBEqC = false; } else if(wi2 == C) { iDest->setLineC(i3->getLineC()); i3->getLineC().invalidate(); i3->bBEqC = false; i3->bAEqC = false; } } if(missingWinIdx != 0) { for(; i3 != end(); ++i3) { e_SrcSelector wi3 = missingWinIdx; if(i3->getLineInFile((e_SrcSelector)wi3) >= 0) { // not found, move the line before iDest Diff3Line d3l; if(wi3 == A) { if(i3->isEqualAB()) // Stop moving lines up if one equal is found. break; d3l.setLineA(i3->getLineA()); i3->getLineA().invalidate(); i3->bAEqB = false; i3->bAEqC = false; } if(wi3 == B) { if(i3->isEqualAB()) break; d3l.setLineB(i3->getLineB()); i3->getLineB().invalidate(); i3->bAEqB = false; i3->bBEqC = false; } if(wi3 == C) { if(i3->isEqualAC()) break; d3l.setLineC(i3->getLineC()); i3->getLineC().invalidate(); i3->bAEqC = false; i3->bBEqC = false; } insert(iDest, d3l); } } // for(), searching for wi3 } break; } } // for(), searching for wi2 } // if, wi found } // for (iMDHL) } // Fourth step void Diff3LineList::calcDiff3LineListTrim( const QVector* pldA, const QVector* pldB, const QVector* pldC, ManualDiffHelpList* pManualDiffHelpList) { const Diff3Line d3l_empty; remove(d3l_empty); Diff3LineList::iterator i3 = begin(); Diff3LineList::iterator i3A = begin(); Diff3LineList::iterator i3B = begin(); Diff3LineList::iterator i3C = begin(); int line = 0; // diff3line counters int lineA = 0; // int lineB = 0; int lineC = 0; ManualDiffHelpList::iterator iMDHL = pManualDiffHelpList->begin(); // The iterator i3 and the variable line look ahead. // The iterators i3A, i3B, i3C and corresponding lineA, lineB and lineC stop at empty lines, if found. // If possible, then the texts from the look ahead will be moved back to the empty places. for(; i3 != end(); ++i3, ++line) { if(iMDHL != pManualDiffHelpList->end()) { if((i3->getLineA().isValid() && i3->getLineA() == iMDHL->getLine1(A)) || (i3->getLineB().isValid() && i3->getLineB() == iMDHL->getLine1(B)) || (i3->getLineC().isValid() && i3->getLineC() == iMDHL->getLine1(C))) { i3A = i3; i3B = i3; i3C = i3; lineA = line; lineB = line; lineC = line; ++iMDHL; } } if(line > lineA && i3->getLineA().isValid() && i3A->getLineB().isValid() && i3A->isEqualBC() && LineData::equal((*pldA)[i3->getLineA()], (*pldB)[i3A->getLineB()], false) && pManualDiffHelpList->isValidMove(i3->getLineA(), i3A->getLineB(), A, B) && pManualDiffHelpList->isValidMove(i3->getLineA(), i3A->getLineC(), A, C)) { // Empty space for A. A matches B and C in the empty line. Move it up. i3A->setLineA(i3->getLineA()); i3A->bAEqB = true; i3A->bAEqC = true; i3->getLineA().invalidate(); i3->bAEqB = false; i3->bAEqC = false; ++i3A; ++lineA; } if(line > lineB && i3->getLineB().isValid() && i3B->getLineA().isValid() && i3B->isEqualAC() && LineData::equal((*pldB)[i3->getLineB()], (*pldA)[i3B->getLineA()], false) && pManualDiffHelpList->isValidMove(i3->getLineB(), i3B->getLineA(), B, A) && pManualDiffHelpList->isValidMove(i3->getLineB(), i3B->getLineC(), B, C)) { // Empty space for B. B matches A and C in the empty line. Move it up. i3B->setLineB(i3->getLineB()); i3B->bAEqB = true; i3B->bBEqC = true; i3->getLineB().invalidate(); i3->bAEqB = false; i3->bBEqC = false; ++i3B; ++lineB; } if(line > lineC && i3->getLineC().isValid() && i3C->getLineA().isValid() && i3C->isEqualAB() && LineData::equal((*pldC)[i3->getLineC()], (*pldA)[i3C->getLineA()], false) && pManualDiffHelpList->isValidMove(i3->getLineC(), i3C->getLineA(), C, A) && pManualDiffHelpList->isValidMove(i3->getLineC(), i3C->getLineB(), C, B)) { // Empty space for C. C matches A and B in the empty line. Move it up. i3C->setLineC(i3->getLineC()); i3C->bAEqC = true; i3C->bBEqC = true; i3->getLineC().invalidate(); i3->bAEqC = false; i3->bBEqC = false; ++i3C; ++lineC; } if(line > lineA && i3->getLineA().isValid() && !i3->isEqualAB() && !i3->isEqualAC() && pManualDiffHelpList->isValidMove(i3->getLineA(), i3A->getLineB(), A, B) && pManualDiffHelpList->isValidMove(i3->getLineA(), i3A->getLineC(), A, C)) { // Empty space for A. A doesn't match B or C. Move it up. i3A->setLineA(i3->getLineA()); i3->getLineA().invalidate(); if(i3A->getLineB().isValid() && LineData::equal((*pldA)[i3A->getLineA()], (*pldB)[i3A->getLineB()], false)) { i3A->bAEqB = true; } if((i3A->isEqualAB() && i3A->isEqualBC()) || (i3A->getLineC().isValid() && LineData::equal((*pldA)[i3A->getLineA()], (*pldC)[i3A->getLineC()], false))) { i3A->bAEqC = true; } ++i3A; ++lineA; } if(line > lineB && i3->getLineB().isValid() && !i3->isEqualAB() && !i3->isEqualBC() && pManualDiffHelpList->isValidMove(i3->getLineB(), i3B->getLineA(), B, A) && pManualDiffHelpList->isValidMove(i3->getLineB(), i3B->getLineC(), B, C)) { // Empty space for B. B matches neither A nor C. Move B up. i3B->setLineB(i3->getLineB()); i3->getLineB().invalidate(); if(i3B->getLineA().isValid() && LineData::equal((*pldA)[i3B->getLineA()], (*pldB)[i3B->getLineB()], false)) { i3B->bAEqB = true; } if((i3B->isEqualAB() && i3B->isEqualAC()) || (i3B->getLineC().isValid() && LineData::equal((*pldB)[i3B->getLineB()], (*pldC)[i3B->getLineC()], false))) { i3B->bBEqC = true; } ++i3B; ++lineB; } if(line > lineC && i3->getLineC().isValid() && !i3->isEqualAC() && !i3->isEqualBC() && pManualDiffHelpList->isValidMove(i3->getLineC(), i3C->getLineA(), C, A) && pManualDiffHelpList->isValidMove(i3->getLineC(), i3C->getLineB(), C, B)) { // Empty space for C. C matches neither A nor B. Move C up. i3C->setLineC(i3->getLineC()); i3->getLineC().invalidate(); if(i3C->getLineA().isValid() && LineData::equal((*pldA)[i3C->getLineA()], (*pldC)[i3C->getLineC()], false)) { i3C->bAEqC = true; } if((i3C->isEqualAC() && i3C->isEqualAB()) || (i3C->getLineB().isValid() && LineData::equal((*pldB)[i3C->getLineB()], (*pldC)[i3C->getLineC()], false))) { i3C->bBEqC = true; } ++i3C; ++lineC; } if(line > lineA && line > lineB && i3->getLineA().isValid() && i3->isEqualAB() && !i3->isEqualAC()) { // Empty space for A and B. A matches B, but not C. Move A & B up. Diff3LineList::iterator i = lineA > lineB ? i3A : i3B; int l = lineA > lineB ? lineA : lineB; if(pManualDiffHelpList->isValidMove(i->getLineC(), i3->getLineA(), C, A) && pManualDiffHelpList->isValidMove(i->getLineC(), i3->getLineB(), C, B)) { i->setLineA(i3->getLineA()); i->setLineB(i3->getLineB()); i->bAEqB = true; if(i->getLineC().isValid() && LineData::equal((*pldA)[i->getLineA()], (*pldC)[i->getLineC()], false)) { i->bAEqC = true; i->bBEqC = true; } i3->getLineA().invalidate(); i3->getLineB().invalidate(); i3->bAEqB = false; i3A = i; i3B = i; ++i3A; ++i3B; lineA = l + 1; lineB = l + 1; } } else if(line > lineA && line > lineC && i3->getLineA().isValid() && i3->isEqualAC() && !i3->isEqualAB()) { // Empty space for A and C. A matches C, but not B. Move A & C up. Diff3LineList::iterator i = lineA > lineC ? i3A : i3C; int l = lineA > lineC ? lineA : lineC; if(pManualDiffHelpList->isValidMove(i->getLineB(), i3->getLineA(), B, A) && pManualDiffHelpList->isValidMove(i->getLineB(), i3->getLineC(), B, C)) { i->setLineA(i3->getLineA()); i->setLineC(i3->getLineC()); i->bAEqC = true; if(i->getLineB().isValid() && LineData::equal((*pldA)[i->getLineA()], (*pldB)[i->getLineB()], false)) { i->bAEqB = true; i->bBEqC = true; } i3->getLineA().invalidate(); i3->getLineC().invalidate(); i3->bAEqC = false; i3A = i; i3C = i; ++i3A; ++i3C; lineA = l + 1; lineC = l + 1; } } else if(line > lineB && line > lineC && i3->getLineB().isValid() && i3->isEqualBC() && !i3->isEqualAC()) { // Empty space for B and C. B matches C, but not A. Move B & C up. Diff3LineList::iterator i = lineB > lineC ? i3B : i3C; int l = lineB > lineC ? lineB : lineC; if(pManualDiffHelpList->isValidMove(i->getLineA(), i3->getLineB(), A, B) && pManualDiffHelpList->isValidMove(i->getLineA(), i3->getLineC(), A, C)) { i->setLineB(i3->getLineB()); i->setLineC(i3->getLineC()); i->bBEqC = true; if(i->getLineA().isValid() && LineData::equal((*pldA)[i->getLineA()], (*pldB)[i->getLineB()], false)) { i->bAEqB = true; i->bAEqC = true; } i3->getLineB().invalidate(); i3->getLineC().invalidate(); i3->bBEqC = false; i3B = i; i3C = i; ++i3B; ++i3C; lineB = l + 1; lineC = l + 1; } } if(i3->getLineA().isValid()) { lineA = line + 1; i3A = i3; ++i3A; } if(i3->getLineB().isValid()) { lineB = line + 1; i3B = i3; ++i3B; } if(i3->getLineC().isValid()) { lineC = line + 1; i3C = i3; ++i3C; } } remove(d3l_empty); /* Diff3LineList::iterator it = d3ll.begin(); int li=0; for( ; it!=d3ll.end(); ++it, ++li ) { printf( "%4d %4d %4d %4d A%c=B A%c=C B%c=C\n", li, it->getLineA(), it->getLineB(), it->getLineC(), it->isEqualAB() ? '=' : '!', it->isEqualAC() ? '=' : '!', it->isEqualBC() ? '=' : '!' ); } */ } void DiffBufferInfo::init(Diff3LineList* pD3ll, const Diff3LineVector* pD3lv, const QVector* pldA, LineCount sizeA, const QVector* pldB, LineCount sizeB, const QVector* pldC, LineCount sizeC) { m_pDiff3LineList = pD3ll; m_pDiff3LineVector = pD3lv; mLineDataA = pldA; mLineDataB = pldB; mLineDataC = pldC; m_sizeA = sizeA; m_sizeB = sizeB; m_sizeC = sizeC; Diff3LineList::iterator i3 = pD3ll->begin(); for(; i3 != pD3ll->end(); ++i3) { i3->m_pDiffBufferInfo = this; } } void Diff3LineList::calcWhiteDiff3Lines( const QVector* pldA, const QVector* pldB, const QVector* pldC) { Diff3LineList::iterator i3; for(i3 = begin(); i3 != end(); ++i3) { i3->bWhiteLineA = (!i3->getLineA().isValid() || pldA == nullptr || (*pldA)[i3->getLineA()].whiteLine() || (*pldA)[i3->getLineA()].isPureComment()); i3->bWhiteLineB = (!i3->getLineB().isValid() || pldB == nullptr || (*pldB)[i3->getLineB()].whiteLine() || (*pldB)[i3->getLineB()].isPureComment()); i3->bWhiteLineC = (!i3->getLineC().isValid() || pldC == nullptr || (*pldC)[i3->getLineC()].whiteLine() || (*pldC)[i3->getLineC()].isPureComment()); } } // My own diff-invention: void calcDiff(const QString& line1, const QString& line2, DiffList& diffList, int match, int maxSearchRange) { diffList.clear(); QString::const_iterator p1=line1.begin(), p2=line2.begin(); /* This loop should never reach the exit condition specified here. However it must have a hard wired stopping point to prevent runaway allocation if something unexpected happens. diffList is therefor hard capped at aprox 50 MB in size. */ for(; diffList.size() * sizeof(Diff) + sizeof(DiffList) < (50 << 20);) { int nofEquals = 0; while(p1 != line1.end() && p2 != line2.end() && *p1 == *p2) { ++p1; ++p2; ++nofEquals; } bool bBestValid = false; int bestI1 = 0; int bestI2 = 0; int i1 = 0; int i2 = 0; for(i1 = 0;; ++i1) { if(p1[i1] == *line1.end() || (bBestValid && i1 >= bestI1 + bestI2)) { break; } for(i2 = 0; i2 < maxSearchRange; ++i2) { if(p2[i2] == *line2.end() || (bBestValid && i1 + i2 >= bestI1 + bestI2)) { break; } else if(p2[i2] == p1[i1] && (match == 1 || abs(i1 - i2) < 3 || (p2[i2 + 1] == *line2.end() && p1[i1 + 1] == *line1.end()) || (p2[i2 + 1] != *line2.end() && p1[i1 + 1] != *line1.end() && p2[i2 + 1] == p1[i1 + 1]))) { if(i1 + i2 < bestI1 + bestI2 || !bBestValid) { bestI1 = i1; bestI2 = i2; bBestValid = true; break; } } } } // The match was found using the strict search. Go back if there are non-strict // matches. while(bestI1 >= 1 && bestI2 >= 1 && p1[bestI1 - 1] == p2[bestI2 - 1]) { --bestI1; --bestI2; } bool bEndReached = false; if(bBestValid) { // continue somehow Diff d(nofEquals, bestI1, bestI2); Q_ASSERT(nofEquals + bestI1 + bestI2 != 0); diffList.push_back(d); p1 += bestI1; p2 += bestI2; } else { // Nothing else to match. Diff d(nofEquals, line1.end() - p1, line2.end() - p2); diffList.push_back(d); bEndReached = true; //break; } // Sometimes the algorithm that chooses the first match unfortunately chooses // a match where later actually equal parts don't match anymore. // A different match could be achieved, if we start at the end. // Do it, if it would be a better match. int nofUnmatched = 0; QString::const_iterator pu1 = p1 - 1; QString::const_iterator pu2 = p2 - 1; while(pu1 >= line1.begin() && pu2 >= line2.begin() && *pu1 == *pu2) { ++nofUnmatched; --pu1; --pu2; } Diff d = diffList.back(); if(nofUnmatched > 0) { // We want to go backwards the nofUnmatched elements and redo // the matching d = diffList.back(); Diff origBack = d; diffList.pop_back(); while(nofUnmatched > 0) { if(d.diff1() > 0 && d.diff2() > 0) { d.adjustDiff1(-1); d.adjustDiff2(-1); --nofUnmatched; } else if(d.numberOfEquals() > 0) { d.adjustNumberOfEquals(-1); --nofUnmatched; } if(d.numberOfEquals() == 0 && (d.diff1() == 0 || d.diff2() == 0) && nofUnmatched > 0) { if(diffList.empty()) break; d.adjustNumberOfEquals(diffList.back().numberOfEquals()); d.adjustDiff1(diffList.back().diff1()); d.adjustDiff2(diffList.back().diff2()); diffList.pop_back(); bEndReached = false; } } if(bEndReached) diffList.push_back(origBack); else { p1 = pu1 + 1 + nofUnmatched; p2 = pu2 + 1 + nofUnmatched; diffList.push_back(d); } } if(bEndReached) break; } Q_ASSERT(diffList.size() * sizeof(Diff) + sizeof(DiffList) <= (50 << 20)); // Verify difflist { qint32 l1 = 0; qint32 l2 = 0; DiffList::const_iterator it; for(it = diffList.begin(); it != diffList.end(); ++it) { l1 += (it->numberOfEquals() + it->diff1()); l2 += (it->numberOfEquals() + it->diff2()); } Q_ASSERT(l1 == line1.size() && l2 == line2.size()); } } bool Diff3Line::fineDiff(bool inBTextsTotalEqual, const e_SrcSelector selector, const QVector* v1, const QVector* v2) { LineRef k1 = 0; LineRef k2 = 0; int maxSearchLength = 500; bool bTextsTotalEqual = inBTextsTotalEqual; Q_ASSERT(selector == A || selector == B || selector == C); if(selector == A) { k1 = getLineA(); k2 = getLineB(); } else if(selector == B) { k1 = getLineB(); k2 = getLineC(); } else if(selector == C) { k1 = getLineC(); k2 = getLineA(); } qDebug(kdiffCore) << "k1 = " << k1 << ", k2 = " << k2; if((!k1.isValid() && k2.isValid()) || (k1.isValid() && !k2.isValid())) bTextsTotalEqual = false; if(k1.isValid() && k2.isValid()) { if((*v1)[k1].size() != (*v2)[k2].size() || QString::compare((*v1)[k1].getLine(), (*v2)[k2].getLine()) != 0) { bTextsTotalEqual = false; DiffList* pDiffList = new DiffList; calcDiff((*v1)[k1].getLine(), (*v2)[k2].getLine(), *pDiffList, 2, maxSearchLength); // Optimize the diff list. DiffList::iterator dli; bool bUsefulFineDiff = false; for(dli = pDiffList->begin(); dli != pDiffList->end(); ++dli) { if(dli->numberOfEquals() >= 4) { bUsefulFineDiff = true; break; } } for(dli = pDiffList->begin(); dli != pDiffList->end(); ++dli) { if(dli->numberOfEquals() < 4 && (dli->diff1() > 0 || dli->diff2() > 0) && !(bUsefulFineDiff && dli == pDiffList->begin())) { dli->adjustDiff1(dli->numberOfEquals()); dli->adjustDiff2(dli->numberOfEquals()); dli->setNumberOfEquals(0); } } setFineDiff(selector, pDiffList); } if(((*v1)[k1].isPureComment() || (*v1)[k1].whiteLine()) && ((*v2)[k2].isPureComment() || (*v2)[k2].whiteLine())) { if(selector == A) { bAEqB = true; } else if(selector == B) { bBEqC = true; } else if(selector == C) { bAEqC = true; } } } return bTextsTotalEqual; } -void Diff3Line::getLineInfo(const e_SrcSelector winIdx, const bool isTriple, int& lineIdx, +void Diff3Line::getLineInfo(const e_SrcSelector winIdx, const bool isTriple, LineRef& lineIdx, DiffList*& pFineDiff1, DiffList*& pFineDiff2, // return values - int& changed, int& changed2) const + ChangeFlags& changed, ChangeFlags& changed2) const { - changed = 0; - changed2 = 0; + changed = NoChange; + changed2 = NoChange; bool bAEqualB = this->isEqualAB() || (bWhiteLineA && bWhiteLineB); bool bAEqualC = this->isEqualAC() || (bWhiteLineA && bWhiteLineC); bool bBEqualC = this->isEqualBC() || (bWhiteLineB && bWhiteLineC); Q_ASSERT(winIdx >= A && winIdx <= C); if(winIdx == A) { lineIdx = getLineA(); pFineDiff1 = pFineAB; pFineDiff2 = pFineCA; - changed |= ((!getLineB().isValid()) != (lineIdx == -1) ? 1 : 0) + - ((!getLineC().isValid()) != (lineIdx == -1) && isTriple ? 2 : 0); - changed2 |= (bAEqualB ? 0 : 1) + (bAEqualC || !isTriple ? 0 : 2); + + changed = ((!getLineB().isValid()) != (!lineIdx.isValid()) ? AChanged : NoChange) | + ((!getLineC().isValid()) != (!lineIdx.isValid()) && isTriple ? BChanged : NoChange); + changed2 = (bAEqualB ? NoChange : AChanged) | (bAEqualC || !isTriple ? NoChange : BChanged); } else if(winIdx == B) { lineIdx = getLineB(); pFineDiff1 = pFineBC; pFineDiff2 = pFineAB; - changed |= ((!getLineC().isValid()) != (lineIdx == -1) && isTriple ? 1 : 0) + - ((!getLineA().isValid()) != (lineIdx == -1) ? 2 : 0); - changed2 |= (bBEqualC || !isTriple ? 0 : 1) + (bAEqualB ? 0 : 2); + changed = ((!getLineC().isValid()) != (!lineIdx.isValid()) && isTriple ? AChanged : NoChange) | + ((!getLineA().isValid()) != (!lineIdx.isValid()) ? BChanged : NoChange); + changed2 = (bBEqualC || !isTriple ? NoChange : AChanged) | (bAEqualB ? NoChange : BChanged); } else if(winIdx == C) { lineIdx = getLineC(); pFineDiff1 = pFineCA; pFineDiff2 = pFineBC; - changed |= ((!getLineA().isValid()) != (lineIdx == -1) ? 1 : 0) + - ((!getLineB().isValid()) != (lineIdx == -1) ? 2 : 0); - changed2 |= (bAEqualC ? 0 : 1) + (bBEqualC ? 0 : 2); + changed = ((!getLineA().isValid()) != (!lineIdx.isValid()) ? AChanged : NoChange) | + ((!getLineB().isValid()) != (!lineIdx.isValid()) ? BChanged : NoChange); + changed2 = (bAEqualC ? NoChange : AChanged) | (bBEqualC ? NoChange : BChanged); } } bool Diff3LineList::fineDiff(const e_SrcSelector selector, const QVector* v1, const QVector* v2) { // Finetuning: Diff each line with deltas ProgressProxy pp; Diff3LineList::iterator i; bool bTextsTotalEqual = true; int listSize = size(); pp.setMaxNofSteps(listSize); int listIdx = 0; for(i = begin(); i != end(); ++i) { bTextsTotalEqual = i->fineDiff(bTextsTotalEqual, selector, v1, v2); ++listIdx; pp.step(); } return bTextsTotalEqual; } // Convert the list to a vector of pointers void Diff3LineList::calcDiff3LineVector(Diff3LineVector& d3lv) { d3lv.resize(size()); Diff3LineList::iterator i; int j = 0; for(i = begin(); i != end(); ++i, ++j) { d3lv[j] = &(*i); } Q_ASSERT(j == d3lv.size()); } + diff --git a/src/diff.h b/src/diff.h index 8dc57f6..cccd58d 100644 --- a/src/diff.h +++ b/src/diff.h @@ -1,493 +1,503 @@ /*************************************************************************** * Copyright (C) 2003-2007 by Joachim Eibl * * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * * * * 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. * * * ***************************************************************************/ #ifndef DIFF_H #define DIFF_H #include "common.h" #include "fileaccess.h" #include "LineRef.h" #include "SourceData.h" #include "Logging.h" #include #include class Options; //These enums must be sequential with no gaps to allow loop interiation of values enum e_SrcSelector { Min = -1, Invalid=-1, None=0, A = 1, B = 2, C = 3, Max=C }; enum e_MergeDetails { eDefault, eNoChange, eBChanged, eCChanged, eBCChanged, // conflict eBCChangedAndEqual, // possible conflict eBDeleted, eCDeleted, eBCDeleted, // possible conflict eBChanged_CDeleted, // conflict eCChanged_BDeleted, // conflict eBAdded, eCAdded, eBCAdded, // conflict eBCAddedAndEqual // possible conflict }; +enum ChangeFlag{ + NoChange = 0, + AChanged = 0x1, + BChanged = 0x2, + Both = AChanged | BChanged +}; + +Q_DECLARE_FLAGS(ChangeFlags, ChangeFlag); +Q_DECLARE_OPERATORS_FOR_FLAGS(ChangeFlags); + // Each range with matching elements is followed by a range with differences on either side. // Then again range of matching elements should follow. class Diff { private: qint32 nofEquals = 0; qint64 mDiff1 = 0; qint64 mDiff2 = 0; public: Diff(qint32 eq, const qint64 inDiff1, const qint64 inDiff2) { nofEquals = eq; mDiff1 = inDiff1; mDiff2 = inDiff2; } inline qint32 numberOfEquals() const { return nofEquals; }; inline qint64 diff1() const { return mDiff1; }; inline qint64 diff2() const { return mDiff2; }; inline void setNumberOfEquals(const qint32 inNumOfEquals) { nofEquals = inNumOfEquals; } inline void adjustNumberOfEquals(const qint64 delta) { nofEquals += delta; } inline void adjustDiff1(const qint64 delta) { mDiff1 += delta; } inline void adjustDiff2(const qint64 delta) { mDiff2 += delta; } }; typedef std::list DiffList; class LineData { private: QSharedPointer mBuffer; //QString pLine; QtNumberType mFirstNonWhiteChar = 0; qint64 mOffset = 0; QtNumberType mSize = 0; bool bContainsPureComment = false;//TODO: Move me public: explicit LineData() = default; // needed for Qt internal reasons should not be used. inline LineData(const QSharedPointer &buffer, const qint64 inOffset, QtNumberType inSize = 0, QtNumberType inFirstNonWhiteChar=0, bool inIsPureComment=false) { mBuffer = buffer; mOffset = inOffset; mSize = inSize; bContainsPureComment = inIsPureComment; mFirstNonWhiteChar = inFirstNonWhiteChar; } Q_REQUIRED_RESULT inline int size() const { return mSize; } Q_REQUIRED_RESULT inline qint32 getFirstNonWhiteChar() const { return mFirstNonWhiteChar; } /* QString::fromRawData allows us to create a light weight QString backed by the buffer memmory. */ Q_REQUIRED_RESULT inline const QString getLine() const { return QString::fromRawData(mBuffer->data() + mOffset, mSize); } Q_REQUIRED_RESULT inline const QSharedPointer& getBuffer() const { return mBuffer; } Q_REQUIRED_RESULT inline qint64 getOffset() const { return mOffset; } Q_REQUIRED_RESULT int width(int tabSize) const; // Calcs width considering tabs. //int occurrences; inline bool whiteLine() const { return mFirstNonWhiteChar == mSize - 1; } inline bool isPureComment() const { return bContainsPureComment; } inline void setPureComment(const bool bPureComment) { bContainsPureComment = bPureComment; } static bool equal(const LineData& l1, const LineData& l2, bool bStrict); }; class ManualDiffHelpList; // A list of corresponding ranges class Diff3LineList; class Diff3LineVector; class DiffBufferInfo { private: const QVector* mLineDataA; const QVector* mLineDataB; const QVector* mLineDataC; LineCount m_sizeA; LineCount m_sizeB; LineCount m_sizeC; const Diff3LineList* m_pDiff3LineList; const Diff3LineVector* m_pDiff3LineVector; public: void init(Diff3LineList* d3ll, const Diff3LineVector* d3lv, const QVector* pldA, LineCount sizeA, const QVector* pldB, LineCount sizeB, const QVector* pldC, LineCount sizeC); inline const QVector* getLineData(e_SrcSelector srcIndex) const { switch(srcIndex) { case A: return mLineDataA; case B: return mLineDataB; case C: return mLineDataC; default: return nullptr; } } }; class Diff3Line { private: friend class Diff3LineList; LineRef lineA; LineRef lineB; LineRef lineC; bool bAEqC = false; // These are true if equal or only white-space changes exist. bool bBEqC = false; bool bAEqB = false; public: bool bWhiteLineA = false; bool bWhiteLineB = false; bool bWhiteLineC = false; DiffList* pFineAB = nullptr; // These are NULL only if completely equal or if either source doesn't exist. DiffList* pFineBC = nullptr; DiffList* pFineCA = nullptr; int linesNeededForDisplay = 1; // Due to wordwrap int sumLinesNeededForDisplay = 0; // For fast conversion to m_diff3WrapLineVector DiffBufferInfo* m_pDiffBufferInfo = nullptr; // For convenience ~Diff3Line() { if(pFineAB != nullptr) delete pFineAB; if(pFineBC != nullptr) delete pFineBC; if(pFineCA != nullptr) delete pFineCA; pFineAB = nullptr; pFineBC = nullptr; pFineCA = nullptr; } LineRef getLineA() const { return lineA; } LineRef getLineB() const { return lineB; } LineRef getLineC() const { return lineC; } inline void setLineA(const LineRef& line) { lineA = line; } inline void setLineB(const LineRef& line) { lineB = line; } inline void setLineC(const LineRef& line) { lineC = line; } inline bool isEqualAB() const { return bAEqB; } inline bool isEqualAC() const { return bAEqC; } inline bool isEqualBC() const { return bBEqC; } bool operator==(const Diff3Line& d3l) const { return lineA == d3l.lineA && lineB == d3l.lineB && lineC == d3l.lineC && bAEqB == d3l.bAEqB && bAEqC == d3l.bAEqC && bBEqC == d3l.bBEqC; } const LineData* getLineData(e_SrcSelector src) const { Q_ASSERT(m_pDiffBufferInfo != nullptr); //Use at here not [] to avoid using really wierd syntax - if(src == A && lineA >= 0) return &m_pDiffBufferInfo->getLineData(src)->at(lineA); - if(src == B && lineB >= 0) return &m_pDiffBufferInfo->getLineData(src)->at(lineB); - if(src == C && lineC >= 0) return &m_pDiffBufferInfo->getLineData(src)->at(lineC); + if(src == A && lineA.isValid()) return &m_pDiffBufferInfo->getLineData(src)->at(lineA); + if(src == B && lineB.isValid()) return &m_pDiffBufferInfo->getLineData(src)->at(lineB); + if(src == C && lineC.isValid()) return &m_pDiffBufferInfo->getLineData(src)->at(lineC); return nullptr; } const QString getString(const e_SrcSelector src) const { const LineData* pld = getLineData(src); if(pld) return pld->getLine(); else return QString(); } LineRef getLineInFile(e_SrcSelector src) const { if(src == A) return lineA; if(src == B) return lineB; if(src == C) return lineC; return -1; } bool fineDiff(bool bTextsTotalEqual, const e_SrcSelector selector, const QVector* v1, const QVector* v2); void mergeOneLine(e_MergeDetails& mergeDetails, bool& bConflict, bool& bLineRemoved, e_SrcSelector& src, bool bTwoInputs) const; - void getLineInfo(const e_SrcSelector winIdx, const bool isTriple, int& lineIdx, + void getLineInfo(const e_SrcSelector winIdx, const bool isTriple, LineRef& lineIdx, DiffList*& pFineDiff1, DiffList*& pFineDiff2, // return values - int& changed, int& changed2) const; + ChangeFlags& changed, ChangeFlags& changed2) const; private: void setFineDiff(const e_SrcSelector selector, DiffList* pDiffList) { Q_ASSERT(selector == A || selector == B || selector == C); if(selector == A) { if(pFineAB != nullptr) delete pFineAB; pFineAB = pDiffList; } else if(selector == B) { if(pFineBC != nullptr) delete pFineBC; pFineBC = pDiffList; } else if(selector == C) { if(pFineCA) delete pFineCA; pFineCA = pDiffList; } } }; class Diff3LineList : public std::list { public: bool fineDiff(const e_SrcSelector selector, const QVector* v1, const QVector* v2); void calcDiff3LineVector(Diff3LineVector& d3lv); void calcWhiteDiff3Lines(const QVector* pldA, const QVector* pldB, const QVector* pldC); void calcDiff3LineListUsingAB(const DiffList* pDiffListAB); void calcDiff3LineListUsingAC(const DiffList* pDiffListAC); void calcDiff3LineListUsingBC(const DiffList* pDiffListBC); void correctManualDiffAlignment(ManualDiffHelpList* pManualDiffHelpList); void calcDiff3LineListTrim(const QVector* pldA, const QVector* pldB, const QVector* pldC, ManualDiffHelpList* pManualDiffHelpList); LineCount recalcWordWrap() { LineCount sumOfLines = 0; for(Diff3Line d3l: *this) { d3l.linesNeededForDisplay = 1; d3l.sumLinesNeededForDisplay = sumOfLines; sumOfLines += d3l.linesNeededForDisplay; } return sumOfLines; } //TODO: Add safety guards to prevent list from getting too large. Same problem as with QLinkedList. int size() const { if(std::list::size() > (size_t)TYPE_MAX(int))//explicit cast to silence gcc { qCDebug(kdiffMain) << "Diff3Line: List too large. size=" << std::list::size(); Q_ASSERT(false); //Unsupported size return 0; } return (int)std::list::size(); } //safe for small files same limit as exited with QLinkedList. This should ultimatly be removed. void debugLineCheck(const LineCount size, const e_SrcSelector srcSelector) const; }; class Diff3LineVector : public QVector { }; class Diff3WrapLine { public: Diff3Line* pD3L; int diff3LineIndex; int wrapLineOffset; int wrapLineLength; }; typedef QVector Diff3WrapLineVector; class TotalDiffStatus { public: inline void reset() { bBinaryAEqC = false; bBinaryBEqC = false; bBinaryAEqB = false; bTextAEqC = false; bTextBEqC = false; bTextAEqB = false; nofUnsolvedConflicts = 0; nofSolvedConflicts = 0; nofWhitespaceConflicts = 0; } inline int getUnsolvedConflicts() const { return nofUnsolvedConflicts; } inline void setUnsolvedConflicts(const int unsolved) { nofUnsolvedConflicts = unsolved; } inline int getSolvedConflicts() const { return nofSolvedConflicts; } inline void setSolvedConflicts(const int solved) { nofSolvedConflicts = solved; } inline int getWhitespaceConflicts() const { return nofWhitespaceConflicts; } inline void setWhitespaceConflicts(const int wintespace) { nofWhitespaceConflicts = wintespace; } inline int getNonWhitespaceConflicts() { return getUnsolvedConflicts() + getSolvedConflicts() - getWhitespaceConflicts(); } bool isBinaryEqualAC() const { return bBinaryAEqC; } bool isBinaryEqualBC() const { return bBinaryBEqC; } bool isBinaryEqualAB() const { return bBinaryAEqB; } bool bBinaryAEqC = false; bool bBinaryBEqC = false; bool bBinaryAEqB = false; bool bTextAEqC = false; bool bTextBEqC = false; bool bTextAEqB = false; private: int nofUnsolvedConflicts = 0; int nofSolvedConflicts = 0; int nofWhitespaceConflicts = 0; }; // Three corresponding ranges. (Minimum size of a valid range is one line.) class ManualDiffHelpEntry { private: LineRef lineA1; LineRef lineA2; LineRef lineB1; LineRef lineB2; LineRef lineC1; LineRef lineC2; public: LineRef& firstLine(e_SrcSelector winIdx) { return winIdx == A ? lineA1 : (winIdx == B ? lineB1 : lineC1); } LineRef& lastLine(e_SrcSelector winIdx) { return winIdx == A ? lineA2 : (winIdx == B ? lineB2 : lineC2); } bool isLineInRange(LineRef line, e_SrcSelector winIdx) { - return line >= 0 && line >= firstLine(winIdx) && line <= lastLine(winIdx); + return line.isValid() && line >= firstLine(winIdx) && line <= lastLine(winIdx); } bool operator==(const ManualDiffHelpEntry& r) const { return lineA1 == r.lineA1 && lineB1 == r.lineB1 && lineC1 == r.lineC1 && lineA2 == r.lineA2 && lineB2 == r.lineB2 && lineC2 == r.lineC2; } int calcManualDiffFirstDiff3LineIdx(const Diff3LineVector& d3lv); - void getRangeForUI(const e_SrcSelector winIdx, int *rangeLine1, int *rangeLine2) const { + void getRangeForUI(const e_SrcSelector winIdx, LineRef *rangeLine1, LineRef *rangeLine2) const { if(winIdx == A) { *rangeLine1 = lineA1; *rangeLine2 = lineA2; } if(winIdx == B) { *rangeLine1 = lineB1; *rangeLine2 = lineB2; } if(winIdx == C) { *rangeLine1 = lineC1; *rangeLine2 = lineC2; } } inline int getLine1(const e_SrcSelector winIdx) const { return winIdx == A ? lineA1 : winIdx == B ? lineB1 : lineC1;} inline int getLine2(const e_SrcSelector winIdx) const { return winIdx == A ? lineA2 : winIdx == B ? lineB2 : lineC2;} bool isValidMove(int line1, int line2, e_SrcSelector winIdx1, e_SrcSelector winIdx2) const; }; // A list of corresponding ranges class ManualDiffHelpList: public std::list { public: bool isValidMove(int line1, int line2, e_SrcSelector winIdx1, e_SrcSelector winIdx2) const; void insertEntry(e_SrcSelector winIdx, LineRef firstLine, LineRef lastLine); bool runDiff(const QVector* p1, LineRef size1, const QVector* p2, LineRef size2, DiffList& diffList, e_SrcSelector winIdx1, e_SrcSelector winIdx2, const QSharedPointer &pOptions); }; void calcDiff(const QString &line1, const QString &line2, DiffList& diffList, int match, int maxSearchRange); bool fineDiff( Diff3LineList& diff3LineList, int selector, const QVector* v1, const QVector* v2); inline bool isWhite(QChar c) { return c == ' ' || c == '\t' || c == '\r'; } /** Returns the number of equivalent spaces at position outPos. */ inline int tabber(int outPos, int tabSize) { return tabSize - (outPos % tabSize); } /** Returns a line number where the linerange [line, line+nofLines] can be displayed best. If it fits into the currently visible range then the returned value is the current firstLine. */ int getBestFirstLine(int line, int nofLines, int firstLine, int visibleLines); extern bool g_bIgnoreWhiteSpace; extern bool g_bIgnoreTrivialMatches; extern bool g_bAutoSolve; // Cursor conversions that consider g_tabSize. int convertToPosInText(const QString& s, int posOnScreen, int tabSize); int convertToPosOnScreen(const QString& s, int posInText, int tabSize); enum e_CoordType { eFileCoords, eD3LLineCoords, eWrapCoords }; void calcTokenPos(const QString&, int posOnScreen, int& pos1, int& pos2, int tabSize); QString calcHistorySortKey(const QString& keyOrder, QRegExp& matchedRegExpr, const QStringList& parenthesesGroupList); bool findParenthesesGroups(const QString& s, QStringList& sl); #endif diff --git a/src/difftextwindow.cpp b/src/difftextwindow.cpp index 251b48b..fb36eac 100644 --- a/src/difftextwindow.cpp +++ b/src/difftextwindow.cpp @@ -1,2067 +1,2052 @@ /*************************************************************************** * Copyright (C) 2003-2007 by Joachim Eibl * * joachim.eibl at gmx.de * * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * * * * 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. * * * ***************************************************************************/ #include "difftextwindow.h" #include "common.h" // for getAtomic, max3, min3 #include "FileNameLineEdit.h" #include "kdiff3.h" #include "merger.h" #include "options.h" #include "progress.h" #include "RLPainter.h" #include "selection.h" #include "SourceData.h" // for SourceData #include "Utils.h" // for Utils #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include QAtomicInt s_runnableCount = 0; class DiffTextWindowData { public: explicit DiffTextWindowData(DiffTextWindow* p) { m_pDiffTextWindow = p; m_pLineData = nullptr; m_size = 0; m_bWordWrap = false; m_delayedDrawTimer = 0; m_pDiff3LineVector = nullptr; m_pManualDiffHelpList = nullptr; m_pOptions = nullptr; m_fastSelectorLine1 = 0; m_fastSelectorNofLines = 0; m_bTriple = false; m_winIdx = None; m_firstLine = 0; m_oldFirstLine = 0; m_horizScrollOffset = 0; m_lineNumberWidth = 0; m_maxTextWidth = -1; m_pStatusBar = nullptr; m_scrollDeltaX = 0; m_scrollDeltaY = 0; m_bMyUpdate = false; m_bSelectionInProgress = false; m_pTextCodec = nullptr; #if defined(Q_OS_WIN) m_eLineEndStyle = eLineEndStyleDos; #else m_eLineEndStyle = eLineEndStyleUnix; #endif } DiffTextWindow* m_pDiffTextWindow; DiffTextWindowFrame* m_pDiffTextWindowFrame = nullptr; QTextCodec* m_pTextCodec; e_LineEndStyle m_eLineEndStyle; const QVector* m_pLineData; int m_size; QString m_filename; bool m_bWordWrap; int m_delayedDrawTimer; const Diff3LineVector* m_pDiff3LineVector = nullptr; Diff3WrapLineVector m_diff3WrapLineVector; const ManualDiffHelpList* m_pManualDiffHelpList; class WrapLineCacheData { public: WrapLineCacheData() {} WrapLineCacheData(int d3LineIdx, int textStart, int textLength) : m_d3LineIdx(d3LineIdx), m_textStart(textStart), m_textLength(textLength) {} int m_d3LineIdx = 0; int m_textStart = 0; int m_textLength = 0; }; QList> m_wrapLineCacheList; QSharedPointer m_pOptions; QColor m_cThis; QColor m_cDiff1; QColor m_cDiff2; QColor m_cDiffBoth; int m_fastSelectorLine1; int m_fastSelectorNofLines; bool m_bTriple; e_SrcSelector m_winIdx; int m_firstLine; int m_oldFirstLine; int m_horizScrollOffset; int m_lineNumberWidth; QAtomicInt m_maxTextWidth; - void getLineInfo( - const Diff3Line& d, - int& lineIdx, - DiffList*& pFineDiff1, DiffList*& pFineDiff2, // return values - int& changed, int& changed2); - QString getString(int d3lIdx); QString getLineString(int line); void writeLine( RLPainter& p, const LineData* pld, - const DiffList* pLineDiff1, const DiffList* pLineDiff2, int line, - int whatChanged, int whatChanged2, int srcLineIdx, + const DiffList* pLineDiff1, const DiffList* pLineDiff2, const LineRef& line, + const ChangeFlags whatChanged, const ChangeFlags whatChanged2, const LineRef& srcLineIdx, int wrapLineOffset, int wrapLineLength, bool bWrapLine, const QRect& invalidRect, int deviceWidth); void draw(RLPainter& p, const QRect& invalidRect, int deviceWidth, int beginLine, int endLine); QStatusBar* m_pStatusBar; Selection m_selection; int m_scrollDeltaX; int m_scrollDeltaY; bool m_bMyUpdate; void myUpdate(int afterMilliSecs); int leftInfoWidth() { return 4 + m_lineNumberWidth; } // Nr of information columns on left side int convertLineOnScreenToLineInSource(int lineOnScreen, e_CoordType coordType, bool bFirstLine); bool m_bSelectionInProgress; QPoint m_lastKnownMousePos; void prepareTextLayout(QTextLayout& textLayout, bool bFirstLine, int visibleTextWidth = -1); }; DiffTextWindow::DiffTextWindow( DiffTextWindowFrame* pParent, QStatusBar* pStatusBar, const QSharedPointer &pOptions, e_SrcSelector winIdx) : QWidget(pParent) { setObjectName(QString("DiffTextWindow%1").arg(winIdx)); setAttribute(Qt::WA_OpaquePaintEvent); //setAttribute( Qt::WA_PaintOnScreen ); setUpdatesEnabled(false); d = new DiffTextWindowData(this); d->m_pDiffTextWindowFrame = pParent; setFocusPolicy(Qt::ClickFocus); setAcceptDrops(true); d->m_pOptions = pOptions; init(QString(""), nullptr, d->m_eLineEndStyle, nullptr, 0, nullptr, nullptr, false); setMinimumSize(QSize(20, 20)); d->m_pStatusBar = pStatusBar; setUpdatesEnabled(true); d->m_bWordWrap = false; d->m_winIdx = winIdx; setFont(d->m_pOptions->m_font); } DiffTextWindow::~DiffTextWindow() { delete d; } void DiffTextWindow::init( const QString& filename, QTextCodec* pTextCodec, e_LineEndStyle eLineEndStyle, const QVector* pLineData, int size, const Diff3LineVector* pDiff3LineVector, const ManualDiffHelpList* pManualDiffHelpList, bool bTriple) { d->m_filename = filename; d->m_pLineData = pLineData; d->m_size = size; d->m_pDiff3LineVector = pDiff3LineVector; d->m_diff3WrapLineVector.clear(); d->m_pManualDiffHelpList = pManualDiffHelpList; d->m_firstLine = 0; d->m_oldFirstLine = -1; d->m_horizScrollOffset = 0; d->m_bTriple = bTriple; d->m_scrollDeltaX = 0; d->m_scrollDeltaY = 0; d->m_bMyUpdate = false; d->m_fastSelectorLine1 = 0; d->m_fastSelectorNofLines = 0; d->m_lineNumberWidth = 0; d->m_maxTextWidth = -1; d->m_pTextCodec = pTextCodec; d->m_eLineEndStyle = eLineEndStyle; update(); d->m_pDiffTextWindowFrame->init(); } void DiffTextWindow::reset() { d->m_pLineData = nullptr; d->m_size = 0; d->m_pDiff3LineVector = nullptr; d->m_filename = ""; d->m_diff3WrapLineVector.clear(); } void DiffTextWindow::setPaintingAllowed(bool bAllowPainting) { if(updatesEnabled() != bAllowPainting) { setUpdatesEnabled(bAllowPainting); if(bAllowPainting) update(); else reset(); } } void DiffTextWindow::dragEnterEvent(QDragEnterEvent* e) { e->setAccepted(e->mimeData()->hasUrls() || e->mimeData()->hasText()); // TODO: Move this to DiffTextWindow::dropEvent // Note that the corresponding drop is handled in KDiff3App::eventFilter(). } void DiffTextWindow::printWindow(RLPainter& painter, const QRect& view, const QString& headerText, int line, int linesPerPage, const QColor& fgColor) { QRect clipRect = view; clipRect.setTop(0); painter.setClipRect(clipRect); painter.translate(view.left(), 0); QFontMetrics fm = painter.fontMetrics(); //if ( fm.width(headerText) > view.width() ) { // A simple wrapline algorithm int l = 0; for(int p = 0; p < headerText.length();) { QString s = headerText.mid(p); int i; for(i = 2; i < s.length(); ++i) if(Utils::getHorizontalAdvance(fm, s, i) > view.width()) { --i; break; } //QString s2 = s.left(i); painter.drawText(0, l * fm.height() + fm.ascent(), s.left(i)); p += i; ++l; } painter.setPen(fgColor); painter.drawLine(0, view.top() - 2, view.width(), view.top() - 2); } painter.translate(0, view.top()); print(painter, view, line, linesPerPage); painter.resetTransform(); } void DiffTextWindow::setFirstLine(QtNumberType firstLine) { int fontHeight = fontMetrics().lineSpacing(); LineRef newFirstLine = std::max(0, firstLine); int deltaY = fontHeight * (d->m_firstLine - newFirstLine); d->m_firstLine = newFirstLine; if(d->m_bSelectionInProgress && d->m_selection.isValidFirstLine()) { LineRef line; int pos; convertToLinePos(d->m_lastKnownMousePos.x(), d->m_lastKnownMousePos.y(), line, pos); d->m_selection.end(line, pos); update(); } else { scroll(0, deltaY); } d->m_pDiffTextWindowFrame->setFirstLine(d->m_firstLine); } int DiffTextWindow::getFirstLine() { return d->m_firstLine; } void DiffTextWindow::setHorizScrollOffset(int horizScrollOffset) { int fontWidth = Utils::getHorizontalAdvance(fontMetrics(), '0'); int xOffset = d->leftInfoWidth() * fontWidth; int deltaX = d->m_horizScrollOffset - std::max(0, horizScrollOffset); d->m_horizScrollOffset = std::max(0, horizScrollOffset); QRect r(xOffset, 0, width() - xOffset, height()); if(d->m_pOptions->m_bRightToLeftLanguage) { deltaX = -deltaX; r = QRect(width() - xOffset - 2, 0, -(width() - xOffset), height()).normalized(); } if(d->m_bSelectionInProgress && d->m_selection.isValidFirstLine()) { LineRef line; int pos; convertToLinePos(d->m_lastKnownMousePos.x(), d->m_lastKnownMousePos.y(), line, pos); d->m_selection.end(line, pos); update(); } else { scroll(deltaX, 0, r); } } int DiffTextWindow::getMaxTextWidth() { if(d->m_bWordWrap) { return getVisibleTextAreaWidth(); } else if(getAtomic(d->m_maxTextWidth) < 0) { d->m_maxTextWidth = 0; QTextLayout textLayout(QString(), font(), this); for(int i = 0; i < d->m_size; ++i) { textLayout.clearLayout(); textLayout.setText(d->getString(i)); d->prepareTextLayout(textLayout, true); if(textLayout.maximumWidth() > getAtomic(d->m_maxTextWidth)) d->m_maxTextWidth = qCeil(textLayout.maximumWidth()); } } return getAtomic(d->m_maxTextWidth); } LineCount DiffTextWindow::getNofLines() { return d->m_bWordWrap ? d->m_diff3WrapLineVector.size() : d->m_pDiff3LineVector->size(); } int DiffTextWindow::convertLineToDiff3LineIdx(LineRef line) { - if(line >= 0 && d->m_bWordWrap && d->m_diff3WrapLineVector.size() > 0) + if(line.isValid() && d->m_bWordWrap && d->m_diff3WrapLineVector.size() > 0) return d->m_diff3WrapLineVector[std::min((LineRef::LineType)line, d->m_diff3WrapLineVector.size() - 1)].diff3LineIndex; else return line; } LineRef DiffTextWindow::convertDiff3LineIdxToLine(int d3lIdx) { if(d->m_bWordWrap && d->m_pDiff3LineVector != nullptr && d->m_pDiff3LineVector->size() > 0) return (*d->m_pDiff3LineVector)[std::min(d3lIdx, (int)d->m_pDiff3LineVector->size() - 1)]->sumLinesNeededForDisplay; else return d3lIdx; } /** Returns a line number where the linerange [line, line+nofLines] can be displayed best. If it fits into the currently visible range then the returned value is the current firstLine. */ int getBestFirstLine(int line, int nofLines, int firstLine, int visibleLines) { int newFirstLine = firstLine; if(line < firstLine || line + nofLines + 2 > firstLine + visibleLines) { if(nofLines > visibleLines || nofLines <= (2 * visibleLines / 3 - 1)) newFirstLine = line - visibleLines / 3; else newFirstLine = line - (visibleLines - nofLines); } return newFirstLine; } void DiffTextWindow::setFastSelectorRange(int line1, int nofLines) { d->m_fastSelectorLine1 = line1; d->m_fastSelectorNofLines = nofLines; if(isVisible()) { int newFirstLine = getBestFirstLine( convertDiff3LineIdxToLine(d->m_fastSelectorLine1), convertDiff3LineIdxToLine(d->m_fastSelectorLine1 + d->m_fastSelectorNofLines) - convertDiff3LineIdxToLine(d->m_fastSelectorLine1), d->m_firstLine, getNofVisibleLines()); if(newFirstLine != d->m_firstLine) { emit scrollDiffTextWindow(0, newFirstLine - d->m_firstLine); } update(); } } /* Takes the line number estimated from mouse position and converts it to the actual line in the file. Then sets the status message accordingly. emits lineClicked signal. */ void DiffTextWindow::showStatusLine(const LineRef aproxLine) { int d3lIdx = convertLineToDiff3LineIdx(aproxLine); if(d->m_pDiff3LineVector != nullptr && d3lIdx >= 0 && d3lIdx < (int)d->m_pDiff3LineVector->size()) { const Diff3Line* pD3l = (*d->m_pDiff3LineVector)[d3lIdx]; if(pD3l != nullptr) { LineRef actualLine = pD3l->getLineInFile(d->m_winIdx); QString message; if(actualLine.isValid()) message = i18n("File %1: Line %2", d->m_filename, actualLine + 1); else message = i18n("File %1: Line not available", d->m_filename); if(d->m_pStatusBar != nullptr) d->m_pStatusBar->showMessage(message); emit lineClicked(d->m_winIdx, actualLine); } } } void DiffTextWindow::focusInEvent(QFocusEvent* e) { emit gotFocus(); QWidget::focusInEvent(e); } void DiffTextWindow::mousePressEvent(QMouseEvent* e) { if(e->button() == Qt::LeftButton) { LineRef line; int pos; convertToLinePos(e->x(), e->y(), line, pos); int fontWidth = Utils::getHorizontalAdvance(fontMetrics(), '0'); int xOffset = d->leftInfoWidth() * fontWidth; if((!d->m_pOptions->m_bRightToLeftLanguage && e->x() < xOffset) || (d->m_pOptions->m_bRightToLeftLanguage && e->x() > width() - xOffset)) { emit setFastSelectorLine(convertLineToDiff3LineIdx(line)); d->m_selection.reset(); // Disable current d->m_selection } else { // Selection resetSelection(); d->m_selection.start(line, pos); d->m_selection.end(line, pos); d->m_bSelectionInProgress = true; d->m_lastKnownMousePos = e->pos(); showStatusLine(line); } } } bool isCTokenChar(QChar c) { return (c == '_') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'); } /// Calculate where a token starts and ends, given the x-position on screen. void calcTokenPos(const QString& s, int posOnScreen, int& pos1, int& pos2, int tabSize) { // Cursor conversions that consider g_tabSize int pos = convertToPosInText(s, std::max(0, posOnScreen), tabSize); if(pos >= (int)s.length()) { pos1 = s.length(); pos2 = s.length(); return; } pos1 = pos; pos2 = pos + 1; if(isCTokenChar(s[pos1])) { while(pos1 >= 0 && isCTokenChar(s[pos1])) --pos1; ++pos1; while(pos2 < (int)s.length() && isCTokenChar(s[pos2])) ++pos2; } } void DiffTextWindow::mouseDoubleClickEvent(QMouseEvent* e) { d->m_bSelectionInProgress = false; d->m_lastKnownMousePos = e->pos(); if(e->button() == Qt::LeftButton) { LineRef line; int pos; convertToLinePos(e->x(), e->y(), line, pos); // Get the string data of the current line QString s; if(d->m_bWordWrap) { if(line < 0 || line >= (int)d->m_diff3WrapLineVector.size()) return; const Diff3WrapLine& d3wl = d->m_diff3WrapLineVector[line]; s = d->getString(d3wl.diff3LineIndex).mid(d3wl.wrapLineOffset, d3wl.wrapLineLength); } else { if(line < 0 || line >= (int)d->m_pDiff3LineVector->size()) return; s = d->getString(line); } if(!s.isEmpty()) { int pos1, pos2; calcTokenPos(s, pos, pos1, pos2, d->m_pOptions->m_tabSize); resetSelection(); d->m_selection.start(line, convertToPosOnScreen(s, pos1, d->m_pOptions->m_tabSize)); d->m_selection.end(line, convertToPosOnScreen(s, pos2, d->m_pOptions->m_tabSize)); update(); // emit d->m_selectionEnd() happens in the mouseReleaseEvent. showStatusLine(line); } } } void DiffTextWindow::mouseReleaseEvent(QMouseEvent* e) { d->m_bSelectionInProgress = false; d->m_lastKnownMousePos = e->pos(); //if ( e->button() == LeftButton ) { if(d->m_delayedDrawTimer) killTimer(d->m_delayedDrawTimer); d->m_delayedDrawTimer = 0; if(d->m_selection.isValidFirstLine()) { emit selectionEnd(); } } d->m_scrollDeltaX = 0; d->m_scrollDeltaY = 0; } inline int sqr(int x) { return x * x; } void DiffTextWindow::mouseMoveEvent(QMouseEvent* e) { LineRef line; int pos; convertToLinePos(e->x(), e->y(), line, pos); d->m_lastKnownMousePos = e->pos(); if(d->m_selection.isValidFirstLine()) { d->m_selection.end(line, pos); showStatusLine(line); // Scroll because mouse moved out of the window const QFontMetrics& fm = fontMetrics(); int fontWidth = Utils::getHorizontalAdvance(fm, '0'); int deltaX = 0; int deltaY = 0; if(!d->m_pOptions->m_bRightToLeftLanguage) { if(e->x() < d->leftInfoWidth() * fontWidth) deltaX = -1 - abs(e->x() - d->leftInfoWidth() * fontWidth) / fontWidth; if(e->x() > width()) deltaX = +1 + abs(e->x() - width()) / fontWidth; } else { if(e->x() > width() - 1 - d->leftInfoWidth() * fontWidth) deltaX = +1 + abs(e->x() - (width() - 1 - d->leftInfoWidth() * fontWidth)) / fontWidth; if(e->x() < fontWidth) deltaX = -1 - abs(e->x() - fontWidth) / fontWidth; } if(e->y() < 0) deltaY = -1 - sqr(e->y()) / sqr(fm.lineSpacing()); if(e->y() > height()) deltaY = +1 + sqr(e->y() - height()) / sqr(fm.lineSpacing()); if((deltaX != 0 && d->m_scrollDeltaX != deltaX) || (deltaY != 0 && d->m_scrollDeltaY != deltaY)) { d->m_scrollDeltaX = deltaX; d->m_scrollDeltaY = deltaY; emit scrollDiffTextWindow(deltaX, deltaY); if(d->m_delayedDrawTimer) killTimer(d->m_delayedDrawTimer); d->m_delayedDrawTimer = startTimer(50); } else { d->m_scrollDeltaX = deltaX; d->m_scrollDeltaY = deltaY; d->myUpdate(0); } } } void DiffTextWindowData::myUpdate(int afterMilliSecs) { if(m_delayedDrawTimer) m_pDiffTextWindow->killTimer(m_delayedDrawTimer); m_bMyUpdate = true; m_delayedDrawTimer = m_pDiffTextWindow->startTimer(afterMilliSecs); } void DiffTextWindow::timerEvent(QTimerEvent*) { killTimer(d->m_delayedDrawTimer); d->m_delayedDrawTimer = 0; if(d->m_bMyUpdate) { int fontHeight = fontMetrics().lineSpacing(); if(d->m_selection.getOldLastLine().isValid()) { int lastLine; int firstLine; if(d->m_selection.getOldFirstLine().isValid()) { firstLine = min3(d->m_selection.getOldFirstLine(), d->m_selection.getLastLine(), d->m_selection.getOldLastLine()); lastLine = max3(d->m_selection.getOldFirstLine(), d->m_selection.getLastLine(), d->m_selection.getOldLastLine()); } else { firstLine = std::min(d->m_selection.getLastLine(), d->m_selection.getOldLastLine()); lastLine = std::max(d->m_selection.getLastLine(), d->m_selection.getOldLastLine()); } int y1 = (firstLine - d->m_firstLine) * fontHeight; int y2 = std::min(height(), (lastLine - d->m_firstLine + 1) * fontHeight); if(y1 < height() && y2 > 0) { QRect invalidRect = QRect(0, y1 - 1, width(), y2 - y1 + fontHeight); // Some characters in exotic exceed the regular bottom. update(invalidRect); } } d->m_bMyUpdate = false; } if(d->m_scrollDeltaX != 0 || d->m_scrollDeltaY != 0) { d->m_selection.end(d->m_selection.getLastLine() + d->m_scrollDeltaY, d->m_selection.getLastPos() + d->m_scrollDeltaX); emit scrollDiffTextWindow(d->m_scrollDeltaX, d->m_scrollDeltaY); killTimer(d->m_delayedDrawTimer); d->m_delayedDrawTimer = startTimer(50); } } void DiffTextWindow::resetSelection() { d->m_selection.reset(); update(); } void DiffTextWindow::convertToLinePos(int x, int y, LineRef& line, int& pos) { const QFontMetrics& fm = fontMetrics(); int fontHeight = fm.lineSpacing(); int yOffset = -d->m_firstLine * fontHeight; line = (y - yOffset) / fontHeight; - if(line >= 0 && (!d->m_pOptions->m_bWordWrap || line < d->m_diff3WrapLineVector.count())) + if(line.isValid() && (!d->m_pOptions->m_bWordWrap || line < d->m_diff3WrapLineVector.count())) { QString s = d->getLineString(line); QTextLayout textLayout(s, font(), this); d->prepareTextLayout(textLayout, !d->m_pOptions->m_bWordWrap || d->m_diff3WrapLineVector[line].wrapLineOffset == 0); pos = textLayout.lineAt(0).xToCursor(x - textLayout.position().x()); } else pos = -1; } class FormatRangeHelper { private: QFont m_font; QPen m_pen; QColor m_background; int m_currentPos; QVector m_formatRanges; public: inline operator QVector() { return m_formatRanges; } FormatRangeHelper() { m_pen = QColor(Qt::black); m_background = QColor(Qt::white); m_currentPos = 0; } void setFont(const QFont& f) { m_font = f; } void setPen(const QPen& pen) { m_pen = pen; } void setBackground(const QColor& background) { m_background = background; } void next() { if(m_formatRanges.isEmpty() || m_formatRanges.back().format.foreground().color() != m_pen.color() || m_formatRanges.back().format.background().color() != m_background) { QTextLayout::FormatRange fr; fr.length = 1; fr.start = m_currentPos; fr.format.setForeground(m_pen.color()); fr.format.setBackground(m_background); m_formatRanges.append(fr); } else { ++m_formatRanges.back().length; } ++m_currentPos; } }; void DiffTextWindowData::prepareTextLayout(QTextLayout& textLayout, bool /*bFirstLine*/, int visibleTextWidth) { QTextOption textOption; #if QT_VERSION < QT_VERSION_CHECK(5,10,0) textOption.setTabStop(QFontMetricsF(m_pDiffTextWindow->font()).width(' ') * m_pOptions->m_tabSize); #else textOption.setTabStopDistance(QFontMetricsF(m_pDiffTextWindow->font()).width(' ') * m_pOptions->m_tabSize); #endif if(m_pOptions->m_bShowWhiteSpaceCharacters) textOption.setFlags(QTextOption::ShowTabsAndSpaces); if(m_pOptions->m_bRightToLeftLanguage) textOption.setAlignment(Qt::AlignRight); // only relevant for multi line text layout if(visibleTextWidth >= 0) textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); textLayout.setTextOption(textOption); if(m_pOptions->m_bShowWhiteSpaceCharacters) { // This additional format is only necessary for the tab arrow QVector formats; QTextLayout::FormatRange formatRange; formatRange.start = 0; formatRange.length = textLayout.text().length(); formatRange.format.setFont(m_pDiffTextWindow->font()); formats.append(formatRange); textLayout.setFormats(formats); } textLayout.beginLayout(); int leading = m_pDiffTextWindow->fontMetrics().leading(); int height = 0; int fontWidth = Utils::getHorizontalAdvance(m_pDiffTextWindow->fontMetrics(), '0'); int xOffset = leftInfoWidth() * fontWidth - m_horizScrollOffset; int textWidth = visibleTextWidth; if(textWidth < 0) textWidth = m_pDiffTextWindow->width() - xOffset; int indentation = 0; while(true) { QTextLine line = textLayout.createLine(); if(!line.isValid()) break; height += leading; //if ( !bFirstLine ) // indentation = m_pDiffTextWindow->fontMetrics().width(' ') * m_pOptions->m_tabSize; if(visibleTextWidth >= 0) { line.setLineWidth(visibleTextWidth - indentation); line.setPosition(QPointF(indentation, height)); height += qCeil(line.height()); //bFirstLine = false; } else // only one line { line.setPosition(QPointF(indentation, height)); break; } } textLayout.endLayout(); if(m_pOptions->m_bRightToLeftLanguage) textLayout.setPosition(QPointF(textWidth - textLayout.maximumWidth(), 0)); else textLayout.setPosition(QPointF(xOffset, 0)); } void DiffTextWindowData::writeLine( RLPainter& p, const LineData* pld, const DiffList* pLineDiff1, const DiffList* pLineDiff2, - int line, - int whatChanged, - int whatChanged2, - int srcLineIdx, + const LineRef& line, + const ChangeFlags whatChanged, + const ChangeFlags whatChanged2, + const LineRef& srcLineIdx, int wrapLineOffset, int wrapLineLength, bool bWrapLine, const QRect& invalidRect, int deviceWidth) { QFont normalFont = p.font(); const QFontMetrics& fm = p.fontMetrics(); int fontHeight = fm.lineSpacing(); int fontAscent = fm.ascent(); int fontWidth = Utils::getHorizontalAdvance(fm, '0'); int xOffset = leftInfoWidth() * fontWidth - m_horizScrollOffset; int yOffset = (line - m_firstLine) * fontHeight; QRect lineRect(xOffset, yOffset, deviceWidth, fontHeight); if(!invalidRect.intersects(lineRect)) { return; } int fastSelectorLine1 = m_pDiffTextWindow->convertDiff3LineIdxToLine(m_fastSelectorLine1); int fastSelectorLine2 = m_pDiffTextWindow->convertDiff3LineIdxToLine(m_fastSelectorLine1 + m_fastSelectorNofLines) - 1; bool bFastSelectionRange = (line >= fastSelectorLine1 && line <= fastSelectorLine2); QColor bgColor = m_pOptions->m_bgColor; QColor diffBgColor = m_pOptions->m_diffBgColor; if(bFastSelectionRange) { bgColor = m_pOptions->m_currentRangeBgColor; diffBgColor = m_pOptions->m_currentRangeDiffBgColor; } if(yOffset + fontHeight < invalidRect.top() || invalidRect.bottom() < yOffset - fontHeight) return; - int changed = whatChanged; - if(pLineDiff1 != nullptr) changed |= 1; - if(pLineDiff2 != nullptr) changed |= 2; + ChangeFlags changed = whatChanged; + if(pLineDiff1 != nullptr) changed |= AChanged; + if(pLineDiff2 != nullptr) changed |= BChanged; QColor c = m_pOptions->m_fgColor; p.setPen(c); - if(changed == 2) { + if(changed == BChanged) { c = m_cDiff2; } - else if(changed == 1) + else if(changed == AChanged) { c = m_cDiff1; } - else if(changed == 3) + else if(changed == Both) { c = m_cDiffBoth; } if(pld != nullptr) { // First calculate the "changed" information for each character. int i = 0; QString lineString = pld->getLine(); if(!lineString.isEmpty()) { switch(lineString[lineString.length() - 1].unicode()) { case '\n': lineString[lineString.length() - 1] = 0x00B6; break; // "Pilcrow", "paragraph mark" case '\r': lineString[lineString.length() - 1] = 0x00A4; break; // Currency sign ;0x2761 "curved stem paragraph sign ornament" //case '\0b' : lineString[lineString.length()-1] = 0x2756; break; // some other nice looking character } } - QVector charChanged(pld->size()); + QVector charChanged(pld->size()); Merger merger(pLineDiff1, pLineDiff2); while(!merger.isEndReached() && i < pld->size()) { if(i < pld->size()) { charChanged[i] = merger.whatChanged(); ++i; } merger.next(); } int outPos = 0; int lineLength = m_bWordWrap ? wrapLineOffset + wrapLineLength : lineString.length(); FormatRangeHelper frh; for(i = wrapLineOffset; i < lineLength; ++i) { c = m_pOptions->m_fgColor; - int cchanged = charChanged[i] | whatChanged; + ChangeFlags cchanged = charChanged[i] | whatChanged; - if(cchanged == 2) { + if(cchanged == BChanged) { c = m_cDiff2; } - else if(cchanged == 1) + else if(cchanged == AChanged) { c = m_cDiff1; } - else if(cchanged == 3) + else if(cchanged == Both) { c = m_cDiffBoth; } if(c != m_pOptions->m_fgColor && whatChanged2 == 0 && !m_pOptions->m_bShowWhiteSpace) { // The user doesn't want to see highlighted white space. c = m_pOptions->m_fgColor; } { frh.setBackground(bgColor); if(!m_selection.within(line, outPos)) { if(c != m_pOptions->m_fgColor) { QColor lightc = diffBgColor; frh.setBackground(lightc); // Setting italic font here doesn't work: Changing the font only when drawing is too late } frh.setPen(c); frh.next(); frh.setFont(normalFont); } else { frh.setBackground(m_pDiffTextWindow->palette().highlight().color()); frh.setPen(m_pDiffTextWindow->palette().highlightedText().color()); frh.next(); m_selection.bSelectionContainsData = true; } } ++outPos; } // end for QTextLayout textLayout(lineString.mid(wrapLineOffset, lineLength - wrapLineOffset), m_pDiffTextWindow->font(), m_pDiffTextWindow); prepareTextLayout(textLayout, !m_bWordWrap || wrapLineOffset == 0); textLayout.draw(&p, QPoint(0, yOffset), frh /*, const QRectF & clip = QRectF() */); } p.fillRect(0, yOffset, leftInfoWidth() * fontWidth, fontHeight, m_pOptions->m_bgColor); xOffset = (m_lineNumberWidth + 2) * fontWidth; int xLeft = m_lineNumberWidth * fontWidth; p.setPen(m_pOptions->m_fgColor); if(pld != nullptr) { if(m_pOptions->m_bShowLineNumbers && !bWrapLine) { QString num; num.sprintf("%0*d", m_lineNumberWidth, srcLineIdx + 1); p.drawText(0, yOffset + fontAscent, num); //p.drawLine( xLeft -1, yOffset, xLeft -1, yOffset+fontHeight-1 ); } if(!bWrapLine || wrapLineLength > 0) { Qt::PenStyle wrapLinePenStyle = Qt::DotLine; p.setPen(QPen(m_pOptions->m_fgColor, 0, bWrapLine ? wrapLinePenStyle : Qt::SolidLine)); p.drawLine(xOffset + 1, yOffset, xOffset + 1, yOffset + fontHeight - 1); p.setPen(QPen(m_pOptions->m_fgColor, 0, Qt::SolidLine)); } } if(c != m_pOptions->m_fgColor && whatChanged2 == 0) //&& whatChanged==0 ) { if(m_pOptions->m_bShowWhiteSpace) { p.setBrushOrigin(0, 0); p.fillRect(xLeft, yOffset, fontWidth * 2 - 1, fontHeight, QBrush(c, Qt::Dense5Pattern)); } } else { p.fillRect(xLeft, yOffset, fontWidth * 2 - 1, fontHeight, c == m_pOptions->m_fgColor ? bgColor : c); } if(bFastSelectionRange) { p.fillRect(xOffset + fontWidth - 1, yOffset, 3, fontHeight, m_pOptions->m_fgColor); } // Check if line needs a manual diff help mark ManualDiffHelpList::const_iterator ci; for(ci = m_pManualDiffHelpList->begin(); ci != m_pManualDiffHelpList->end(); ++ci) { const ManualDiffHelpEntry& mdhe = *ci; - int rangeLine1 = -1; - int rangeLine2 = -1; + LineRef rangeLine1; + LineRef rangeLine2; mdhe.getRangeForUI(m_winIdx, &rangeLine1, &rangeLine2); - if(rangeLine1 >= 0 && rangeLine2 >= 0 && srcLineIdx >= rangeLine1 && srcLineIdx <= rangeLine2) + if(rangeLine1.isValid() && rangeLine2.isValid() && srcLineIdx >= rangeLine1 && srcLineIdx <= rangeLine2) { p.fillRect(xOffset - fontWidth, yOffset, fontWidth - 1, fontHeight, m_pOptions->m_manualHelpRangeColor); break; } } } void DiffTextWindow::paintEvent(QPaintEvent* e) { QRect invalidRect = e->rect(); if(invalidRect.isEmpty()) return; if(d->m_pDiff3LineVector == nullptr || (d->m_diff3WrapLineVector.empty() && d->m_bWordWrap)) { QPainter p(this); p.fillRect(invalidRect, d->m_pOptions->m_bgColor); return; } bool bOldSelectionContainsData = d->m_selection.bSelectionContainsData; d->m_selection.bSelectionContainsData = false; int endLine = std::min(d->m_firstLine + getNofVisibleLines() + 2, getNofLines()); RLPainter p(this, d->m_pOptions->m_bRightToLeftLanguage, width(), Utils::getHorizontalAdvance(fontMetrics(), '0')); p.setFont(font()); p.QPainter::fillRect(invalidRect, d->m_pOptions->m_bgColor); d->draw(p, invalidRect, width(), d->m_firstLine, endLine); p.end(); d->m_oldFirstLine = d->m_firstLine; d->m_selection.clearOldSelection(); if(!bOldSelectionContainsData && d->m_selection.selectionContainsData()) emit newSelection(); } void DiffTextWindow::print(RLPainter& p, const QRect&, int firstLine, int nofLinesPerPage) { if(d->m_pDiff3LineVector == nullptr || !updatesEnabled() || (d->m_diff3WrapLineVector.empty() && d->m_bWordWrap)) return; resetSelection(); int oldFirstLine = d->m_firstLine; d->m_firstLine = firstLine; QRect invalidRect = QRect(0, 0, 1000000000, 1000000000); QColor bgColor = d->m_pOptions->m_bgColor; d->m_pOptions->m_bgColor = Qt::white; d->draw(p, invalidRect, p.window().width(), firstLine, std::min(firstLine + nofLinesPerPage, getNofLines())); d->m_pOptions->m_bgColor = bgColor; d->m_firstLine = oldFirstLine; } void DiffTextWindowData::draw(RLPainter& p, const QRect& invalidRect, int deviceWidth, int beginLine, int endLine) { m_lineNumberWidth = m_pOptions->m_bShowLineNumbers ? (int)log10((double)std::max(m_size, 1)) + 1 : 0; if(m_winIdx == A) { m_cThis = m_pOptions->m_colorA; m_cDiff1 = m_pOptions->m_colorB; m_cDiff2 = m_pOptions->m_colorC; } if(m_winIdx == B) { m_cThis = m_pOptions->m_colorB; m_cDiff1 = m_pOptions->m_colorC; m_cDiff2 = m_pOptions->m_colorA; } if(m_winIdx == C) { m_cThis = m_pOptions->m_colorC; m_cDiff1 = m_pOptions->m_colorA; m_cDiff2 = m_pOptions->m_colorB; } m_cDiffBoth = m_pOptions->m_colorForConflict; // Conflict color p.setPen(m_cThis); for(int line = beginLine; line < endLine; ++line) { int wrapLineOffset = 0; int wrapLineLength = 0; const Diff3Line* d3l = nullptr; bool bWrapLine = false; if(m_bWordWrap) { Diff3WrapLine& d3wl = m_diff3WrapLineVector[line]; wrapLineOffset = d3wl.wrapLineOffset; wrapLineLength = d3wl.wrapLineLength; d3l = d3wl.pD3L; bWrapLine = line > 0 && m_diff3WrapLineVector[line - 1].pD3L == d3l; } else { d3l = (*m_pDiff3LineVector)[line]; } DiffList* pFineDiff1; DiffList* pFineDiff2; - int changed = 0; - int changed2 = 0; + ChangeFlags changed = NoChange; + ChangeFlags changed2 = NoChange; - int srcLineIdx = -1; - getLineInfo(*d3l, srcLineIdx, pFineDiff1, pFineDiff2, changed, changed2); + LineRef srcLineIdx; + d3l->getLineInfo(m_winIdx, m_bTriple, srcLineIdx, pFineDiff1, pFineDiff2, changed, changed2); writeLine( p, // QPainter - srcLineIdx == -1 ? nullptr : &(*m_pLineData)[srcLineIdx], // Text in this line + !srcLineIdx.isValid() ? nullptr : &(*m_pLineData)[srcLineIdx], // Text in this line pFineDiff1, pFineDiff2, line, // Line on the screen changed, changed2, srcLineIdx, wrapLineOffset, wrapLineLength, bWrapLine, invalidRect, deviceWidth); } } QString DiffTextWindowData::getString(int d3lIdx) { if(d3lIdx < 0 || d3lIdx >= (int)m_pDiff3LineVector->size()) return QString(); const Diff3Line* d3l = (*m_pDiff3LineVector)[d3lIdx]; DiffList* pFineDiff1; DiffList* pFineDiff2; - int changed = 0; - int changed2 = 0; - int lineIdx = -1; + ChangeFlags changed = NoChange; + ChangeFlags changed2 = NoChange; + LineRef lineIdx; - getLineInfo(*d3l, lineIdx, pFineDiff1, pFineDiff2, changed, changed2); + d3l->getLineInfo(m_winIdx, m_bTriple, lineIdx, pFineDiff1, pFineDiff2, changed, changed2); - if(lineIdx == -1) + if(!lineIdx.isValid()) return QString(); else { const LineData* ld = &(*m_pLineData)[lineIdx]; return ld->getLine(); } return QString(); } QString DiffTextWindowData::getLineString(int line) { if(m_bWordWrap) { if(line < m_diff3WrapLineVector.count()) { int d3LIdx = m_pDiffTextWindow->convertLineToDiff3LineIdx(line); return getString(d3LIdx).mid(m_diff3WrapLineVector[line].wrapLineOffset, m_diff3WrapLineVector[line].wrapLineLength); } else return QString(); } else { return getString(line); } } -void DiffTextWindowData::getLineInfo( - const Diff3Line& d3l, - int& lineIdx, - DiffList*& pFineDiff1, DiffList*& pFineDiff2, // return values - int& changed, int& changed2) -{ - d3l.getLineInfo(m_winIdx, m_bTriple, lineIdx, pFineDiff1, pFineDiff2, changed, changed2); -} - void DiffTextWindow::resizeEvent(QResizeEvent* e) { QSize s = e->size(); QFontMetrics fm = fontMetrics(); int visibleLines = s.height() / fm.lineSpacing() - 2; int visibleColumns = s.width() / Utils::getHorizontalAdvance(fm, '0') - d->leftInfoWidth(); if(e->size().height() != e->oldSize().height()) emit resizeHeightChangedSignal(visibleLines); if(e->size().width() != e->oldSize().width()) emit resizeWidthChangedSignal(visibleColumns); QWidget::resizeEvent(e); } int DiffTextWindow::getNofVisibleLines() { QFontMetrics fm = fontMetrics(); int fmh = fm.lineSpacing(); int h = height(); return h / fmh - 1; } int DiffTextWindow::getVisibleTextAreaWidth() { QFontMetrics fm = fontMetrics(); return width() - d->leftInfoWidth() * Utils::getHorizontalAdvance(fm, '0'); } QString DiffTextWindow::getSelection() { if(d->m_pLineData == nullptr) return QString(); QString selectionString; int line = 0; int lineIdx = 0; int it; int vectorSize = d->m_bWordWrap ? d->m_diff3WrapLineVector.size() : d->m_pDiff3LineVector->size(); for(it = 0; it < vectorSize; ++it) { const Diff3Line* d3l = d->m_bWordWrap ? d->m_diff3WrapLineVector[it].pD3L : (*d->m_pDiff3LineVector)[it]; Q_ASSERT(d->m_winIdx >= 1 && d->m_winIdx <= 3); if(d->m_winIdx == A) { lineIdx = d3l->getLineA(); } else if(d->m_winIdx == B) { lineIdx = d3l->getLineB(); } else if(d->m_winIdx == C) { lineIdx = d3l->getLineC(); } if(lineIdx != -1) { int size = (*d->m_pLineData)[lineIdx].size(); QString lineString = (*d->m_pLineData)[lineIdx].getLine(); if(d->m_bWordWrap) { size = d->m_diff3WrapLineVector[it].wrapLineLength; lineString = lineString.mid(d->m_diff3WrapLineVector[it].wrapLineOffset, size); } for(int i = 0; i < size; ++i) { if(d->m_selection.within(line, i)) { selectionString += lineString[i]; } } if(d->m_selection.within(line, size) && !(d->m_bWordWrap && it + 1 < vectorSize && d3l == d->m_diff3WrapLineVector[it + 1].pD3L)) { #if defined(Q_OS_WIN) selectionString += '\r'; #endif selectionString += '\n'; } } ++line; } return selectionString; } bool DiffTextWindow::findString(const QString& s, LineRef& d3vLine, int& posInLine, bool bDirDown, bool bCaseSensitive) { int it = d3vLine; int endIt = bDirDown ? d->m_pDiff3LineVector->size() : -1; int step = bDirDown ? 1 : -1; int startPos = posInLine; for(; it != endIt; it += step) { QString line = d->getString(it); if(!line.isEmpty()) { int pos = line.indexOf(s, startPos, bCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive); if(pos != -1) { d3vLine = it; posInLine = pos; return true; } startPos = 0; } } return false; } void DiffTextWindow::convertD3LCoordsToLineCoords(int d3LIdx, int d3LPos, int& line, int& pos) { if(d->m_bWordWrap) { int wrapPos = d3LPos; int wrapLine = convertDiff3LineIdxToLine(d3LIdx); while(wrapPos > d->m_diff3WrapLineVector[wrapLine].wrapLineLength) { wrapPos -= d->m_diff3WrapLineVector[wrapLine].wrapLineLength; ++wrapLine; } pos = wrapPos; line = wrapLine; } else { pos = d3LPos; line = d3LIdx; } } void DiffTextWindow::convertLineCoordsToD3LCoords(int line, int pos, int& d3LIdx, int& d3LPos) { if(d->m_bWordWrap) { d3LPos = pos; d3LIdx = convertLineToDiff3LineIdx(line); int wrapLine = convertDiff3LineIdxToLine(d3LIdx); // First wrap line belonging to this d3LIdx while(wrapLine < line) { d3LPos += d->m_diff3WrapLineVector[wrapLine].wrapLineLength; ++wrapLine; } } else { d3LPos = pos; d3LIdx = line; } } void DiffTextWindow::setSelection(LineRef firstLine, int startPos, LineRef lastLine, int endPos, LineRef& l, int& p) { d->m_selection.reset(); if(lastLine >= getNofLines()) { lastLine = getNofLines() - 1; const Diff3Line* d3l = (*d->m_pDiff3LineVector)[convertLineToDiff3LineIdx(lastLine)]; LineRef line; if(d->m_winIdx == A) line = d3l->getLineA(); if(d->m_winIdx == B) line = d3l->getLineB(); if(d->m_winIdx == C) line = d3l->getLineC(); if(line.isValid()) endPos = (*d->m_pLineData)[line].width(d->m_pOptions->m_tabSize); } if(d->m_bWordWrap && d->m_pDiff3LineVector != nullptr) { QString s1 = d->getString(firstLine); int firstWrapLine = convertDiff3LineIdxToLine(firstLine); int wrapStartPos = startPos; while(wrapStartPos > d->m_diff3WrapLineVector[firstWrapLine].wrapLineLength) { wrapStartPos -= d->m_diff3WrapLineVector[firstWrapLine].wrapLineLength; s1 = s1.mid(d->m_diff3WrapLineVector[firstWrapLine].wrapLineLength); ++firstWrapLine; } QString s2 = d->getString(lastLine); int lastWrapLine = convertDiff3LineIdxToLine(lastLine); int wrapEndPos = endPos; while(wrapEndPos > d->m_diff3WrapLineVector[lastWrapLine].wrapLineLength) { wrapEndPos -= d->m_diff3WrapLineVector[lastWrapLine].wrapLineLength; s2 = s2.mid(d->m_diff3WrapLineVector[lastWrapLine].wrapLineLength); ++lastWrapLine; } d->m_selection.start(firstWrapLine, convertToPosOnScreen(s1, wrapStartPos, d->m_pOptions->m_tabSize)); d->m_selection.end(lastWrapLine, convertToPosOnScreen(s2, wrapEndPos, d->m_pOptions->m_tabSize)); l = firstWrapLine; p = wrapStartPos; } else { if(d->m_pDiff3LineVector != nullptr){ d->m_selection.start(firstLine, convertToPosOnScreen(d->getString(firstLine), startPos, d->m_pOptions->m_tabSize)); d->m_selection.end(lastLine, convertToPosOnScreen(d->getString(lastLine), endPos, d->m_pOptions->m_tabSize)); l = firstLine; p = startPos; } } update(); } int DiffTextWindowData::convertLineOnScreenToLineInSource(int lineOnScreen, e_CoordType coordType, bool bFirstLine) { LineRef line = -1; if(lineOnScreen >= 0) { if(coordType == eWrapCoords) return lineOnScreen; int d3lIdx = m_pDiffTextWindow->convertLineToDiff3LineIdx(lineOnScreen); if(!bFirstLine && d3lIdx >= m_pDiff3LineVector->size()) d3lIdx = m_pDiff3LineVector->size() - 1; if(coordType == eD3LLineCoords) return d3lIdx; while(line < 0 && d3lIdx < m_pDiff3LineVector->size() && d3lIdx >= 0) { const Diff3Line* d3l = (*m_pDiff3LineVector)[d3lIdx]; if(m_winIdx == A) line = d3l->getLineA(); if(m_winIdx == B) line = d3l->getLineB(); if(m_winIdx == C) line = d3l->getLineC(); if(bFirstLine) ++d3lIdx; else --d3lIdx; } if(coordType == eFileCoords) return line; } return line; } void DiffTextWindow::getSelectionRange(LineRef* pFirstLine, LineRef* pLastLine, e_CoordType coordType) { if(pFirstLine) *pFirstLine = d->convertLineOnScreenToLineInSource(d->m_selection.beginLine(), coordType, true); if(pLastLine) *pLastLine = d->convertLineOnScreenToLineInSource(d->m_selection.endLine(), coordType, false); } void DiffTextWindow::convertSelectionToD3LCoords() { if(d->m_pDiff3LineVector == nullptr || !updatesEnabled() || !isVisible() || d->m_selection.isEmpty()) { return; } // convert the d->m_selection to unwrapped coordinates: Later restore to new coords int firstD3LIdx, firstD3LPos; QString s = d->getLineString(d->m_selection.beginLine()); int firstPosInText = convertToPosInText(s, d->m_selection.beginPos(), d->m_pOptions->m_tabSize); convertLineCoordsToD3LCoords(d->m_selection.beginLine(), firstPosInText, firstD3LIdx, firstD3LPos); int lastD3LIdx, lastD3LPos; s = d->getLineString(d->m_selection.endLine()); int lastPosInText = convertToPosInText(s, d->m_selection.endPos(), d->m_pOptions->m_tabSize); convertLineCoordsToD3LCoords(d->m_selection.endLine(), lastPosInText, lastD3LIdx, lastD3LPos); d->m_selection.start(firstD3LIdx, firstD3LPos); d->m_selection.end(lastD3LIdx, lastD3LPos); } int s_maxNofRunnables = 0; class RecalcWordWrapRunnable : public QRunnable { DiffTextWindow* m_pDTW; // DiffTextWindowData* m_pDTWData; // TODO unused? int m_visibleTextWidth; int m_cacheIdx; public: RecalcWordWrapRunnable(DiffTextWindow* p, DiffTextWindowData* pData, int visibleTextWidth, int cacheIdx) : m_pDTW(p), /* m_pDTWData(pData),*/ m_visibleTextWidth(visibleTextWidth), m_cacheIdx(cacheIdx) { Q_UNUSED(pData) // TODO really unused? setAutoDelete(true); s_runnableCount.fetchAndAddOrdered(1); } void run() override { m_pDTW->recalcWordWrapHelper(0, m_visibleTextWidth, m_cacheIdx); int newValue = s_runnableCount.fetchAndAddOrdered(-1) - 1; g_pProgressDialog->setCurrent(s_maxNofRunnables - getAtomic(s_runnableCount)); if(newValue == 0) { QWidget* p = m_pDTW; while(p) { p = p->parentWidget(); if(KDiff3App* pKDiff3App = dynamic_cast(p)) { QMetaObject::invokeMethod(pKDiff3App, "slotFinishRecalcWordWrap", Qt::QueuedConnection, Q_ARG(int, m_visibleTextWidth)); break; } } } } }; QList s_runnables; bool startRunnables() { if(s_runnables.count() == 0) { return false; } else { g_pProgressDialog->setStayHidden(true); g_pProgressDialog->push(); g_pProgressDialog->setMaxNofSteps(s_runnables.count()); s_maxNofRunnables = s_runnables.count(); g_pProgressDialog->setCurrent(0); for(int i = 0; i < s_runnables.count(); ++i) { QThreadPool::globalInstance()->start(s_runnables[i]); } s_runnables.clear(); return true; } } // Use conexpr when supported. QT const int s_linesPerRunnable = 2000; void DiffTextWindow::recalcWordWrap(bool bWordWrap, int wrapLineVectorSize, int visibleTextWidth) { if(d->m_pDiff3LineVector == nullptr || !isVisible()) { d->m_bWordWrap = bWordWrap; if(!bWordWrap) d->m_diff3WrapLineVector.resize(0); return; } d->m_bWordWrap = bWordWrap; if(bWordWrap) { d->m_lineNumberWidth = d->m_pOptions->m_bShowLineNumbers ? (int)log10((double)std::max(d->m_size, 1)) + 1 : 0; d->m_diff3WrapLineVector.resize(wrapLineVectorSize); if(wrapLineVectorSize == 0) { d->m_wrapLineCacheList.clear(); setUpdatesEnabled(false); for(int i = 0, j = 0; i < d->m_pDiff3LineVector->size(); i += s_linesPerRunnable, ++j) //int i=0; { d->m_wrapLineCacheList.append(QVector()); s_runnables.push_back(new RecalcWordWrapRunnable(this, d, visibleTextWidth, j)); } } else { recalcWordWrapHelper(wrapLineVectorSize, visibleTextWidth, 0); setUpdatesEnabled(true); } } else { if(wrapLineVectorSize == 0 && getAtomic(d->m_maxTextWidth) < 0) { d->m_diff3WrapLineVector.resize(0); d->m_wrapLineCacheList.clear(); setUpdatesEnabled(false); for(int i = 0, j = 0; i < d->m_pDiff3LineVector->size(); i += s_linesPerRunnable, ++j) { s_runnables.push_back(new RecalcWordWrapRunnable(this, d, visibleTextWidth, j)); } } else { setUpdatesEnabled(true); } } } void DiffTextWindow::recalcWordWrapHelper(int wrapLineVectorSize, int visibleTextWidth, int cacheListIdx) { if(d->m_bWordWrap) { if(g_pProgressDialog->wasCancelled()) return; if(visibleTextWidth < 0) visibleTextWidth = getVisibleTextAreaWidth(); else visibleTextWidth -= d->leftInfoWidth() * Utils::getHorizontalAdvance(fontMetrics(), '0'); int i; int wrapLineIdx = 0; int size = d->m_pDiff3LineVector->size(); int firstD3LineIdx = wrapLineVectorSize > 0 ? 0 : cacheListIdx * s_linesPerRunnable; int endIdx = wrapLineVectorSize > 0 ? size : std::min(firstD3LineIdx + s_linesPerRunnable, size); QVector& wrapLineCache = d->m_wrapLineCacheList[cacheListIdx]; int cacheListIdx2 = 0; QTextLayout textLayout(QString(), font(), this); for(i = firstD3LineIdx; i < endIdx; ++i) { if(g_pProgressDialog->wasCancelled()) return; int linesNeeded = 0; if(wrapLineVectorSize == 0) { QString s = d->getString(i); textLayout.clearLayout(); textLayout.setText(s); d->prepareTextLayout(textLayout, true, visibleTextWidth); linesNeeded = textLayout.lineCount(); for(int l = 0; l < linesNeeded; ++l) { QTextLine line = textLayout.lineAt(l); wrapLineCache.push_back(DiffTextWindowData::WrapLineCacheData(i, line.textStart(), line.textLength())); } } else if(wrapLineVectorSize > 0 && cacheListIdx2 < d->m_wrapLineCacheList.count()) { DiffTextWindowData::WrapLineCacheData* pWrapLineCache = d->m_wrapLineCacheList[cacheListIdx2].data(); int cacheIdx = 0; int clc = d->m_wrapLineCacheList.count() - 1; int cllc = d->m_wrapLineCacheList.last().count(); int curCount = d->m_wrapLineCacheList[cacheListIdx2].count() - 1; int l = 0; while((cacheListIdx2 < clc || (cacheListIdx2 == clc && cacheIdx < cllc)) && pWrapLineCache->m_d3LineIdx <= i) { if(pWrapLineCache->m_d3LineIdx == i) { Diff3WrapLine* pDiff3WrapLine = &d->m_diff3WrapLineVector[wrapLineIdx + l]; pDiff3WrapLine->wrapLineOffset = pWrapLineCache->m_textStart; pDiff3WrapLine->wrapLineLength = pWrapLineCache->m_textLength; ++l; } if(cacheIdx < curCount) { ++cacheIdx; ++pWrapLineCache; } else { ++cacheListIdx2; if(cacheListIdx2 >= d->m_wrapLineCacheList.count()) break; pWrapLineCache = d->m_wrapLineCacheList[cacheListIdx2].data(); curCount = d->m_wrapLineCacheList[cacheListIdx2].count(); cacheIdx = 0; } } linesNeeded = l; } Diff3Line& d3l = *(*d->m_pDiff3LineVector)[i]; if(d3l.linesNeededForDisplay < linesNeeded) { Q_ASSERT(wrapLineVectorSize == 0); d3l.linesNeededForDisplay = linesNeeded; } if(wrapLineVectorSize > 0) { int j; for(j = 0; j < d3l.linesNeededForDisplay; ++j, ++wrapLineIdx) { Diff3WrapLine& d3wl = d->m_diff3WrapLineVector[wrapLineIdx]; d3wl.diff3LineIndex = i; d3wl.pD3L = (*d->m_pDiff3LineVector)[i]; if(j >= linesNeeded) { d3wl.wrapLineOffset = 0; d3wl.wrapLineLength = 0; } } } } if(wrapLineVectorSize > 0) { d->m_firstLine = std::min(d->m_firstLine, wrapLineVectorSize - 1); d->m_horizScrollOffset = 0; d->m_pDiffTextWindowFrame->setFirstLine(d->m_firstLine); } } else // no word wrap, just calc the maximum text width { if(g_pProgressDialog->wasCancelled()) return; int size = d->m_pDiff3LineVector->size(); int firstD3LineIdx = cacheListIdx * s_linesPerRunnable; int endIdx = std::min(firstD3LineIdx + s_linesPerRunnable, size); int maxTextWidth = getAtomic(d->m_maxTextWidth); // current value QTextLayout textLayout(QString(), font(), this); for(int i = firstD3LineIdx; i < endIdx; ++i) { if(g_pProgressDialog->wasCancelled()) return; textLayout.clearLayout(); textLayout.setText(d->getString(i)); d->prepareTextLayout(textLayout, true); if(textLayout.maximumWidth() > maxTextWidth) maxTextWidth = qCeil(textLayout.maximumWidth()); } for(;;) { int prevMaxTextWidth = d->m_maxTextWidth.fetchAndStoreOrdered(maxTextWidth); if(prevMaxTextWidth <= maxTextWidth) break; maxTextWidth = prevMaxTextWidth; } } if(!d->m_selection.isEmpty() && (!d->m_bWordWrap || wrapLineVectorSize > 0)) { // Assume unwrapped coordinates //( Why? ->Conversion to unwrapped coords happened a few lines above in this method. // Also see KDiff3App::recalcWordWrap() on the role of wrapLineVectorSize) // Wrap them now. // convert the d->m_selection to unwrapped coordinates. int firstLine, firstPos; convertD3LCoordsToLineCoords(d->m_selection.beginLine(), d->m_selection.beginPos(), firstLine, firstPos); int lastLine, lastPos; convertD3LCoordsToLineCoords(d->m_selection.endLine(), d->m_selection.endPos(), lastLine, lastPos); d->m_selection.start(firstLine, convertToPosOnScreen(d->getLineString(firstLine), firstPos, d->m_pOptions->m_tabSize)); d->m_selection.end(lastLine, convertToPosOnScreen(d->getLineString(lastLine), lastPos, d->m_pOptions->m_tabSize)); } } class DiffTextWindowFrameData { public: DiffTextWindow* m_pDiffTextWindow; FileNameLineEdit* m_pFileSelection; QPushButton* m_pBrowseButton; QSharedPointer m_pOptions; QLabel* m_pLabel; QLabel* m_pTopLine; QLabel* m_pEncoding; QLabel* m_pLineEndStyle; QWidget* m_pTopLineWidget; e_SrcSelector m_winIdx; }; DiffTextWindowFrame::DiffTextWindowFrame(QWidget* pParent, QStatusBar* pStatusBar, const QSharedPointer &pOptions, e_SrcSelector winIdx, SourceData* psd) : QWidget(pParent) { d = new DiffTextWindowFrameData; d->m_winIdx = winIdx; setAutoFillBackground(true); d->m_pOptions = pOptions; d->m_pTopLineWidget = new QWidget(this); d->m_pFileSelection = new FileNameLineEdit(d->m_pTopLineWidget); d->m_pBrowseButton = new QPushButton("...", d->m_pTopLineWidget); d->m_pBrowseButton->setFixedWidth(30); connect(d->m_pBrowseButton, &QPushButton::clicked, this, &DiffTextWindowFrame::slotBrowseButtonClicked); connect(d->m_pFileSelection, &QLineEdit::returnPressed, this, &DiffTextWindowFrame::slotReturnPressed); d->m_pFileSelection->setAcceptDrops(true); d->m_pLabel = new QLabel("A:", d->m_pTopLineWidget); d->m_pTopLine = new QLabel(d->m_pTopLineWidget); d->m_pDiffTextWindow = nullptr; d->m_pDiffTextWindow = new DiffTextWindow(this, pStatusBar, pOptions, winIdx); QVBoxLayout* pVTopLayout = new QVBoxLayout(d->m_pTopLineWidget); pVTopLayout->setMargin(2); pVTopLayout->setSpacing(0); QHBoxLayout* pHL = new QHBoxLayout(); QHBoxLayout* pHL2 = new QHBoxLayout(); pVTopLayout->addLayout(pHL); pVTopLayout->addLayout(pHL2); // Upper line: pHL->setMargin(0); pHL->setSpacing(2); pHL->addWidget(d->m_pLabel, 0); pHL->addWidget(d->m_pFileSelection, 1); pHL->addWidget(d->m_pBrowseButton, 0); pHL->addWidget(d->m_pTopLine, 0); // Lower line pHL2->setMargin(0); pHL2->setSpacing(2); pHL2->addWidget(d->m_pTopLine, 0); d->m_pEncoding = new EncodingLabel(i18n("Encoding:"), psd, pOptions); connect((EncodingLabel*) d->m_pEncoding, &EncodingLabel::encodingChanged, this, &DiffTextWindowFrame::slotEncodingChanged); d->m_pLineEndStyle = new QLabel(i18n("Line end style:")); pHL2->addWidget(d->m_pEncoding); pHL2->addWidget(d->m_pLineEndStyle); QVBoxLayout* pVL = new QVBoxLayout(this); pVL->setMargin(0); pVL->setSpacing(0); pVL->addWidget(d->m_pTopLineWidget, 0); pVL->addWidget(d->m_pDiffTextWindow, 1); d->m_pDiffTextWindow->installEventFilter(this); d->m_pFileSelection->installEventFilter(this); d->m_pBrowseButton->installEventFilter(this); init(); } DiffTextWindowFrame::~DiffTextWindowFrame() { delete d; } void DiffTextWindowFrame::init() { DiffTextWindow* pDTW = d->m_pDiffTextWindow; if(pDTW) { QString s = QDir::toNativeSeparators(pDTW->d->m_filename); d->m_pFileSelection->setText(s); QString winId = pDTW->d->m_winIdx == A ? (pDTW->d->m_bTriple ? i18n("A (Base)") : i18n("A")) : (pDTW->d->m_winIdx == B ? i18n("B") : i18n("C")); d->m_pLabel->setText(winId + ':'); d->m_pEncoding->setText(i18n("Encoding: %1", pDTW->d->m_pTextCodec != nullptr ? QLatin1String(pDTW->d->m_pTextCodec->name()) : QString())); d->m_pLineEndStyle->setText(i18n("Line end style: %1", pDTW->d->m_eLineEndStyle == eLineEndStyleDos ? i18n("DOS") : i18n("Unix"))); } } // Search for the first visible line (search loop needed when no line exists for this file.) LineRef DiffTextWindow::calcTopLineInFile(const LineRef firstLine) { LineRef currentLine; for(int i = convertLineToDiff3LineIdx(firstLine); i < (int)d->m_pDiff3LineVector->size(); ++i) { const Diff3Line* d3l = (*d->m_pDiff3LineVector)[i]; currentLine = d3l->getLineInFile(d->m_winIdx); if(currentLine.isValid()) break; } return currentLine; } void DiffTextWindowFrame::setFirstLine(QtNumberType firstLine) { DiffTextWindow* pDTW = d->m_pDiffTextWindow; if(pDTW && pDTW->d->m_pDiff3LineVector) { QString s = i18n("Top line"); int lineNumberWidth = (int)log10((double)std::max(pDTW->d->m_size, 1)) + 1; LineRef topVisiableLine = pDTW->calcTopLineInFile(firstLine); int w = Utils::getHorizontalAdvance(d->m_pTopLine->fontMetrics(), s + ' ' + QString().fill('0', lineNumberWidth)); d->m_pTopLine->setMinimumWidth(w); if(!topVisiableLine.isValid()) s = i18n("End"); else s += ' ' + QString::number(topVisiableLine + 1); d->m_pTopLine->setText(s); d->m_pTopLine->repaint(); } } DiffTextWindow* DiffTextWindowFrame::getDiffTextWindow() { return d->m_pDiffTextWindow; } bool DiffTextWindowFrame::eventFilter(QObject* o, QEvent* e) { Q_UNUSED(o); if(e->type() == QEvent::FocusIn || e->type() == QEvent::FocusOut) { QColor c1 = d->m_pOptions->m_bgColor; QColor c2; if(d->m_winIdx == A) c2 = d->m_pOptions->m_colorA; else if(d->m_winIdx == B) c2 = d->m_pOptions->m_colorB; else if(d->m_winIdx == C) c2 = d->m_pOptions->m_colorC; QPalette p = d->m_pTopLineWidget->palette(); if(e->type() == QEvent::FocusOut) std::swap(c1, c2); p.setColor(QPalette::Window, c2); setPalette(p); p.setColor(QPalette::WindowText, c1); d->m_pLabel->setPalette(p); d->m_pTopLine->setPalette(p); d->m_pEncoding->setPalette(p); d->m_pLineEndStyle->setPalette(p); } return false; } void DiffTextWindowFrame::slotReturnPressed() { DiffTextWindow* pDTW = d->m_pDiffTextWindow; if(pDTW->d->m_filename != d->m_pFileSelection->text()) { emit fileNameChanged(d->m_pFileSelection->text(), pDTW->d->m_winIdx); } } void DiffTextWindowFrame::slotBrowseButtonClicked() { QString current = d->m_pFileSelection->text(); QUrl newURL = QFileDialog::getOpenFileUrl(this, i18n("Open File"), QUrl::fromUserInput(current, QString(), QUrl::AssumeLocalFile)); if(!newURL.isEmpty()) { DiffTextWindow* pDTW = d->m_pDiffTextWindow; emit fileNameChanged(newURL.url(), pDTW->d->m_winIdx); } } EncodingLabel::EncodingLabel(const QString& text, SourceData* pSD, const QSharedPointer &pOptions) : QLabel(text) { m_pOptions = pOptions; m_pSourceData = pSD; m_pContextEncodingMenu = nullptr; setMouseTracking(true); } void EncodingLabel::mouseMoveEvent(QMouseEvent*) { // When there is no data to display or it came from clipboard, // we will be use UTF-8 only, // in that case there is no possibility to change the encoding in the SourceData // so, we should hide the HandCursor and display usual ArrowCursor if(m_pSourceData->isFromBuffer() || m_pSourceData->isEmpty()) setCursor(QCursor(Qt::ArrowCursor)); else setCursor(QCursor(Qt::PointingHandCursor)); } void EncodingLabel::mousePressEvent(QMouseEvent*) { if(!(m_pSourceData->isFromBuffer() || m_pSourceData->isEmpty())) { delete m_pContextEncodingMenu; m_pContextEncodingMenu = new QMenu(this); QMenu* pContextEncodingSubMenu = new QMenu(m_pContextEncodingMenu); int currentTextCodecEnum = m_pSourceData->getEncoding()->mibEnum(); // the codec that will be checked in the context menu QList mibs = QTextCodec::availableMibs(); QList codecEnumList; // Adding "main" encodings insertCodec(i18n("Unicode, 8 bit"), QTextCodec::codecForName("UTF-8"), codecEnumList, m_pContextEncodingMenu, currentTextCodecEnum); if(QTextCodec::codecForName("System")) { insertCodec(QString(), QTextCodec::codecForName("System"), codecEnumList, m_pContextEncodingMenu, currentTextCodecEnum); } // Adding recent encodings if(m_pOptions != nullptr) { QStringList& recentEncodings = m_pOptions->m_recentEncodings; foreach(const QString& s, recentEncodings) { insertCodec("", QTextCodec::codecForName(s.toLatin1()), codecEnumList, m_pContextEncodingMenu, currentTextCodecEnum); } } // Submenu to add the rest of available encodings pContextEncodingSubMenu->setTitle(i18n("Other")); foreach(int i, mibs) { QTextCodec* c = QTextCodec::codecForMib(i); if(c != nullptr) insertCodec("", c, codecEnumList, pContextEncodingSubMenu, currentTextCodecEnum); } m_pContextEncodingMenu->addMenu(pContextEncodingSubMenu); m_pContextEncodingMenu->exec(QCursor::pos()); } } void EncodingLabel::insertCodec(const QString& visibleCodecName, QTextCodec* pCodec, QList& codecEnumList, QMenu* pMenu, int currentTextCodecEnum) { int CodecMIBEnum = pCodec->mibEnum(); if(pCodec != nullptr && !codecEnumList.contains(CodecMIBEnum)) { QAction* pAction = new QAction(pMenu); // menu takes ownership, so deleting the menu deletes the action too. QByteArray nameArray = pCodec->name(); QLatin1String codecName = QLatin1String(nameArray); pAction->setText(visibleCodecName.isEmpty() ? codecName : visibleCodecName + QLatin1String(" (") + codecName + QLatin1String(")")); pAction->setData(CodecMIBEnum); pAction->setCheckable(true); if(currentTextCodecEnum == CodecMIBEnum) pAction->setChecked(true); pMenu->addAction(pAction); connect(pAction, &QAction::triggered, this, &EncodingLabel::slotSelectEncoding); codecEnumList.append(CodecMIBEnum); } } void EncodingLabel::slotSelectEncoding() { QAction* pAction = qobject_cast(sender()); if(pAction) { QTextCodec* pCodec = QTextCodec::codecForMib(pAction->data().toInt()); if(pCodec != nullptr) { QString s(QLatin1String(pCodec->name())); QStringList& recentEncodings = m_pOptions->m_recentEncodings; if(!recentEncodings.contains(s) && s != "UTF-8" && s != "System") { int itemsToRemove = recentEncodings.size() - m_maxRecentEncodings + 1; for(int i = 0; i < itemsToRemove; ++i) { recentEncodings.removeFirst(); } recentEncodings.append(s); } } emit encodingChanged(pCodec); } } diff --git a/src/kdiff3.cpp b/src/kdiff3.cpp index 7978e0b..7b74d7b 100644 --- a/src/kdiff3.cpp +++ b/src/kdiff3.cpp @@ -1,1043 +1,1042 @@ /*************************************************************************** * Copyright (C) 2003-2007 by Joachim Eibl * * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * * * * 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. * * * ***************************************************************************/ // application specific includes #include "kdiff3.h" #include "directorymergewindow.h" #include "fileaccess.h" #include "guiutils.h" #include "kdiff3_part.h" #include "kdiff3_shell.h" #include "optiondialog.h" #include "progress.h" #include "smalldialogs.h" #include "difftextwindow.h" #include "mergeresultwindow.h" #include "RLPainter.h" #include "Utils.h" #ifndef Q_OS_WIN #include #endif // include files for QT #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // include files for KDE #include #include #include #include #include #include #include #include #include #include #define ID_STATUS_MSG 1 #define MAIN_TOOLBAR_NAME QLatin1String("mainToolBar") KActionCollection* KDiff3App::actionCollection() { if(m_pKDiff3Shell == nullptr) return m_pKDiff3Part->actionCollection(); else return m_pKDiff3Shell->actionCollection(); } QStatusBar* KDiff3App::statusBar() { if(m_pKDiff3Shell == nullptr) return nullptr; else return m_pKDiff3Shell->statusBar(); } KToolBar* KDiff3App::toolBar(const QLatin1String toolBarId) { if(m_pKDiff3Shell == nullptr) return nullptr; else return m_pKDiff3Shell->toolBar(toolBarId); } bool KDiff3App::isPart() { return m_pKDiff3Shell == nullptr; } bool KDiff3App::isFileSaved() { return m_bFileSaved; } bool KDiff3App::isDirComparison() { return m_bDirCompare; } KDiff3App::KDiff3App(QWidget* pParent, const QString& name, KDiff3Part* pKDiff3Part) : QSplitter(pParent) //previously KMainWindow { setObjectName(name); m_pKDiff3Part = pKDiff3Part; m_pKDiff3Shell = qobject_cast(pParent); setWindowTitle("KDiff3"); setOpaqueResize(false); // faster resizing setUpdatesEnabled(false); KCrash::initialize(); // set Disabled to same color as enabled to prevent flicker in DirectoryMergeWindow QPalette pal; pal.setBrush(QPalette::Base, pal.brush(QPalette::Active, QPalette::Base)); pal.setColor(QPalette::Text, pal.color(QPalette::Active, QPalette::Text)); setPalette(pal); // Needed before any file operations via FileAccess happen. if(g_pProgressDialog == nullptr) { g_pProgressDialog = new ProgressDialog(this, statusBar()); g_pProgressDialog->setStayHidden(true); } // All default values must be set before calling readOptions(). m_pOptionDialog = new OptionDialog(m_pKDiff3Shell != nullptr, this); connect(m_pOptionDialog, &OptionDialog::applyDone, this, &KDiff3App::slotRefresh); // This is just a convenience variable to make code that accesses options more readable m_pOptions = m_pOptionDialog->getOptions(); m_pOptionDialog->readOptions(KSharedConfig::openConfig()); // Option handling: Only when pParent==0 (no parent) int argCount = KDiff3Shell::getParser()->optionNames().count() + KDiff3Shell::getParser()->positionalArguments().count(); bool hasArgs = !isPart() && argCount > 0; if(hasArgs) { QString s; QString title; if(KDiff3Shell::getParser()->isSet("confighelp")) { s = m_pOptionDialog->calcOptionHelp(); title = i18n("Current Configuration:"); } else { s = m_pOptionDialog->parseOptions(KDiff3Shell::getParser()->values("cs")); title = i18n("Config Option Error:"); } if(!s.isEmpty()) { //KMessageBox::information(0, s,i18n("KDiff3-Usage")); #ifndef Q_OS_WIN if(isatty(fileno(stderr)) != 1) #endif { QPointer pDialog = QPointer(new QDialog(this)); pDialog->setAttribute(Qt::WA_DeleteOnClose); pDialog->setModal(true); pDialog->setWindowTitle(title); QVBoxLayout* pVBoxLayout = new QVBoxLayout(pDialog); QPointer pTextEdit = QPointer(new KTextEdit(pDialog)); pTextEdit->setText(s); pTextEdit->setReadOnly(true); pTextEdit->setWordWrapMode(QTextOption::NoWrap); pVBoxLayout->addWidget(pTextEdit); pDialog->resize(600, 400); pDialog->exec(); } #if !defined(Q_OS_WIN) else { // Launched from a console QTextStream outStream(stdout); outStream << title << "\n"; outStream << s;//newline already appended by parseOptions } #endif exit(1); } } m_sd1.setOptions(m_pOptions); m_sd2.setOptions(m_pOptions); m_sd3.setOptions(m_pOptions); #ifdef ENABLE_AUTO m_bAutoFlag = hasArgs && KDiff3Shell::getParser()->isSet("auto"); #else m_bAutoFlag = false; #endif m_bAutoMode = m_bAutoFlag || m_pOptions->m_bAutoSaveAndQuitOnMergeWithoutConflicts; if(hasArgs) { m_outputFilename = KDiff3Shell::getParser()->value("output"); if(m_outputFilename.isEmpty()) m_outputFilename = KDiff3Shell::getParser()->value("out"); if(!m_outputFilename.isEmpty()) m_outputFilename = FileAccess(m_outputFilename, true).absoluteFilePath(); if(m_bAutoMode && m_outputFilename.isEmpty()) { if(m_bAutoFlag) { QTextStream(stderr) << i18n("Option --auto used, but no output file specified.") << "\n"; } m_bAutoMode = false; } if(m_outputFilename.isEmpty() && KDiff3Shell::getParser()->isSet("merge")) { m_outputFilename = "unnamed.txt"; m_bDefaultFilename = true; } else { m_bDefaultFilename = false; } g_bAutoSolve = !KDiff3Shell::getParser()->isSet("qall"); // Note that this is effective only once. QStringList args = KDiff3Shell::getParser()->positionalArguments(); m_sd1.setFilename(KDiff3Shell::getParser()->value("base")); if(m_sd1.isEmpty()) { if(args.count() > 0) m_sd1.setFilename(args[0]); // args->arg(0) if(args.count() > 1) m_sd2.setFilename(args[1]); if(args.count() > 2) m_sd3.setFilename(args[2]); } else { if(args.count() > 0) m_sd2.setFilename(args[0]); if(args.count() > 1) m_sd3.setFilename(args[1]); } //Set m_bDirCompare flag m_bDirCompare = FileAccess(m_sd1.getFilename()).isDir(); QStringList aliasList = KDiff3Shell::getParser()->values( "fname" ); QStringList::Iterator ali = aliasList.begin(); QString an1 = KDiff3Shell::getParser()->value("L1"); if(!an1.isEmpty()) { m_sd1.setAliasName(an1); } else if(ali != aliasList.end()) { m_sd1.setAliasName(*ali); ++ali; } QString an2 = KDiff3Shell::getParser()->value("L2"); if(!an2.isEmpty()) { m_sd2.setAliasName(an2); } else if(ali != aliasList.end()) { m_sd2.setAliasName(*ali); ++ali; } QString an3 = KDiff3Shell::getParser()->value("L3"); if(!an3.isEmpty()) { m_sd3.setAliasName(an3); } else if(ali != aliasList.end()) { m_sd3.setAliasName(*ali); ++ali; } } else { m_bDefaultFilename = false; g_bAutoSolve = false; } g_pProgressDialog->setStayHidden(m_bAutoMode); /////////////////////////////////////////////////////////////////// // call inits to invoke all other construction parts initActions(actionCollection()); //Warning: Call this before connecting KDiff3App::slotUpdateAvailabilities or calling KXMLGUIClient::setXMLFile MergeResultWindow::initActions(actionCollection()); initStatusBar(); m_pFindDialog = new FindDialog(this); connect(m_pFindDialog, &FindDialog::findNext, this, &KDiff3App::slotEditFindNext); autoAdvance->setChecked(m_pOptions->m_bAutoAdvance); showWhiteSpaceCharacters->setChecked(m_pOptions->m_bShowWhiteSpaceCharacters); showWhiteSpace->setChecked(m_pOptions->m_bShowWhiteSpace); showWhiteSpaceCharacters->setEnabled(m_pOptions->m_bShowWhiteSpace); showLineNumbers->setChecked(m_pOptions->m_bShowLineNumbers); wordWrap->setChecked(m_pOptions->m_bWordWrap); if(!isPart()) { viewStatusBar->setChecked(m_pOptions->isStatusBarVisable()); slotViewStatusBar(); KToolBar *mainToolBar = toolBar(MAIN_TOOLBAR_NAME); if(mainToolBar != nullptr){ mainToolBar->mainWindow()->addToolBar(m_pOptions->getToolbarPos(), mainToolBar); } // TODO restore window size/pos? /* QSize size = m_pOptions->m_geometry; QPoint pos = m_pOptions->m_position; if(!size.isEmpty()) { m_pKDiff3Shell->resize( size ); QRect visibleRect = QRect( pos, size ) & QApplication::desktop()->rect(); if ( visibleRect.width()>100 && visibleRect.height()>100 ) m_pKDiff3Shell->move( pos ); }*/ } slotRefresh(); m_pMainSplitter = this; m_pMainSplitter->setOrientation(Qt::Vertical); // setCentralWidget( m_pMainSplitter ); m_pDirectoryMergeSplitter = new QSplitter(m_pMainSplitter); m_pDirectoryMergeSplitter->setObjectName("DirectoryMergeSplitter"); m_pMainSplitter->addWidget(m_pDirectoryMergeSplitter); m_pDirectoryMergeSplitter->setOrientation(Qt::Horizontal); m_pDirectoryMergeWindow = new DirectoryMergeWindow(m_pDirectoryMergeSplitter, m_pOptions, this); m_pDirectoryMergeSplitter->addWidget(m_pDirectoryMergeWindow); m_pDirectoryMergeInfo = new DirectoryMergeInfo(m_pDirectoryMergeSplitter); m_pDirectoryMergeWindow->setDirectoryMergeInfo(m_pDirectoryMergeInfo); m_pDirectoryMergeSplitter->addWidget(m_pDirectoryMergeInfo); //Warning: Make sure DirectoryMergeWindow::initActions is called before this point or we can crash when selectionChanged is sent. connect(m_pDirectoryMergeWindow, &DirectoryMergeWindow::startDiffMerge, this, &KDiff3App::slotFileOpen2); connect(m_pDirectoryMergeWindow->selectionModel(), &QItemSelectionModel::selectionChanged, this, &KDiff3App::slotUpdateAvailabilities); connect(m_pDirectoryMergeWindow->selectionModel(), &QItemSelectionModel::currentChanged, this, &KDiff3App::slotUpdateAvailabilities); connect(m_pDirectoryMergeWindow, &DirectoryMergeWindow::checkIfCanContinue, this, &KDiff3App::slotCheckIfCanContinue); connect(m_pDirectoryMergeWindow, static_cast(&DirectoryMergeWindow::updateAvailabilities), this, &KDiff3App::slotUpdateAvailabilities); connect(m_pDirectoryMergeWindow, &DirectoryMergeWindow::statusBarMessage, this, &KDiff3App::slotStatusMsg); connect(QApplication::clipboard(), &QClipboard::dataChanged, this, &KDiff3App::slotClipboardChanged); m_pDirectoryMergeWindow->initDirectoryMergeActions(this, actionCollection()); delete KDiff3Shell::getParser(); if(m_pKDiff3Shell == nullptr) { completeInit(QString()); } } void KDiff3App::completeInit(const QString& fn1, const QString& fn2, const QString& fn3) { if(m_pKDiff3Shell != nullptr) { QSize size = m_pOptions->getGeometry(); QPoint pos = m_pOptions->getPosition(); if(!size.isEmpty()) { m_pKDiff3Shell->resize(size); QRect visibleRect = QRect(pos, size) & QApplication::desktop()->rect(); if(visibleRect.width() > 100 && visibleRect.height() > 100) m_pKDiff3Shell->move(pos); if(!m_bAutoMode) { //Here we want the extra setup showMaximized does since the window has not be shown before if(m_pOptions->isMaximised()) m_pKDiff3Shell->showMaximized();// krazy:exclude=qmethods else m_pKDiff3Shell->show(); } } } if(!fn1.isEmpty()) { m_sd1.setFilename(fn1); } if(!fn2.isEmpty()) { m_sd2.setFilename(fn2); } if(!fn3.isEmpty()) { m_sd3.setFilename(fn3); } //should not happen now. Q_ASSERT(m_bDirCompare == FileAccess(m_sd1.getFilename()).isDir()); bool bSuccess = improveFilenames(false); if(m_bAutoFlag && m_bAutoMode && m_bDirCompare) { QTextStream(stderr) << i18n("Option --auto ignored for directory comparison.") << "\n"; m_bAutoMode = false; } if(!m_bDirCompare) { m_pDirectoryMergeSplitter->hide(); mainInit(); if(m_bAutoMode) { SourceData* pSD = nullptr; if(m_sd3.isEmpty()) { if(m_totalDiffStatus.isBinaryEqualAB()) { pSD = &m_sd1; } } else { if(m_totalDiffStatus.isBinaryEqualBC() || m_totalDiffStatus.isBinaryEqualAB()) { //if B==C (assume A is old), if A==B then C has changed pSD = &m_sd3; } else if(m_totalDiffStatus.isBinaryEqualAC()) { pSD = &m_sd2; // assuming B has changed } } if(pSD != nullptr) { // Save this file directly, not via the merge result window. FileAccess fa(m_outputFilename); if(m_pOptions->m_bDmCreateBakFiles && fa.exists()) { fa.createBackup(".orig"); } bSuccess = pSD->saveNormalDataAs(m_outputFilename); if(bSuccess) ::exit(0); else KMessageBox::error(this, i18n("Saving failed.")); } else if(m_pMergeResultWindow->getNrOfUnsolvedConflicts() == 0) { bSuccess = m_pMergeResultWindow->saveDocument(m_pMergeResultWindowTitle->getFileName(), m_pMergeResultWindowTitle->getEncoding(), m_pMergeResultWindowTitle->getLineEndStyle()); if(bSuccess) ::exit(0); } } } m_bAutoMode = false; if(m_pKDiff3Shell != nullptr) { if(m_pOptions->isMaximised()) //We want showMaximized here as the window has never been shown. m_pKDiff3Shell->showMaximized();// krazy:exclude=qmethods else m_pKDiff3Shell->show(); } g_pProgressDialog->setStayHidden(false); if(statusBar() != nullptr) statusBar()->setSizeGripEnabled(true); slotClipboardChanged(); // For initialisation. slotUpdateAvailabilities(); if(!m_bDirCompare && m_pKDiff3Shell != nullptr) { bool bFileOpenError = false; if((!m_sd1.isEmpty() && !m_sd1.hasData()) || (!m_sd2.isEmpty() && !m_sd2.hasData()) || (!m_sd3.isEmpty() && !m_sd3.hasData())) { QString text(i18n("Opening of these files failed:")); text += "\n\n"; if(!m_sd1.isEmpty() && !m_sd1.hasData()) text += " - " + m_sd1.getAliasName() + '\n'; if(!m_sd2.isEmpty() && !m_sd2.hasData()) text += " - " + m_sd2.getAliasName() + '\n'; if(!m_sd3.isEmpty() && !m_sd3.hasData()) text += " - " + m_sd3.getAliasName() + '\n'; KMessageBox::sorry(this, text, i18n("File Open Error")); bFileOpenError = true; } if(m_sd1.isEmpty() || m_sd2.isEmpty() || bFileOpenError) slotFileOpen(); } else if(!bSuccess) // Directory open failed { slotFileOpen(); } } KDiff3App::~KDiff3App() { } /** * Helper function used to create actions into the ac collection */ void KDiff3App::initActions(KActionCollection* ac) { if(ac == nullptr){ KMessageBox::error(nullptr, "actionCollection==0"); exit(-1);//we cannot recover from this. } fileOpen = KStandardAction::open(this, &KDiff3App::slotFileOpen, ac); fileOpen->setStatusTip(i18n("Opens documents for comparison...")); fileReload = GuiUtils::createAction(i18n("Reload"), QKeySequence(QKeySequence::Refresh), this, &KDiff3App::slotReload, ac, QLatin1String("file_reload")); fileSave = KStandardAction::save(this, &KDiff3App::slotFileSave, ac); fileSave->setStatusTip(i18n("Saves the merge result. All conflicts must be solved!")); fileSaveAs = KStandardAction::saveAs(this, &KDiff3App::slotFileSaveAs, ac); fileSaveAs->setStatusTip(i18n("Saves the current document as...")); #ifndef QT_NO_PRINTER filePrint = KStandardAction::print(this, &KDiff3App::slotFilePrint, ac); filePrint->setStatusTip(i18n("Print the differences")); #endif fileQuit = KStandardAction::quit(this, &KDiff3App::slotFileQuit, ac); fileQuit->setStatusTip(i18n("Quits the application")); editCut = KStandardAction::cut(this, &KDiff3App::slotEditCut, ac); editCut->setShortcuts(QKeySequence::Cut); editCut->setStatusTip(i18n("Cuts the selected section and puts it to the clipboard")); editCopy = KStandardAction::copy(this, &KDiff3App::slotEditCopy, ac); editCopy->setShortcut(QKeySequence::Copy); editCopy->setStatusTip(i18n("Copies the selected section to the clipboard")); editPaste = KStandardAction::paste(this, &KDiff3App::slotEditPaste, ac); editPaste->setStatusTip(i18n("Pastes the clipboard contents to current position")); editCut->setShortcut(QKeySequence::Paste); editSelectAll = KStandardAction::selectAll(this, &KDiff3App::slotEditSelectAll, ac); editSelectAll->setStatusTip(i18n("Select everything in current window")); editFind = KStandardAction::find(this, &KDiff3App::slotEditFind, ac); editFind->setShortcut(QKeySequence::Find); editFind->setStatusTip(i18n("Search for a string")); editFindNext = KStandardAction::findNext(this, &KDiff3App::slotEditFindNext, ac); editFindNext->setStatusTip(i18n("Search again for the string")); /* FIXME figure out how to implement this action viewToolBar = KStandardAction::showToolbar(this, &KDiff3App::slotViewToolBar, ac); viewToolBar->setStatusTip(i18n("Enables/disables the toolbar")); */ viewStatusBar = KStandardAction::showStatusbar(this, &KDiff3App::slotViewStatusBar, ac); viewStatusBar->setStatusTip(i18n("Enables/disables the statusbar")); KStandardAction::keyBindings(this, &KDiff3App::slotConfigureKeys, ac); QAction* pAction = KStandardAction::preferences(this, &KDiff3App::slotConfigure, ac); if(isPart()) pAction->setText(i18n("Configure KDiff3...")); #include "xpm/autoadvance.xpm" #include "xpm/currentpos.xpm" #include "xpm/down1arrow.xpm" #include "xpm/down2arrow.xpm" #include "xpm/downend.xpm" #include "xpm/iconA.xpm" #include "xpm/iconB.xpm" #include "xpm/iconC.xpm" #include "xpm/nextunsolved.xpm" #include "xpm/prevunsolved.xpm" #include "xpm/showlinenumbers.xpm" #include "xpm/showwhitespace.xpm" #include "xpm/showwhitespacechars.xpm" #include "xpm/up1arrow.xpm" #include "xpm/up2arrow.xpm" #include "xpm/upend.xpm" goCurrent = GuiUtils::createAction(i18n("Go to Current Delta"), QIcon(QPixmap(currentpos)), i18n("Current\nDelta"), QKeySequence(Qt::CTRL + Qt::Key_Space), this, &KDiff3App::slotGoCurrent, ac, "go_current"); goTop = GuiUtils::createAction(i18n("Go to First Delta"), QIcon(QPixmap(upend)), i18n("First\nDelta"), this, &KDiff3App::slotGoTop, ac, "go_top"); goBottom = GuiUtils::createAction(i18n("Go to Last Delta"), QIcon(QPixmap(downend)), i18n("Last\nDelta"), this, &KDiff3App::slotGoBottom, ac, "go_bottom"); QString omitsWhitespace = ".\n" + i18n("(Skips white space differences when \"Show White Space\" is disabled.)"); QString includeWhitespace = ".\n" + i18n("(Does not skip white space differences even when \"Show White Space\" is disabled.)"); goPrevDelta = GuiUtils::createAction(i18n("Go to Previous Delta"), QIcon(QPixmap(up1arrow)), i18n("Prev\nDelta"), QKeySequence(Qt::CTRL + Qt::Key_Up), this, &KDiff3App::slotGoPrevDelta, ac, "go_prev_delta"); goPrevDelta->setToolTip(goPrevDelta->text() + omitsWhitespace); goNextDelta = GuiUtils::createAction(i18n("Go to Next Delta"), QIcon(QPixmap(down1arrow)), i18n("Next\nDelta"), QKeySequence(Qt::CTRL + Qt::Key_Down), this, &KDiff3App::slotGoNextDelta, ac, "go_next_delta"); goNextDelta->setToolTip(goNextDelta->text() + omitsWhitespace); goPrevConflict = GuiUtils::createAction(i18n("Go to Previous Conflict"), QIcon(QPixmap(up2arrow)), i18n("Prev\nConflict"), QKeySequence(Qt::CTRL + Qt::Key_PageUp), this, &KDiff3App::slotGoPrevConflict, ac, "go_prev_conflict"); goPrevConflict->setToolTip(goPrevConflict->text() + omitsWhitespace); goNextConflict = GuiUtils::createAction(i18n("Go to Next Conflict"), QIcon(QPixmap(down2arrow)), i18n("Next\nConflict"), QKeySequence(Qt::CTRL + Qt::Key_PageDown), this, &KDiff3App::slotGoNextConflict, ac, "go_next_conflict"); goNextConflict->setToolTip(goNextConflict->text() + omitsWhitespace); goPrevUnsolvedConflict = GuiUtils::createAction(i18n("Go to Previous Unsolved Conflict"), QIcon(QPixmap(prevunsolved)), i18n("Prev\nUnsolved"), this, &KDiff3App::slotGoPrevUnsolvedConflict, ac, "go_prev_unsolved_conflict"); goPrevUnsolvedConflict->setToolTip(goPrevUnsolvedConflict->text() + includeWhitespace); goNextUnsolvedConflict = GuiUtils::createAction(i18n("Go to Next Unsolved Conflict"), QIcon(QPixmap(nextunsolved)), i18n("Next\nUnsolved"), this, &KDiff3App::slotGoNextUnsolvedConflict, ac, "go_next_unsolved_conflict"); goNextUnsolvedConflict->setToolTip(goNextUnsolvedConflict->text() + includeWhitespace); chooseA = GuiUtils::createAction(i18n("Select Line(s) From A"), QIcon(QPixmap(iconA)), i18n("Choose\nA"), QKeySequence(Qt::CTRL + Qt::Key_1), this, &KDiff3App::slotChooseA, ac, "merge_choose_a"); chooseB = GuiUtils::createAction(i18n("Select Line(s) From B"), QIcon(QPixmap(iconB)), i18n("Choose\nB"), QKeySequence(Qt::CTRL + Qt::Key_2), this, &KDiff3App::slotChooseB, ac, "merge_choose_b"); chooseC = GuiUtils::createAction(i18n("Select Line(s) From C"), QIcon(QPixmap(iconC)), i18n("Choose\nC"), QKeySequence(Qt::CTRL + Qt::Key_3), this, &KDiff3App::slotChooseC, ac, "merge_choose_c"); autoAdvance = GuiUtils::createAction(i18n("Automatically Go to Next Unsolved Conflict After Source Selection"), QIcon(QPixmap(autoadvance)), i18n("Auto\nNext"), this, &KDiff3App::slotAutoAdvanceToggled, ac, "merge_autoadvance"); showWhiteSpaceCharacters = GuiUtils::createAction(i18n("Show Space && Tabulator Characters"), QIcon(QPixmap(showwhitespacechars)), i18n("White\nCharacters"), this, &KDiff3App::slotShowWhiteSpaceToggled, ac, "diff_show_whitespace_characters"); showWhiteSpace = GuiUtils::createAction(i18n("Show White Space"), QIcon(QPixmap(showwhitespace)), i18n("White\nDeltas"), this, &KDiff3App::slotShowWhiteSpaceToggled, ac, "diff_show_whitespace"); showLineNumbers = GuiUtils::createAction(i18n("Show Line Numbers"), QIcon(QPixmap(showlinenumbers)), i18n("Line\nNumbers"), this, &KDiff3App::slotShowLineNumbersToggled, ac, "diff_showlinenumbers"); autoSolve = GuiUtils::createAction(i18n("Automatically Solve Simple Conflicts"), this, &KDiff3App::slotAutoSolve, ac, "merge_autosolve"); unsolve = GuiUtils::createAction(i18n("Set Deltas to Conflicts"), this, &KDiff3App::slotUnsolve, ac, "merge_autounsolve"); mergeRegExp = GuiUtils::createAction(i18n("Run Regular Expression Auto Merge"), this, &KDiff3App::slotRegExpAutoMerge, ac, "merge_regexp_automerge"); mergeHistory = GuiUtils::createAction(i18n("Automatically Solve History Conflicts"), this, &KDiff3App::slotMergeHistory, ac, "merge_versioncontrol_history"); splitDiff = GuiUtils::createAction(i18n("Split Diff At Selection"), this, &KDiff3App::slotSplitDiff, ac, "merge_splitdiff"); joinDiffs = GuiUtils::createAction(i18n("Join Selected Diffs"), this, &KDiff3App::slotJoinDiffs, ac, "merge_joindiffs"); showWindowA = GuiUtils::createAction(i18n("Show Window A"), this, &KDiff3App::slotShowWindowAToggled, ac, "win_show_a"); showWindowB = GuiUtils::createAction(i18n("Show Window B"), this, &KDiff3App::slotShowWindowBToggled, ac, "win_show_b"); showWindowC = GuiUtils::createAction(i18n("Show Window C"), this, &KDiff3App::slotShowWindowCToggled, ac, "win_show_c"); overviewModeNormal = GuiUtils::createAction(i18n("Normal Overview"), this, &KDiff3App::slotOverviewNormal, ac, "diff_overview_normal"); overviewModeAB = GuiUtils::createAction(i18n("A vs. B Overview"), this, &KDiff3App::slotOverviewAB, ac, "diff_overview_ab"); overviewModeAC = GuiUtils::createAction(i18n("A vs. C Overview"), this, &KDiff3App::slotOverviewAC, ac, "diff_overview_ac"); overviewModeBC = GuiUtils::createAction(i18n("B vs. C Overview"), this, &KDiff3App::slotOverviewBC, ac, "diff_overview_bc"); wordWrap = GuiUtils::createAction(i18n("Word Wrap Diff Windows"), this, &KDiff3App::slotWordWrapToggled, ac, "diff_wordwrap"); addManualDiffHelp = GuiUtils::createAction(i18n("Add Manual Diff Alignment"), QKeySequence(Qt::CTRL + Qt::Key_Y), this, &KDiff3App::slotAddManualDiffHelp, ac, "diff_add_manual_diff_help"); clearManualDiffHelpList = GuiUtils::createAction(i18n("Clear All Manual Diff Alignments"), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Y), this, &KDiff3App::slotClearManualDiffHelpList, ac, "diff_clear_manual_diff_help_list"); winFocusNext = GuiUtils::createAction(i18n("Focus Next Window"), QKeySequence(Qt::ALT + Qt::Key_Right), this, &KDiff3App::slotWinFocusNext, ac, "win_focus_next"); winFocusPrev = GuiUtils::createAction(i18n("Focus Prev Window"), QKeySequence(Qt::ALT + Qt::Key_Left), this, &KDiff3App::slotWinFocusPrev, ac, "win_focus_prev"); winToggleSplitOrientation = GuiUtils::createAction(i18n("Toggle Split Orientation"), this, &KDiff3App::slotWinToggleSplitterOrientation, ac, "win_toggle_split_orientation"); dirShowBoth = GuiUtils::createAction(i18n("Dir && Text Split Screen View"), this, &KDiff3App::slotDirShowBoth, ac, "win_dir_show_both"); dirShowBoth->setChecked(true); dirViewToggle = GuiUtils::createAction(i18n("Toggle Between Dir && Text View"), this, &KDiff3App::slotDirViewToggle, ac, "win_dir_view_toggle"); m_pMergeEditorPopupMenu = new QMenu(this); /* chooseA->plug( m_pMergeEditorPopupMenu ); chooseB->plug( m_pMergeEditorPopupMenu ); chooseC->plug( m_pMergeEditorPopupMenu );*/ m_pMergeEditorPopupMenu->addAction(chooseA); m_pMergeEditorPopupMenu->addAction(chooseB); m_pMergeEditorPopupMenu->addAction(chooseC); } void KDiff3App::showPopupMenu(const QPoint& point) { m_pMergeEditorPopupMenu->popup(point); } void KDiff3App::initStatusBar() { /////////////////////////////////////////////////////////////////// // STATUSBAR if(statusBar() != nullptr) statusBar()->showMessage(i18n("Ready.")); } void KDiff3App::saveOptions(KSharedConfigPtr config) { if(!m_bAutoMode) { if(!isPart()) { m_pOptions->setMaximised(m_pKDiff3Shell->isMaximized()); if(!m_pKDiff3Shell->isMaximized() && m_pKDiff3Shell->isVisible()) { m_pOptions->setGeometry(m_pKDiff3Shell->size()); m_pOptions->setPosition(m_pKDiff3Shell->pos()); } /* TODO change this option as now KToolbar uses QToolbar positioning style if ( toolBar(MAIN_TOOLBAR_NAME)!=0 ) m_pOptionDialog->m_toolBarPos = (int) toolBar(MAIN_TOOLBAR_NAME)->allowedAreas();*/ } m_pOptionDialog->saveOptions(std::move(config)); } } bool KDiff3App::queryClose() { saveOptions(KSharedConfig::openConfig()); if(m_bOutputModified) { int result = KMessageBox::warningYesNoCancel(this, i18n("The merge result has not been saved."), i18n("Warning"), KGuiItem(i18n("Save && Quit")), KGuiItem(i18n("Quit Without Saving"))); if(result == KMessageBox::Cancel) return false; else if(result == KMessageBox::Yes) { slotFileSave(); if(m_bOutputModified) { KMessageBox::sorry(this, i18n("Saving the merge result failed."), i18n("Warning")); return false; } } } m_bOutputModified = false; if(m_pDirectoryMergeWindow->isDirectoryMergeInProgress()) { int result = KMessageBox::warningYesNo(this, i18n("You are currently doing a directory merge. Are you sure, you want to abort?"), i18n("Warning"), KStandardGuiItem::quit(), KStandardGuiItem::cont() /* i18n("Continue Merging") */); if(result != KMessageBox::Yes) return false; } return true; } ///////////////////////////////////////////////////////////////////// // SLOT IMPLEMENTATION ///////////////////////////////////////////////////////////////////// void KDiff3App::slotFileSave() { if(m_bDefaultFilename) { slotFileSaveAs(); } else { slotStatusMsg(i18n("Saving file...")); bool bSuccess = m_pMergeResultWindow->saveDocument(m_outputFilename, m_pMergeResultWindowTitle->getEncoding(), m_pMergeResultWindowTitle->getLineEndStyle()); if(bSuccess) { m_bFileSaved = true; m_bOutputModified = false; if(m_bDirCompare) m_pDirectoryMergeWindow->mergeResultSaved(m_outputFilename); } slotStatusMsg(i18n("Ready.")); } } void KDiff3App::slotFileSaveAs() { slotStatusMsg(i18n("Saving file with a new filename...")); QString s = QFileDialog::getSaveFileUrl(this, i18n("Save As..."), QUrl::fromLocalFile(QDir::currentPath())).url(QUrl::PreferLocalFile); if(!s.isEmpty()) { m_outputFilename = s; m_pMergeResultWindowTitle->setFileName(m_outputFilename); bool bSuccess = m_pMergeResultWindow->saveDocument(m_outputFilename, m_pMergeResultWindowTitle->getEncoding(), m_pMergeResultWindowTitle->getLineEndStyle()); if(bSuccess) { m_bOutputModified = false; if(m_bDirCompare) m_pDirectoryMergeWindow->mergeResultSaved(m_outputFilename); } //setWindowTitle(url.fileName(),doc->isModified()); m_bDefaultFilename = false; } slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotFilePrint() { if(m_pDiffTextWindow1 == nullptr) return; #ifdef QT_NO_PRINTER slotStatusMsg(i18n("Printing not implemented.")); #else QPrinter printer; QPointer printDialog=QPointer(new QPrintDialog(&printer, this)); LineRef firstSelectionD3LIdx; LineRef lastSelectionD3LIdx; m_pDiffTextWindow1->getSelectionRange(&firstSelectionD3LIdx, &lastSelectionD3LIdx, eD3LLineCoords); if(firstSelectionD3LIdx < 0 && m_pDiffTextWindow2 != nullptr) { m_pDiffTextWindow2->getSelectionRange(&firstSelectionD3LIdx, &lastSelectionD3LIdx, eD3LLineCoords); } if(firstSelectionD3LIdx < 0 && m_pDiffTextWindow3 != nullptr) { m_pDiffTextWindow3->getSelectionRange(&firstSelectionD3LIdx, &lastSelectionD3LIdx, eD3LLineCoords); } if(firstSelectionD3LIdx >= 0) { printDialog->addEnabledOption(QPrintDialog::PrintSelection); //printer.setOptionEnabled(QPrinter::PrintSelection,true); printDialog->setPrintRange(QAbstractPrintDialog::Selection); } if(firstSelectionD3LIdx == -1) printDialog->setPrintRange(QAbstractPrintDialog::AllPages); //printDialog.setMinMax(0,0); printDialog->setFromTo(0, 0); int currentFirstLine = m_pDiffTextWindow1->getFirstLine(); int currentFirstD3LIdx = m_pDiffTextWindow1->convertLineToDiff3LineIdx(currentFirstLine); // do some printer initialization printer.setFullPage(false); // initialize the printer using the print dialog if(printDialog->exec() == QDialog::Accepted) { slotStatusMsg(i18n("Printing...")); // create a painter to paint on the printer object RLPainter painter(&printer, m_pOptions->m_bRightToLeftLanguage, width(), Utils::getHorizontalAdvance(fontMetrics(),'W')); QPaintDevice* pPaintDevice = painter.device(); int dpiy = pPaintDevice->logicalDpiY(); int columnDistance = qRound((0.5 / 2.54) * dpiy); // 0.5 cm between the columns int columns = m_bTripleDiff ? 3 : 2; int columnWidth = (pPaintDevice->width() - (columns - 1) * columnDistance) / columns; QFont f = m_pOptions->m_font; f.setPointSizeF(f.pointSizeF() - 1); // Print with slightly smaller font. painter.setFont(f); QFontMetrics fm = painter.fontMetrics(); QString topLineText = i18n("Top line"); //int headerWidth = fm.width( m_sd1.getAliasName() + ", "+topLineText+": 01234567" ); int headerLines = Utils::getHorizontalAdvance(fm, m_sd1.getAliasName() + ", " + topLineText + ": 01234567") / columnWidth + 1; int headerMargin = headerLines * fm.height() + 3; // Text + one horizontal line int footerMargin = fm.height() + 3; QRect view(0, headerMargin, pPaintDevice->width(), pPaintDevice->height() - (headerMargin + footerMargin)); QRect view1(0 * (columnWidth + columnDistance), view.top(), columnWidth, view.height()); QRect view2(1 * (columnWidth + columnDistance), view.top(), columnWidth, view.height()); QRect view3(2 * (columnWidth + columnDistance), view.top(), columnWidth, view.height()); int linesPerPage = view.height() / fm.lineSpacing(); QEventLoop eventLoopForPrinting; m_pEventLoopForPrinting = &eventLoopForPrinting; if(m_pOptions->m_bWordWrap) { // For printing the lines are wrapped differently (this invalidates the first line) recalcWordWrap(columnWidth); m_pEventLoopForPrinting->exec(); } LineCount totalNofLines = std::max(m_pDiffTextWindow1->getNofLines(), m_pDiffTextWindow2->getNofLines()); if(m_bTripleDiff && m_pDiffTextWindow3 != nullptr) totalNofLines = std::max(totalNofLines, m_pDiffTextWindow3->getNofLines()); QList pageList; // = printer.pageList(); bool bPrintCurrentPage = false; bool bFirstPrintedPage = false; bool bPrintSelection = false; int totalNofPages = (totalNofLines + linesPerPage - 1) / linesPerPage; LineRef line; LineRef selectionEndLine; if(printer.printRange() == QPrinter::AllPages) { pageList.clear(); for(int i = 0; i < totalNofPages; ++i) { pageList.push_back(i + 1); } } else if(printer.printRange() == QPrinter::PageRange) { pageList.clear(); for(int i = printer.fromPage(); i <= printer.toPage(); ++i) { pageList.push_back(i); } } if(printer.printRange() == QPrinter::Selection) { bPrintSelection = true; if(firstSelectionD3LIdx >= 0) { line = m_pDiffTextWindow1->convertDiff3LineIdxToLine(firstSelectionD3LIdx); selectionEndLine = m_pDiffTextWindow1->convertDiff3LineIdxToLine(lastSelectionD3LIdx + 1); totalNofPages = (selectionEndLine - line + linesPerPage - 1) / linesPerPage; } } int page = 1; ProgressProxy pp; pp.setMaxNofSteps(totalNofPages); QList::iterator pageListIt = pageList.begin(); for(;;) { pp.setInformation(i18n("Printing page %1 of %2", page, totalNofPages), false); pp.setCurrent(page - 1); if(pp.wasCancelled()) { printer.abort(); break; } if(!bPrintSelection) { if(pageListIt == pageList.end()) break; page = *pageListIt; line = (page - 1) * linesPerPage; if(page == 10000) { // This means "Print the current page" bPrintCurrentPage = true; // Detect the first visible line in the window. line = m_pDiffTextWindow1->convertDiff3LineIdxToLine(currentFirstD3LIdx); } } else { if(line >= selectionEndLine) { break; } else { if(selectionEndLine - line < linesPerPage) linesPerPage = selectionEndLine - line; } } - if(line >= 0 && line < totalNofLines) + if(line.isValid() && line < totalNofLines) { - if(bFirstPrintedPage) printer.newPage(); painter.setClipping(true); painter.setPen(m_pOptions->m_colorA); QString headerText1 = m_sd1.getAliasName() + ", " + topLineText + ": " + QString::number(m_pDiffTextWindow1->calcTopLineInFile(line) + 1); m_pDiffTextWindow1->printWindow(painter, view1, headerText1, line, linesPerPage, m_pOptions->m_fgColor); painter.setPen(m_pOptions->m_colorB); QString headerText2 = m_sd2.getAliasName() + ", " + topLineText + ": " + QString::number(m_pDiffTextWindow2->calcTopLineInFile(line) + 1); m_pDiffTextWindow2->printWindow(painter, view2, headerText2, line, linesPerPage, m_pOptions->m_fgColor); if(m_bTripleDiff && m_pDiffTextWindow3 != nullptr) { painter.setPen(m_pOptions->m_colorC); QString headerText3 = m_sd3.getAliasName() + ", " + topLineText + ": " + QString::number(m_pDiffTextWindow3->calcTopLineInFile(line) + 1); m_pDiffTextWindow3->printWindow(painter, view3, headerText3, line, linesPerPage, m_pOptions->m_fgColor); } painter.setClipping(false); painter.setPen(m_pOptions->m_fgColor); painter.drawLine(0, view.bottom() + 3, view.width(), view.bottom() + 3); QString s = bPrintCurrentPage ? QString("") : QString::number(page) + '/' + QString::number(totalNofPages); if(bPrintSelection) s += i18n(" (Selection)"); painter.drawText((view.right() - Utils::getHorizontalAdvance(painter.fontMetrics(), s)) / 2, view.bottom() + painter.fontMetrics().ascent() + 5, s); bFirstPrintedPage = true; } if(bPrintSelection) { line += linesPerPage; ++page; } else { ++pageListIt; } } painter.end(); if(m_pOptions->m_bWordWrap) { recalcWordWrap(); m_pEventLoopForPrinting->exec(); m_pDiffVScrollBar->setValue(m_pDiffTextWindow1->convertDiff3LineIdxToLine(currentFirstD3LIdx)); } slotStatusMsg(i18n("Printing completed.")); } else { slotStatusMsg(i18n("Printing aborted.")); } #endif } void KDiff3App::slotFileQuit() { slotStatusMsg(i18n("Exiting...")); if(!queryClose()) return; // Don't quit QApplication::exit(isFileSaved() || isDirComparison() ? 0 : 1); } void KDiff3App::slotViewToolBar() { Q_ASSERT(viewToolBar != nullptr); slotStatusMsg(i18n("Toggling toolbar...")); m_pOptions->setToolbarState(viewToolBar->isChecked()); /////////////////////////////////////////////////////////////////// // turn Toolbar on or off if(toolBar(MAIN_TOOLBAR_NAME) != nullptr) { if(!m_pOptions->isToolBarVisable()) { toolBar(MAIN_TOOLBAR_NAME)->hide(); } else { toolBar(MAIN_TOOLBAR_NAME)->show(); } } slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotViewStatusBar() { slotStatusMsg(i18n("Toggle the statusbar...")); m_pOptions->setStatusBarState(viewStatusBar->isChecked()); /////////////////////////////////////////////////////////////////// //turn Statusbar on or off if(statusBar() != nullptr) { if(!viewStatusBar->isChecked()) { statusBar()->hide(); } else { statusBar()->show(); } } slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotStatusMsg(const QString& text) { /////////////////////////////////////////////////////////////////// // change status message permanently if(statusBar() != nullptr) { statusBar()->clearMessage(); statusBar()->showMessage(text); } } //#include "kdiff3.moc" diff --git a/src/merger.cpp b/src/merger.cpp index bce65f6..5d3e69e 100644 --- a/src/merger.cpp +++ b/src/merger.cpp @@ -1,77 +1,77 @@ /*************************************************************************** * Copyright (C) 2003-2007 by Joachim Eibl * * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * * * * 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. * * * ***************************************************************************/ #include "merger.h" #include Merger::Merger(const DiffList* pDiffList1, const DiffList* pDiffList2) : md1(pDiffList1, 0), md2(pDiffList2, 1) { } Merger::MergeData::MergeData(const DiffList* p, int i) : d(0, 0, 0) { idx = i; pDiffList = p; if(p != nullptr) { it = p->begin(); update(); } } bool Merger::MergeData::eq() { return pDiffList == nullptr || d.numberOfEquals() > 0; } bool Merger::MergeData::isEnd() { return (pDiffList == nullptr || (it == pDiffList->end() && d.numberOfEquals() == 0 && (idx == 0 ? d.diff1() == 0 : d.diff2() == 0))); } void Merger::MergeData::update() { if(d.numberOfEquals() > 0) d.adjustNumberOfEquals(-1); else if(idx == 0 && d.diff1() > 0) d.adjustDiff1(-1); else if(idx == 1 && d.diff2() > 0) d.adjustDiff2(-1); while(d.numberOfEquals() == 0 && ((idx == 0 && d.diff1() == 0) || (idx == 1 && d.diff2() == 0)) && pDiffList != nullptr && it != pDiffList->end()) { d = *it; ++it; } } void Merger::next() { md1.update(); md2.update(); } -int Merger::whatChanged() +ChangeFlags Merger::whatChanged() { - int changed = 0; - changed |= md1.eq() ? 0 : 1; - changed |= md2.eq() ? 0 : 2; + ChangeFlags changed = ChangeFlag::NoChange; + changed |= md1.eq() ? ChangeFlag::NoChange : ChangeFlag::AChanged; + changed |= md2.eq() ? ChangeFlag::NoChange : ChangeFlag::BChanged; return changed; } bool Merger::isEndReached() { return md1.isEnd() && md2.isEnd(); } diff --git a/src/merger.h b/src/merger.h index 7e730a5..d019271 100644 --- a/src/merger.h +++ b/src/merger.h @@ -1,53 +1,53 @@ /*************************************************************************** * Copyright (C) 2003-2007 by Joachim Eibl * * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * * * * 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. * * * ***************************************************************************/ #ifndef MERGER_H #define MERGER_H #include "diff.h" class Merger { public: Merger(const DiffList* pDiffList1, const DiffList* pDiffList2); /** Go one step. */ void next(); /** Information about what changed. Can be used for coloring. The return value is 0 if nothing changed here, bit 1 is set if a difference from pDiffList1 was detected, bit 2 is set if a difference from pDiffList2 was detected. */ - int whatChanged(); + ChangeFlags whatChanged(); /** End of both diff lists reached. */ bool isEndReached(); private: struct MergeData { DiffList::const_iterator it; const DiffList* pDiffList; Diff d; int idx; MergeData(const DiffList* p, int i); bool eq(); void update(); bool isEnd(); }; MergeData md1; MergeData md2; }; #endif