diff --git a/src/Logging.cpp b/src/Logging.cpp index fa3a582..48dc087 100644 --- a/src/Logging.cpp +++ b/src/Logging.cpp @@ -1,22 +1,23 @@ /** * Copyright (C) 2019 Michael Reeves * * This file is part of KDiff3. * * KDiff3 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. * * KDiff3 is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with KDiff3. If not, see . */ #include "Logging.h" Q_LOGGING_CATEGORY(kdiffMain, "org.kde.kdiff3") +Q_LOGGING_CATEGORY(kdiffCore, "org.kde.kdiff3.core") //very noisey diff --git a/src/Logging.h b/src/Logging.h index 397e908..ad64af0 100644 --- a/src/Logging.h +++ b/src/Logging.h @@ -1,25 +1,27 @@ /** * Copyright (C) 2019 Michael Reeves * * This file is part of KDiff3. * * KDiff3 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. * * KDiff3 is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with KDiff3. If not, see . */ #ifndef LOGGING_H #define LOGGING_H #include Q_DECLARE_LOGGING_CATEGORY(kdiffMain) +Q_DECLARE_LOGGING_CATEGORY(kdiffCore) //very noisey shows internal state information for kdiffs core. + #endif // !LOGGING_H diff --git a/src/SourceData.cpp b/src/SourceData.cpp index 7f67970..0d0c6f4 100644 --- a/src/SourceData.cpp +++ b/src/SourceData.cpp @@ -1,953 +1,953 @@ /*************************************************************************** * 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. * ***************************************************************************/ /* Features of class SourceData: - Read a file (from the given URL) or accept data via a string. - Allocate and free buffers as necessary. - Run a preprocessor, when specified. - Run the line-matching preprocessor, when specified. - Run other preprocessing steps: Uppercase, ignore comments, remove carriage return, ignore numbers. Order of operation: 1. If data was given via a string then save it to a temp file. (see setData()) 2. If the specified file is nonlocal (URL) copy it to a temp file. 3. If a preprocessor was specified, run the input file through it. 4. Read the output of the preprocessor. 5. If Uppercase was specified: Turn the read data to uppercase. 6. Write the result to a temp file. 7. If a line-matching preprocessor was specified, run the temp file through it. 8. Read the output of the line-matching preprocessor. 9. If ignore numbers was specified, strip the LMPP-output of all numbers. 10. If ignore comments was specified, strip the LMPP-output of comments. Optimizations: Skip unneeded steps. */ #include "SourceData.h" #include "Utils.h" #include "diff.h" #include "Logging.h" #include #include #include #include #include #include #include SourceData::SourceData() { m_pOptions = nullptr; reset(); } SourceData::~SourceData() { reset(); } void SourceData::reset() { m_pEncoding = nullptr; m_fileAccess = FileAccess(); m_normalData.reset(); m_lmppData.reset(); if(!m_tempInputFileName.isEmpty()) { m_tempFile.remove(); m_tempInputFileName = ""; } } void SourceData::setFilename(const QString& filename) { if(filename.isEmpty()) { reset(); } else { FileAccess fa(filename); setFileAccess(fa); } } bool SourceData::isEmpty() { return getFilename().isEmpty(); } bool SourceData::hasData() { return m_normalData.m_pBuf != nullptr; } bool SourceData::isValid() { return isEmpty() || hasData(); } void SourceData::setOptions(Options* pOptions) { m_pOptions = pOptions; } QString SourceData::getFilename() { return m_fileAccess.absoluteFilePath(); } QString SourceData::getAliasName() { return m_aliasName.isEmpty() ? m_fileAccess.prettyAbsPath() : m_aliasName; } void SourceData::setAliasName(const QString& name) { m_aliasName = name; } void SourceData::setFileAccess(const FileAccess& fileAccess) { m_fileAccess = fileAccess; m_aliasName = QString(); if(!m_tempInputFileName.isEmpty()) { m_tempFile.remove(); m_tempInputFileName = ""; } } void SourceData::setEncoding(QTextCodec* pEncoding) { m_pEncoding = pEncoding; } QStringList SourceData::setData(const QString& data) { QStringList errors; // Create a temp file for preprocessing: if(m_tempInputFileName.isEmpty()) { FileAccess::createTempFile(m_tempFile); m_tempInputFileName = m_tempFile.fileName(); } FileAccess f(m_tempInputFileName); QByteArray ba = QTextCodec::codecForName("UTF-8")->fromUnicode(data); bool bSuccess = f.writeFile(ba.constData(), ba.length()); if(!bSuccess) { errors.append(i18n("Writing clipboard data to temp file failed.")); } else { m_aliasName = i18n("From Clipboard"); m_fileAccess = FileAccess(""); // Effect: m_fileAccess.isValid() is false } return errors; } const LineData* SourceData::getLineDataForDiff() const { if(m_lmppData.m_pBuf == nullptr) return m_normalData.m_v.size() > 0 ? &m_normalData.m_v[0] : nullptr; else return m_lmppData.m_v.size() > 0 ? &m_lmppData.m_v[0] : nullptr; } const LineData* SourceData::getLineDataForDisplay() const { return m_normalData.m_v.size() > 0 ? &m_normalData.m_v[0] : nullptr; } LineRef SourceData::getSizeLines() const { return (LineRef)(m_normalData.m_vSize); } qint64 SourceData::getSizeBytes() const { return m_normalData.m_size; } const char* SourceData::getBuf() const { return m_normalData.m_pBuf; } const QString& SourceData::getText() const { return m_normalData.m_unicodeBuf; } bool SourceData::isText() { return m_normalData.isText(); } bool SourceData::isIncompleteConversion() { return m_normalData.m_bIncompleteConversion; } bool SourceData::isFromBuffer() { return !m_fileAccess.isValid(); } bool SourceData::isBinaryEqualWith(const SourceData& other) const { return m_fileAccess.exists() && other.m_fileAccess.exists() && getSizeBytes() == other.getSizeBytes() && (getSizeBytes() == 0 || memcmp(getBuf(), other.getBuf(), getSizeBytes()) == 0); } void SourceData::FileData::reset() { delete[](char*) m_pBuf; m_pBuf = nullptr; m_v.clear(); m_size = 0; m_vSize = 0; m_bIsText = false; m_bIncompleteConversion = false; m_eLineEndStyle = eLineEndStyleUndefined; } bool SourceData::FileData::readFile(FileAccess& file) { reset(); if(file.fileName().isEmpty()) { return true; } //FileAccess fa(filename); if(!file.isNormal()) return true; m_size = file.sizeForReading(); char* pBuf; m_pBuf = pBuf = new char[m_size + 100]; // Alloc 100 byte extra: Safety hack, not nice but does no harm. // Some extra bytes at the end of the buffer are needed by // the diff algorithm. See also GnuDiff::diff_2_files(). bool bSuccess = file.readFile(pBuf, m_size); if(!bSuccess) { delete[] pBuf; m_pBuf = nullptr; m_size = 0; } return bSuccess; } bool SourceData::FileData::readFile(const QString& filename) { reset(); if(filename.isEmpty()) { return true; } FileAccess fa(filename); if(!fa.isNormal()) return true; m_size = fa.sizeForReading(); char* pBuf; m_pBuf = pBuf = new char[m_size + 100]; // Alloc 100 byte extra: Safety hack, not nice but does no harm. // Some extra bytes at the end of the buffer are needed by // the diff algorithm. See also GnuDiff::diff_2_files(). bool bSuccess = fa.readFile(pBuf, m_size); if(!bSuccess) { delete[] pBuf; m_pBuf = nullptr; m_size = 0; } return bSuccess; } bool SourceData::saveNormalDataAs(const QString& fileName) { return m_normalData.writeFile(fileName); } bool SourceData::FileData::writeFile(const QString& filename) { if(filename.isEmpty()) { return true; } FileAccess fa(filename); bool bSuccess = fa.writeFile(m_pBuf, m_size); return bSuccess; } void SourceData::FileData::copyBufFrom(const FileData& src) { reset(); char* pBuf; m_size = src.m_size; m_pBuf = pBuf = new char[m_size + 100]; Q_ASSERT(src.m_pBuf != nullptr); memcpy(pBuf, src.m_pBuf, m_size); } QTextCodec* SourceData::detectEncoding(const QString& fileName, QTextCodec* pFallbackCodec) { QFile f(fileName); if(f.open(QIODevice::ReadOnly)) { char buf[200]; qint64 size = f.read(buf, sizeof(buf)); qint64 skipBytes = 0; QTextCodec* pCodec = detectEncoding(buf, size, skipBytes); if(pCodec) return pCodec; } return pFallbackCodec; } QStringList SourceData::readAndPreprocess(QTextCodec* pEncoding, bool bAutoDetectUnicode) { m_pEncoding = pEncoding; QTemporaryFile fileIn1, fileOut1; QString fileNameIn1; QString fileNameOut1; QString fileNameIn2; QString fileNameOut2; QStringList errors; if(m_fileAccess.isValid() && !m_fileAccess.isNormal()) { errors.append(i18n("%1 is not a normal file.", m_fileAccess.prettyAbsPath())); return errors; } bool bTempFileFromClipboard = !m_fileAccess.isValid(); // Detect the input for the preprocessing operations if(!bTempFileFromClipboard) { if(m_fileAccess.isLocal()) { fileNameIn1 = m_fileAccess.absoluteFilePath(); } else // File is not local: create a temporary local copy: { if(m_tempInputFileName.isEmpty()) { m_fileAccess.createLocalCopy(); m_tempInputFileName = m_fileAccess.getTempName(); } fileNameIn1 = m_tempInputFileName; } if(bAutoDetectUnicode) { m_pEncoding = detectEncoding(fileNameIn1, pEncoding); } } else // The input was set via setData(), probably from clipboard. { fileNameIn1 = m_tempInputFileName; m_pEncoding = QTextCodec::codecForName("UTF-8"); } QTextCodec* pEncoding1 = m_pEncoding; QTextCodec* pEncoding2 = m_pEncoding; m_normalData.reset(); m_lmppData.reset(); FileAccess faIn(fileNameIn1); qint64 fileInSize = faIn.size(); if(faIn.exists()) { // Run the first preprocessor if(m_pOptions->m_PreProcessorCmd.isEmpty()) { // No preprocessing: Read the file directly: if(!m_normalData.readFile(faIn)) { errors.append(faIn.getStatusText()); return errors; } } else { QTemporaryFile tmpInPPFile; QString fileNameInPP = fileNameIn1; if(pEncoding1 != m_pOptions->m_pEncodingPP) { // Before running the preprocessor convert to the format that the preprocessor expects. FileAccess::createTempFile(tmpInPPFile); fileNameInPP = tmpInPPFile.fileName(); pEncoding1 = m_pOptions->m_pEncodingPP; convertFileEncoding(fileNameIn1, pEncoding, fileNameInPP, pEncoding1); } QString ppCmd = m_pOptions->m_PreProcessorCmd; FileAccess::createTempFile(fileOut1); fileNameOut1 = fileOut1.fileName(); QProcess ppProcess; ppProcess.setStandardInputFile(fileNameInPP); ppProcess.setStandardOutputFile(fileNameOut1); QString program; QStringList args; QString errorReason = Utils::getArguments(ppCmd, program, args); if(errorReason.isEmpty()) { ppProcess.start(program, args); ppProcess.waitForFinished(-1); } else errorReason = "\n(" + errorReason + ')'; bool bSuccess = errorReason.isEmpty() && m_normalData.readFile(fileNameOut1); if(fileInSize > 0 && (!bSuccess || m_normalData.m_size == 0)) { if(!m_normalData.readFile(faIn)) { errors.append(faIn.getStatusText()); errors.append(i18n(" Temp file is: %1", fileNameIn1)); return errors; } //Don't fail the preprocessor command if the file cann't be read. errors.append( i18n("Preprocessing possibly failed. Check this command:\n\n %1" "\n\nThe preprocessing command will be disabled now.", ppCmd) + errorReason); m_pOptions->m_PreProcessorCmd = ""; pEncoding1 = m_pEncoding; } } if(!m_normalData.preprocess(m_pOptions->m_bPreserveCarriageReturn, pEncoding1)) { errors.append(i18n("File %1 too large to process. Skipping.", fileNameIn1)); return errors; } //exit early for non text data further processing assumes a text file as input if(!m_normalData.isText()) return errors; // LineMatching Preprocessor if(!m_pOptions->m_LineMatchingPreProcessorCmd.isEmpty()) { QTemporaryFile tempOut2, fileInPP; fileNameIn2 = fileNameOut1.isEmpty() ? fileNameIn1 : fileNameOut1; QString fileNameInPP = fileNameIn2; pEncoding2 = pEncoding1; if(pEncoding2 != m_pOptions->m_pEncodingPP) { // Before running the preprocessor convert to the format that the preprocessor expects. FileAccess::createTempFile(fileInPP); fileNameInPP = fileInPP.fileName(); pEncoding2 = m_pOptions->m_pEncodingPP; convertFileEncoding(fileNameIn2, pEncoding1, fileNameInPP, pEncoding2); } QString ppCmd = m_pOptions->m_LineMatchingPreProcessorCmd; FileAccess::createTempFile(tempOut2); fileNameOut2 = tempOut2.fileName(); QProcess ppProcess; ppProcess.setStandardInputFile(fileNameInPP); ppProcess.setStandardOutputFile(fileNameOut2); QString program; QStringList args; QString errorReason = Utils::getArguments(ppCmd, program, args); if(errorReason.isEmpty()) { ppProcess.start(program, args); ppProcess.waitForFinished(-1); } else errorReason = "\n(" + errorReason + ')'; bool bSuccess = errorReason.isEmpty() && m_lmppData.readFile(fileNameOut2); if(FileAccess(fileNameIn2).size() > 0 && (!bSuccess || m_lmppData.m_size == 0)) { errors.append( i18n("The line-matching-preprocessing possibly failed. Check this command:\n\n %1" "\n\nThe line-matching-preprocessing command will be disabled now.", ppCmd) + errorReason); m_pOptions->m_LineMatchingPreProcessorCmd = ""; if(!m_lmppData.readFile(fileNameIn2)) { errors.append(i18n("Failed to read file: %1", fileNameIn2)); return errors; } } } else if(m_pOptions->m_bIgnoreComments || m_pOptions->m_bIgnoreCase) { // We need a copy of the normal data. m_lmppData.copyBufFrom(m_normalData); } } else { //exit early for non-existant files return errors; } if(!m_lmppData.preprocess(false, pEncoding2)) { errors.append(i18n("File %1 too large to process. Skipping.", fileNameIn1)); return errors; } Q_ASSERT(m_lmppData.isText()); //TODO: Needed? if(m_lmppData.m_vSize < m_normalData.m_vSize) { // Preprocessing command may result in smaller data buffer so adjust size m_lmppData.m_v.resize((int)m_normalData.m_vSize); for(qint64 i = m_lmppData.m_vSize; i < m_normalData.m_vSize; ++i) { // Set all empty lines to point to the end of the buffer. m_lmppData.m_v[(int)i].setLine(m_lmppData.m_unicodeBuf.unicode() + m_lmppData.m_unicodeBuf.length()); } m_lmppData.m_vSize = m_normalData.m_vSize; } // Ignore comments if(m_pOptions->m_bIgnoreComments) { m_lmppData.removeComments(); qint64 vSize = std::min(m_normalData.m_vSize, m_lmppData.m_vSize); Q_ASSERT(vSize < std::numeric_limits::max()); for(int i = 0; i < vSize; ++i) { m_normalData.m_v[i].setPureComment(m_lmppData.m_v[i].isPureComment()); //Don't crash if vSize is too large. if(i == std::numeric_limits::max()) break; } } return errors; } /** Prepare the linedata vector for every input line.*/ bool SourceData::FileData::preprocess(bool bPreserveCR, QTextCodec* pEncoding) { qint64 i; // detect line end style QVector vOrigDataLineEndStyle; m_eLineEndStyle = eLineEndStyleUndefined; for(i = 0; i < m_size; ++i) { if(m_pBuf[i] == '\r') { if(i + 1 < m_size && m_pBuf[i + 1] == '\n') // not 16-bit unicode { vOrigDataLineEndStyle.push_back(eLineEndStyleDos); ++i; } else if(i > 0 && i + 2 < m_size && m_pBuf[i - 1] == '\0' && m_pBuf[i + 1] == '\0' && m_pBuf[i + 2] == '\n') // 16-bit unicode { vOrigDataLineEndStyle.push_back(eLineEndStyleDos); i += 2; } else // old mac line end style ? { vOrigDataLineEndStyle.push_back(eLineEndStyleUndefined); const_cast(m_pBuf)[i] = '\n'; // fix it in original data } } else if(m_pBuf[i] == '\n') { vOrigDataLineEndStyle.push_back(eLineEndStyleUnix); } } if(!vOrigDataLineEndStyle.isEmpty()) m_eLineEndStyle = vOrigDataLineEndStyle[0]; qint64 skipBytes = 0; QTextCodec* pCodec = detectEncoding(m_pBuf, m_size, skipBytes); if(pCodec != pEncoding) skipBytes = 0; if(m_size - skipBytes > TYPE_MAX(QtNumberType)) return false; QByteArray ba = QByteArray::fromRawData(m_pBuf + skipBytes, (int)(m_size - skipBytes)); QTextStream ts(ba, QIODevice::ReadOnly | QIODevice::Text); ts.setCodec(pEncoding); ts.setAutoDetectUnicode(false); m_unicodeBuf = ts.readAll(); ba.clear(); int ucSize = m_unicodeBuf.length(); const QChar* p = m_unicodeBuf.unicode(); m_bIsText = true; LineCount lines = 1; m_bIncompleteConversion = false; for(i = 0; i < ucSize; ++i) { if(i >= ucSize || p[i] == '\n') { if(lines >= TYPE_MAX(LineCount) - 5) return false; ++lines; } if(p[i].isNull()) { m_bIsText = false; } if(p[i] == QChar::ReplacementCharacter) { m_bIncompleteConversion = true; } } m_v.resize(lines + 5); int lineIdx = 0; int lineLength = 0; bool bNonWhiteFound = false; int whiteLength = 0; for(i = 0; i <= ucSize; ++i) { if(i >= ucSize || p[i] == '\n') { const QChar* pLine = &p[i - lineLength]; m_v[lineIdx].setLine(&p[i - lineLength]); while(/*!bPreserveCR &&*/ lineLength > 0 && m_v[lineIdx].getLine()[lineLength - 1] == '\r') { --lineLength; } m_v[lineIdx].setFirstNonWhiteChar(m_v[lineIdx].getLine() + std::min(whiteLength, lineLength)); if(lineIdx < vOrigDataLineEndStyle.count() && bPreserveCR && i < ucSize) { ++lineLength; const_cast(pLine)[lineLength] = '\r'; //switch ( vOrigDataLineEndStyle[lineIdx] ) //{ //case eLineEndStyleUnix: const_cast(pLine)[lineLength] = '\n'; break; //case eLineEndStyleDos: const_cast(pLine)[lineLength] = '\r'; break; //case eLineEndStyleUndefined: const_cast(pLine)[lineLength] = '\x0b'; break; //} } m_v[lineIdx].setSize(lineLength); lineLength = 0; bNonWhiteFound = false; whiteLength = 0; ++lineIdx; } else { ++lineLength; if(!bNonWhiteFound && isWhite(p[i])) ++whiteLength; else bNonWhiteFound = true; } } Q_ASSERT(lineIdx == lines); m_vSize = lines; return true; } // Must not be entered, when within a comment. // Returns either at a newline-character p[i]=='\n' or when i==size. // A line that contains only comments is still "white". // Comments in white lines must remain, while comments in // non-white lines are overwritten with spaces. void SourceData::FileData::checkLineForComments( const QChar* p, // pointer to start of buffer int& i, // index of current position (in, out) int size, // size of buffer bool& bWhite, // false if this line contains nonwhite characters (in, out) bool& bCommentInLine, // true if any comment is within this line (in, out) bool& bStartsOpenComment // true if the line ends within an comment (out) ) { bStartsOpenComment = false; for(; i < size; ++i) { // A single apostroph ' has prio over a double apostroph " (e.g. '"') // (if not in a string) if(p[i] == '\'') { bWhite = false; ++i; for(; !isLineOrBufEnd(p, i, size) && p[i] != '\''; ++i) ; if(p[i] == '\'') ++i; } // Strings have priority over comments: e.g. "/* Not a comment, but a string. */" else if(p[i] == '"') { bWhite = false; ++i; for(; !isLineOrBufEnd(p, i, size) && !(p[i] == '"' && p[i - 1] != '\\'); ++i) ; if(p[i] == '"') ++i; } // C++-comment else if(p[i] == '/' && i + 1 < size && p[i + 1] == '/') { int commentStart = i; bCommentInLine = true; i += 2; for(; !isLineOrBufEnd(p, i, size); ++i) ; if(!bWhite) { size = i - commentStart; m_unicodeBuf.replace(commentStart, size, QString(" ").repeated(size)); } return; } // C-comment else if(p[i] == '/' && i + 1 < size && p[i + 1] == '*') { int commentStart = i; bCommentInLine = true; i += 2; for(; !isLineOrBufEnd(p, i, size); ++i) { if(i + 1 < size && p[i] == '*' && p[i + 1] == '/') // end of the comment { i += 2; // More comments in the line? checkLineForComments(p, i, size, bWhite, bCommentInLine, bStartsOpenComment); if(!bWhite) { size = i - commentStart; m_unicodeBuf.replace(commentStart, size, QString(" ").repeated(size)); } return; } } bStartsOpenComment = true; return; } if(isLineOrBufEnd(p, i, size)) { return; } else if(!p[i].isSpace()) { bWhite = false; } } } // Modifies the input data, and replaces C/C++ comments with whitespace // when the line contains other data too. If the line contains only // a comment or white data, remember this in the flag bContainsPureComment. void SourceData::FileData::removeComments() { int line = 0; const QChar* p = m_unicodeBuf.unicode(); bool bWithinComment = false; int size = m_unicodeBuf.length(); - qDebug(kdiffMain) << "m_v.size() = " << m_v.size() << ", size = " << size; + qCDebug(kdiffCore) << "m_v.size() = " << m_v.size() << ", size = " << size; Q_ASSERT(m_v.size() > 0); for(int i = 0; i < size; ++i) { - qDebug(kdiffMain) << " line= " << QString(&p[i], m_v[line].size()); + qCDebug(kdiffCore) << "line= " << QString(&p[i], m_v[line].size()); bool bWhite = true; bool bCommentInLine = false; if(bWithinComment) { int commentStart = i; bCommentInLine = true; for(; !isLineOrBufEnd(p, i, size); ++i) { if(i + 1 < size && p[i] == '*' && p[i + 1] == '/') // end of the comment { i += 2; // More comments in the line? checkLineForComments(p, i, size, bWhite, bCommentInLine, bWithinComment); if(!bWhite) { size = i - commentStart; m_unicodeBuf.replace(commentStart, size, QString(" ").repeated(size)); } break; } } } else { checkLineForComments(p, i, size, bWhite, bCommentInLine, bWithinComment); } // end of line Q_ASSERT(isLineOrBufEnd(p, i, size)); m_v[line].setPureComment(bCommentInLine && bWhite); /* std::cout << line << " : " << ( bCommentInLine ? "c" : " " ) << ( bWhite ? "w " : " ") << std::string(pLD[line].pLine, pLD[line].size) << std::endl;*/ ++line; } } bool SourceData::isLineOrBufEnd(const QChar* p, int i, int size) { return i >= size // End of file || Utils::isEndOfLine(p[i]) // Normal end of line // No support for Mac-end of line yet, because incompatible with GNU-diff-routines. // || ( p[i]=='\r' && (i>=size-1 || p[i+1]!='\n') // && (i==0 || p[i-1]!='\n') ) // Special case: '\r' without '\n' ; } // Convert the input file from input encoding to output encoding and write it to the output file. bool SourceData::convertFileEncoding(const QString& fileNameIn, QTextCodec* pCodecIn, const QString& fileNameOut, QTextCodec* pCodecOut) { QFile in(fileNameIn); if(!in.open(QIODevice::ReadOnly)) return false; QTextStream inStream(&in); inStream.setCodec(pCodecIn); inStream.setAutoDetectUnicode(false); QFile out(fileNameOut); if(!out.open(QIODevice::WriteOnly)) return false; QTextStream outStream(&out); outStream.setCodec(pCodecOut); QString data = inStream.readAll(); outStream << data; return true; } QTextCodec* SourceData::getEncodingFromTag(const QByteArray& s, const QByteArray& encodingTag) { int encodingPos = s.indexOf(encodingTag); if(encodingPos >= 0) { int apostrophPos = s.indexOf('"', encodingPos + encodingTag.length()); int apostroph2Pos = s.indexOf('\'', encodingPos + encodingTag.length()); char apostroph = '"'; if(apostroph2Pos >= 0 && (apostrophPos < 0 || apostroph2Pos < apostrophPos)) { apostroph = '\''; apostrophPos = apostroph2Pos; } int encodingEnd = s.indexOf(apostroph, apostrophPos + 1); if(encodingEnd >= 0) // e.g.: or { QByteArray encoding = s.mid(apostrophPos + 1, encodingEnd - (apostrophPos + 1)); return QTextCodec::codecForName(encoding); } else // e.g.: { QByteArray encoding = s.mid(encodingPos + encodingTag.length(), apostrophPos - (encodingPos + encodingTag.length())); return QTextCodec::codecForName(encoding); } } return nullptr; } QTextCodec* SourceData::detectEncoding(const char* buf, qint64 size, qint64& skipBytes) { if(size >= 2) { if(buf[0] == '\xFF' && buf[1] == '\xFE') { skipBytes = 2; return QTextCodec::codecForName("UTF-16LE"); } if(buf[0] == '\xFE' && buf[1] == '\xFF') { skipBytes = 2; return QTextCodec::codecForName("UTF-16BE"); } } if(size >= 3) { if(buf[0] == '\xEF' && buf[1] == '\xBB' && buf[2] == '\xBF') { skipBytes = 3; return QTextCodec::codecForName("UTF-8-BOM"); } } skipBytes = 0; QByteArray s; /* We don't need the whole file here just the header. ] */ if(size <= 5000) s = QByteArray(buf, (int)size); else s = QByteArray(buf, 5000); int xmlHeaderPos = s.indexOf("= 0) { int xmlHeaderEnd = s.indexOf("?>", xmlHeaderPos); if(xmlHeaderEnd >= 0) { QTextCodec* pCodec = getEncodingFromTag(s.mid(xmlHeaderPos, xmlHeaderEnd - xmlHeaderPos), "encoding="); if(pCodec) return pCodec; } } else // HTML { int metaHeaderPos = s.indexOf("= 0) { int metaHeaderEnd = s.indexOf(">", metaHeaderPos); if(metaHeaderEnd >= 0) { QTextCodec* pCodec = getEncodingFromTag(s.mid(metaHeaderPos, metaHeaderEnd - metaHeaderPos), "charset="); if(pCodec) return pCodec; metaHeaderPos = s.indexOf(" * * 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 "Utils.h" #include "fileaccess.h" #include "gnudiff_diff.h" #include "options.h" #include "progress.h" #include #include #include #include #include #include int LineData::width(int tabSize) const { 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 const QChar* p1 = l1.getLine(); const QChar* p1End = p1 + l1.size(); const QChar* p2 = l2.getLine(); const QChar* p2End = p2 + l2.size(); 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() && memcmp(p1, p2, l1.size()) == 0); } } // First step void calcDiff3LineListUsingAB( const DiffList* pDiffListAB, Diff3LineList& d3ll) { // 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); - qDebug(kdiffMain) << "Enter: calcDiff3LineListUsingAB" ; + qCInfo(kdiffMain) << "Enter: calcDiff3LineListUsingAB" ; for(;;) { if(d.nofEquals == 0 && d.diff1 == 0 && d.diff2 == 0) { if(i != pDiffListAB->end()) { d = *i; ++i; } else break; } Diff3Line d3l; if(d.nofEquals > 0) { d3l.bAEqB = true; d3l.setLineA(lineA); d3l.setLineB(lineB); --d.nofEquals; ++lineA; ++lineB; } else if(d.diff1 > 0 && d.diff2 > 0) { d3l.setLineA(lineA); d3l.setLineB(lineB); --d.diff1; --d.diff2; ++lineA; ++lineB; } else if(d.diff1 > 0) { d3l.setLineA(lineA); --d.diff1; ++lineA; } else if(d.diff2 > 0) { d3l.setLineB(lineB); --d.diff2; ++lineB; } Q_ASSERT(d.nofEquals >= 0); - qDebug(kdiffMain) << "lineA = " << d3l.getLineA() << "lineB = " << d3l.getLineB() ; + qCDebug(kdiffCore) << "lineA = " << d3l.getLineA() << ", lineB = " << d3l.getLineB() ; d3ll.push_back(d3l); } - qDebug(kdiffMain) << "Leave: calcDiff3LineListUsingAB" ; + qCInfo(kdiffMain) << "Leave: calcDiff3LineListUsingAB" ; } // Second step void calcDiff3LineListUsingAC( const DiffList* pDiffListAC, Diff3LineList& d3ll) { //////////////// // Now insert data from C using pDiffListAC DiffList::const_iterator i = pDiffListAC->begin(); Diff3LineList::iterator i3 = d3ll.begin(); LineRef::LineType lineA = 0; LineRef::LineType lineC = 0; Diff d(0, 0, 0); for(;;) { if(d.nofEquals == 0 && d.diff1 == 0 && d.diff2 == 0) { if(i != pDiffListAC->end()) { d = *i; ++i; } else break; } Diff3Line d3l; if(d.nofEquals > 0) { // Find the corresponding lineA while(i3->getLineA() != lineA) ++i3; i3->setLineC(lineC); i3->bAEqC = true; i3->bBEqC = i3->isEqualAB(); --d.nofEquals; ++lineA; ++lineC; ++i3; } else if(d.diff1 > 0 && d.diff2 > 0) { d3l.setLineC(lineC); d3ll.insert(i3, d3l); --d.diff1; --d.diff2; ++lineA; ++lineC; } else if(d.diff1 > 0) { --d.diff1; ++lineA; } else if(d.diff2 > 0) { d3l.setLineC(lineC); d3ll.insert(i3, d3l); --d.diff2; ++lineC; } } } // Third step void calcDiff3LineListUsingBC( const DiffList* pDiffListBC, Diff3LineList& d3ll) { //////////////// // 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 = d3ll.begin(); Diff3LineList::iterator i3c = d3ll.begin(); LineRef::LineType lineB = 0; LineRef::LineType lineC = 0; Diff d(0, 0, 0); for(;;) { if(d.nofEquals == 0 && d.diff1 == 0 && d.diff2 == 0) { if(i != pDiffListBC->end()) { d = *i; ++i; } else break; } Diff3Line d3l; if(d.nofEquals > 0) { // Find the corresponding lineB and lineC while(i3b != d3ll.end() && i3b->getLineB() != lineB) ++i3b; while(i3c != d3ll.end() && i3c->getLineC() != lineC) ++i3c; Q_ASSERT(i3b != d3ll.end()); Q_ASSERT(i3c != d3ll.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 != d3ll.end() || i3c1 != d3ll.end()); if(i3c1 != d3ll.end()) ++i3c1; if(i3b1 != d3ll.end()) ++i3b1; } if(i3c1 == i3b && !i3b->isEqualAB()) // i3c before i3b { Diff3LineList::iterator i3 = i3c; int nofDisturbingLines = 0; while(i3 != i3b && i3 != d3ll.end()) { if(i3->getLineB().isValid()) ++nofDisturbingLines; ++i3; } if(nofDisturbingLines > 0) //&& nofDisturbingLines < d.nofEquals*d.nofEquals+4 ) { Diff3LineList::iterator i3_last_equal_A = d3ll.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 != d3ll.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; d3ll.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 != d3ll.end()) { if(i3->getLineC().isValid()) ++nofDisturbingLines; ++i3; } if(nofDisturbingLines > 0) //&& nofDisturbingLines < d.nofEquals*d.nofEquals+4 ) { Diff3LineList::iterator i3_last_equal_A = d3ll.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 != d3ll.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; d3ll.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.nofEquals; ++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); d3ll.insert(i3b, d3l); i3->getLineB().invalidate(); } else { i3b = i3; } --d.diff1; ++lineB; ++i3b; if(d.diff2 > 0) { --d.diff2; ++lineC; } } else if(d.diff2 > 0) { --d.diff2; ++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 LineData* p1, LineRef size1, const LineData* p2, LineRef size2, DiffList& diffList, Options* pOptions) { ProgressProxy pp; static GnuDiff gnuDiff; // All values are initialized with zeros. pp.setCurrent(0); diffList.clear(); if(p1 == nullptr || p1[0].getLine() == nullptr || p2 == nullptr || p2[0].getLine() == nullptr || size1 == 0 || size2 == 0) { Diff d(0, 0, 0); if(p1 != nullptr && p2 != nullptr && p1[0].getLine() == nullptr && p2[0].getLine() == nullptr && size1 == size2) d.nofEquals = size1; else { d.diff1 = size1; d.diff2 = size2; } diffList.push_back(d); } else { GnuDiff::comparison comparisonInput; memset(&comparisonInput, 0, sizeof(comparisonInput)); comparisonInput.parent = nullptr; comparisonInput.file[0].buffer = p1[0].getLine(); //ptr to buffer comparisonInput.file[0].buffered = (p1[size1 - 1].getLine() - p1[0].getLine() + p1[size1 - 1].size()); // size of buffer comparisonInput.file[1].buffer = p2[0].getLine(); //ptr to buffer comparisonInput.file[1].buffered = (p2[size2 - 1].getLine() - p2[0].getLine() + p2[size2 - 1].size()); // 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(0, 0, 0); d.nofEquals = (LineRef)(e->line0 - currentLine1); Q_ASSERT(d.nofEquals == e->line1 - currentLine2); d.diff1 = e->deleted; d.diff2 = e->inserted; currentLine1 += (LineRef)(d.nofEquals + d.diff1); currentLine2 += (LineRef)(d.nofEquals + d.diff2); diffList.push_back(d); p = e->link; free(e); } if(diffList.empty()) { Diff d(0, 0, 0); d.nofEquals = std::min(size1, size2); d.diff1 = size1 - d.nofEquals; d.diff2 = size2 - d.nofEquals; diffList.push_back(d); } else { diffList.front().nofEquals += equalLinesAtStart; currentLine1 += equalLinesAtStart; currentLine2 += equalLinesAtStart; LineRef nofEquals = std::min(size1 - currentLine1, size2 - currentLine2); if(nofEquals == 0) { diffList.back().diff1 += size1 - currentLine1; diffList.back().diff2 += 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->nofEquals + i->diff1; l2 += i->nofEquals + i->diff2; } Q_ASSERT(l1 == size1 && l2 == size2); } pp.setCurrent(1); return true; } bool ManualDiffHelpList::runDiff(const LineData* p1, LineRef size1, const LineData* p2, LineRef size2, DiffList& diffList, e_SrcSelector winIdx1, e_SrcSelector winIdx2, Options* 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 correctManualDiffAlignment(Diff3LineList& d3ll, 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 = d3ll.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 != d3ll.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 != d3ll.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; d3ll.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 != d3ll.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; } d3ll.insert(iDest, d3l); } } // for(), searching for wi3 } break; } } // for(), searching for wi2 } // if, wi found } // for (iMDHL) } // Fourth step void calcDiff3LineListTrim( Diff3LineList& d3ll, const LineData* pldA, const LineData* pldB, const LineData* pldC, ManualDiffHelpList* pManualDiffHelpList) { const Diff3Line d3l_empty; d3ll.remove(d3l_empty); Diff3LineList::iterator i3 = d3ll.begin(); Diff3LineList::iterator i3A = d3ll.begin(); Diff3LineList::iterator i3B = d3ll.begin(); Diff3LineList::iterator i3C = d3ll.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 != d3ll.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; } } d3ll.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 LineData* pldA, LineCount sizeA, const LineData* pldB, LineCount sizeB, const LineData* pldC, LineCount sizeC) { m_pDiff3LineList = pD3ll; m_pDiff3LineVector = pD3lv; m_pLineDataA = pldA; m_pLineDataB = pldB; m_pLineDataC = 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 LineData* pldA, const LineData* pldB, const LineData* 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->getLineA().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 QChar* p1, LineRef size1, const QChar* p2, LineRef size2, DiffList& diffList, int match, int maxSearchRange) { diffList.clear(); const QChar* p1start = p1; const QChar* p2start = p2; const QChar* p1end = p1 + size1; const QChar* p2end = p2 + size2; for(;;) { int nofEquals = 0; while(p1 != p1end && p2 != p2end && *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] == p1end || (bBestValid && i1 >= bestI1 + bestI2)) { break; } for(i2 = 0; i2 < maxSearchRange; ++i2) { if(&p2[i2] == p2end || (bBestValid && i1 + i2 >= bestI1 + bestI2)) { break; } else if(p2[i2] == p1[i1] && (match == 1 || abs(i1 - i2) < 3 || (&p2[i2 + 1] == p2end && &p1[i1 + 1] == p1end) || (&p2[i2 + 1] != p2end && &p1[i1 + 1] != p1end && 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); diffList.push_back(d); p1 += bestI1; p2 += bestI2; } else { // Nothing else to match. Diff d(nofEquals, p1end - p1, p2end - 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; const QChar* pu1 = p1 - 1; const QChar* pu2 = p2 - 1; while(pu1 >= p1start && pu2 >= p2start && *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.diff1; --d.diff2; --nofUnmatched; } else if(d.nofEquals > 0) { --d.nofEquals; --nofUnmatched; } if(d.nofEquals == 0 && (d.diff1 == 0 || d.diff2 == 0) && nofUnmatched > 0) { if(diffList.empty()) break; d.nofEquals += diffList.back().nofEquals; d.diff1 += diffList.back().diff1; d.diff2 += 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; } // Verify difflist { LineRef l1 = 0; LineRef l2 = 0; DiffList::iterator i; for(i = diffList.begin(); i != diffList.end(); ++i) { l1 += (LineRef)(i->nofEquals + i->diff1); l2 += (LineRef)(i->nofEquals + i->diff2); } Q_ASSERT(l1 == size1 && l2 == size2); } } bool Diff3Line::fineDiff(bool inBTextsTotalEqual, const e_SrcSelector selector, const LineData* v1, const LineData* 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() || memcmp(v1[k1].getLine(), v2[k2].getLine(), v1[k1].size() << 1) != 0) { bTextsTotalEqual = false; DiffList* pDiffList = new DiffList; calcDiff(v1[k1].getLine(), v1[k1].size(), v2[k2].getLine(), v2[k2].size(), *pDiffList, 2, maxSearchLength); // Optimize the diff list. DiffList::iterator dli; bool bUsefulFineDiff = false; for(dli = pDiffList->begin(); dli != pDiffList->end(); ++dli) { if(dli->nofEquals >= 4) { bUsefulFineDiff = true; break; } } for(dli = pDiffList->begin(); dli != pDiffList->end(); ++dli) { if(dli->nofEquals < 4 && (dli->diff1 > 0 || dli->diff2 > 0) && !(bUsefulFineDiff && dli == pDiffList->begin())) { dli->diff1 += dli->nofEquals; dli->diff2 += dli->nofEquals; dli->nofEquals = 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, DiffList*& pFineDiff1, DiffList*& pFineDiff2, // return values int& changed, int& changed2) const { changed = 0; changed2 = 0; 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); } 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); } 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); } } bool Diff3LineList::fineDiff(const e_SrcSelector selector, const LineData* v1, const LineData* 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/pdiff.cpp b/src/pdiff.cpp index 57342c6..c7de355 100644 --- a/src/pdiff.cpp +++ b/src/pdiff.cpp @@ -1,2499 +1,2499 @@ /*************************************************************************** * 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 "difftextwindow.h" #include "directorymergewindow.h" #include "fileaccess.h" #include "Logging.h" #include "kdiff3.h" #include "optiondialog.h" #include "progress.h" #include "Utils.h" #include "DirectoryInfo.h" #include "mergeresultwindow.h" #include "smalldialogs.h" #include #include #include #include #include #include #include #include #include // QKeyEvent, QDropEvent, QInputEvent #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include bool g_bIgnoreWhiteSpace = true; bool g_bIgnoreTrivialMatches = true; // Just make sure that all input lines are in the output too, exactly once. static void debugLineCheck(Diff3LineList& d3ll, LineRef size, e_SrcSelector idx) { Diff3LineList::iterator it = d3ll.begin(); int i = 0; for(it = d3ll.begin(); it != d3ll.end(); ++it) { LineRef l = 0; Q_ASSERT(idx >= A && idx <= C); if(idx == A) l = it->getLineA(); else if(idx == B) l = it->getLineB(); else if(idx == C) l = it->getLineC(); if(l != -1) { if(l != i) { KMessageBox::error(nullptr, i18n( "Data loss error:\n" "If it is reproducible please contact the author.\n"), i18n("Severe Internal Error")); - qCritical(kdiffMain) << "Severe Internal Error. Line not set for idx=" << idx << "\n"; + qCCritical(kdiffMain) << "Severe Internal Error. Line not set for idx=" << idx << "\n"; ::exit(-1); } ++i; } } if(size != i) { KMessageBox::error(nullptr, i18n( "Data loss error:\n" "If it is reproducible please contact the author.\n"), i18n("Severe Internal Error")); - qCritical(kdiffMain) << "Severe Internal Error.: " << size << " != " << i << "\n"; + qCCritical(kdiffMain) << "Severe Internal Error.: " << size << " != " << i << "\n"; ::exit(-1); } } void KDiff3App::mainInit(TotalDiffStatus* pTotalDiffStatus, bool bLoadFiles, bool bUseCurrentEncoding) { ProgressProxy pp; QStringList errors; // When doing a full analysis in the directory-comparison, then the statistics-results // will be stored in the given TotalDiffStatus. Otherwise it will be 0. bool bGUI = pTotalDiffStatus == nullptr; if(pTotalDiffStatus == nullptr) pTotalDiffStatus = &m_totalDiffStatus; //bool bPreserveCarriageReturn = m_pOptions->m_bPreserveCarriageReturn; bool bVisibleMergeResultWindow = !m_outputFilename.isEmpty(); if(bVisibleMergeResultWindow && bGUI) { //bPreserveCarriageReturn = false; QString msg; if(!m_pOptions->m_PreProcessorCmd.isEmpty()) { msg += "- " + i18n("PreprocessorCmd: ") + m_pOptions->m_PreProcessorCmd + '\n'; } if(!msg.isEmpty()) { int result = KMessageBox::warningYesNo(this, i18n("The following option(s) you selected might change data:\n") + msg + i18n("\nMost likely this is not wanted during a merge.\n" "Do you want to disable these settings or continue with these settings active?"), i18n("Option Unsafe for Merging"), KGuiItem(i18n("Use These Options During Merge")), KGuiItem(i18n("Disable Unsafe Options"))); if(result == KMessageBox::No) { m_pOptions->m_PreProcessorCmd = ""; } } } // Because of the progressdialog paintevents can occur, but data is invalid, // so painting must be suppressed if(bGUI) setLockPainting(true); m_diff3LineList.clear(); m_diff3LineVector.clear(); if(bLoadFiles) { m_manualDiffHelpList.clear(); if(m_sd3.isEmpty()) pp.setMaxNofSteps(4); // Read 2 files, 1 comparison, 1 finediff else pp.setMaxNofSteps(9); // Read 3 files, 3 comparisons, 3 finediffs // First get all input data. pp.setInformation(i18n("Loading A")); - qInfo(kdiffMain) << i18n("Loading A: ") << m_sd1.getFilename() ; + qCInfo(kdiffMain) << i18n("Loading A: %1 ", m_sd1.getFilename()) ; if(bUseCurrentEncoding) errors = m_sd1.readAndPreprocess(m_sd1.getEncoding(), false); else errors = m_sd1.readAndPreprocess(m_pOptions->m_pEncodingA, m_pOptions->m_bAutoDetectUnicodeA); if(!errors.isEmpty()) { KMessageBox::errorList(m_pOptionDialog, i18n("Errors occurred during pre-processing of file A."), errors); } pp.step(); pp.setInformation(i18n("Loading B")); - qInfo(kdiffMain) << i18n("Loading B: ") << m_sd2.getFilename() ; + qCInfo(kdiffMain) << i18n("Loading B: ", m_sd2.getFilename()); if(bUseCurrentEncoding) errors = m_sd2.readAndPreprocess(m_sd2.getEncoding(), false); else errors = m_sd2.readAndPreprocess(m_pOptions->m_pEncodingB, m_pOptions->m_bAutoDetectUnicodeB); if(!errors.isEmpty()) KMessageBox::errorList(m_pOptionDialog, i18n("Errors occurred during pre-processing of file B."), errors); pp.step(); } else { if(m_sd3.isEmpty()) pp.setMaxNofSteps(2); // 1 comparison, 1 finediff else pp.setMaxNofSteps(6); // 3 comparisons, 3 finediffs } if(pTotalDiffStatus) pTotalDiffStatus->reset(); if(errors.isEmpty()) { // Run the diff. if(m_sd3.isEmpty()) { pTotalDiffStatus->bBinaryAEqB = m_sd1.isBinaryEqualWith(m_sd2); if(m_sd1.isText() && m_sd2.isText()) { pp.setInformation(i18n("Diff: A <-> B")); - qInfo(kdiffMain) << i18n("Diff: A <-> B") ; + qCInfo(kdiffMain) << i18n("Diff: A <-> B") ; m_manualDiffHelpList.runDiff(m_sd1.getLineDataForDiff(), m_sd1.getSizeLines(), m_sd2.getLineDataForDiff(), m_sd2.getSizeLines(), m_diffList12, A, B, &m_pOptionDialog->m_options); pp.step(); pp.setInformation(i18n("Linediff: A <-> B")); - qInfo(kdiffMain) << i18n("Linediff: A <-> B") ; + qCInfo(kdiffMain) << i18n("Linediff: A <-> B") ; calcDiff3LineListUsingAB(&m_diffList12, m_diff3LineList); pTotalDiffStatus->bTextAEqB = m_diff3LineList.fineDiff(A, m_sd1.getLineDataForDisplay(), m_sd2.getLineDataForDisplay()); if(m_sd1.getSizeBytes() == 0) pTotalDiffStatus->bTextAEqB = false; pp.step(); } else { pp.step(); pp.step(); } } else { if(bLoadFiles) { pp.setInformation(i18n("Loading C")); if(bUseCurrentEncoding) errors = m_sd3.readAndPreprocess(m_sd3.getEncoding(), false); else errors = m_sd3.readAndPreprocess(m_pOptions->m_pEncodingC, m_pOptions->m_bAutoDetectUnicodeC); if(!errors.isEmpty()) KMessageBox::errorList(m_pOptionDialog, i18n("Errors occurred during pre-processing of file C."), errors); pp.step(); } pTotalDiffStatus->bBinaryAEqB = m_sd1.isBinaryEqualWith(m_sd2); pTotalDiffStatus->bBinaryAEqC = m_sd1.isBinaryEqualWith(m_sd3); pTotalDiffStatus->bBinaryBEqC = m_sd3.isBinaryEqualWith(m_sd2); pp.setInformation(i18n("Diff: A <-> B")); if(m_sd1.isText() && m_sd2.isText()) { m_manualDiffHelpList.runDiff(m_sd1.getLineDataForDiff(), m_sd1.getSizeLines(), m_sd2.getLineDataForDiff(), m_sd2.getSizeLines(), m_diffList12, A, B, &m_pOptionDialog->m_options); calcDiff3LineListUsingAB(&m_diffList12, m_diff3LineList); } pp.step(); pp.setInformation(i18n("Diff: A <-> C")); if(m_sd1.isText() && m_sd3.isText()) { m_manualDiffHelpList.runDiff(m_sd1.getLineDataForDiff(), m_sd1.getSizeLines(), m_sd3.getLineDataForDiff(), m_sd3.getSizeLines(), m_diffList13, A, C, &m_pOptionDialog->m_options); calcDiff3LineListUsingAC(&m_diffList13, m_diff3LineList); correctManualDiffAlignment(m_diff3LineList, &m_manualDiffHelpList); calcDiff3LineListTrim(m_diff3LineList, m_sd1.getLineDataForDiff(), m_sd2.getLineDataForDiff(), m_sd3.getLineDataForDiff(), &m_manualDiffHelpList); } pp.step(); pp.setInformation(i18n("Diff: B <-> C")); if(m_sd2.isText() && m_sd3.isText()) { m_manualDiffHelpList.runDiff(m_sd2.getLineDataForDiff(), m_sd2.getSizeLines(), m_sd3.getLineDataForDiff(), m_sd3.getSizeLines(), m_diffList23, B, C, &m_pOptionDialog->m_options); if(m_pOptions->m_bDiff3AlignBC) { calcDiff3LineListUsingBC(&m_diffList23, m_diff3LineList); correctManualDiffAlignment(m_diff3LineList, &m_manualDiffHelpList); calcDiff3LineListTrim(m_diff3LineList, m_sd1.getLineDataForDiff(), m_sd2.getLineDataForDiff(), m_sd3.getLineDataForDiff(), &m_manualDiffHelpList); } } pp.step(); debugLineCheck(m_diff3LineList, m_sd1.getSizeLines(), A); debugLineCheck(m_diff3LineList, m_sd2.getSizeLines(), B); debugLineCheck(m_diff3LineList, m_sd3.getSizeLines(), C); pp.setInformation(i18n("Linediff: A <-> B")); if(m_sd1.hasData() && m_sd2.hasData() && m_sd1.isText() && m_sd2.isText()) pTotalDiffStatus->bTextAEqB = m_diff3LineList.fineDiff(A, m_sd1.getLineDataForDisplay(), m_sd2.getLineDataForDisplay()); pp.step(); pp.setInformation(i18n("Linediff: B <-> C")); if(m_sd2.hasData() && m_sd3.hasData() && m_sd2.isText() && m_sd3.isText()) pTotalDiffStatus->bTextBEqC = m_diff3LineList.fineDiff(B, m_sd2.getLineDataForDisplay(), m_sd3.getLineDataForDisplay()); pp.step(); pp.setInformation(i18n("Linediff: A <-> C")); if(m_sd1.hasData() && m_sd3.hasData() && m_sd1.isText() && m_sd3.isText()) pTotalDiffStatus->bTextAEqC = m_diff3LineList.fineDiff(C, m_sd3.getLineDataForDisplay(), m_sd1.getLineDataForDisplay()); debugLineCheck(m_diff3LineList, m_sd2.getSizeLines(), B); debugLineCheck(m_diff3LineList, m_sd3.getSizeLines(), C); pp.setInformation(i18n("Linediff: A <-> B")); if(m_sd1.hasData() && m_sd2.hasData() && m_sd1.isText() && m_sd2.isText()) pTotalDiffStatus->bTextAEqB = m_diff3LineList.fineDiff(A, m_sd1.getLineDataForDisplay(), m_sd2.getLineDataForDisplay()); pp.step(); pp.setInformation(i18n("Linediff: B <-> C")); if(m_sd3.hasData() && m_sd2.hasData() && m_sd3.isText() && m_sd2.isText()) pTotalDiffStatus->bTextBEqC = m_diff3LineList.fineDiff(B, m_sd2.getLineDataForDisplay(), m_sd3.getLineDataForDisplay()); pp.step(); pp.setInformation(i18n("Linediff: A <-> C")); if(m_sd1.hasData() && m_sd3.hasData() && m_sd1.isText() && m_sd3.isText()) pTotalDiffStatus->bTextAEqC = m_diff3LineList.fineDiff(C, m_sd3.getLineDataForDisplay(), m_sd1.getLineDataForDisplay()); pp.step(); if(m_sd1.getSizeBytes() == 0) { pTotalDiffStatus->bTextAEqB = false; pTotalDiffStatus->bTextAEqC = false; } if(m_sd2.getSizeBytes() == 0) { pTotalDiffStatus->bTextAEqB = false; pTotalDiffStatus->bTextBEqC = false; } } } else { pp.clear(); } if(errors.isEmpty() && m_sd1.isText() && m_sd2.isText()) { m_diffBufferInfo.init(&m_diff3LineList, &m_diff3LineVector, m_sd1.getLineDataForDiff(), m_sd1.getSizeLines(), m_sd2.getLineDataForDiff(), m_sd2.getSizeLines(), m_sd3.getLineDataForDiff(), m_sd3.getSizeLines()); m_diff3LineList.calcWhiteDiff3Lines(m_sd1.getLineDataForDiff(), m_sd2.getLineDataForDiff(), m_sd3.getLineDataForDiff()); m_diff3LineList.calcDiff3LineVector(m_diff3LineVector); } // Calc needed lines for display m_neededLines = m_diff3LineList.size(); QList oldHeights; if(m_pDirectoryMergeSplitter->isVisible()) oldHeights = m_pMainSplitter->sizes(); initView(); m_pMergeResultWindow->initActions(actionCollection()); if(m_pDirectoryMergeSplitter->isVisible()) { if(oldHeights.count() < 2) oldHeights.append(0); if(oldHeights[1] == 0) // Distribute the available space evenly between the two widgets. { oldHeights[1] = oldHeights[0] / 2; oldHeights[0] -= oldHeights[1]; } if(oldHeights[0] == 0 && oldHeights[1] == 0) { oldHeights[1] = 100; oldHeights[0] = 100; } m_pMainSplitter->setSizes(oldHeights); } m_pMainWidget->setVisible(bGUI); m_bTripleDiff = !m_sd3.isEmpty(); m_pMergeResultWindowTitle->setEncodings(m_sd1.getEncoding(), m_sd2.getEncoding(), m_sd3.getEncoding()); if(!m_pOptions->m_bAutoSelectOutEncoding) m_pMergeResultWindowTitle->setEncoding(m_pOptions->m_pEncodingOut); m_pMergeResultWindowTitle->setLineEndStyles(m_sd1.getLineEndStyle(), m_sd2.getLineEndStyle(), m_sd3.getLineEndStyle()); if(bGUI) { const ManualDiffHelpList* pMDHL = &m_manualDiffHelpList; m_pDiffTextWindow1->init(m_sd1.getAliasName(), m_sd1.getEncoding(), m_sd1.getLineEndStyle(), m_sd1.getLineDataForDisplay(), m_sd1.getSizeLines(), &m_diff3LineVector, pMDHL, m_bTripleDiff); m_pDiffTextWindow2->init(m_sd2.getAliasName(), m_sd2.getEncoding(), m_sd2.getLineEndStyle(), m_sd2.getLineDataForDisplay(), m_sd2.getSizeLines(), &m_diff3LineVector, pMDHL, m_bTripleDiff); m_pDiffTextWindow3->init(m_sd3.getAliasName(), m_sd3.getEncoding(), m_sd3.getLineEndStyle(), m_sd3.getLineDataForDisplay(), m_sd3.getSizeLines(), &m_diff3LineVector, pMDHL, m_bTripleDiff); m_pDiffTextWindowFrame3->setVisible(m_bTripleDiff); } m_bOutputModified = bVisibleMergeResultWindow; m_pMergeResultWindow->init( m_sd1.getLineDataForDisplay(), m_sd1.getSizeLines(), m_sd2.getLineDataForDisplay(), m_sd2.getSizeLines(), m_bTripleDiff ? m_sd3.getLineDataForDisplay() : nullptr, m_sd3.getSizeLines(), &m_diff3LineList, pTotalDiffStatus); m_pMergeResultWindowTitle->setFileName(m_outputFilename.isEmpty() ? QString("unnamed.txt") : m_outputFilename); if(!bGUI) { // We now have all needed information. The rest below is only for GUI-activation. m_sd1.reset(); m_sd2.reset(); m_sd3.reset(); } else { m_pOverview->init(&m_diff3LineList, m_bTripleDiff); m_pDiffVScrollBar->setValue(0); m_pHScrollBar->setValue(0); m_pMergeVScrollBar->setValue(0); setLockPainting(false); if(!bVisibleMergeResultWindow) m_pMergeWindowFrame->hide(); else m_pMergeWindowFrame->show(); // Try to create a meaningful but not too long caption if(!isPart() && errors.isEmpty()) { createCaption(); } //initialize wheel tracking to zero m_iCumulativeWheelDelta = 0; m_bFinishMainInit = true; // call slotFinishMainInit after finishing the word wrap m_bLoadFiles = bLoadFiles; postRecalcWordWrap(); } } void KDiff3App::setLockPainting(bool bLock) { if(m_pDiffTextWindow1) m_pDiffTextWindow1->setPaintingAllowed(!bLock); if(m_pDiffTextWindow2) m_pDiffTextWindow2->setPaintingAllowed(!bLock); if(m_pDiffTextWindow3) m_pDiffTextWindow3->setPaintingAllowed(!bLock); if(m_pOverview) m_pOverview->setPaintingAllowed(!bLock); if(m_pMergeResultWindow) m_pMergeResultWindow->setPaintingAllowed(!bLock); } void KDiff3App::createCaption() { // Try to create a meaningful but not too long caption // 1. If the filenames are equal then show only one filename QString caption; QString f1 = m_sd1.getAliasName(); QString f2 = m_sd2.getAliasName(); QString f3 = m_sd3.getAliasName(); int p; if((p = f1.lastIndexOf('/')) >= 0 || (p = f1.lastIndexOf('\\')) >= 0) f1 = f1.mid(p + 1); if((p = f2.lastIndexOf('/')) >= 0 || (p = f2.lastIndexOf('\\')) >= 0) f2 = f2.mid(p + 1); if((p = f3.lastIndexOf('/')) >= 0 || (p = f3.lastIndexOf('\\')) >= 0) f3 = f3.mid(p + 1); if(!f1.isEmpty()) { if((f2.isEmpty() && f3.isEmpty()) || (f2.isEmpty() && f1 == f3) || (f3.isEmpty() && f1 == f2) || (f1 == f2 && f1 == f3)) caption = f1; } else if(!f2.isEmpty()) { if(f3.isEmpty() || f2 == f3) caption = f2; } else if(!f3.isEmpty()) caption = f3; // 2. If the files don't have the same name then show all names if(caption.isEmpty() && (!f1.isEmpty() || !f2.isEmpty() || !f3.isEmpty())) { caption = (f1.isEmpty() ? QString("") : f1); caption += QLatin1String(caption.isEmpty() || f2.isEmpty() ? "" : " <-> ") + (f2.isEmpty() ? QString("") : f2); caption += QLatin1String(caption.isEmpty() || f3.isEmpty() ? "" : " <-> ") + (f3.isEmpty() ? QString("") : f3); } m_pKDiff3Shell->setWindowTitle(caption.isEmpty() ? QString("KDiff3") : caption + QString(" - KDiff3")); } void KDiff3App::setHScrollBarRange() { int w1 = m_pDiffTextWindow1 != nullptr && m_pDiffTextWindow1->isVisible() ? m_pDiffTextWindow1->getMaxTextWidth() : 0; int w2 = m_pDiffTextWindow2 != nullptr && m_pDiffTextWindow2->isVisible() ? m_pDiffTextWindow2->getMaxTextWidth() : 0; int w3 = m_pDiffTextWindow3 != nullptr && m_pDiffTextWindow3->isVisible() ? m_pDiffTextWindow3->getMaxTextWidth() : 0; int wm = m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isVisible() ? m_pMergeResultWindow->getMaxTextWidth() : 0; int v1 = m_pDiffTextWindow1 != nullptr && m_pDiffTextWindow1->isVisible() ? m_pDiffTextWindow1->getVisibleTextAreaWidth() : 0; int v2 = m_pDiffTextWindow2 != nullptr && m_pDiffTextWindow2->isVisible() ? m_pDiffTextWindow2->getVisibleTextAreaWidth() : 0; int v3 = m_pDiffTextWindow3 != nullptr && m_pDiffTextWindow3->isVisible() ? m_pDiffTextWindow3->getVisibleTextAreaWidth() : 0; int vm = m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isVisible() ? m_pMergeResultWindow->getVisibleTextAreaWidth() : 0; // Find the minimum, but don't consider 0. int pageStep = 0; if((pageStep == 0 || pageStep > v1) && v1 > 0) pageStep = v1; if((pageStep == 0 || pageStep > v2) && v2 > 0) pageStep = v2; if((pageStep == 0 || pageStep > v3) && v3 > 0) pageStep = v3; if((pageStep == 0 || pageStep > vm) && vm > 0) pageStep = vm; int rangeMax = 0; if(w1 > v1 && w1 - v1 > rangeMax && v1 > 0) rangeMax = w1 - v1; if(w2 > v2 && w2 - v2 > rangeMax && v2 > 0) rangeMax = w2 - v2; if(w3 > v3 && w3 - v3 > rangeMax && v3 > 0) rangeMax = w3 - v3; if(wm > vm && wm - vm > rangeMax && vm > 0) rangeMax = wm - vm; m_pHScrollBar->setRange(0, rangeMax); m_pHScrollBar->setPageStep(pageStep); } void KDiff3App::resizeDiffTextWindowHeight(int newHeight) { m_DTWHeight = newHeight; m_pDiffVScrollBar->setRange(0, std::max(0, m_neededLines + 1 - newHeight)); m_pDiffVScrollBar->setPageStep(newHeight); m_pOverview->setRange(m_pDiffVScrollBar->value(), m_pDiffVScrollBar->pageStep()); setHScrollBarRange(); } void KDiff3App::resizeMergeResultWindow() { MergeResultWindow* p = m_pMergeResultWindow; m_pMergeVScrollBar->setRange(0, std::max(0, p->getNofLines() - p->getNofVisibleLines())); m_pMergeVScrollBar->setPageStep(p->getNofVisibleLines()); setHScrollBarRange(); } void KDiff3App::scrollDiffTextWindow(int deltaX, int deltaY) { if(deltaY != 0) { m_pDiffVScrollBar->setValue(m_pDiffVScrollBar->value() + deltaY); m_pOverview->setRange(m_pDiffVScrollBar->value(), m_pDiffVScrollBar->pageStep()); } if(deltaX != 0) m_pHScrollBar->QScrollBar::setValue(m_pHScrollBar->value() + deltaX); } void KDiff3App::scrollMergeResultWindow(int deltaX, int deltaY) { if(deltaY != 0) m_pMergeVScrollBar->setValue(m_pMergeVScrollBar->value() + deltaY); if(deltaX != 0) m_pHScrollBar->setValue(m_pHScrollBar->value() + deltaX); } void KDiff3App::setDiff3Line(int line) { m_pDiffVScrollBar->setValue(line); } void KDiff3App::sourceMask(int srcMask, int enabledMask) { chooseA->blockSignals(true); chooseB->blockSignals(true); chooseC->blockSignals(true); chooseA->setChecked((srcMask & 1) != 0); chooseB->setChecked((srcMask & 2) != 0); chooseC->setChecked((srcMask & 4) != 0); chooseA->blockSignals(false); chooseB->blockSignals(false); chooseC->blockSignals(false); chooseA->setEnabled((enabledMask & 1) != 0); chooseB->setEnabled((enabledMask & 2) != 0); chooseC->setEnabled((enabledMask & 4) != 0); } // Function uses setMinSize( sizeHint ) before adding the widget. // void addWidget(QBoxLayout* layout, QWidget* widget); template void addWidget(L* layout, W* widget) { QSize s = widget->sizeHint(); widget->setMinimumSize(QSize(std::max(s.width(), 0), std::max(s.height(), 0))); layout->addWidget(widget); } void KDiff3App::initView() { // set the main widget here if(m_pMainWidget != nullptr) { return; //delete m_pMainWidget; } m_pMainWidget = new QWidget(); // Contains vertical splitter and horiz scrollbar m_pMainSplitter->addWidget(m_pMainWidget); m_pMainWidget->setObjectName("MainWidget"); QVBoxLayout* pVLayout = new QVBoxLayout(m_pMainWidget); pVLayout->setMargin(0); pVLayout->setSpacing(0); QSplitter* pVSplitter = new QSplitter(); pVSplitter->setObjectName("VSplitter"); pVSplitter->setOpaqueResize(false); pVSplitter->setOrientation(Qt::Vertical); pVLayout->addWidget(pVSplitter); QWidget* pDiffWindowFrame = new QWidget(); // Contains diff windows, overview and vert scrollbar pDiffWindowFrame->setObjectName("DiffWindowFrame"); QHBoxLayout* pDiffHLayout = new QHBoxLayout(pDiffWindowFrame); pDiffHLayout->setMargin(0); pDiffHLayout->setSpacing(0); pVSplitter->addWidget(pDiffWindowFrame); m_pDiffWindowSplitter = new QSplitter(); m_pDiffWindowSplitter->setObjectName("DiffWindowSplitter"); m_pDiffWindowSplitter->setOpaqueResize(false); m_pDiffWindowSplitter->setOrientation(m_pOptions->m_bHorizDiffWindowSplitting ? Qt::Horizontal : Qt::Vertical); pDiffHLayout->addWidget(m_pDiffWindowSplitter); m_pOverview = new Overview(&m_pOptionDialog->m_options); m_pOverview->setObjectName("Overview"); pDiffHLayout->addWidget(m_pOverview); connect(m_pOverview, &Overview::setLine, this, &KDiff3App::setDiff3Line); m_pDiffVScrollBar = new QScrollBar(Qt::Vertical, pDiffWindowFrame); pDiffHLayout->addWidget(m_pDiffVScrollBar); m_pDiffTextWindowFrame1 = new DiffTextWindowFrame(m_pDiffWindowSplitter, statusBar(), &m_pOptionDialog->m_options, A, &m_sd1); m_pDiffWindowSplitter->addWidget(m_pDiffTextWindowFrame1); m_pDiffTextWindowFrame2 = new DiffTextWindowFrame(m_pDiffWindowSplitter, statusBar(), &m_pOptionDialog->m_options, B, &m_sd2); m_pDiffWindowSplitter->addWidget(m_pDiffTextWindowFrame2); m_pDiffTextWindowFrame3 = new DiffTextWindowFrame(m_pDiffWindowSplitter, statusBar(), &m_pOptionDialog->m_options, C, &m_sd3); m_pDiffWindowSplitter->addWidget(m_pDiffTextWindowFrame3); m_pDiffTextWindow1 = m_pDiffTextWindowFrame1->getDiffTextWindow(); m_pDiffTextWindow2 = m_pDiffTextWindowFrame2->getDiffTextWindow(); m_pDiffTextWindow3 = m_pDiffTextWindowFrame3->getDiffTextWindow(); connect(m_pDiffTextWindowFrame1, &DiffTextWindowFrame::fileNameChanged, this, &KDiff3App::slotFileNameChanged); connect(m_pDiffTextWindowFrame2, &DiffTextWindowFrame::fileNameChanged, this, &KDiff3App::slotFileNameChanged); connect(m_pDiffTextWindowFrame3, &DiffTextWindowFrame::fileNameChanged, this, &KDiff3App::slotFileNameChanged); connect(m_pDiffTextWindowFrame1, &DiffTextWindowFrame::encodingChanged, this, &KDiff3App::slotEncodingChangedA); connect(m_pDiffTextWindowFrame2, &DiffTextWindowFrame::encodingChanged, this, &KDiff3App::slotEncodingChangedB); connect(m_pDiffTextWindowFrame3, &DiffTextWindowFrame::encodingChanged, this, &KDiff3App::slotEncodingChangedC); // Merge window m_pMergeWindowFrame = new QWidget(pVSplitter); m_pMergeWindowFrame->setObjectName("MergeWindowFrame"); pVSplitter->addWidget(m_pMergeWindowFrame); QHBoxLayout* pMergeHLayout = new QHBoxLayout(m_pMergeWindowFrame); pMergeHLayout->setMargin(0); pMergeHLayout->setSpacing(0); QVBoxLayout* pMergeVLayout = new QVBoxLayout(); pMergeHLayout->addLayout(pMergeVLayout, 1); m_pMergeResultWindowTitle = new WindowTitleWidget(&m_pOptionDialog->m_options); pMergeVLayout->addWidget(m_pMergeResultWindowTitle); m_pMergeResultWindow = new MergeResultWindow(m_pMergeWindowFrame, &m_pOptionDialog->m_options, statusBar()); pMergeVLayout->addWidget(m_pMergeResultWindow, 1); m_pMergeVScrollBar = new QScrollBar(Qt::Vertical, m_pMergeWindowFrame); pMergeHLayout->addWidget(m_pMergeVScrollBar); m_pMainSplitter->addWidget(m_pMainWidget); autoAdvance->setEnabled(true); QList sizes = pVSplitter->sizes(); int total = sizes[0] + sizes[1]; if(total < 10) total = 100; sizes[0] = total / 2; sizes[1] = total / 2; pVSplitter->setSizes(sizes); QList hSizes; hSizes << 1 << 1 << 1; m_pDiffWindowSplitter->setSizes(hSizes); m_pMergeResultWindow->installEventFilter(this); // for drop and focus events m_pMergeResultWindow->installEventFilter(m_pMergeResultWindowTitle); // for focus tracking QHBoxLayout* pHScrollBarLayout = new QHBoxLayout(); pVLayout->addLayout(pHScrollBarLayout); m_pHScrollBar = new ReversibleScrollBar(Qt::Horizontal, &m_pOptions->m_bRightToLeftLanguage); pHScrollBarLayout->addWidget(m_pHScrollBar); m_pCornerWidget = new QWidget(m_pMainWidget); pHScrollBarLayout->addWidget(m_pCornerWidget); connect(m_pDiffVScrollBar, &QScrollBar::valueChanged, m_pOverview, &Overview::setFirstLine); connect(m_pDiffVScrollBar, &QScrollBar::valueChanged, m_pDiffTextWindow1, &DiffTextWindow::setFirstLine); connect(m_pHScrollBar, &ReversibleScrollBar::valueChanged2, m_pDiffTextWindow1, &DiffTextWindow::setHorizScrollOffset); connect(m_pDiffTextWindow1, &DiffTextWindow::newSelection, this, &KDiff3App::slotSelectionStart); connect(m_pDiffTextWindow1, &DiffTextWindow::selectionEnd, this, &KDiff3App::slotSelectionEnd); connect(m_pDiffTextWindow1, &DiffTextWindow::scrollDiffTextWindow, this, &KDiff3App::scrollDiffTextWindow); m_pDiffTextWindow1->installEventFilter(this); connect(m_pDiffVScrollBar, &QScrollBar::valueChanged, m_pDiffTextWindow2, &DiffTextWindow::setFirstLine); connect(m_pHScrollBar, &ReversibleScrollBar::valueChanged2, m_pDiffTextWindow2, &DiffTextWindow::setHorizScrollOffset); connect(m_pDiffTextWindow2, &DiffTextWindow::newSelection, this, &KDiff3App::slotSelectionStart); connect(m_pDiffTextWindow2, &DiffTextWindow::selectionEnd, this, &KDiff3App::slotSelectionEnd); connect(m_pDiffTextWindow2, &DiffTextWindow::scrollDiffTextWindow, this, &KDiff3App::scrollDiffTextWindow); m_pDiffTextWindow2->installEventFilter(this); connect(m_pDiffVScrollBar, &QScrollBar::valueChanged, m_pDiffTextWindow3, &DiffTextWindow::setFirstLine); connect(m_pHScrollBar, &ReversibleScrollBar::valueChanged2, m_pDiffTextWindow3, &DiffTextWindow::setHorizScrollOffset); connect(m_pDiffTextWindow3, &DiffTextWindow::newSelection, this, &KDiff3App::slotSelectionStart); connect(m_pDiffTextWindow3, &DiffTextWindow::selectionEnd, this, &KDiff3App::slotSelectionEnd); connect(m_pDiffTextWindow3, &DiffTextWindow::scrollDiffTextWindow, this, &KDiff3App::scrollDiffTextWindow); m_pDiffTextWindow3->installEventFilter(this); MergeResultWindow* p = m_pMergeResultWindow; connect(m_pMergeVScrollBar, &QScrollBar::valueChanged, p, &MergeResultWindow::setFirstLine); connect(m_pHScrollBar, &ReversibleScrollBar::valueChanged2, p, &MergeResultWindow::setHorizScrollOffset); connect(p, &MergeResultWindow::scrollMergeResultWindow, this, &KDiff3App::scrollMergeResultWindow); connect(p, &MergeResultWindow::sourceMask, this, &KDiff3App::sourceMask); connect(p, &MergeResultWindow::resizeSignal, this, &KDiff3App::resizeMergeResultWindow); connect(p, &MergeResultWindow::selectionEnd, this, &KDiff3App::slotSelectionEnd); connect(p, &MergeResultWindow::newSelection, this, &KDiff3App::slotSelectionStart); connect(p, &MergeResultWindow::modifiedChanged, this, &KDiff3App::slotOutputModified); connect(p, &MergeResultWindow::modifiedChanged, m_pMergeResultWindowTitle, &WindowTitleWidget::slotSetModified); connect(p, &MergeResultWindow::updateAvailabilities, this, &KDiff3App::slotUpdateAvailabilities); connect(p, &MergeResultWindow::showPopupMenu, this, &KDiff3App::showPopupMenu); connect(p, &MergeResultWindow::noRelevantChangesDetected, this, &KDiff3App::slotNoRelevantChangesDetected); sourceMask(0, 0); connect(p, &MergeResultWindow::setFastSelectorRange, m_pDiffTextWindow1, &DiffTextWindow::setFastSelectorRange); connect(p, &MergeResultWindow::setFastSelectorRange, m_pDiffTextWindow2, &DiffTextWindow::setFastSelectorRange); connect(p, &MergeResultWindow::setFastSelectorRange, m_pDiffTextWindow3, &DiffTextWindow::setFastSelectorRange); connect(m_pDiffTextWindow1, &DiffTextWindow::setFastSelectorLine, p, &MergeResultWindow::slotSetFastSelectorLine); connect(m_pDiffTextWindow2, &DiffTextWindow::setFastSelectorLine, p, &MergeResultWindow::slotSetFastSelectorLine); connect(m_pDiffTextWindow3, &DiffTextWindow::setFastSelectorLine, p, &MergeResultWindow::slotSetFastSelectorLine); connect(m_pDiffTextWindow1, &DiffTextWindow::gotFocus, p, &MergeResultWindow::updateSourceMask); connect(m_pDiffTextWindow2, &DiffTextWindow::gotFocus, p, &MergeResultWindow::updateSourceMask); connect(m_pDiffTextWindow3, &DiffTextWindow::gotFocus, p, &MergeResultWindow::updateSourceMask); connect(m_pDirectoryMergeInfo, &DirectoryMergeInfo::gotFocus, p, &MergeResultWindow::updateSourceMask); connect(m_pDiffTextWindow1, &DiffTextWindow::resizeHeightChangedSignal, this, &KDiff3App::resizeDiffTextWindowHeight); // The following two connects cause the wordwrap to be recalced thrice, just to make sure. Better than forgetting one. connect(m_pDiffTextWindow1, &DiffTextWindow::resizeWidthChangedSignal, this, &KDiff3App::postRecalcWordWrap); connect(m_pDiffTextWindow2, &DiffTextWindow::resizeWidthChangedSignal, this, &KDiff3App::postRecalcWordWrap); connect(m_pDiffTextWindow3, &DiffTextWindow::resizeWidthChangedSignal, this, &KDiff3App::postRecalcWordWrap); m_pDiffTextWindow1->setFocus(); m_pMainWidget->setMinimumSize(50, 50); m_pCornerWidget->setFixedSize(m_pDiffVScrollBar->width(), m_pHScrollBar->height()); showWindowA->setChecked(true); showWindowB->setChecked(true); showWindowC->setChecked(true); } int ManualDiffHelpEntry::calcManualDiffFirstDiff3LineIdx(const Diff3LineVector& d3lv) { int i; for(i = 0; i < d3lv.size(); ++i) { const Diff3Line& d3l = *d3lv[i]; if((lineA1 >= 0 && lineA1 == d3l.getLineA()) || (lineB1 >= 0 && lineB1 == d3l.getLineB()) || (lineC1 >= 0 && lineC1 == d3l.getLineC())) return i; } return -1; } // called after word wrap is complete void KDiff3App::slotFinishMainInit() { Q_ASSERT(m_pDiffTextWindow1 != nullptr && m_pDiffVScrollBar != nullptr); setHScrollBarRange(); int newHeight = m_pDiffTextWindow1->getNofVisibleLines(); /*int newWidth = m_pDiffTextWindow1->getNofVisibleColumns();*/ m_DTWHeight = newHeight; m_pDiffVScrollBar->setRange(0, std::max(0, m_neededLines + 1 - newHeight)); m_pDiffVScrollBar->setPageStep(newHeight); m_pOverview->setRange(m_pDiffVScrollBar->value(), m_pDiffVScrollBar->pageStep()); int d3l = -1; if(!m_manualDiffHelpList.empty()) d3l = m_manualDiffHelpList.front().calcManualDiffFirstDiff3LineIdx(m_diff3LineVector); if(d3l >= 0 && m_pDiffTextWindow1) { int line = m_pDiffTextWindow1->convertDiff3LineIdxToLine(d3l); m_pDiffVScrollBar->setValue(std::max(0, line - 1)); } else { m_pMergeResultWindow->slotGoTop(); if(!m_outputFilename.isEmpty() && !m_pMergeResultWindow->isUnsolvedConflictAtCurrent()) m_pMergeResultWindow->slotGoNextUnsolvedConflict(); } if(m_pCornerWidget) m_pCornerWidget->setFixedSize(m_pDiffVScrollBar->width(), m_pHScrollBar->height()); slotUpdateAvailabilities(); setUpdatesEnabled(true); // TODO What bug? Seems fixed. // Workaround for a Qt-bug /*QList treeViews = findChildren(); foreach(QTreeView* pTreeView, treeViews) { pTreeView->setUpdatesEnabled(true); }*/ bool bVisibleMergeResultWindow = !m_outputFilename.isEmpty(); TotalDiffStatus* pTotalDiffStatus = &m_totalDiffStatus; if(m_bLoadFiles) { if(bVisibleMergeResultWindow) m_pMergeResultWindow->showNrOfConflicts(); else if( // Avoid showing this message during startup without parameters. !(m_sd1.getAliasName().isEmpty() && m_sd2.getAliasName().isEmpty() && m_sd3.getAliasName().isEmpty()) && (m_sd1.isValid() && m_sd2.isValid() && m_sd3.isValid())) { QString totalInfo; if(pTotalDiffStatus->bBinaryAEqB && pTotalDiffStatus->bBinaryAEqC) totalInfo += i18n("All input files are binary equal."); else if(pTotalDiffStatus->bTextAEqB && pTotalDiffStatus->bTextAEqC) totalInfo += i18n("All input files contain the same text, but are not binary equal."); else { if(pTotalDiffStatus->bBinaryAEqB) totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("A"), i18n("B")); else if(pTotalDiffStatus->bTextAEqB) totalInfo += i18n("Files %1 and %2 have equal text, but are not binary equal. \n", i18n("A"), i18n("B")); if(pTotalDiffStatus->bBinaryAEqC) totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("A"), i18n("C")); else if(pTotalDiffStatus->bTextAEqC) totalInfo += i18n("Files %1 and %2 have equal text, but are not binary equal. \n", i18n("A"), i18n("C")); if(pTotalDiffStatus->bBinaryBEqC) totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("B"), i18n("C")); else if(pTotalDiffStatus->bTextBEqC) totalInfo += i18n("Files %1 and %2 have equal text, but are not binary equal. \n", i18n("B"), i18n("C")); } if(!totalInfo.isEmpty()) KMessageBox::information(this, totalInfo); } if(bVisibleMergeResultWindow && (!m_sd1.isText() || !m_sd2.isText() || !m_sd3.isText())) { KMessageBox::information(this, i18n( "Some input files do not seem to be pure text files.\n" "Note that the KDiff3 merge was not meant for binary data.\n" "Continue at your own risk.")); } if(m_sd1.isIncompleteConversion() || m_sd2.isIncompleteConversion() || m_sd3.isIncompleteConversion()) { QString files; if(m_sd1.isIncompleteConversion()) files += i18n("A"); if(m_sd2.isIncompleteConversion()) files += files.isEmpty() ? i18n("B") : i18n(", B"); if(m_sd3.isIncompleteConversion()) files += files.isEmpty() ? i18n("C") : i18n(", C"); KMessageBox::information(this, i18n("Some input characters could not be converted to valid unicode.\n" "You might be using the wrong codec. (e.g. UTF-8 for non UTF-8 files).\n" "Do not save the result if unsure. Continue at your own risk.\n" "Affected input files are in %1.", files)); } } if(bVisibleMergeResultWindow && m_pMergeResultWindow) { m_pMergeResultWindow->setFocus(); } else if(m_pDiffTextWindow1) { m_pDiffTextWindow1->setFocus(); } } void KDiff3App::resizeEvent(QResizeEvent* e) { QSplitter::resizeEvent(e); if(m_pCornerWidget) m_pCornerWidget->setFixedSize(m_pDiffVScrollBar->width(), m_pHScrollBar->height()); } void KDiff3App::wheelEvent(QWheelEvent* pWheelEvent) { pWheelEvent->accept(); int deltaX = 0; int d = pWheelEvent->delta(); //As per QT documentation, some mice/OS combos send delta values //less than 120 units(15 degrees) d = d + m_iCumulativeWheelDelta; if(d > -120 && d < 120) { //not enough for a full step in either direction, add it up //to use on a successive call m_iCumulativeWheelDelta = d; } else { //reset cumulative tracking of the wheel since we have enough //for a 15 degree movement m_iCumulativeWheelDelta = 0; } int deltaY = -d / 120 * QApplication::wheelScrollLines(); scrollDiffTextWindow(deltaX, deltaY); } void KDiff3App::keyPressEvent(QKeyEvent* keyEvent) { if(keyEvent->key() == Qt::Key_Escape && m_pKDiff3Shell && m_pOptions->m_bEscapeKeyQuits) { m_pKDiff3Shell->close(); return; } //FIXME: Move use QAction int deltaX = 0; int deltaY = 0; int pageSize = m_DTWHeight; bool bCtrl = (keyEvent->QInputEvent::modifiers() & Qt::ControlModifier) != 0; switch(keyEvent->key()) { case Qt::Key_Down: if(!bCtrl) ++deltaY; break; case Qt::Key_Up: if(!bCtrl) --deltaY; break; case Qt::Key_PageDown: if(!bCtrl) deltaY += pageSize; break; case Qt::Key_PageUp: if(!bCtrl) deltaY -= pageSize; break; case Qt::Key_Left: if(!bCtrl) --deltaX; break; case Qt::Key_Right: if(!bCtrl) ++deltaX; break; case Qt::Key_Home: if(bCtrl) m_pDiffVScrollBar->setValue(0); else m_pHScrollBar->setValue(0); break; case Qt::Key_End: if(bCtrl) m_pDiffVScrollBar->setValue(m_pDiffVScrollBar->maximum()); else m_pHScrollBar->setValue(m_pHScrollBar->maximum()); break; default: break; } scrollDiffTextWindow(deltaX, deltaY); } bool KDiff3App::eventFilter(QObject* o, QEvent* e) { //TODO: Move this into DiffTextWindow::DropEvent if(e->type() == QEvent::Drop) { QDropEvent* pDropEvent = static_cast(e); pDropEvent->accept(); if(pDropEvent->mimeData()->hasUrls()) { QList urlList = pDropEvent->mimeData()->urls(); if(canContinue() && !urlList.isEmpty()) { raise(); QString filename = urlList.first().toLocalFile(); if(o == m_pDiffTextWindow1) m_sd1.setFilename(filename); else if(o == m_pDiffTextWindow2) m_sd2.setFilename(filename); else if(o == m_pDiffTextWindow3) m_sd3.setFilename(filename); mainInit(); } } else if(pDropEvent->mimeData()->hasText()) { QString text = pDropEvent->mimeData()->text(); if(canContinue()) { QStringList errors; raise(); if(o == m_pDiffTextWindow1) errors = m_sd1.setData(text); else if(o == m_pDiffTextWindow2) errors = m_sd2.setData(text); else if(o == m_pDiffTextWindow3) errors = m_sd3.setData(text); foreach(const QString& error, errors) { KMessageBox::error(m_pOptionDialog, error); } mainInit(); } } } return QSplitter::eventFilter(o, e); // standard event processing } void KDiff3App::slotFileOpen() { if(!canContinue()) return; //create dummy DirectoryInfo record for first run so we don't crash. if(m_dirinfo == nullptr) m_dirinfo = QSharedPointer::create(); 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"), KGuiItem(i18n("Abort")), KGuiItem(i18n("Continue Merging"))); if(result != KMessageBox::Yes) return; } slotStatusMsg(i18n("Opening files...")); for(;;) { QPointer d = QPointer(new OpenDialog(this, QDir::toNativeSeparators(m_bDirCompare ? m_dirinfo->dirA().prettyAbsPath() : m_sd1.isFromBuffer() ? QString("") : m_sd1.getAliasName()), QDir::toNativeSeparators(m_bDirCompare ? m_dirinfo->dirB().prettyAbsPath() : m_sd2.isFromBuffer() ? QString("") : m_sd2.getAliasName()), QDir::toNativeSeparators(m_bDirCompare ? m_dirinfo->dirC().prettyAbsPath() : m_sd3.isFromBuffer() ? QString("") : m_sd3.getAliasName()), m_bDirCompare ? !m_dirinfo->destDir().prettyAbsPath().isEmpty() : !m_outputFilename.isEmpty(), QDir::toNativeSeparators(m_bDefaultFilename ? QString("") : m_outputFilename), &m_pOptionDialog->m_options)); int status = d->exec(); if(status == QDialog::Accepted) { m_sd1.setFilename(d->m_pLineA->currentText()); m_sd2.setFilename(d->m_pLineB->currentText()); m_sd3.setFilename(d->m_pLineC->currentText()); if(d->m_pMerge->isChecked()) { if(d->m_pLineOut->currentText().isEmpty()) { m_outputFilename = "unnamed.txt"; m_bDefaultFilename = true; } else { m_outputFilename = d->m_pLineOut->currentText(); m_bDefaultFilename = false; } } else m_outputFilename = ""; m_bDirCompare = FileAccess(m_sd1.getFilename()).isDir(); bool bSuccess = improveFilenames(false); if(!bSuccess) continue; if(m_bDirCompare) { m_pDirectoryMergeSplitter->show(); if(m_pMainWidget != nullptr) { m_pMainWidget->hide(); } break; } else { m_pDirectoryMergeSplitter->hide(); mainInit(); 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")); continue; } } } break; } slotUpdateAvailabilities(); slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotFileOpen2(const QString& fn1, const QString& fn2, const QString& fn3, const QString& ofn, const QString& an1, const QString& an2, const QString& an3, TotalDiffStatus* pTotalDiffStatus) { if(!canContinue()) return; if(fn1.isEmpty() && fn2.isEmpty() && fn3.isEmpty() && ofn.isEmpty() && m_pMainWidget != nullptr) { m_pMainWidget->hide(); return; } slotStatusMsg(i18n("Opening files...")); m_sd1.setFilename(fn1); m_sd2.setFilename(fn2); m_sd3.setFilename(fn3); m_sd1.setAliasName(an1); m_sd2.setAliasName(an2); m_sd3.setAliasName(an3); if(!ofn.isEmpty()) { m_outputFilename = ofn; m_bDefaultFilename = false; } else { m_outputFilename = ""; m_bDefaultFilename = true; } improveFilenames(true); // Create new window for KDiff3 for directory comparison. if(!FileAccess(m_sd1.getFilename()).isDir()) { mainInit(pTotalDiffStatus); if(pTotalDiffStatus != nullptr) return; if(!((!m_sd1.isEmpty() && !m_sd1.hasData()) || (!m_sd2.isEmpty() && !m_sd2.hasData()) || (!m_sd3.isEmpty() && !m_sd3.hasData()))) { if(m_pDirectoryMergeWindow != nullptr && m_pDirectoryMergeWindow->isVisible() && !dirShowBoth->isChecked()) { slotDirViewToggle(); } } } slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotFileNameChanged(const QString& fileName, e_SrcSelector winIdx) { QString fn1 = m_sd1.getFilename(); QString an1 = m_sd1.getAliasName(); QString fn2 = m_sd2.getFilename(); QString an2 = m_sd2.getAliasName(); QString fn3 = m_sd3.getFilename(); QString an3 = m_sd3.getAliasName(); if(winIdx == A) { fn1 = fileName; an1 = ""; } if(winIdx == B) { fn2 = fileName; an2 = ""; } if(winIdx == C) { fn3 = fileName; an3 = ""; } slotFileOpen2(fn1, fn2, fn3, m_outputFilename, an1, an2, an3, nullptr); } void KDiff3App::slotEditCut() { slotStatusMsg(i18n("Cutting selection...")); QString s; if(m_pMergeResultWindow != nullptr) { s = m_pMergeResultWindow->getSelection(); m_pMergeResultWindow->deleteSelection(); m_pMergeResultWindow->update(); } if(!s.isEmpty()) { QApplication::clipboard()->setText(s, QClipboard::Clipboard); } slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotEditCopy() { slotStatusMsg(i18n("Copying selection to clipboard...")); QString s; if(m_pDiffTextWindow1 != nullptr) s = m_pDiffTextWindow1->getSelection(); if(s.isEmpty() && m_pDiffTextWindow2 != nullptr) s = m_pDiffTextWindow2->getSelection(); if(s.isEmpty() && m_pDiffTextWindow3 != nullptr) s = m_pDiffTextWindow3->getSelection(); if(s.isEmpty() && m_pMergeResultWindow != nullptr) s = m_pMergeResultWindow->getSelection(); if(!s.isEmpty()) { QApplication::clipboard()->setText(s, QClipboard::Clipboard); } slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotEditPaste() { slotStatusMsg(i18n("Inserting clipboard contents...")); if(m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isVisible()) { m_pMergeResultWindow->pasteClipboard(false); } else if(canContinue()) { QStringList errors; bool do_init = false; if(m_pDiffTextWindow1->hasFocus()) { errors = m_sd1.setData(QApplication::clipboard()->text(QClipboard::Clipboard)); do_init = true; } else if(m_pDiffTextWindow2->hasFocus()) { errors = m_sd2.setData(QApplication::clipboard()->text(QClipboard::Clipboard)); do_init = true; } else if(m_pDiffTextWindow3->hasFocus()) { errors = m_sd3.setData(QApplication::clipboard()->text(QClipboard::Clipboard)); do_init = true; } foreach(const QString& error, errors) { KMessageBox::error(m_pOptionDialog, error); } if(do_init) { mainInit(); } } slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotEditSelectAll() { LineRef l = 0; int p = 0; // needed as dummy return values if(m_pMergeResultWindow && m_pMergeResultWindow->hasFocus()) { m_pMergeResultWindow->setSelection(0, 0, m_pMergeResultWindow->getNofLines(), 0); } else if(m_pDiffTextWindow1 && m_pDiffTextWindow1->hasFocus()) { m_pDiffTextWindow1->setSelection(0, 0, m_pDiffTextWindow1->getNofLines(), 0, l, p); } else if(m_pDiffTextWindow2 && m_pDiffTextWindow2->hasFocus()) { m_pDiffTextWindow2->setSelection(0, 0, m_pDiffTextWindow2->getNofLines(), 0, l, p); } else if(m_pDiffTextWindow3 && m_pDiffTextWindow3->hasFocus()) { m_pDiffTextWindow3->setSelection(0, 0, m_pDiffTextWindow3->getNofLines(), 0, l, p); } slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotGoCurrent() { if(m_pMergeResultWindow) m_pMergeResultWindow->slotGoCurrent(); } void KDiff3App::slotGoTop() { if(m_pMergeResultWindow) m_pMergeResultWindow->slotGoTop(); } void KDiff3App::slotGoBottom() { if(m_pMergeResultWindow) m_pMergeResultWindow->slotGoBottom(); } void KDiff3App::slotGoPrevUnsolvedConflict() { if(m_pMergeResultWindow) m_pMergeResultWindow->slotGoPrevUnsolvedConflict(); } void KDiff3App::slotGoNextUnsolvedConflict() { m_bTimerBlock = false; if(m_pMergeResultWindow) m_pMergeResultWindow->slotGoNextUnsolvedConflict(); } void KDiff3App::slotGoPrevConflict() { if(m_pMergeResultWindow) m_pMergeResultWindow->slotGoPrevConflict(); } void KDiff3App::slotGoNextConflict() { m_bTimerBlock = false; if(m_pMergeResultWindow) m_pMergeResultWindow->slotGoNextConflict(); } void KDiff3App::slotGoPrevDelta() { if(m_pMergeResultWindow) m_pMergeResultWindow->slotGoPrevDelta(); } void KDiff3App::slotGoNextDelta() { if(m_pMergeResultWindow) m_pMergeResultWindow->slotGoNextDelta(); } void KDiff3App::choose(e_SrcSelector choice) { if(!m_bTimerBlock) { if(m_pDirectoryMergeWindow && m_pDirectoryMergeWindow->hasFocus()) { if(choice == A) m_pDirectoryMergeWindow->slotCurrentChooseA(); if(choice == B) m_pDirectoryMergeWindow->slotCurrentChooseB(); if(choice == C) m_pDirectoryMergeWindow->slotCurrentChooseC(); chooseA->setChecked(false); chooseB->setChecked(false); chooseC->setChecked(false); } else if(m_pMergeResultWindow) { m_pMergeResultWindow->choose(choice); if(autoAdvance->isChecked()) { m_bTimerBlock = true; QTimer::singleShot(m_pOptions->m_autoAdvanceDelay, this, &KDiff3App::slotGoNextUnsolvedConflict); } } } } void KDiff3App::slotChooseA() { choose(A); } void KDiff3App::slotChooseB() { choose(B); } void KDiff3App::slotChooseC() { choose(C); } void KDiff3App::slotAutoSolve() { if(m_pMergeResultWindow) { m_pMergeResultWindow->slotAutoSolve(); // m_pMergeWindowFrame->show(); incompatible with bPreserveCarriageReturn m_pMergeResultWindow->showNrOfConflicts(); slotUpdateAvailabilities(); } } void KDiff3App::slotUnsolve() { if(m_pMergeResultWindow) { m_pMergeResultWindow->slotUnsolve(); } } void KDiff3App::slotMergeHistory() { if(m_pMergeResultWindow) { m_pMergeResultWindow->slotMergeHistory(); } } void KDiff3App::slotRegExpAutoMerge() { if(m_pMergeResultWindow) { m_pMergeResultWindow->slotRegExpAutoMerge(); } } void KDiff3App::slotSplitDiff() { LineRef firstLine; LineRef lastLine; DiffTextWindow* pDTW = nullptr; if(m_pDiffTextWindow1) { pDTW = m_pDiffTextWindow1; pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords); } if(firstLine < 0 && m_pDiffTextWindow2) { pDTW = m_pDiffTextWindow2; pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords); } if(firstLine < 0 && m_pDiffTextWindow3) { pDTW = m_pDiffTextWindow3; pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords); } if(pDTW && firstLine >= 0 && m_pMergeResultWindow) { pDTW->resetSelection(); m_pMergeResultWindow->slotSplitDiff(firstLine, lastLine); } } void KDiff3App::slotJoinDiffs() { LineRef firstLine; LineRef lastLine; DiffTextWindow* pDTW = nullptr; if(m_pDiffTextWindow1) { pDTW = m_pDiffTextWindow1; pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords); } if(firstLine < 0 && m_pDiffTextWindow2) { pDTW = m_pDiffTextWindow2; pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords); } if(firstLine < 0 && m_pDiffTextWindow3) { pDTW = m_pDiffTextWindow3; pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords); } if(pDTW && firstLine >= 0 && m_pMergeResultWindow) { pDTW->resetSelection(); m_pMergeResultWindow->slotJoinDiffs(firstLine, lastLine); } } void KDiff3App::slotConfigure() { m_pOptionDialog->setState(); m_pOptionDialog->setMinimumHeight(m_pOptionDialog->minimumHeight() + 40); m_pOptionDialog->exec(); slotRefresh(); } void KDiff3App::slotConfigureKeys() { KShortcutsDialog::configure(actionCollection(), KShortcutsEditor::LetterShortcutsAllowed, this); } void KDiff3App::slotRefresh() { QApplication::setFont(m_pOptions->m_appFont); if(m_pDiffTextWindow1 != nullptr) { m_pDiffTextWindow1->setFont(m_pOptions->m_font); m_pDiffTextWindow1->update(); } if(m_pDiffTextWindow2 != nullptr) { m_pDiffTextWindow2->setFont(m_pOptions->m_font); m_pDiffTextWindow2->update(); } if(m_pDiffTextWindow3 != nullptr) { m_pDiffTextWindow3->setFont(m_pOptions->m_font); m_pDiffTextWindow3->update(); } if(m_pMergeResultWindow != nullptr) { m_pMergeResultWindow->setFont(m_pOptions->m_font); m_pMergeResultWindow->update(); } if(m_pHScrollBar != nullptr) { m_pHScrollBar->setAgain(); } if(m_pDiffWindowSplitter != nullptr) { m_pDiffWindowSplitter->setOrientation(m_pOptions->m_bHorizDiffWindowSplitting ? Qt::Horizontal : Qt::Vertical); } if(m_pDirectoryMergeWindow) { m_pDirectoryMergeWindow->updateFileVisibilities(); } } void KDiff3App::slotSelectionStart() { //editCopy->setEnabled( false ); //editCut->setEnabled( false ); const QObject* s = sender(); if(m_pDiffTextWindow1 && s != m_pDiffTextWindow1) m_pDiffTextWindow1->resetSelection(); if(m_pDiffTextWindow2 && s != m_pDiffTextWindow2) m_pDiffTextWindow2->resetSelection(); if(m_pDiffTextWindow3 && s != m_pDiffTextWindow3) m_pDiffTextWindow3->resetSelection(); if(m_pMergeResultWindow && s != m_pMergeResultWindow) m_pMergeResultWindow->resetSelection(); } void KDiff3App::slotSelectionEnd() { //const QObject* s = sender(); //editCopy->setEnabled(true); //editCut->setEnabled( s==m_pMergeResultWindow ); if(m_pOptions->m_bAutoCopySelection) { slotEditCopy(); } else { QClipboard* clipBoard = QApplication::clipboard(); if(clipBoard->supportsSelection()) { QString s; if(m_pDiffTextWindow1 != nullptr) s = m_pDiffTextWindow1->getSelection(); if(s.isEmpty() && m_pDiffTextWindow2 != nullptr) s = m_pDiffTextWindow2->getSelection(); if(s.isEmpty() && m_pDiffTextWindow3 != nullptr) s = m_pDiffTextWindow3->getSelection(); if(s.isEmpty() && m_pMergeResultWindow != nullptr) s = m_pMergeResultWindow->getSelection(); if(!s.isEmpty()) { clipBoard->setText(s, QClipboard::Selection); } } } } void KDiff3App::slotClipboardChanged() { const QClipboard* clipboard = QApplication::clipboard(); const QMimeData* mimeData = clipboard->mimeData(); if(mimeData->hasText()) { QString s = clipboard->text(); editPaste->setEnabled(!s.isEmpty()); } else { editPaste->setEnabled(false); } } void KDiff3App::slotOutputModified(bool bModified) { if(bModified && !m_bOutputModified) { m_bOutputModified = true; slotUpdateAvailabilities(); } } void KDiff3App::slotAutoAdvanceToggled() { m_pOptions->m_bAutoAdvance = autoAdvance->isChecked(); } void KDiff3App::slotWordWrapToggled() { m_pOptions->m_bWordWrap = wordWrap->isChecked(); postRecalcWordWrap(); } // Enable or disable all widgets except the status bar widget. void KDiff3App::mainWindowEnable(bool bEnable) { if(QMainWindow* pWindow = dynamic_cast(window())) { QWidget* pStatusBarWidget = pWindow->statusBar(); pWindow->setEnabled(bEnable); pStatusBarWidget->setEnabled(true); } } void KDiff3App::postRecalcWordWrap() { if(!m_bRecalcWordWrapPosted) { m_bRecalcWordWrapPosted = true; m_firstD3LIdx = -1; QTimer::singleShot(1 /* ms */, this, &KDiff3App::slotRecalcWordWrap); } else { g_pProgressDialog->cancel(ProgressDialog::eResize); } } void KDiff3App::slotRecalcWordWrap() { recalcWordWrap(); } // visibleTextWidthForPrinting is >=0 only for printing, otherwise the really visible width is used void KDiff3App::recalcWordWrap(int visibleTextWidthForPrinting) { m_bRecalcWordWrapPosted = true; mainWindowEnable(false); if(m_firstD3LIdx < 0) { m_firstD3LIdx = 0; if(m_pDiffTextWindow1) m_firstD3LIdx = m_pDiffTextWindow1->convertLineToDiff3LineIdx(m_pDiffTextWindow1->getFirstLine()); } // Convert selection to D3L-coords (converting back happens in DiffTextWindow::recalcWordWrap() if(m_pDiffTextWindow1) m_pDiffTextWindow1->convertSelectionToD3LCoords(); if(m_pDiffTextWindow2) m_pDiffTextWindow2->convertSelectionToD3LCoords(); if(m_pDiffTextWindow3) m_pDiffTextWindow3->convertSelectionToD3LCoords(); g_pProgressDialog->clearCancelState(); // clear cancelled state if previously set if(!m_diff3LineList.empty()) { if(m_pOptions->m_bWordWrap) { Diff3LineList::iterator i; int sumOfLines = 0; for(i = m_diff3LineList.begin(); i != m_diff3LineList.end(); ++i) { Diff3Line& d3l = *i; d3l.linesNeededForDisplay = 1; d3l.sumLinesNeededForDisplay = sumOfLines; sumOfLines += d3l.linesNeededForDisplay; } // Let every window calc how many lines will be needed. if(m_pDiffTextWindow1) { m_pDiffTextWindow1->recalcWordWrap(true, 0, visibleTextWidthForPrinting); } if(m_pDiffTextWindow2) { m_pDiffTextWindow2->recalcWordWrap(true, 0, visibleTextWidthForPrinting); } if(m_pDiffTextWindow3) { m_pDiffTextWindow3->recalcWordWrap(true, 0, visibleTextWidthForPrinting); } } else { m_neededLines = m_diff3LineVector.size(); if(m_pDiffTextWindow1) m_pDiffTextWindow1->recalcWordWrap(false, 0, 0); if(m_pDiffTextWindow2) m_pDiffTextWindow2->recalcWordWrap(false, 0, 0); if(m_pDiffTextWindow3) m_pDiffTextWindow3->recalcWordWrap(false, 0, 0); } bool bRunnablesStarted = startRunnables(); if(!bRunnablesStarted) slotFinishRecalcWordWrap(visibleTextWidthForPrinting); else { g_pProgressDialog->setInformation(m_pOptions->m_bWordWrap ? i18n("Word wrap (Cancel disables word wrap)") : i18n("Calculating max width for horizontal scrollbar"), false); } } else { //don't leave proccessing incomplete if m_diff3LineList isEmpty as when an error occures during reading. slotFinishRecalcWordWrap(visibleTextWidthForPrinting); } } void KDiff3App::slotFinishRecalcWordWrap(int visibleTextWidthForPrinting) { g_pProgressDialog->pop(); if(m_pOptions->m_bWordWrap && g_pProgressDialog->wasCancelled()) { if(g_pProgressDialog->cancelReason() == ProgressDialog::eUserAbort) { wordWrap->setChecked(false); m_pOptions->m_bWordWrap = wordWrap->isChecked(); QTimer::singleShot(1 /* ms */, this, &KDiff3App::slotRecalcWordWrap); // do it again } else // eResize { QTimer::singleShot(1 /* ms */, this, &KDiff3App::slotRecalcWordWrap); // do it again } return; } else { m_bRecalcWordWrapPosted = false; } g_pProgressDialog->setStayHidden(false); bool bPrinting = visibleTextWidthForPrinting >= 0; if(!m_diff3LineList.empty()) { if(m_pOptions->m_bWordWrap) { Diff3LineList::iterator i; int sumOfLines = 0; for(i = m_diff3LineList.begin(); i != m_diff3LineList.end(); ++i) { Diff3Line& d3l = *i; d3l.sumLinesNeededForDisplay = sumOfLines; sumOfLines += d3l.linesNeededForDisplay; } // Finish the word wrap if(m_pDiffTextWindow1) m_pDiffTextWindow1->recalcWordWrap(true, sumOfLines, visibleTextWidthForPrinting); if(m_pDiffTextWindow2) m_pDiffTextWindow2->recalcWordWrap(true, sumOfLines, visibleTextWidthForPrinting); if(m_pDiffTextWindow3) m_pDiffTextWindow3->recalcWordWrap(true, sumOfLines, visibleTextWidthForPrinting); m_neededLines = sumOfLines; } else { if(m_pDiffTextWindow1) m_pDiffTextWindow1->recalcWordWrap(false, 1, 0); if(m_pDiffTextWindow2) m_pDiffTextWindow2->recalcWordWrap(false, 1, 0); if(m_pDiffTextWindow3) m_pDiffTextWindow3->recalcWordWrap(false, 1, 0); } slotStatusMsg(QString()); } if(!bPrinting) { if(m_pOverview) m_pOverview->slotRedraw(); if(m_pDiffVScrollBar) m_pDiffVScrollBar->setRange(0, std::max(0, m_neededLines + 1 - m_DTWHeight)); if(m_pDiffTextWindow1) { if(m_pDiffVScrollBar) m_pDiffVScrollBar->setValue(m_pDiffTextWindow1->convertDiff3LineIdxToLine(m_firstD3LIdx)); setHScrollBarRange(); m_pHScrollBar->setValue(0); } } mainWindowEnable(true); if(m_bFinishMainInit) { m_bFinishMainInit = false; slotFinishMainInit(); } if(m_pEventLoopForPrinting) m_pEventLoopForPrinting->quit(); } void KDiff3App::slotShowWhiteSpaceToggled() { m_pOptions->m_bShowWhiteSpaceCharacters = showWhiteSpaceCharacters->isChecked(); m_pOptions->m_bShowWhiteSpace = showWhiteSpace->isChecked(); if(m_pDiffTextWindow1 != nullptr) m_pDiffTextWindow1->update(); if(m_pDiffTextWindow2 != nullptr) m_pDiffTextWindow2->update(); if(m_pDiffTextWindow3 != nullptr) m_pDiffTextWindow3->update(); if(m_pMergeResultWindow != nullptr) m_pMergeResultWindow->update(); if(m_pOverview != nullptr) m_pOverview->slotRedraw(); } void KDiff3App::slotShowLineNumbersToggled() { m_pOptions->m_bShowLineNumbers = showLineNumbers->isChecked(); if(wordWrap->isChecked()) recalcWordWrap(); if(m_pDiffTextWindow1 != nullptr) m_pDiffTextWindow1->update(); if(m_pDiffTextWindow2 != nullptr) m_pDiffTextWindow2->update(); if(m_pDiffTextWindow3 != nullptr) m_pDiffTextWindow3->update(); } /// Return true for success, else false bool KDiff3App::improveFilenames(bool bCreateNewInstance) { FileAccess f1(m_sd1.getFilename()); FileAccess f2(m_sd2.getFilename()); FileAccess f3(m_sd3.getFilename()); FileAccess f4(m_outputFilename); if(f1.isFile() && f1.exists()) { if(f2.isDir()) { f2.addPath(f1.fileName()); if(f2.isFile() && f2.exists()) m_sd2.setFileAccess(f2); } if(f3.isDir()) { f3.addPath(f1.fileName()); if(f3.isFile() && f3.exists()) m_sd3.setFileAccess(f3); } if(f4.isDir()) { f4.addPath(f1.fileName()); if(f4.isFile() && f4.exists()) m_outputFilename = f4.absoluteFilePath(); } } else if(f1.isDir()) { if(bCreateNewInstance) { emit createNewInstance(f1.absoluteFilePath(), f2.absoluteFilePath(), f3.absoluteFilePath()); } else { bool bDirCompare = m_bDirCompare; FileAccess destDir; if(!m_bDefaultFilename) destDir = f4; m_pDirectoryMergeSplitter->show(); if(m_pMainWidget != nullptr) m_pMainWidget->hide(); setUpdatesEnabled(true); m_dirinfo = QSharedPointer::create(f1, f2, f3, destDir); bool bSuccess = m_pDirectoryMergeWindow->init( m_dirinfo, !m_outputFilename.isEmpty()); //This is a bug if it still happens. Q_ASSERT(m_bDirCompare == bDirCompare); if(bSuccess) { m_sd1.reset(); if(m_pDiffTextWindow1 != nullptr) m_pDiffTextWindow1->init(QString(""), nullptr, eLineEndStyleDos, nullptr, 0, nullptr, nullptr, false); m_sd2.reset(); if(m_pDiffTextWindow2 != nullptr) m_pDiffTextWindow2->init(QString(""), nullptr, eLineEndStyleDos, nullptr, 0, nullptr, nullptr, false); m_sd3.reset(); if(m_pDiffTextWindow3 != nullptr) m_pDiffTextWindow3->init(QString(""), nullptr, eLineEndStyleDos, nullptr, 0, nullptr, nullptr, false); } slotUpdateAvailabilities(); return bSuccess; } } return true; } void KDiff3App::slotReload() { if(!canContinue()) return; mainInit(); } bool KDiff3App::canContinue() { // First test if anything must be saved. if(m_bOutputModified) { int result = KMessageBox::warningYesNoCancel(this, i18n("The merge result has not been saved."), i18n("Warning"), KGuiItem(i18n("Save && Continue")), KGuiItem(i18n("Continue 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; return true; } void KDiff3App::slotCheckIfCanContinue(bool* pbContinue) { if(pbContinue != nullptr) *pbContinue = canContinue(); } void KDiff3App::slotDirShowBoth() { if(dirShowBoth->isChecked()) { if(m_pDirectoryMergeSplitter) m_pDirectoryMergeSplitter->setVisible(m_bDirCompare); if(m_pMainWidget != nullptr) m_pMainWidget->show(); } else { bool bTextDataAvailable = (m_sd1.hasData() || m_sd2.hasData() || m_sd3.hasData()); if(m_pMainWidget != nullptr && bTextDataAvailable) { m_pMainWidget->show(); m_pDirectoryMergeSplitter->hide(); } else if(m_bDirCompare) { m_pDirectoryMergeSplitter->show(); } } slotUpdateAvailabilities(); } void KDiff3App::slotDirViewToggle() { if(m_bDirCompare) { if(!m_pDirectoryMergeSplitter->isVisible()) { m_pDirectoryMergeSplitter->show(); if(m_pMainWidget != nullptr) m_pMainWidget->hide(); } else { if(m_pMainWidget != nullptr) { m_pDirectoryMergeSplitter->hide(); m_pMainWidget->show(); } } } slotUpdateAvailabilities(); } void KDiff3App::slotShowWindowAToggled() { if(m_pDiffTextWindow1 != nullptr) { m_pDiffTextWindowFrame1->setVisible(showWindowA->isChecked()); slotUpdateAvailabilities(); } } void KDiff3App::slotShowWindowBToggled() { if(m_pDiffTextWindow2 != nullptr) { m_pDiffTextWindowFrame2->setVisible(showWindowB->isChecked()); slotUpdateAvailabilities(); } } void KDiff3App::slotShowWindowCToggled() { if(m_pDiffTextWindow3 != nullptr) { m_pDiffTextWindowFrame3->setVisible(showWindowC->isChecked()); slotUpdateAvailabilities(); } } void KDiff3App::slotEditFind() { m_pFindDialog->currentLine = 0; m_pFindDialog->currentPos = 0; m_pFindDialog->currentWindow = 1; // Use currently selected text: QString s; if(m_pDiffTextWindow1 != nullptr) s = m_pDiffTextWindow1->getSelection(); if(s.isEmpty() && m_pDiffTextWindow2 != nullptr) s = m_pDiffTextWindow2->getSelection(); if(s.isEmpty() && m_pDiffTextWindow3 != nullptr) s = m_pDiffTextWindow3->getSelection(); if(s.isEmpty() && m_pMergeResultWindow != nullptr) s = m_pMergeResultWindow->getSelection(); if(!s.isEmpty() && !s.contains('\n')) { m_pFindDialog->m_pSearchString->setText(s); } if(QDialog::Accepted == m_pFindDialog->exec()) { slotEditFindNext(); } } void KDiff3App::slotEditFindNext() { QString s = m_pFindDialog->m_pSearchString->text(); if(s.isEmpty()) { slotEditFind(); return; } bool bDirDown = true; bool bCaseSensitive = m_pFindDialog->m_pCaseSensitive->isChecked(); LineRef d3vLine = m_pFindDialog->currentLine; int posInLine = m_pFindDialog->currentPos; LineRef l = 0; int p = 0; if(m_pFindDialog->currentWindow == 1) { if(m_pFindDialog->m_pSearchInA->isChecked() && m_pDiffTextWindow1 != nullptr && m_pDiffTextWindow1->findString(s, d3vLine, posInLine, bDirDown, bCaseSensitive)) { m_pDiffTextWindow1->setSelection(d3vLine, posInLine, d3vLine, posInLine + s.length(), l, p); m_pDiffVScrollBar->setValue(l - m_pDiffVScrollBar->pageStep() / 2); m_pHScrollBar->setValue(std::max(0, p + s.length() - m_pHScrollBar->pageStep())); m_pFindDialog->currentLine = d3vLine; m_pFindDialog->currentPos = posInLine + 1; return; } m_pFindDialog->currentWindow = 2; m_pFindDialog->currentLine = 0; m_pFindDialog->currentPos = 0; } d3vLine = m_pFindDialog->currentLine; posInLine = m_pFindDialog->currentPos; if(m_pFindDialog->currentWindow == 2) { if(m_pFindDialog->m_pSearchInB->isChecked() && m_pDiffTextWindow2 != nullptr && m_pDiffTextWindow2->findString(s, d3vLine, posInLine, bDirDown, bCaseSensitive)) { m_pDiffTextWindow2->setSelection(d3vLine, posInLine, d3vLine, posInLine + s.length(), l, p); m_pDiffVScrollBar->setValue(l - m_pDiffVScrollBar->pageStep() / 2); m_pHScrollBar->setValue(std::max(0, p + s.length() - m_pHScrollBar->pageStep())); m_pFindDialog->currentLine = d3vLine; m_pFindDialog->currentPos = posInLine + 1; return; } m_pFindDialog->currentWindow = 3; m_pFindDialog->currentLine = 0; m_pFindDialog->currentPos = 0; } d3vLine = m_pFindDialog->currentLine; posInLine = m_pFindDialog->currentPos; if(m_pFindDialog->currentWindow == 3) { if(m_pFindDialog->m_pSearchInC->isChecked() && m_pDiffTextWindow3 != nullptr && m_pDiffTextWindow3->findString(s, d3vLine, posInLine, bDirDown, bCaseSensitive)) { m_pDiffTextWindow3->setSelection(d3vLine, posInLine, d3vLine, posInLine + s.length(), l, p); m_pDiffVScrollBar->setValue(l - m_pDiffVScrollBar->pageStep() / 2); m_pHScrollBar->setValue(std::max(0, p + s.length() - m_pHScrollBar->pageStep())); m_pFindDialog->currentLine = d3vLine; m_pFindDialog->currentPos = posInLine + 1; return; } m_pFindDialog->currentWindow = 4; m_pFindDialog->currentLine = 0; m_pFindDialog->currentPos = 0; } d3vLine = m_pFindDialog->currentLine; posInLine = m_pFindDialog->currentPos; if(m_pFindDialog->currentWindow == 4) { if(m_pFindDialog->m_pSearchInOutput->isChecked() && m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isVisible() && m_pMergeResultWindow->findString(s, d3vLine, posInLine, bDirDown, bCaseSensitive)) { m_pMergeResultWindow->setSelection(d3vLine, posInLine, d3vLine, posInLine + s.length()); m_pMergeVScrollBar->setValue(d3vLine - m_pMergeVScrollBar->pageStep() / 2); m_pHScrollBar->setValue(std::max(0, posInLine + s.length() - m_pHScrollBar->pageStep())); m_pFindDialog->currentLine = d3vLine; m_pFindDialog->currentPos = posInLine + 1; return; } m_pFindDialog->currentWindow = 5; m_pFindDialog->currentLine = 0; m_pFindDialog->currentPos = 0; } KMessageBox::information(this, i18n("Search complete."), i18n("Search Complete")); m_pFindDialog->currentWindow = 1; m_pFindDialog->currentLine = 0; m_pFindDialog->currentPos = 0; } void KDiff3App::slotMergeCurrentFile() { if(m_bDirCompare && m_pDirectoryMergeWindow->isVisible() && m_pDirectoryMergeWindow->isFileSelected()) { m_pDirectoryMergeWindow->mergeCurrentFile(); } else if(m_pMainWidget != nullptr && m_pMainWidget->isVisible()) { if(!canContinue()) return; if(m_outputFilename.isEmpty()) { if(!m_sd3.isEmpty() && !m_sd3.isFromBuffer()) { m_outputFilename = m_sd3.getFilename(); } else if(!m_sd2.isEmpty() && !m_sd2.isFromBuffer()) { m_outputFilename = m_sd2.getFilename(); } else if(!m_sd1.isEmpty() && !m_sd1.isFromBuffer()) { m_outputFilename = m_sd1.getFilename(); } else { m_outputFilename = "unnamed.txt"; m_bDefaultFilename = true; } } mainInit(); } } void KDiff3App::slotWinFocusNext() { QWidget* focus = qApp->focusWidget(); if(focus == m_pDirectoryMergeWindow && m_pDirectoryMergeWindow->isVisible() && !dirShowBoth->isChecked()) { slotDirViewToggle(); } std::list visibleWidgetList; if(m_pDiffTextWindow1 && m_pDiffTextWindow1->isVisible()) visibleWidgetList.push_back(m_pDiffTextWindow1); if(m_pDiffTextWindow2 && m_pDiffTextWindow2->isVisible()) visibleWidgetList.push_back(m_pDiffTextWindow2); if(m_pDiffTextWindow3 && m_pDiffTextWindow3->isVisible()) visibleWidgetList.push_back(m_pDiffTextWindow3); if(m_pMergeResultWindow && m_pMergeResultWindow->isVisible()) visibleWidgetList.push_back(m_pMergeResultWindow); if(m_bDirCompare /*m_pDirectoryMergeWindow->isVisible()*/) visibleWidgetList.push_back(m_pDirectoryMergeWindow); //if ( m_pDirectoryMergeInfo->isVisible() ) visibleWidgetList.push_back(m_pDirectoryMergeInfo->getInfoList()); std::list::iterator i = std::find(visibleWidgetList.begin(), visibleWidgetList.end(), focus); ++i; if(i == visibleWidgetList.end()) i = visibleWidgetList.begin(); if(i != visibleWidgetList.end()) { if(*i == m_pDirectoryMergeWindow && !dirShowBoth->isChecked()) { slotDirViewToggle(); } (*i)->setFocus(); } } void KDiff3App::slotWinFocusPrev() { QWidget* focus = qApp->focusWidget(); if(focus == m_pDirectoryMergeWindow && m_pDirectoryMergeWindow->isVisible() && !dirShowBoth->isChecked()) { slotDirViewToggle(); } std::list visibleWidgetList; if(m_pDiffTextWindow1 && m_pDiffTextWindow1->isVisible()) visibleWidgetList.push_back(m_pDiffTextWindow1); if(m_pDiffTextWindow2 && m_pDiffTextWindow2->isVisible()) visibleWidgetList.push_back(m_pDiffTextWindow2); if(m_pDiffTextWindow3 && m_pDiffTextWindow3->isVisible()) visibleWidgetList.push_back(m_pDiffTextWindow3); if(m_pMergeResultWindow && m_pMergeResultWindow->isVisible()) visibleWidgetList.push_back(m_pMergeResultWindow); if(m_bDirCompare /* m_pDirectoryMergeWindow->isVisible() */) visibleWidgetList.push_back(m_pDirectoryMergeWindow); //if ( m_pDirectoryMergeInfo->isVisible() ) visibleWidgetList.push_back(m_pDirectoryMergeInfo->getInfoList()); std::list::iterator i = std::find(visibleWidgetList.begin(), visibleWidgetList.end(), focus); if(i == visibleWidgetList.begin()) i = visibleWidgetList.end(); --i; if(i != visibleWidgetList.end()) { if(*i == m_pDirectoryMergeWindow && !dirShowBoth->isChecked()) { slotDirViewToggle(); } (*i)->setFocus(); } } void KDiff3App::slotWinToggleSplitterOrientation() { if(m_pDiffWindowSplitter != nullptr) { m_pDiffWindowSplitter->setOrientation( m_pDiffWindowSplitter->orientation() == Qt::Vertical ? Qt::Horizontal : Qt::Vertical); m_pOptions->m_bHorizDiffWindowSplitting = m_pDiffWindowSplitter->orientation() == Qt::Horizontal; } } void KDiff3App::slotOverviewNormal() { if(m_pOverview != nullptr) m_pOverview->setOverviewMode(Overview::eOMNormal); if(m_pMergeResultWindow != nullptr) m_pMergeResultWindow->setOverviewMode(Overview::eOMNormal); slotUpdateAvailabilities(); } void KDiff3App::slotOverviewAB() { if(m_pOverview != nullptr) m_pOverview->setOverviewMode(Overview::eOMAvsB); m_pMergeResultWindow->setOverviewMode(Overview::eOMAvsB); slotUpdateAvailabilities(); } void KDiff3App::slotOverviewAC() { if(m_pOverview != nullptr) m_pOverview->setOverviewMode(Overview::eOMAvsC); if(m_pMergeResultWindow != nullptr) m_pMergeResultWindow->setOverviewMode(Overview::eOMAvsC); slotUpdateAvailabilities(); } void KDiff3App::slotOverviewBC() { if(m_pOverview != nullptr) m_pOverview->setOverviewMode(Overview::eOMBvsC); if(m_pMergeResultWindow != nullptr) m_pMergeResultWindow->setOverviewMode(Overview::eOMBvsC); slotUpdateAvailabilities(); } void KDiff3App::slotNoRelevantChangesDetected() { if(m_bTripleDiff && !m_outputFilename.isEmpty()) { //KMessageBox::information( this, "No relevant changes detected", "KDiff3" ); if(!m_pOptions->m_IrrelevantMergeCmd.isEmpty()) { /* QProcess doesn't check for single quotes and uses non-standard escaping syntax for double quotes. The distinction between single and double quotes is purely a command shell issue. So we split the command string ourselves. */ QStringList args; QString program; Utils::getArguments(m_pOptions->m_IrrelevantMergeCmd, program, args); QProcess process; process.start(program, args); process.waitForFinished(-1); } } } void ManualDiffHelpList::insertEntry(e_SrcSelector winIdx, LineRef firstLine, LineRef lastLine) { // The manual diff help list must be sorted and compact. // "Compact" means that upper items can't be empty if lower items contain data. // First insert the new item without regarding compactness. // If the new item overlaps with previous items then the previous items will be removed. ManualDiffHelpEntry mdhe; mdhe.firstLine(winIdx) = firstLine; mdhe.lastLine(winIdx) = lastLine; ManualDiffHelpList::iterator i; for(i = begin(); i != end(); ++i) { LineRef& l1 = i->firstLine(winIdx); LineRef& l2 = i->lastLine(winIdx); if(l1 >= 0 && l2 >= 0) { if((firstLine <= l1 && lastLine >= l1) || (firstLine <= l2 && lastLine >= l2)) { // overlap l1.invalidate(); l2.invalidate(); } if(firstLine < l1 && lastLine < l1) { // insert before this position insert(i, mdhe); break; } } } if(i == end()) { insert(i, mdhe); } // Now make the list compact for(int wIdx = A; wIdx <= Max; ++wIdx) { ManualDiffHelpList::iterator iEmpty = begin(); for(i = begin(); i != end(); ++i) { if(iEmpty->firstLine((e_SrcSelector)wIdx) >= 0) { ++iEmpty; continue; } if(i->firstLine((e_SrcSelector)wIdx) >= 0) // Current item is not empty -> move it to the empty place { iEmpty->firstLine((e_SrcSelector)wIdx) = i->firstLine((e_SrcSelector)wIdx); iEmpty->lastLine((e_SrcSelector)wIdx) = i->lastLine((e_SrcSelector)wIdx); i->firstLine((e_SrcSelector)wIdx).invalidate(); i->lastLine((e_SrcSelector)wIdx).invalidate(); ++iEmpty; } } } remove(ManualDiffHelpEntry()); // Remove all completely empty items. } void KDiff3App::slotAddManualDiffHelp() { LineRef firstLine; LineRef lastLine; e_SrcSelector winIdx = Invalid; if(m_pDiffTextWindow1) { m_pDiffTextWindow1->getSelectionRange(&firstLine, &lastLine, eFileCoords); winIdx = A; } if(firstLine < 0 && m_pDiffTextWindow2) { m_pDiffTextWindow2->getSelectionRange(&firstLine, &lastLine, eFileCoords); winIdx = B; } if(firstLine < 0 && m_pDiffTextWindow3) { m_pDiffTextWindow3->getSelectionRange(&firstLine, &lastLine, eFileCoords); winIdx = C; } if(firstLine < 0 || lastLine < 0 || lastLine < firstLine) KMessageBox::information(this, i18n("Nothing is selected in either diff input window."), i18n("Error while adding manual diff range")); else { m_manualDiffHelpList.insertEntry(winIdx, firstLine, lastLine); mainInit(nullptr, false); // Init without reload slotRefresh(); } } void KDiff3App::slotClearManualDiffHelpList() { m_manualDiffHelpList.clear(); mainInit(nullptr, false); // Init without reload slotRefresh(); } void KDiff3App::slotEncodingChangedA(QTextCodec* c) { m_sd1.setEncoding(c); mainInit(nullptr, true, true); // Init with reload slotRefresh(); } void KDiff3App::slotEncodingChangedB(QTextCodec* c) { m_sd2.setEncoding(c); mainInit(nullptr, true, true); // Init with reload slotRefresh(); } void KDiff3App::slotEncodingChangedC(QTextCodec* c) { m_sd3.setEncoding(c); mainInit(nullptr, true, true); // Init with reload slotRefresh(); } void KDiff3App::slotUpdateAvailabilities() { if(m_pMainSplitter == nullptr) return; bool bTextDataAvailable = (m_sd1.hasData() || m_sd2.hasData() || m_sd3.hasData()); if(dirShowBoth->isChecked()) { if(m_pDirectoryMergeSplitter != nullptr) m_pDirectoryMergeSplitter->setVisible(m_bDirCompare); if(m_pMainWidget != nullptr && !m_pMainWidget->isVisible() && bTextDataAvailable && !m_pDirectoryMergeWindow->isScanning()) m_pMainWidget->show(); } bool bDiffWindowVisible = m_pMainWidget != nullptr && m_pMainWidget->isVisible(); bool bMergeEditorVisible = m_pMergeWindowFrame != nullptr && m_pMergeWindowFrame->isVisible() && m_pMergeResultWindow != nullptr; m_pDirectoryMergeWindow->updateAvailabilities(m_bDirCompare, bDiffWindowVisible, chooseA, chooseB, chooseC); dirShowBoth->setEnabled(m_bDirCompare); dirViewToggle->setEnabled( m_bDirCompare && ((m_pDirectoryMergeSplitter != nullptr && m_pMainWidget != nullptr) && ((!m_pDirectoryMergeSplitter->isVisible() && m_pMainWidget->isVisible()) || (m_pDirectoryMergeSplitter->isVisible() && !m_pMainWidget->isVisible() && bTextDataAvailable)))); bool bDirWindowHasFocus = m_pDirectoryMergeSplitter != nullptr && m_pDirectoryMergeSplitter->isVisible() && m_pDirectoryMergeWindow->hasFocus(); showWhiteSpaceCharacters->setEnabled(bDiffWindowVisible); autoAdvance->setEnabled(bMergeEditorVisible); autoSolve->setEnabled(bMergeEditorVisible && m_bTripleDiff); unsolve->setEnabled(bMergeEditorVisible); if(!bDirWindowHasFocus) { chooseA->setEnabled(bMergeEditorVisible); chooseB->setEnabled(bMergeEditorVisible); chooseC->setEnabled(bMergeEditorVisible && m_bTripleDiff); } if(m_pMergeResultWindow != nullptr) { m_pMergeResultWindow->slotUpdateAvailabilities(bMergeEditorVisible, m_bTripleDiff); } mergeHistory->setEnabled(bMergeEditorVisible); mergeRegExp->setEnabled(bMergeEditorVisible); showWindowA->setEnabled(bDiffWindowVisible && (m_pDiffTextWindow2->isVisible() || m_pDiffTextWindow3->isVisible())); showWindowB->setEnabled(bDiffWindowVisible && (m_pDiffTextWindow1->isVisible() || m_pDiffTextWindow3->isVisible())); showWindowC->setEnabled(bDiffWindowVisible && m_bTripleDiff && (m_pDiffTextWindow1->isVisible() || m_pDiffTextWindow2->isVisible())); editFind->setEnabled(bDiffWindowVisible); editFindNext->setEnabled(bDiffWindowVisible); m_pFindDialog->m_pSearchInC->setEnabled(m_bTripleDiff); m_pFindDialog->m_pSearchInOutput->setEnabled(bMergeEditorVisible); bool bSavable = bMergeEditorVisible && m_pMergeResultWindow->getNrOfUnsolvedConflicts() == 0; fileSave->setEnabled(m_bOutputModified && bSavable); fileSaveAs->setEnabled(bSavable); goTop->setEnabled(bDiffWindowVisible && bMergeEditorVisible && m_pMergeResultWindow->isDeltaAboveCurrent()); goBottom->setEnabled(bDiffWindowVisible && bMergeEditorVisible && m_pMergeResultWindow->isDeltaBelowCurrent()); goCurrent->setEnabled(bDiffWindowVisible); goPrevUnsolvedConflict->setEnabled(bMergeEditorVisible && m_pMergeResultWindow->isUnsolvedConflictAboveCurrent()); goNextUnsolvedConflict->setEnabled(bMergeEditorVisible && m_pMergeResultWindow->isUnsolvedConflictBelowCurrent()); goPrevConflict->setEnabled(bDiffWindowVisible && bMergeEditorVisible && m_pMergeResultWindow->isConflictAboveCurrent()); goNextConflict->setEnabled(bDiffWindowVisible && bMergeEditorVisible && m_pMergeResultWindow->isConflictBelowCurrent()); goPrevDelta->setEnabled(bDiffWindowVisible && bMergeEditorVisible && m_pMergeResultWindow->isDeltaAboveCurrent()); goNextDelta->setEnabled(bDiffWindowVisible && bMergeEditorVisible && m_pMergeResultWindow->isDeltaBelowCurrent()); overviewModeNormal->setEnabled(m_bTripleDiff && bDiffWindowVisible); overviewModeAB->setEnabled(m_bTripleDiff && bDiffWindowVisible); overviewModeAC->setEnabled(m_bTripleDiff && bDiffWindowVisible); overviewModeBC->setEnabled(m_bTripleDiff && bDiffWindowVisible); Overview::e_OverviewMode overviewMode = m_pOverview == nullptr ? Overview::eOMNormal : m_pOverview->getOverviewMode(); overviewModeNormal->setChecked(overviewMode == Overview::eOMNormal); overviewModeAB->setChecked(overviewMode == Overview::eOMAvsB); overviewModeAC->setChecked(overviewMode == Overview::eOMAvsC); overviewModeBC->setChecked(overviewMode == Overview::eOMBvsC); winToggleSplitOrientation->setEnabled(bDiffWindowVisible && m_pDiffWindowSplitter != nullptr); }