diff --git a/src/diff.cpp b/src/diff.cpp index e79cc2a..1dd0eeb 100644 --- a/src/diff.cpp +++ b/src/diff.cpp @@ -1,2474 +1,2474 @@ /*************************************************************************** diff.cpp - description ------------------- begin : Mon Mar 18 2002 copyright : (C) 2002-2007 by Joachim Eibl email : joachim.eibl at gmx.de ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifdef _WIN32 #include #endif #include #include "diff.h" #include "fileaccess.h" #include "gnudiff_diff.h" #include "options.h" #include "progress.h" #include #include #include #include #include #include #include #include #include #include //using namespace std; 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 equal(const LineData& l1, const LineData& l2, bool bStrict) { if(l1.pLine == nullptr || l2.pLine == nullptr) return false; if(bStrict && g_bIgnoreTrivialMatches) //&& (l1.occurances>=5 || l2.occurances>=5) ) return false; // Ignore white space diff const QChar* p1 = l1.pLine; const QChar* p1End = p1 + l1.size; const QChar* p2 = l2.pLine; 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 { if(l1.size == l2.size && memcmp(p1, p2, l1.size) == 0) return true; else return false; } } static bool isLineOrBufEnd(const QChar* p, int i, int size) { return i >= size // End of file || 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' ; } /* 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. */ 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()) { FileAccess::removeFile(m_tempInputFileName); 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()) { FileAccess::removeFile(m_tempInputFileName); 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()) { m_tempInputFileName = FileAccess::tempFileName(); } 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; } int SourceData::getSizeLines() const { return m_normalData.m_vSize; } int 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.m_bIsText; } 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 = true; m_bIncompleteConversion = false; m_eLineEndStyle = eLineEndStyleUndefined; } bool SourceData::FileData::readFile(const QString& filename) { reset(); if(filename.isEmpty()) { return true; } FileAccess fa(filename); m_size = fa.sizeForReading(); char* pBuf; m_pBuf = pBuf = new char[m_size + 100]; // Alloc 100 byte extra: Savety 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]; memcpy(pBuf, src.m_pBuf, m_size); } // Convert the input file from input encoding to output encoding and write it to the output file. static bool 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; } static QTextCodec* 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; } static QTextCodec* 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(buf, size); 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(""1" "2"< => >1<, >2< * Eg. >'\'\\'< => >'\< backslash is a meta character between single quotes * Eg. > "\\" < => >\\< but not between double quotes * Eg. >"c:\sed" 's/a/\' /g'< => >c:\sed<, >s/a/' /g< */ static QString getArguments(QString cmd, QString& program, QStringList& args) { program = QString(); args.clear(); for(int i = 0; i < cmd.length(); ++i) { while(i < cmd.length() && cmd[i].isSpace()) { ++i; } if(cmd[i] == '"' || cmd[i] == '\'') // argument beginning with a quote { QChar quoteChar = cmd[i]; ++i; int argStart = i; bool bSkip = false; while(i < cmd.length() && (cmd[i] != quoteChar || bSkip)) { if(bSkip) { bSkip = false; if(cmd[i] == '\\' || cmd[i] == quoteChar) { cmd.remove(i - 1, 1); // remove the backslash '\' continue; } } else if(cmd[i] == '\\' && quoteChar == '\'') bSkip = true; ++i; } if(i < cmd.length()) { args << cmd.mid(argStart, i - argStart); if(i + 1 < cmd.length() && !cmd[i + 1].isSpace()) return i18n("Expecting space after closing apostroph."); } else return i18n("Not matching apostrophs."); continue; } else { int argStart = i; //bool bSkip = false; while(i < cmd.length() && (!cmd[i].isSpace() /*|| bSkip*/)) { /*if ( bSkip ) { bSkip = false; if ( cmd[i]=='\\' || cmd[i]=='"' || cmd[i]=='\'' || cmd[i].isSpace() ) { cmd.remove( i-1, 1 ); // remove the backslash '\' continue; } } else if ( cmd[i]=='\\' ) bSkip = true; else */ if(cmd[i] == '"' || cmd[i] == '\'') return i18n("Unexpected apostroph within argument."); ++i; } args << cmd.mid(argStart, i - argStart); } } if(args.isEmpty()) return i18n("No program specified."); else { program = args[0]; args.pop_front(); #ifdef WIN32 if(program == "sed") { QString prg = QCoreApplication::applicationDirPath() + "/bin/sed.exe"; // in subdir bin if(QFile::exists(prg)) { program = prg; } else { prg = QCoreApplication::applicationDirPath() + "/sed.exe"; // in same dir if(QFile::exists(prg)) { program = prg; } } } #endif } return QString(); } QStringList SourceData::readAndPreprocess(QTextCodec* pEncoding, bool bAutoDetectUnicode) { m_pEncoding = pEncoding; QString fileNameIn1; QString fileNameOut1; QString fileNameIn2; QString fileNameOut2; QStringList 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_tempInputFileName = FileAccess::tempFileName(); } m_fileAccess.copyFile(m_tempInputFileName); 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); int fileInSize = faIn.size(); if(faIn.exists()) // fileInSize > 0 ) { -#if defined(_WIN32) || defined(Q_OS_OS2) +#if defined(_WIN32) QString catCmd = "type"; fileNameIn1.replace('/', "\\"); #else QString catCmd = "cat"; #endif // Run the first preprocessor if(m_pOptions->m_PreProcessorCmd.isEmpty()) { // No preprocessing: Read the file directly: m_normalData.readFile(fileNameIn1); } else { QString fileNameInPP = fileNameIn1; if(pEncoding1 != m_pOptions->m_pEncodingPP) { // Before running the preprocessor convert to the format that the preprocessor expects. fileNameInPP = FileAccess::tempFileName(); pEncoding1 = m_pOptions->m_pEncodingPP; convertFileEncoding(fileNameIn1, pEncoding, fileNameInPP, pEncoding1); } QString ppCmd = m_pOptions->m_PreProcessorCmd; fileNameOut1 = FileAccess::tempFileName(); QProcess ppProcess; ppProcess.setStandardInputFile(fileNameInPP); ppProcess.setStandardOutputFile(fileNameOut1); QString program; QStringList args; QString errorReason = getArguments(ppCmd, program, args); if(errorReason.isEmpty()) { ppProcess.start(program, args); ppProcess.waitForFinished(-1); } else errorReason = "\n(" + errorReason + ")"; //QString cmd = catCmd + " \"" + fileNameInPP + "\" | " + ppCmd + " >\"" + fileNameOut1+"\""; //::system( encodeString(cmd) ); bool bSuccess = errorReason.isEmpty() && m_normalData.readFile(fileNameOut1); if(fileInSize > 0 && (!bSuccess || m_normalData.m_size == 0)) { errors.append( i18n("Preprocessing possibly failed. Check this command:\n\n %1" "\n\nThe preprocessing command will be disabled now.") .arg(ppCmd) + errorReason); m_pOptions->m_PreProcessorCmd = ""; m_normalData.readFile(fileNameIn1); pEncoding1 = m_pEncoding; } if(fileNameInPP != fileNameIn1) { FileAccess::removeTempFile(fileNameInPP); } } // LineMatching Preprocessor if(!m_pOptions->m_LineMatchingPreProcessorCmd.isEmpty()) { 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. fileNameInPP = FileAccess::tempFileName(); pEncoding2 = m_pOptions->m_pEncodingPP; convertFileEncoding(fileNameIn2, pEncoding1, fileNameInPP, pEncoding2); } QString ppCmd = m_pOptions->m_LineMatchingPreProcessorCmd; fileNameOut2 = FileAccess::tempFileName(); QProcess ppProcess; ppProcess.setStandardInputFile(fileNameInPP); ppProcess.setStandardOutputFile(fileNameOut2); QString program; QStringList args; QString errorReason = getArguments(ppCmd, program, args); if(errorReason.isEmpty()) { ppProcess.start(program, args); ppProcess.waitForFinished(-1); } else errorReason = "\n(" + errorReason + ")"; //QString cmd = catCmd + " \"" + fileNameInPP + "\" | " + ppCmd + " >\"" + fileNameOut2 + "\""; //::system( encodeString(cmd) ); 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.") .arg(ppCmd) + errorReason); m_pOptions->m_LineMatchingPreProcessorCmd = ""; m_lmppData.readFile(fileNameIn2); } FileAccess::removeTempFile(fileNameOut2); if(fileNameInPP != fileNameIn2) { FileAccess::removeTempFile(fileNameInPP); } } else if(m_pOptions->m_bIgnoreComments || m_pOptions->m_bIgnoreCase) { // We need a copy of the normal data. m_lmppData.copyBufFrom(m_normalData); } else { // We don't need any lmpp data at all. m_lmppData.reset(); } } m_normalData.preprocess(m_pOptions->m_bPreserveCarriageReturn, pEncoding1); m_lmppData.preprocess(false, pEncoding2); if(m_lmppData.m_vSize < m_normalData.m_vSize) { // This probably is the fault of the LMPP-Command, but not worth reporting. m_lmppData.m_v.resize(m_normalData.m_vSize); for(int 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[i].pLine = m_lmppData.m_unicodeBuf.unicode() + m_lmppData.m_unicodeBuf.length(); } m_lmppData.m_vSize = m_normalData.m_vSize; } // Internal Preprocessing: Uppercase-conversion if(m_pOptions->m_bIgnoreCase) { int i; QChar* pBuf = const_cast(m_lmppData.m_unicodeBuf.unicode()); int ucSize = m_lmppData.m_unicodeBuf.length(); for(i = 0; i < ucSize; ++i) { pBuf[i] = pBuf[i].toUpper(); } } // Ignore comments if(m_pOptions->m_bIgnoreComments) { m_lmppData.removeComments(); int vSize = min2(m_normalData.m_vSize, m_lmppData.m_vSize); for(int i = 0; i < vSize; ++i) { m_normalData.m_v[i].bContainsPureComment = m_lmppData.m_v[i].bContainsPureComment; } } // Remove unneeded temporary files. (A temp file from clipboard must not be deleted.) if(!bTempFileFromClipboard && !m_tempInputFileName.isEmpty()) { FileAccess::removeTempFile(m_tempInputFileName); m_tempInputFileName = ""; } if(!fileNameOut1.isEmpty()) { FileAccess::removeTempFile(fileNameOut1); fileNameOut1 = ""; } return errors; } /** Prepare the linedata vector for every input line.*/ void SourceData::FileData::preprocess(bool bPreserveCR, QTextCodec* pEncoding) { //m_unicodeBuf = decodeString( m_pBuf, m_size, eEncoding ); 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; QByteArray ba = QByteArray::fromRawData(m_pBuf + skipBytes, 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; int lines = 1; m_bIncompleteConversion = false; for(i = 0; i < ucSize; ++i) { if(i >= ucSize || p[i] == '\n') { ++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') { m_v[lineIdx].pLine = &p[i - lineLength]; while(/*!bPreserveCR &&*/ lineLength > 0 && m_v[lineIdx].pLine[lineLength - 1] == '\r') { --lineLength; } m_v[lineIdx].pFirstNonWhiteChar = m_v[lineIdx].pLine + min2(whiteLength, lineLength); m_v[lineIdx].size = lineLength; if(lineIdx < vOrigDataLineEndStyle.count() && bPreserveCR && i < ucSize) { ++m_v[lineIdx].size; const_cast(m_v[lineIdx].pLine)[lineLength] = '\r'; //switch ( vOrigDataLineEndStyle[lineIdx] ) //{ //case eLineEndStyleUnix: const_cast(m_v[lineIdx].pLine)[lineLength] = '\n'; break; //case eLineEndStyleDos: const_cast(m_v[lineIdx].pLine)[lineLength] = '\r'; break; //case eLineEndStyleUndefined: const_cast(m_v[lineIdx].pLine)[lineLength] = '\x0b'; break; //} } lineLength = 0; bNonWhiteFound = false; whiteLength = 0; ++lineIdx; } else { ++lineLength; if(!bNonWhiteFound && isWhite(p[i])) ++whiteLength; else bNonWhiteFound = true; } } assert(lineIdx == lines); m_vSize = lines; } // 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. static void checkLineForComments( 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) { memset(&p[commentStart], ' ', i - commentStart); } 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) { memset(&p[commentStart], ' ', i - commentStart); } 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; QChar* p = const_cast(m_unicodeBuf.unicode()); bool bWithinComment = false; int size = m_unicodeBuf.length(); for(int i = 0; i < size; ++i) { // std::cout << "2 " << std::string(&p[i], m_v[line].size) << std::endl; 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) { memset(&p[commentStart], ' ', i - commentStart); } break; } } } else { checkLineForComments(p, i, size, bWhite, bCommentInLine, bWithinComment); } // end of line assert(isLineOrBufEnd(p, i, size)); m_v[line].bContainsPureComment = bCommentInLine && bWhite; /* std::cout << line << " : " << ( bCommentInLine ? "c" : " " ) << ( bWhite ? "w " : " ") << std::string(pLD[line].pLine, pLD[line].size) << std::endl;*/ ++line; } } // First step void calcDiff3LineListUsingAB( const DiffList* pDiffListAB, Diff3LineList& d3ll) { // First make d3ll for AB (from pDiffListAB) DiffList::const_iterator i = pDiffListAB->begin(); int lineA = 0; int lineB = 0; Diff d(0, 0, 0); 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.lineA = lineA; d3l.lineB = lineB; --d.nofEquals; ++lineA; ++lineB; } else if(d.diff1 > 0 && d.diff2 > 0) { d3l.lineA = lineA; d3l.lineB = lineB; --d.diff1; --d.diff2; ++lineA; ++lineB; } else if(d.diff1 > 0) { d3l.lineA = lineA; --d.diff1; ++lineA; } else if(d.diff2 > 0) { d3l.lineB = lineB; --d.diff2; ++lineB; } else if(d.nofEquals < 0) { assert(false); } d3ll.push_back(d3l); } } // 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(); int lineA = 0; int 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).lineA != lineA) ++i3; (*i3).lineC = lineC; (*i3).bAEqC = true; (*i3).bBEqC = (*i3).bAEqB; --d.nofEquals; ++lineA; ++lineC; ++i3; } else if(d.diff1 > 0 && d.diff2 > 0) { d3l.lineC = 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.lineC = 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(); int lineB = 0; int 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).lineB != lineB) ++i3b; while(i3c != d3ll.end() && (*i3c).lineC != lineC) ++i3c; assert(i3b != d3ll.end()); assert(i3c != d3ll.end()); if(i3b == i3c) { assert((*i3b).lineC == 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) { assert(i3b1 != d3ll.end() || i3c1 != d3ll.end()); if(i3c1 != d3ll.end()) ++i3c1; if(i3b1 != d3ll.end()) ++i3b1; } if(i3c1 == i3b && !(*i3b).bAEqB) // i3c before i3b { Diff3LineList::iterator i3 = i3c; int nofDisturbingLines = 0; while(i3 != i3b && i3 != d3ll.end()) { if((*i3).lineB != -1) ++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->bAEqB) { 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).lineB != -1 || (before_or_on_equal_line_in_A && i3->lineA != -1)) { Diff3Line d3l; d3l.lineB = (*i3).lineB; (*i3).lineB = -1; // Move A along if it matched B if(before_or_on_equal_line_in_A) { d3l.lineA = i3->lineA; d3l.bAEqB = i3->bAEqB; i3->lineA = -1; 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).lineB = -1; // This might leave an empty line: removed later. (*i3b).bAEqB = false; (*i3b).bBEqC = false; (*i3c).lineB = lineB; (*i3c).bBEqC = true; (*i3c).bAEqB = (*i3c).bAEqC; } } else if(i3b1 == i3c && !(*i3c).bAEqC) { Diff3LineList::iterator i3 = i3b; int nofDisturbingLines = 0; while(i3 != i3c && i3 != d3ll.end()) { if((*i3).lineC != -1) ++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->bAEqC) { 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).lineC != -1 || (before_or_on_equal_line_in_A && i3->lineA != -1)) { Diff3Line d3l; d3l.lineC = (*i3).lineC; (*i3).lineC = -1; // Move A along if it matched C if(before_or_on_equal_line_in_A) { d3l.lineA = i3->lineA; d3l.bAEqC = i3->bAEqC; i3->lineA = -1; 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).lineC = -1; // This might leave an empty line: removed later. (*i3c).bAEqC = false; (*i3c).bBEqC = false; (*i3b).lineC = lineC; (*i3b).bBEqC = true; (*i3b).bAEqC = (*i3b).bAEqB; } } } --d.nofEquals; ++lineB; ++lineC; ++i3b; ++i3c; } else if(d.diff1 > 0) { Diff3LineList::iterator i3 = i3b; while((*i3).lineB != lineB) ++i3; if(i3 != i3b && (*i3).bAEqB == false) { // Take B from this line and move it up as far as possible d3l.lineB = lineB; d3ll.insert(i3b, d3l); (*i3).lineB = -1; } 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).lineA, (*it).lineB, (*it).lineC, (*it).bAEqB ? '=' : '!', (*it).bAEqC ? '=' : '!', (*it).bBEqC ? '=' : '!' ); } printf("\n");*/ } #ifdef _WIN32 using ::equal; #endif // Test if the move would pass a barrier. Return true if not. static bool isValidMove(ManualDiffHelpList* pManualDiffHelpList, int line1, int line2, int winIdx1, int winIdx2) { if(line1 >= 0 && line2 >= 0) { ManualDiffHelpList::const_iterator i; for(i = pManualDiffHelpList->begin(); i != pManualDiffHelpList->end(); ++i) { const ManualDiffHelpEntry& mdhe = *i; // Barrier int l1 = winIdx1 == 1 ? mdhe.lineA1 : winIdx1 == 2 ? mdhe.lineB1 : mdhe.lineC1; int l2 = winIdx2 == 1 ? mdhe.lineA1 : winIdx2 == 2 ? mdhe.lineB1 : mdhe.lineC1; if(l1 >= 0 && l2 >= 0) { if((line1 >= l1 && line2 < l2) || (line1 < l1 && line2 >= l2)) return false; l1 = winIdx1 == 1 ? mdhe.lineA2 : winIdx1 == 2 ? mdhe.lineB2 : mdhe.lineC2; l2 = winIdx2 == 1 ? mdhe.lineA2 : winIdx2 == 2 ? mdhe.lineB2 : mdhe.lineC2; ++l1; ++l2; if((line1 >= l1 && line2 < l2) || (line1 < l1 && line2 >= l2)) return false; } } } return true; // no barrier passed. } static bool runDiff(const LineData* p1, int size1, const LineData* p2, int size2, DiffList& diffList, Options* pOptions) { ProgressProxy pp; static GnuDiff gnuDiff; // All values are initialized with zeros. pp.setCurrent(0); diffList.clear(); if(p1[0].pLine == nullptr || p2[0].pLine == nullptr || size1 == 0 || size2 == 0) { Diff d(0, 0, 0); if(p1[0].pLine == nullptr && p2[0].pLine == 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].pLine; //ptr to buffer comparisonInput.file[0].buffered = (p1[size1 - 1].pLine - p1[0].pLine + p1[size1 - 1].size); // size of buffer comparisonInput.file[1].buffer = p2[0].pLine; //ptr to buffer comparisonInput.file[1].buffered = (p2[size2 - 1].pLine - p2[0].pLine + 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); int equalLinesAtStart = comparisonInput.file[0].prefix_lines; int currentLine1 = 0; int currentLine2 = 0; GnuDiff::change* p = nullptr; for(GnuDiff::change* e = script; e; e = p) { Diff d(0, 0, 0); d.nofEquals = e->line0 - currentLine1; assert(d.nofEquals == e->line1 - currentLine2); d.diff1 = e->deleted; d.diff2 = e->inserted; currentLine1 += d.nofEquals + d.diff1; currentLine2 += d.nofEquals + d.diff2; diffList.push_back(d); p = e->link; free(e); } if(diffList.empty()) { Diff d(0, 0, 0); d.nofEquals = min2(size1, size2); d.diff1 = size1 - d.nofEquals; d.diff2 = size2 - d.nofEquals; diffList.push_back(d); /* Diff d(0,0,0); d.nofEquals = equalLinesAtStart; if ( gnuDiff.files[0].missing_newline != gnuDiff.files[1].missing_newline ) { d.diff1 = gnuDiff.files[0].missing_newline ? 0 : 1; d.diff2 = gnuDiff.files[1].missing_newline ? 0 : 1; ++d.nofEquals; } else if ( !gnuDiff.files[0].missing_newline ) { ++d.nofEquals; } diffList.push_back(d); */ } else { diffList.front().nofEquals += equalLinesAtStart; currentLine1 += equalLinesAtStart; currentLine2 += equalLinesAtStart; int nofEquals = min2(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); } /* if ( gnuDiff.files[0].missing_newline != gnuDiff.files[1].missing_newline ) { diffList.back().diff1 += gnuDiff.files[0].missing_newline ? 0 : 1; diffList.back().diff2 += gnuDiff.files[1].missing_newline ? 0 : 1; } else if ( !gnuDiff.files[0].missing_newline ) { ++ diffList.back().nofEquals; } */ } } #ifndef NDEBUG // Verify difflist { int l1 = 0; int l2 = 0; DiffList::iterator i; for(i = diffList.begin(); i != diffList.end(); ++i) { l1 += i->nofEquals + i->diff1; l2 += i->nofEquals + i->diff2; } //if( l1!=p1-p1start || l2!=p2-p2start ) if(l1 != size1 || l2 != size2) assert(false); } #endif pp.setCurrent(1.0); return true; } bool runDiff(const LineData* p1, int size1, const LineData* p2, int size2, DiffList& diffList, int winIdx1, int winIdx2, ManualDiffHelpList* pManualDiffHelpList, Options* pOptions) { diffList.clear(); DiffList diffList2; int l1begin = 0; int l2begin = 0; ManualDiffHelpList::const_iterator i; for(i = pManualDiffHelpList->begin(); i != pManualDiffHelpList->end(); ++i) { const ManualDiffHelpEntry& mdhe = *i; int l1end = winIdx1 == 1 ? mdhe.lineA1 : winIdx1 == 2 ? mdhe.lineB1 : mdhe.lineC1; int l2end = winIdx2 == 1 ? mdhe.lineA1 : winIdx2 == 2 ? mdhe.lineB1 : mdhe.lineC1; 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 = winIdx1 == 1 ? mdhe.lineA2 : winIdx1 == 2 ? mdhe.lineB2 : mdhe.lineC2; l2end = winIdx2 == 1 ? mdhe.lineA2 : winIdx2 == 2 ? mdhe.lineB2 : mdhe.lineC2; 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(); int missingWinIdx = 0; int alignedSum = (iMDHL->lineA1 < 0 ? 0 : 1) + (iMDHL->lineB1 < 0 ? 0 : 1) + (iMDHL->lineC1 < 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->lineA1 < 0 ? 1 : (iMDHL->lineB1 < 0 ? 2 : 3); } 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 = 0; for(; i3 != d3ll.end(); ++i3) { for(wi = 1; wi <= 3; ++wi) { if(i3->getLineInFile(wi) >= 0 && iMDHL->firstLine(wi) == i3->getLineInFile(wi)) break; } if(wi <= 3) break; } if(wi >= 1 && wi <= 3) { // 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 = 0; for(; i3 != d3ll.end(); ++i3) { for(wi2 = 1; wi2 <= 3; ++wi2) { if(wi != wi2 && i3->getLineInFile(wi2) >= 0 && iMDHL->firstLine(wi2) == i3->getLineInFile(wi2)) break; } if(wi2 > 3) { // Not yet found // Move both others up Diff3Line d3l; // Move both up if(wi == 1) // Move B and C up { d3l.bBEqC = i3->bBEqC; d3l.lineB = i3->lineB; d3l.lineC = i3->lineC; i3->lineB = -1; i3->lineC = -1; } if(wi == 2) // Move A and C up { d3l.bAEqC = i3->bAEqC; d3l.lineA = i3->lineA; d3l.lineC = i3->lineC; i3->lineA = -1; i3->lineC = -1; } if(wi == 3) // Move A and B up { d3l.bAEqB = i3->bAEqB; d3l.lineA = i3->lineA; d3l.lineB = i3->lineB; i3->lineA = -1; i3->lineB = -1; } 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 == 1) { iDest->lineA = i3->lineA; i3->lineA = -1; i3->bAEqB = false; i3->bAEqC = false; } else if(wi2 == 2) { iDest->lineB = i3->lineB; i3->lineB = -1; i3->bAEqB = false; i3->bBEqC = false; } else if(wi2 == 3) { iDest->lineC = i3->lineC; i3->lineC = -1; i3->bBEqC = false; i3->bAEqC = false; } } if(missingWinIdx != 0) { for(; i3 != d3ll.end(); ++i3) { int wi3 = missingWinIdx; if(i3->getLineInFile(wi3) >= 0) { // not found, move the line before iDest Diff3Line d3l; if(wi3 == 1) { if(i3->bAEqB) // Stop moving lines up if one equal is found. break; d3l.lineA = i3->lineA; i3->lineA = -1; i3->bAEqB = false; i3->bAEqC = false; } if(wi3 == 2) { if(i3->bAEqB) break; d3l.lineB = i3->lineB; i3->lineB = -1; i3->bAEqB = false; i3->bBEqC = false; } if(wi3 == 3) { if(i3->bAEqC) break; d3l.lineC = i3->lineC; i3->lineC = -1; 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.removeAll(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->lineA >= 0 && i3->lineA == iMDHL->lineA1) || (i3->lineB >= 0 && i3->lineB == iMDHL->lineB1) || (i3->lineC >= 0 && i3->lineC == iMDHL->lineC1)) { i3A = i3; i3B = i3; i3C = i3; lineA = line; lineB = line; lineC = line; ++iMDHL; } } if(line > lineA && (*i3).lineA != -1 && (*i3A).lineB != -1 && (*i3A).bBEqC && ::equal(pldA[(*i3).lineA], pldB[(*i3A).lineB], false) && isValidMove(pManualDiffHelpList, (*i3).lineA, (*i3A).lineB, 1, 2) && isValidMove(pManualDiffHelpList, (*i3).lineA, (*i3A).lineC, 1, 3)) { // Empty space for A. A matches B and C in the empty line. Move it up. (*i3A).lineA = (*i3).lineA; (*i3A).bAEqB = true; (*i3A).bAEqC = true; (*i3).lineA = -1; (*i3).bAEqB = false; (*i3).bAEqC = false; ++i3A; ++lineA; } if(line > lineB && (*i3).lineB != -1 && (*i3B).lineA != -1 && (*i3B).bAEqC && ::equal(pldB[(*i3).lineB], pldA[(*i3B).lineA], false) && isValidMove(pManualDiffHelpList, (*i3).lineB, (*i3B).lineA, 2, 1) && isValidMove(pManualDiffHelpList, (*i3).lineB, (*i3B).lineC, 2, 3)) { // Empty space for B. B matches A and C in the empty line. Move it up. (*i3B).lineB = (*i3).lineB; (*i3B).bAEqB = true; (*i3B).bBEqC = true; (*i3).lineB = -1; (*i3).bAEqB = false; (*i3).bBEqC = false; ++i3B; ++lineB; } if(line > lineC && (*i3).lineC != -1 && (*i3C).lineA != -1 && (*i3C).bAEqB && ::equal(pldC[(*i3).lineC], pldA[(*i3C).lineA], false) && isValidMove(pManualDiffHelpList, (*i3).lineC, (*i3C).lineA, 3, 1) && isValidMove(pManualDiffHelpList, (*i3).lineC, (*i3C).lineB, 3, 2)) { // Empty space for C. C matches A and B in the empty line. Move it up. (*i3C).lineC = (*i3).lineC; (*i3C).bAEqC = true; (*i3C).bBEqC = true; (*i3).lineC = -1; (*i3).bAEqC = false; (*i3).bBEqC = false; ++i3C; ++lineC; } if(line > lineA && (*i3).lineA != -1 && !(*i3).bAEqB && !(*i3).bAEqC && isValidMove(pManualDiffHelpList, (*i3).lineA, (*i3A).lineB, 1, 2) && isValidMove(pManualDiffHelpList, (*i3).lineA, (*i3A).lineC, 1, 3)) { // Empty space for A. A doesn't match B or C. Move it up. (*i3A).lineA = (*i3).lineA; (*i3).lineA = -1; if(i3A->lineB != -1 && ::equal(pldA[i3A->lineA], pldB[i3A->lineB], false)) { i3A->bAEqB = true; } if((i3A->bAEqB && i3A->bBEqC) || (i3A->lineC != -1 && ::equal(pldA[i3A->lineA], pldC[i3A->lineC], false))) { i3A->bAEqC = true; } ++i3A; ++lineA; } if(line > lineB && (*i3).lineB != -1 && !(*i3).bAEqB && !(*i3).bBEqC && isValidMove(pManualDiffHelpList, (*i3).lineB, (*i3B).lineA, 2, 1) && isValidMove(pManualDiffHelpList, (*i3).lineB, (*i3B).lineC, 2, 3)) { // Empty space for B. B matches neither A nor C. Move B up. (*i3B).lineB = (*i3).lineB; (*i3).lineB = -1; if(i3B->lineA != -1 && ::equal(pldA[i3B->lineA], pldB[i3B->lineB], false)) { i3B->bAEqB = true; } if((i3B->bAEqB && i3B->bAEqC) || (i3B->lineC != -1 && ::equal(pldB[i3B->lineB], pldC[i3B->lineC], false))) { i3B->bBEqC = true; } ++i3B; ++lineB; } if(line > lineC && (*i3).lineC != -1 && !(*i3).bAEqC && !(*i3).bBEqC && isValidMove(pManualDiffHelpList, (*i3).lineC, (*i3C).lineA, 3, 1) && isValidMove(pManualDiffHelpList, (*i3).lineC, (*i3C).lineB, 3, 2)) { // Empty space for C. C matches neither A nor B. Move C up. (*i3C).lineC = (*i3).lineC; (*i3).lineC = -1; if(i3C->lineA != -1 && ::equal(pldA[i3C->lineA], pldC[i3C->lineC], false)) { i3C->bAEqC = true; } if((i3C->bAEqC && i3C->bAEqB) || (i3C->lineB != -1 && ::equal(pldB[i3C->lineB], pldC[i3C->lineC], false))) { i3C->bBEqC = true; } ++i3C; ++lineC; } if(line > lineA && line > lineB && (*i3).lineA != -1 && (*i3).bAEqB && !(*i3).bAEqC) { // 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(isValidMove(pManualDiffHelpList, i->lineC, (*i3).lineA, 3, 1) && isValidMove(pManualDiffHelpList, i->lineC, (*i3).lineB, 3, 2)) { (*i).lineA = (*i3).lineA; (*i).lineB = (*i3).lineB; (*i).bAEqB = true; if(i->lineC != -1 && ::equal(pldA[i->lineA], pldC[i->lineC], false)) { (*i).bAEqC = true; (*i).bBEqC = true; } (*i3).lineA = -1; (*i3).lineB = -1; (*i3).bAEqB = false; i3A = i; i3B = i; ++i3A; ++i3B; lineA = l + 1; lineB = l + 1; } } else if(line > lineA && line > lineC && (*i3).lineA != -1 && (*i3).bAEqC && !(*i3).bAEqB) { // 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(isValidMove(pManualDiffHelpList, i->lineB, (*i3).lineA, 2, 1) && isValidMove(pManualDiffHelpList, i->lineB, (*i3).lineC, 2, 3)) { (*i).lineA = (*i3).lineA; (*i).lineC = (*i3).lineC; (*i).bAEqC = true; if(i->lineB != -1 && ::equal(pldA[i->lineA], pldB[i->lineB], false)) { (*i).bAEqB = true; (*i).bBEqC = true; } (*i3).lineA = -1; (*i3).lineC = -1; (*i3).bAEqC = false; i3A = i; i3C = i; ++i3A; ++i3C; lineA = l + 1; lineC = l + 1; } } else if(line > lineB && line > lineC && (*i3).lineB != -1 && (*i3).bBEqC && !(*i3).bAEqC) { // 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(isValidMove(pManualDiffHelpList, i->lineA, (*i3).lineB, 1, 2) && isValidMove(pManualDiffHelpList, i->lineA, (*i3).lineC, 1, 3)) { (*i).lineB = (*i3).lineB; (*i).lineC = (*i3).lineC; (*i).bBEqC = true; if(i->lineA != -1 && ::equal(pldA[i->lineA], pldB[i->lineB], false)) { (*i).bAEqB = true; (*i).bAEqC = true; } (*i3).lineB = -1; (*i3).lineC = -1; (*i3).bBEqC = false; i3B = i; i3C = i; ++i3B; ++i3C; lineB = l + 1; lineC = l + 1; } } if((*i3).lineA != -1) { lineA = line + 1; i3A = i3; ++i3A; } if((*i3).lineB != -1) { lineB = line + 1; i3B = i3; ++i3B; } if((*i3).lineC != -1) { lineC = line + 1; i3C = i3; ++i3C; } } d3ll.removeAll(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).lineA, (*it).lineB, (*it).lineC, (*it).bAEqB ? '=' : '!', (*it).bAEqC ? '=' : '!', (*it).bBEqC ? '=' : '!' ); } */ } void DiffBufferInfo::init(Diff3LineList* pD3ll, const Diff3LineVector* pD3lv, const LineData* pldA, int sizeA, const LineData* pldB, int sizeB, const LineData* pldC, int 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 calcWhiteDiff3Lines( Diff3LineList& d3ll, const LineData* pldA, const LineData* pldB, const LineData* pldC) { Diff3LineList::iterator i3 = d3ll.begin(); for(; i3 != d3ll.end(); ++i3) { i3->bWhiteLineA = ((*i3).lineA == -1 || pldA == nullptr || pldA[(*i3).lineA].whiteLine() || pldA[(*i3).lineA].bContainsPureComment); i3->bWhiteLineB = ((*i3).lineB == -1 || pldB == nullptr || pldB[(*i3).lineB].whiteLine() || pldB[(*i3).lineB].bContainsPureComment); i3->bWhiteLineC = ((*i3).lineC == -1 || pldC == nullptr || pldC[(*i3).lineC].whiteLine() || pldC[(*i3).lineC].bContainsPureComment); } } inline bool equal(QChar c1, QChar c2, bool /*bStrict*/) { // If bStrict then white space doesn't match //if ( bStrict && ( c1==' ' || c1=='\t' ) ) // return false; return c1 == c2; } // My own diff-invention: template void calcDiff(const T* p1, int size1, const T* p2, int size2, DiffList& diffList, int match, int maxSearchRange) { diffList.clear(); const T* p1start = p1; const T* p2start = p2; const T* p1end = p1 + size1; const T* p2end = p2 + size2; for(;;) { int nofEquals = 0; while(p1 != p1end && p2 != p2end && equal(*p1, *p2, false)) { ++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(equal(p2[i2], p1[i1], true) && (match == 1 || abs(i1 - i2) < 3 || (&p2[i2 + 1] == p2end && &p1[i1 + 1] == p1end) || (&p2[i2 + 1] != p2end && &p1[i1 + 1] != p1end && equal(p2[i2 + 1], p1[i1 + 1], false)))) { if(i1 + i2 < bestI1 + bestI2 || bBestValid == false) { 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 && equal(p1[bestI1 - 1], p2[bestI2 - 1], false)) { --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 T* pu1 = p1 - 1; const T* pu2 = p2 - 1; while(pu1 >= p1start && pu2 >= p2start && equal(*pu1, *pu2, false)) { ++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; } #ifndef NDEBUG // Verify difflist { int l1 = 0; int l2 = 0; DiffList::iterator i; for(i = diffList.begin(); i != diffList.end(); ++i) { l1 += i->nofEquals + i->diff1; l2 += i->nofEquals + i->diff2; } //if( l1!=p1-p1start || l2!=p2-p2start ) if(l1 != size1 || l2 != size2) assert(false); } #endif } bool fineDiff( Diff3LineList& diff3LineList, int selector, const LineData* v1, const LineData* v2) { // Finetuning: Diff each line with deltas ProgressProxy pp; int maxSearchLength = 500; Diff3LineList::iterator i; int k1 = 0; int k2 = 0; bool bTextsTotalEqual = true; int listSize = diff3LineList.size(); pp.setMaxNofSteps(listSize); int listIdx = 0; for(i = diff3LineList.begin(); i != diff3LineList.end(); ++i) { if(selector == 1) { k1 = i->lineA; k2 = i->lineB; } else if(selector == 2) { k1 = i->lineB; k2 = i->lineC; } else if(selector == 3) { k1 = i->lineC; k2 = i->lineA; } else assert(false); if((k1 == -1 && k2 != -1) || (k1 != -1 && k2 == -1)) bTextsTotalEqual = false; if(k1 != -1 && k2 != -1) { if(v1[k1].size != v2[k2].size || memcmp(v1[k1].pLine, v2[k2].pLine, v1[k1].size << 1) != 0) { bTextsTotalEqual = false; DiffList* pDiffList = new DiffList; calcDiff(v1[k1].pLine, v1[k1].size, v2[k2].pLine, 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; } } if(selector == 1) { delete(*i).pFineAB; (*i).pFineAB = pDiffList; } else if(selector == 2) { delete(*i).pFineBC; (*i).pFineBC = pDiffList; } else if(selector == 3) { delete(*i).pFineCA; (*i).pFineCA = pDiffList; } else assert(false); } if((v1[k1].bContainsPureComment || v1[k1].whiteLine()) && (v2[k2].bContainsPureComment || v2[k2].whiteLine())) { if(selector == 1) { i->bAEqB = true; } else if(selector == 2) { i->bBEqC = true; } else if(selector == 3) { i->bAEqC = true; } else assert(false); } } ++listIdx; pp.step(); } return bTextsTotalEqual; } // Convert the list to a vector of pointers void calcDiff3LineVector(Diff3LineList& d3ll, Diff3LineVector& d3lv) { d3lv.resize(d3ll.size()); Diff3LineList::iterator i; int j = 0; for(i = d3ll.begin(); i != d3ll.end(); ++i, ++j) { d3lv[j] = &(*i); } assert(j == (int)d3lv.size()); } diff --git a/src/difftextwindow.cpp b/src/difftextwindow.cpp index 4035555..41ba43c 100644 --- a/src/difftextwindow.cpp +++ b/src/difftextwindow.cpp @@ -1,2169 +1,2169 @@ /*************************************************************************** difftextwindow.cpp - description ------------------- begin : Mon Apr 8 2002 copyright : (C) 2002-2007 by Joachim Eibl email : joachim.eibl at gmx.de ***************************************************************************/ /*************************************************************************** * * * 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 "kdiff3.h" #include "merger.h" #include "options.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static QAtomicInt s_runnableCount = 0; class DiffTextWindowData { public: explicit DiffTextWindowData(DiffTextWindow* p) { m_pDiffTextWindow = p; m_bPaintingAllowed = false; m_pLineData = nullptr; m_size = 0; m_bWordWrap = false; m_delayedDrawTimer = 0; m_pDiff3LineVector = nullptr; m_pManualDiffHelpList = nullptr; m_pOptions = nullptr; m_fastSelectorLine1 = 0; m_fastSelectorNofLines = 0; m_bTriple = 0; m_winIdx = 0; m_firstLine = 0; m_oldFirstLine = 0; m_horizScrollOffset = 0; m_lineNumberWidth = 0; m_maxTextWidth = -1; m_pStatusBar = nullptr; m_scrollDeltaX = 0; m_scrollDeltaY = 0; m_bMyUpdate = false; m_bSelectionInProgress = false; m_pTextCodec = nullptr; -#if defined(_WIN32) || defined(Q_OS_OS2) +#if defined(_WIN32) m_eLineEndStyle = eLineEndStyleDos; #else m_eLineEndStyle = eLineEndStyleUnix; #endif } DiffTextWindow* m_pDiffTextWindow; DiffTextWindowFrame* m_pDiffTextWindowFrame = nullptr; QTextCodec* m_pTextCodec; e_LineEndStyle m_eLineEndStyle; bool m_bPaintingAllowed; const LineData* m_pLineData; int m_size; QString m_filename; bool m_bWordWrap; int m_delayedDrawTimer; const Diff3LineVector* m_pDiff3LineVector; Diff3WrapLineVector m_diff3WrapLineVector; const ManualDiffHelpList* m_pManualDiffHelpList; class WrapLineCacheData { public: WrapLineCacheData() : m_d3LineIdx(0), m_textStart(0), m_textLength(0) {} WrapLineCacheData(int d3LineIdx, int textStart, int textLength) : m_d3LineIdx(d3LineIdx), m_textStart(textStart), m_textLength(textLength) {} int m_d3LineIdx; int m_textStart; int m_textLength; }; QList> m_wrapLineCacheList; Options* m_pOptions; QColor m_cThis; QColor m_cDiff1; QColor m_cDiff2; QColor m_cDiffBoth; int m_fastSelectorLine1; int m_fastSelectorNofLines; bool m_bTriple; int m_winIdx; int m_firstLine; int m_oldFirstLine; int m_horizScrollOffset; int m_lineNumberWidth; QAtomicInt m_maxTextWidth; void getLineInfo( const Diff3Line& d, int& lineIdx, DiffList*& pFineDiff1, DiffList*& pFineDiff2, // return values int& changed, int& changed2); QString getString(int d3lIdx); QString getLineString(int line); void writeLine( MyPainter& p, const LineData* pld, const DiffList* pLineDiff1, const DiffList* pLineDiff2, int line, int whatChanged, int whatChanged2, int srcLineIdx, int wrapLineOffset, int wrapLineLength, bool bWrapLine, const QRect& invalidRect, int deviceWidth); void draw(MyPainter& p, const QRect& invalidRect, int deviceWidth, int beginLine, int endLine); QStatusBar* m_pStatusBar; Selection m_selection; int m_scrollDeltaX; int m_scrollDeltaY; bool m_bMyUpdate; void myUpdate(int afterMilliSecs); int leftInfoWidth() { return 4 + m_lineNumberWidth; } // Nr of information columns on left side int convertLineOnScreenToLineInSource(int lineOnScreen, e_CoordType coordType, bool bFirstLine); bool m_bSelectionInProgress; QPoint m_lastKnownMousePos; void prepareTextLayout(QTextLayout& textLayout, bool bFirstLine, int visibleTextWidth = -1); }; DiffTextWindow::DiffTextWindow( DiffTextWindowFrame* pParent, QStatusBar* pStatusBar, Options* pOptions, int winIdx) : QWidget(pParent) { setObjectName(QString("DiffTextWindow%1").arg(winIdx)); setAttribute(Qt::WA_OpaquePaintEvent); //setAttribute( Qt::WA_PaintOnScreen ); d = new DiffTextWindowData(this); d->m_pDiffTextWindowFrame = pParent; setFocusPolicy(Qt::ClickFocus); setAcceptDrops(true); d->m_pOptions = pOptions; init(nullptr, nullptr, d->m_eLineEndStyle, nullptr, 0, nullptr, nullptr, false); setMinimumSize(QSize(20, 20)); d->m_pStatusBar = pStatusBar; d->m_bPaintingAllowed = true; d->m_bWordWrap = false; d->m_winIdx = winIdx; setFont(d->m_pOptions->m_font); } DiffTextWindow::~DiffTextWindow() { delete d; } void DiffTextWindow::init( const QString& filename, QTextCodec* pTextCodec, e_LineEndStyle eLineEndStyle, const LineData* pLineData, int size, const Diff3LineVector* pDiff3LineVector, const ManualDiffHelpList* pManualDiffHelpList, bool bTriple) { d->m_filename = filename; d->m_pLineData = pLineData; d->m_size = size; d->m_pDiff3LineVector = pDiff3LineVector; d->m_diff3WrapLineVector.clear(); d->m_pManualDiffHelpList = pManualDiffHelpList; d->m_firstLine = 0; d->m_oldFirstLine = -1; d->m_horizScrollOffset = 0; d->m_bTriple = bTriple; d->m_scrollDeltaX = 0; d->m_scrollDeltaY = 0; d->m_bMyUpdate = false; d->m_fastSelectorLine1 = 0; d->m_fastSelectorNofLines = 0; d->m_lineNumberWidth = 0; d->m_maxTextWidth = -1; d->m_pTextCodec = pTextCodec; d->m_eLineEndStyle = eLineEndStyle; update(); d->m_pDiffTextWindowFrame->init(); } void DiffTextWindow::reset() { d->m_pLineData = nullptr; d->m_size = 0; d->m_pDiff3LineVector = nullptr; d->m_filename = ""; d->m_diff3WrapLineVector.clear(); } void DiffTextWindow::setPaintingAllowed(bool bAllowPainting) { if(d->m_bPaintingAllowed != bAllowPainting) { d->m_bPaintingAllowed = bAllowPainting; if(d->m_bPaintingAllowed) update(); else reset(); } } void DiffTextWindow::dragEnterEvent(QDragEnterEvent* e) { e->setAccepted(e->mimeData()->hasUrls() || e->mimeData()->hasText()); // Note that the corresponding drop is handled in KDiff3App::eventFilter(). } void DiffTextWindow::setFirstLine(int firstLine) { int fontHeight = fontMetrics().lineSpacing(); int newFirstLine = max2(0, firstLine); int deltaY = fontHeight * (d->m_firstLine - newFirstLine); d->m_firstLine = newFirstLine; if(d->m_bSelectionInProgress && d->m_selection.isValidFirstLine()) { int line, pos; convertToLinePos(d->m_lastKnownMousePos.x(), d->m_lastKnownMousePos.y(), line, pos); d->m_selection.end(line, pos); update(); } else { QWidget::scroll(0, deltaY); } d->m_pDiffTextWindowFrame->setFirstLine(d->m_firstLine); } int DiffTextWindow::getFirstLine() { return d->m_firstLine; } void DiffTextWindow::setHorizScrollOffset(int horizScrollOffset) { int fontWidth = fontMetrics().width('0'); int xOffset = d->leftInfoWidth() * fontWidth; int deltaX = d->m_horizScrollOffset - qMax(0, horizScrollOffset); d->m_horizScrollOffset = qMax(0, horizScrollOffset); QRect r(xOffset, 0, width() - xOffset, height()); if(d->m_pOptions->m_bRightToLeftLanguage) { deltaX = -deltaX; r = QRect(width() - xOffset - 2, 0, -(width() - xOffset), height()).normalized(); } if(d->m_bSelectionInProgress && d->m_selection.isValidFirstLine()) { int line, pos; convertToLinePos(d->m_lastKnownMousePos.x(), d->m_lastKnownMousePos.y(), line, pos); d->m_selection.end(line, pos); update(); } else { QWidget::scroll(deltaX, 0, r); } } int DiffTextWindow::getMaxTextWidth() { if(d->m_bWordWrap) { return getVisibleTextAreaWidth(); } else if(getAtomic(d->m_maxTextWidth) < 0) { d->m_maxTextWidth = 0; QTextLayout textLayout(QString(), font(), this); for(int i = 0; i < d->m_size; ++i) { textLayout.clearLayout(); textLayout.setText(d->getString(i)); d->prepareTextLayout(textLayout, true); if(textLayout.maximumWidth() > getAtomic(d->m_maxTextWidth)) d->m_maxTextWidth = textLayout.maximumWidth(); } } return getAtomic(d->m_maxTextWidth); } int DiffTextWindow::getNofLines() { return d->m_bWordWrap ? d->m_diff3WrapLineVector.size() : d->m_pDiff3LineVector->size(); } int DiffTextWindow::convertLineToDiff3LineIdx(int line) { if(line >= 0 && d->m_bWordWrap && d->m_diff3WrapLineVector.size() > 0) return d->m_diff3WrapLineVector[min2(line, (int)d->m_diff3WrapLineVector.size() - 1)].diff3LineIndex; else return line; } int DiffTextWindow::convertDiff3LineIdxToLine(int d3lIdx) { if(d->m_bWordWrap && d->m_pDiff3LineVector != nullptr && d->m_pDiff3LineVector->size() > 0) return (*d->m_pDiff3LineVector)[min2(d3lIdx, (int)d->m_pDiff3LineVector->size() - 1)]->sumLinesNeededForDisplay; else return d3lIdx; } /** Returns a line number where the linerange [line, line+nofLines] can be displayed best. If it fits into the currently visible range then the returned value is the current firstLine. */ int getBestFirstLine(int line, int nofLines, int firstLine, int visibleLines) { int newFirstLine = firstLine; if(line < firstLine || line + nofLines + 2 > firstLine + visibleLines) { if(nofLines > visibleLines || nofLines <= (2 * visibleLines / 3 - 1)) newFirstLine = line - visibleLines / 3; else newFirstLine = line - (visibleLines - nofLines); } return newFirstLine; } void DiffTextWindow::setFastSelectorRange(int line1, int nofLines) { d->m_fastSelectorLine1 = line1; d->m_fastSelectorNofLines = nofLines; if(isVisible()) { int newFirstLine = getBestFirstLine( convertDiff3LineIdxToLine(d->m_fastSelectorLine1), convertDiff3LineIdxToLine(d->m_fastSelectorLine1 + d->m_fastSelectorNofLines) - convertDiff3LineIdxToLine(d->m_fastSelectorLine1), d->m_firstLine, getNofVisibleLines()); if(newFirstLine != d->m_firstLine) { scroll(0, newFirstLine - d->m_firstLine); } update(); } } void DiffTextWindow::showStatusLine(int line) { int d3lIdx = convertLineToDiff3LineIdx(line); if(d->m_pDiff3LineVector != nullptr && d3lIdx >= 0 && d3lIdx < (int)d->m_pDiff3LineVector->size()) { const Diff3Line* pD3l = (*d->m_pDiff3LineVector)[d3lIdx]; if(pD3l != nullptr) { int l = pD3l->getLineInFile(d->m_winIdx); QString s = i18n("File") + " " + d->m_filename; if(l != -1) s += ": " + i18n("Line") + " " + QString::number(l + 1); else s += ": " + i18n("Line not available"); if(d->m_pStatusBar != nullptr) d->m_pStatusBar->showMessage(s); emit lineClicked(d->m_winIdx, l); } } } void DiffTextWindow::focusInEvent(QFocusEvent* e) { emit gotFocus(); QWidget::focusInEvent(e); } void DiffTextWindow::mousePressEvent(QMouseEvent* e) { if(e->button() == Qt::LeftButton) { int line; int pos; convertToLinePos(e->x(), e->y(), line, pos); int fontWidth = fontMetrics().width('0'); int xOffset = d->leftInfoWidth() * fontWidth; if((!d->m_pOptions->m_bRightToLeftLanguage && e->x() < xOffset) || (d->m_pOptions->m_bRightToLeftLanguage && e->x() > width() - xOffset)) { emit setFastSelectorLine(convertLineToDiff3LineIdx(line)); d->m_selection.reset(); // Disable current d->m_selection } else { // Selection resetSelection(); d->m_selection.start(line, pos); d->m_selection.end(line, pos); d->m_bSelectionInProgress = true; d->m_lastKnownMousePos = e->pos(); showStatusLine(line); } } } bool isCTokenChar(QChar c) { return (c == '_') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'); } /// Calculate where a token starts and ends, given the x-position on screen. void calcTokenPos(const QString& s, int posOnScreen, int& pos1, int& pos2, int tabSize) { // Cursor conversions that consider g_tabSize int pos = convertToPosInText(s, max2(0, posOnScreen), tabSize); if(pos >= (int)s.length()) { pos1 = s.length(); pos2 = s.length(); return; } pos1 = pos; pos2 = pos + 1; if(isCTokenChar(s[pos1])) { while(pos1 >= 0 && isCTokenChar(s[pos1])) --pos1; ++pos1; while(pos2 < (int)s.length() && isCTokenChar(s[pos2])) ++pos2; } } void DiffTextWindow::mouseDoubleClickEvent(QMouseEvent* e) { d->m_bSelectionInProgress = false; d->m_lastKnownMousePos = e->pos(); if(e->button() == Qt::LeftButton) { int line; int pos; convertToLinePos(e->x(), e->y(), line, pos); // Get the string data of the current line QString s; if(d->m_bWordWrap) { if(line < 0 || line >= (int)d->m_diff3WrapLineVector.size()) return; const Diff3WrapLine& d3wl = d->m_diff3WrapLineVector[line]; s = d->getString(d3wl.diff3LineIndex).mid(d3wl.wrapLineOffset, d3wl.wrapLineLength); } else { if(line < 0 || line >= (int)d->m_pDiff3LineVector->size()) return; s = d->getString(line); } if(!s.isEmpty()) { int pos1, pos2; calcTokenPos(s, pos, pos1, pos2, d->m_pOptions->m_tabSize); resetSelection(); d->m_selection.start(line, convertToPosOnScreen(s, pos1, d->m_pOptions->m_tabSize)); d->m_selection.end(line, convertToPosOnScreen(s, pos2, d->m_pOptions->m_tabSize)); update(); // emit d->m_selectionEnd() happens in the mouseReleaseEvent. showStatusLine(line); } } } void DiffTextWindow::mouseReleaseEvent(QMouseEvent* e) { d->m_bSelectionInProgress = false; d->m_lastKnownMousePos = e->pos(); //if ( e->button() == LeftButton ) { if(d->m_delayedDrawTimer) killTimer(d->m_delayedDrawTimer); d->m_delayedDrawTimer = 0; if(d->m_selection.isValidFirstLine()) { emit selectionEnd(); } } d->m_scrollDeltaX = 0; d->m_scrollDeltaY = 0; } inline int sqr(int x) { return x * x; } void DiffTextWindow::mouseMoveEvent(QMouseEvent* e) { int line; int pos; convertToLinePos(e->x(), e->y(), line, pos); d->m_lastKnownMousePos = e->pos(); if(d->m_selection.isValidFirstLine()) { d->m_selection.end(line, pos); showStatusLine(line); // Scroll because mouse moved out of the window const QFontMetrics& fm = fontMetrics(); int fontWidth = fm.width('0'); int deltaX = 0; int deltaY = 0; if(!d->m_pOptions->m_bRightToLeftLanguage) { if(e->x() < d->leftInfoWidth() * fontWidth) deltaX = -1 - abs(e->x() - d->leftInfoWidth() * fontWidth) / fontWidth; if(e->x() > width()) deltaX = +1 + abs(e->x() - width()) / fontWidth; } else { if(e->x() > width() - 1 - d->leftInfoWidth() * fontWidth) deltaX = +1 + abs(e->x() - (width() - 1 - d->leftInfoWidth() * fontWidth)) / fontWidth; if(e->x() < fontWidth) deltaX = -1 - abs(e->x() - fontWidth) / fontWidth; } if(e->y() < 0) deltaY = -1 - sqr(e->y()) / sqr(fm.lineSpacing()); if(e->y() > height()) deltaY = +1 + sqr(e->y() - height()) / sqr(fm.lineSpacing()); if((deltaX != 0 && d->m_scrollDeltaX != deltaX) || (deltaY != 0 && d->m_scrollDeltaY != deltaY)) { d->m_scrollDeltaX = deltaX; d->m_scrollDeltaY = deltaY; emit scroll(deltaX, deltaY); if(d->m_delayedDrawTimer) killTimer(d->m_delayedDrawTimer); d->m_delayedDrawTimer = startTimer(50); } else { d->m_scrollDeltaX = deltaX; d->m_scrollDeltaY = deltaY; d->myUpdate(0); } } } void DiffTextWindowData::myUpdate(int afterMilliSecs) { if(m_delayedDrawTimer) m_pDiffTextWindow->killTimer(m_delayedDrawTimer); m_bMyUpdate = true; m_delayedDrawTimer = m_pDiffTextWindow->startTimer(afterMilliSecs); } void DiffTextWindow::timerEvent(QTimerEvent*) { killTimer(d->m_delayedDrawTimer); d->m_delayedDrawTimer = 0; if(d->m_bMyUpdate) { int fontHeight = fontMetrics().lineSpacing(); if(d->m_selection.getOldLastLine() != -1) { int lastLine; int firstLine; if(d->m_selection.getOldFirstLine() != -1) { firstLine = min3(d->m_selection.getOldFirstLine(), d->m_selection.getLastLine(), d->m_selection.getOldLastLine()); lastLine = max3(d->m_selection.getOldFirstLine(), d->m_selection.getLastLine(), d->m_selection.getOldLastLine()); } else { firstLine = min2(d->m_selection.getLastLine(), d->m_selection.getOldLastLine()); lastLine = max2(d->m_selection.getLastLine(), d->m_selection.getOldLastLine()); } int y1 = (firstLine - d->m_firstLine) * fontHeight; int y2 = min2(height(), (lastLine - d->m_firstLine + 1) * fontHeight); if(y1 < height() && y2 > 0) { QRect invalidRect = QRect(0, y1 - 1, width(), y2 - y1 + fontHeight); // Some characters in exotic exceed the regular bottom. update(invalidRect); } } d->m_bMyUpdate = false; } if(d->m_scrollDeltaX != 0 || d->m_scrollDeltaY != 0) { d->m_selection.end(d->m_selection.getLastLine() + d->m_scrollDeltaY, d->m_selection.getLastPos() + d->m_scrollDeltaX); emit scroll(d->m_scrollDeltaX, d->m_scrollDeltaY); killTimer(d->m_delayedDrawTimer); d->m_delayedDrawTimer = startTimer(50); } } void DiffTextWindow::resetSelection() { d->m_selection.reset(); update(); } void DiffTextWindow::convertToLinePos(int x, int y, int& line, int& pos) { const QFontMetrics& fm = fontMetrics(); int fontHeight = fm.lineSpacing(); int yOffset = -d->m_firstLine * fontHeight; line = (y - yOffset) / fontHeight; if(line >= 0 && (!d->m_pOptions->m_bWordWrap || line < d->m_diff3WrapLineVector.count())) { QString s = d->getLineString(line); QTextLayout textLayout(s, font(), this); d->prepareTextLayout(textLayout, !d->m_pOptions->m_bWordWrap || d->m_diff3WrapLineVector[line].wrapLineOffset == 0); pos = textLayout.lineAt(0).xToCursor(x - textLayout.position().x()); } else pos = -1; } int Selection::firstPosInLine(int l) { assert(firstLine != -1); int l1 = firstLine; int l2 = lastLine; int p1 = firstPos; int p2 = lastPos; if(l1 > l2) { std::swap(l1, l2); std::swap(p1, p2); } if(l1 == l2 && p1 > p2) { std::swap(p1, p2); } if(l == l1) return p1; return 0; } int Selection::lastPosInLine(int l) { assert(firstLine != -1); int l1 = firstLine; int l2 = lastLine; int p1 = firstPos; int p2 = lastPos; if(l1 > l2) { std::swap(l1, l2); std::swap(p1, p2); } if(l1 == l2 && p1 > p2) { std::swap(p1, p2); } if(l == l2) return p2; return INT_MAX; } bool Selection::within(int l, int p) { if(firstLine == -1) return false; int l1 = firstLine; int l2 = lastLine; int p1 = firstPos; int p2 = lastPos; if(l1 > l2) { std::swap(l1, l2); std::swap(p1, p2); } if(l1 == l2 && p1 > p2) { std::swap(p1, p2); } if(l1 <= l && l <= l2) { if(l1 == l2) return p >= p1 && p < p2; if(l == l1) return p >= p1; if(l == l2) return p < p2; return true; } return false; } bool Selection::lineWithin(int l) { if(firstLine == -1) return false; int l1 = firstLine; int l2 = lastLine; if(l1 > l2) { std::swap(l1, l2); } return (l1 <= l && l <= l2); } class FormatRangeHelper { private: QFont m_font; QPen m_pen; QColor m_background; int m_currentPos; public: QVector m_formatRanges; FormatRangeHelper() { m_pen = QColor(Qt::black); m_background = QColor(Qt::white); m_currentPos = 0; } void setFont(const QFont& f) { m_font = f; } void setPen(const QPen& pen) { m_pen = pen; } void setBackground(const QColor& background) { m_background = background; } void next() { if(m_formatRanges.isEmpty() || m_formatRanges.back().format.foreground().color() != m_pen.color() || m_formatRanges.back().format.background().color() != m_background) { QTextLayout::FormatRange fr; fr.length = 1; fr.start = m_currentPos; fr.format.setForeground(m_pen.color()); fr.format.setBackground(m_background); m_formatRanges.append(fr); } else { ++m_formatRanges.back().length; } ++m_currentPos; } }; void DiffTextWindowData::prepareTextLayout(QTextLayout& textLayout, bool /*bFirstLine*/, int visibleTextWidth) { QTextOption textOption; textOption.setTabStop(QFontMetricsF(m_pDiffTextWindow->font()).width(' ') * m_pOptions->m_tabSize); if(m_pOptions->m_bShowWhiteSpaceCharacters) textOption.setFlags(QTextOption::ShowTabsAndSpaces); if(m_pOptions->m_bRightToLeftLanguage) textOption.setAlignment(Qt::AlignRight); // only relevant for multi line text layout if(visibleTextWidth >= 0) textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); textLayout.setTextOption(textOption); if(m_pOptions->m_bShowWhiteSpaceCharacters) { // This additional format is only necessary for the tab arrow QVector formats; QTextLayout::FormatRange formatRange; formatRange.start = 0; formatRange.length = textLayout.text().length(); formatRange.format.setFont(m_pDiffTextWindow->font()); formats.append(formatRange); textLayout.setFormats(formats); } textLayout.beginLayout(); int leading = m_pDiffTextWindow->fontMetrics().leading(); int height = 0; int fontWidth = m_pDiffTextWindow->fontMetrics().width('0'); int xOffset = leftInfoWidth() * fontWidth - m_horizScrollOffset; int textWidth = visibleTextWidth; if(textWidth < 0) textWidth = m_pDiffTextWindow->width() - xOffset; int indentation = 0; while(1) { QTextLine line = textLayout.createLine(); if(!line.isValid()) break; height += leading; //if ( !bFirstLine ) // indentation = m_pDiffTextWindow->fontMetrics().width(' ') * m_pOptions->m_tabSize; if(visibleTextWidth >= 0) { line.setLineWidth(visibleTextWidth - indentation); line.setPosition(QPointF(indentation, height)); height += line.height(); //bFirstLine = false; } else // only one line { line.setPosition(QPointF(indentation, height)); break; } } textLayout.endLayout(); if(m_pOptions->m_bRightToLeftLanguage) textLayout.setPosition(QPointF(textWidth - textLayout.maximumWidth(), 0)); else textLayout.setPosition(QPointF(xOffset, 0)); } void DiffTextWindowData::writeLine( MyPainter& p, const LineData* pld, const DiffList* pLineDiff1, const DiffList* pLineDiff2, int line, int whatChanged, int whatChanged2, int srcLineIdx, int wrapLineOffset, int wrapLineLength, bool bWrapLine, const QRect& invalidRect, int deviceWidth) { QFont normalFont = p.font(); QFont diffFont = normalFont; //never set //diffFont.setItalic(m_pOptions->m_bItalicForDeltas); const QFontMetrics& fm = p.fontMetrics(); int fontHeight = fm.lineSpacing(); int fontAscent = fm.ascent(); int fontWidth = fm.width('0'); int xOffset = leftInfoWidth() * fontWidth - m_horizScrollOffset; int yOffset = (line - m_firstLine) * fontHeight; QRect lineRect(0, yOffset, deviceWidth, fontHeight); if(!invalidRect.intersects(lineRect)) { return; } int fastSelectorLine1 = m_pDiffTextWindow->convertDiff3LineIdxToLine(m_fastSelectorLine1); int fastSelectorLine2 = m_pDiffTextWindow->convertDiff3LineIdxToLine(m_fastSelectorLine1 + m_fastSelectorNofLines) - 1; bool bFastSelectionRange = (line >= fastSelectorLine1 && line <= fastSelectorLine2); QColor bgColor = m_pOptions->m_bgColor; QColor diffBgColor = m_pOptions->m_diffBgColor; if(bFastSelectionRange) { bgColor = m_pOptions->m_currentRangeBgColor; diffBgColor = m_pOptions->m_currentRangeDiffBgColor; } if(yOffset + fontHeight < invalidRect.top() || invalidRect.bottom() < yOffset - fontHeight) return; int changed = whatChanged; if(pLineDiff1 != nullptr) changed |= 1; if(pLineDiff2 != nullptr) changed |= 2; QColor c = m_pOptions->m_fgColor; p.setPen(c); if(changed == 2) { c = m_cDiff2; } else if(changed == 1) { c = m_cDiff1; } else if(changed == 3) { c = m_cDiffBoth; } if(pld != nullptr) { // First calculate the "changed" information for each character. int i = 0; QString lineString(pld->pLine, pld->size); if(!lineString.isEmpty()) { switch(lineString[lineString.length() - 1].unicode()) { case '\n': lineString[lineString.length() - 1] = 0x00B6; break; // "Pilcrow", "paragraph mark" case '\r': lineString[lineString.length() - 1] = 0x00A4; break; // Currency sign ;0x2761 "curved stem paragraph sign ornament" //case '\0b' : lineString[lineString.length()-1] = 0x2756; break; // some other nice looking character } } QVector charChanged(pld->size); Merger merger(pLineDiff1, pLineDiff2); while(!merger.isEndReached() && i < pld->size) { if(i < pld->size) { charChanged[i] = merger.whatChanged(); ++i; } merger.next(); } int outPos = 0; int lineLength = m_bWordWrap ? wrapLineOffset + wrapLineLength : lineString.length(); FormatRangeHelper frh; for(i = wrapLineOffset; i < lineLength; ++i) { QColor c = m_pOptions->m_fgColor; int cchanged = charChanged[i] | whatChanged; if(cchanged == 2) { c = m_cDiff2; } else if(cchanged == 1) { c = m_cDiff1; } else if(cchanged == 3) { c = m_cDiffBoth; } if(c != m_pOptions->m_fgColor && whatChanged2 == 0 && !m_pOptions->m_bShowWhiteSpace) { // The user doesn't want to see highlighted white space. c = m_pOptions->m_fgColor; } //QRect outRect( xOffset + outPixelPos, yOffset, charWidth*spaces, fontHeight ); //if ( m_pOptions->m_bRightToLeftLanguage ) // outRect = QRect( deviceWidth-1-(xOffset + outPixelPos), yOffset, -charWidth*spaces, fontHeight ).normalized(); //if ( invalidRect.intersects( outRect ) ) { frh.setBackground(bgColor); if(!m_selection.within(line, outPos)) { if(c != m_pOptions->m_fgColor) { QColor lightc = diffBgColor; frh.setBackground(lightc); // Setting italic font here doesn't work: Changing the font only when drawing is too late } frh.setPen(c); frh.next(); frh.setFont(normalFont); } else { frh.setBackground(m_pDiffTextWindow->palette().highlight().color()); frh.setPen(m_pDiffTextWindow->palette().highlightedText().color()); frh.next(); m_selection.bSelectionContainsData = true; } } ++outPos; } // end for QTextLayout textLayout(lineString.mid(wrapLineOffset, lineLength - wrapLineOffset), m_pDiffTextWindow->font(), m_pDiffTextWindow); prepareTextLayout(textLayout, !m_bWordWrap || wrapLineOffset == 0); textLayout.draw(&p, QPoint(0, yOffset), frh.m_formatRanges /*, const QRectF & clip = QRectF() */); } p.fillRect(0, yOffset, leftInfoWidth() * fontWidth, fontHeight, m_pOptions->m_bgColor); xOffset = (m_lineNumberWidth + 2) * fontWidth; int xLeft = m_lineNumberWidth * fontWidth; p.setPen(m_pOptions->m_fgColor); if(pld != nullptr) { if(m_pOptions->m_bShowLineNumbers && !bWrapLine) { QString num; num.sprintf("%0*d", m_lineNumberWidth, srcLineIdx + 1); p.drawText(0, yOffset + fontAscent, num); //p.drawLine( xLeft -1, yOffset, xLeft -1, yOffset+fontHeight-1 ); } if(!bWrapLine || wrapLineLength > 0) { #if defined(__APPLE__) && (QT_VERSION < QT_VERSION_CHECK(5, 0, 0)) Qt::PenStyle wrapLinePenStyle = Qt::DashLine; // Qt::DotLine doesn't work on Mac (Qt4.8.6). #else Qt::PenStyle wrapLinePenStyle = Qt::DotLine; #endif p.setPen(QPen(m_pOptions->m_fgColor, 0, bWrapLine ? wrapLinePenStyle : Qt::SolidLine)); p.drawLine(xOffset + 1, yOffset, xOffset + 1, yOffset + fontHeight - 1); p.setPen(QPen(m_pOptions->m_fgColor, 0, Qt::SolidLine)); } } if(c != m_pOptions->m_fgColor && whatChanged2 == 0) //&& whatChanged==0 ) { if(m_pOptions->m_bShowWhiteSpace) { p.setBrushOrigin(0, 0); p.fillRect(xLeft, yOffset, fontWidth * 2 - 1, fontHeight, QBrush(c, Qt::Dense5Pattern)); } } else { p.fillRect(xLeft, yOffset, fontWidth * 2 - 1, fontHeight, c == m_pOptions->m_fgColor ? bgColor : c); } if(bFastSelectionRange) { p.fillRect(xOffset + fontWidth - 1, yOffset, 3, fontHeight, m_pOptions->m_fgColor); } // Check if line needs a manual diff help mark ManualDiffHelpList::const_iterator ci; for(ci = m_pManualDiffHelpList->begin(); ci != m_pManualDiffHelpList->end(); ++ci) { const ManualDiffHelpEntry& mdhe = *ci; int rangeLine1 = -1; int rangeLine2 = -1; if(m_winIdx == 1) { rangeLine1 = mdhe.lineA1; rangeLine2 = mdhe.lineA2; } if(m_winIdx == 2) { rangeLine1 = mdhe.lineB1; rangeLine2 = mdhe.lineB2; } if(m_winIdx == 3) { rangeLine1 = mdhe.lineC1; rangeLine2 = mdhe.lineC2; } if(rangeLine1 >= 0 && rangeLine2 >= 0 && srcLineIdx >= rangeLine1 && srcLineIdx <= rangeLine2) { p.fillRect(xOffset - fontWidth, yOffset, fontWidth - 1, fontHeight, m_pOptions->m_manualHelpRangeColor); break; } } } void DiffTextWindow::paintEvent(QPaintEvent* e) { QRect invalidRect = e->rect(); if(invalidRect.isEmpty() || !d->m_bPaintingAllowed) return; if(d->m_pDiff3LineVector == nullptr || (d->m_diff3WrapLineVector.empty() && d->m_bWordWrap)) { QPainter p(this); p.fillRect(invalidRect, d->m_pOptions->m_bgColor); return; } bool bOldSelectionContainsData = d->m_selection.bSelectionContainsData; d->m_selection.bSelectionContainsData = false; int endLine = min2(d->m_firstLine + getNofVisibleLines() + 2, getNofLines()); MyPainter p(this, d->m_pOptions->m_bRightToLeftLanguage, width(), fontMetrics().width('0')); p.setFont(font()); p.QPainter::fillRect(invalidRect, d->m_pOptions->m_bgColor); d->draw(p, invalidRect, width(), d->m_firstLine, endLine); p.end(); d->m_oldFirstLine = d->m_firstLine; d->m_selection.clearOldSelection(); if(!bOldSelectionContainsData && d->m_selection.selectionContainsData()) emit newSelection(); } void DiffTextWindow::print(MyPainter& p, const QRect&, int firstLine, int nofLinesPerPage) { if(d->m_pDiff3LineVector == nullptr || !d->m_bPaintingAllowed || (d->m_diff3WrapLineVector.empty() && d->m_bWordWrap)) return; resetSelection(); int oldFirstLine = d->m_firstLine; d->m_firstLine = firstLine; QRect invalidRect = QRect(0, 0, 1000000000, 1000000000); QColor bgColor = d->m_pOptions->m_bgColor; d->m_pOptions->m_bgColor = Qt::white; d->draw(p, invalidRect, p.window().width(), firstLine, min2(firstLine + nofLinesPerPage, getNofLines())); d->m_pOptions->m_bgColor = bgColor; d->m_firstLine = oldFirstLine; } void DiffTextWindowData::draw(MyPainter& p, const QRect& invalidRect, int deviceWidth, int beginLine, int endLine) { m_lineNumberWidth = m_pOptions->m_bShowLineNumbers ? (int)log10((double)qMax(m_size, 1)) + 1 : 0; if(m_winIdx == 1) { m_cThis = m_pOptions->m_colorA; m_cDiff1 = m_pOptions->m_colorB; m_cDiff2 = m_pOptions->m_colorC; } if(m_winIdx == 2) { m_cThis = m_pOptions->m_colorB; m_cDiff1 = m_pOptions->m_colorC; m_cDiff2 = m_pOptions->m_colorA; } if(m_winIdx == 3) { m_cThis = m_pOptions->m_colorC; m_cDiff1 = m_pOptions->m_colorA; m_cDiff2 = m_pOptions->m_colorB; } m_cDiffBoth = m_pOptions->m_colorForConflict; // Conflict color p.setPen(m_cThis); for(int line = beginLine; line < endLine; ++line) { int wrapLineOffset = 0; int wrapLineLength = 0; const Diff3Line* d3l = nullptr; bool bWrapLine = false; if(m_bWordWrap) { Diff3WrapLine& d3wl = m_diff3WrapLineVector[line]; wrapLineOffset = d3wl.wrapLineOffset; wrapLineLength = d3wl.wrapLineLength; d3l = d3wl.pD3L; bWrapLine = line > 0 && m_diff3WrapLineVector[line - 1].pD3L == d3l; } else { d3l = (*m_pDiff3LineVector)[line]; } DiffList* pFineDiff1; DiffList* pFineDiff2; int changed = 0; int changed2 = 0; int srcLineIdx = -1; getLineInfo(*d3l, srcLineIdx, pFineDiff1, pFineDiff2, changed, changed2); writeLine( p, // QPainter srcLineIdx == -1 ? nullptr : &m_pLineData[srcLineIdx], // Text in this line pFineDiff1, pFineDiff2, line, // Line on the screen changed, changed2, srcLineIdx, wrapLineOffset, wrapLineLength, bWrapLine, invalidRect, deviceWidth); } } QString DiffTextWindowData::getString(int d3lIdx) { if(d3lIdx < 0 || d3lIdx >= (int)m_pDiff3LineVector->size()) return QString(); const Diff3Line* d3l = (*m_pDiff3LineVector)[d3lIdx]; DiffList* pFineDiff1; DiffList* pFineDiff2; int changed = 0; int changed2 = 0; int lineIdx; getLineInfo(*d3l, lineIdx, pFineDiff1, pFineDiff2, changed, changed2); if(lineIdx == -1) return QString(); else { const LineData* ld = &m_pLineData[lineIdx]; return QString(ld->pLine, ld->size); } return QString(); } QString DiffTextWindowData::getLineString(int line) { if(m_bWordWrap) { if(line < m_diff3WrapLineVector.count()) { int d3LIdx = m_pDiffTextWindow->convertLineToDiff3LineIdx(line); return getString(d3LIdx).mid(m_diff3WrapLineVector[line].wrapLineOffset, m_diff3WrapLineVector[line].wrapLineLength); } else return QString(); } else { return getString(line); } } void DiffTextWindowData::getLineInfo( const Diff3Line& d3l, int& lineIdx, DiffList*& pFineDiff1, DiffList*& pFineDiff2, // return values int& changed, int& changed2) { changed = 0; changed2 = 0; bool bAEqB = d3l.bAEqB || (d3l.bWhiteLineA && d3l.bWhiteLineB); bool bAEqC = d3l.bAEqC || (d3l.bWhiteLineA && d3l.bWhiteLineC); bool bBEqC = d3l.bBEqC || (d3l.bWhiteLineB && d3l.bWhiteLineC); if(m_winIdx == 1) { lineIdx = d3l.lineA; pFineDiff1 = d3l.pFineAB; pFineDiff2 = d3l.pFineCA; changed |= ((d3l.lineB == -1) != (lineIdx == -1) ? 1 : 0) + ((d3l.lineC == -1) != (lineIdx == -1) && m_bTriple ? 2 : 0); changed2 |= (bAEqB ? 0 : 1) + (bAEqC || !m_bTriple ? 0 : 2); } else if(m_winIdx == 2) { lineIdx = d3l.lineB; pFineDiff1 = d3l.pFineBC; pFineDiff2 = d3l.pFineAB; changed |= ((d3l.lineC == -1) != (lineIdx == -1) && m_bTriple ? 1 : 0) + ((d3l.lineA == -1) != (lineIdx == -1) ? 2 : 0); changed2 |= (bBEqC || !m_bTriple ? 0 : 1) + (bAEqB ? 0 : 2); } else if(m_winIdx == 3) { lineIdx = d3l.lineC; pFineDiff1 = d3l.pFineCA; pFineDiff2 = d3l.pFineBC; changed |= ((d3l.lineA == -1) != (lineIdx == -1) ? 1 : 0) + ((d3l.lineB == -1) != (lineIdx == -1) ? 2 : 0); changed2 |= (bAEqC ? 0 : 1) + (bBEqC ? 0 : 2); } else assert(false); } void DiffTextWindow::resizeEvent(QResizeEvent* e) { QSize s = e->size(); QFontMetrics fm = fontMetrics(); int visibleLines = s.height() / fm.lineSpacing() - 2; int visibleColumns = s.width() / fm.width('0') - d->leftInfoWidth(); if(e->size().height() != e->oldSize().height()) emit resizeHeightChangedSignal(visibleLines); if(e->size().width() != e->oldSize().width()) emit resizeWidthChangedSignal(visibleColumns); QWidget::resizeEvent(e); } int DiffTextWindow::getNofVisibleLines() { QFontMetrics fm = fontMetrics(); int fmh = fm.lineSpacing(); int h = height(); return h / fmh - 1; } int DiffTextWindow::getVisibleTextAreaWidth() { QFontMetrics fm = fontMetrics(); return width() - d->leftInfoWidth() * fm.width('0'); } QString DiffTextWindow::getSelection() { if(d->m_pLineData == nullptr) return QString(); QString selectionString; int line = 0; int lineIdx = 0; int it; int vectorSize = d->m_bWordWrap ? d->m_diff3WrapLineVector.size() : d->m_pDiff3LineVector->size(); for(it = 0; it < vectorSize; ++it) { const Diff3Line* d3l = d->m_bWordWrap ? d->m_diff3WrapLineVector[it].pD3L : (*d->m_pDiff3LineVector)[it]; if(d->m_winIdx == 1) { lineIdx = d3l->lineA; } else if(d->m_winIdx == 2) { lineIdx = d3l->lineB; } else if(d->m_winIdx == 3) { lineIdx = d3l->lineC; } else assert(false); if(lineIdx != -1) { const QChar* pLine = d->m_pLineData[lineIdx].pLine; int size = d->m_pLineData[lineIdx].size; QString lineString = QString(pLine, size); if(d->m_bWordWrap) { size = d->m_diff3WrapLineVector[it].wrapLineLength; lineString = lineString.mid(d->m_diff3WrapLineVector[it].wrapLineOffset, size); } for(int i = 0; i < size; ++i) { if(d->m_selection.within(line, i)) { selectionString += lineString[i]; } } if(d->m_selection.within(line, size) && !(d->m_bWordWrap && it + 1 < vectorSize && d3l == d->m_diff3WrapLineVector[it + 1].pD3L)) { -#if defined(_WIN32) || defined(Q_OS_OS2) +#if defined(_WIN32) selectionString += '\r'; #endif selectionString += '\n'; } } ++line; } return selectionString; } bool DiffTextWindow::findString(const QString& s, int& d3vLine, int& posInLine, bool bDirDown, bool bCaseSensitive) { int it = d3vLine; int endIt = bDirDown ? (int)d->m_pDiff3LineVector->size() : -1; int step = bDirDown ? 1 : -1; int startPos = posInLine; for(; it != endIt; it += step) { QString line = d->getString(it); if(!line.isEmpty()) { int pos = line.indexOf(s, startPos, bCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive); if(pos != -1) { d3vLine = it; posInLine = pos; return true; } startPos = 0; } } return false; } void DiffTextWindow::convertD3LCoordsToLineCoords(int d3LIdx, int d3LPos, int& line, int& pos) { if(d->m_bWordWrap) { int wrapPos = d3LPos; int wrapLine = convertDiff3LineIdxToLine(d3LIdx); while(wrapPos > d->m_diff3WrapLineVector[wrapLine].wrapLineLength) { wrapPos -= d->m_diff3WrapLineVector[wrapLine].wrapLineLength; ++wrapLine; } pos = wrapPos; line = wrapLine; } else { pos = d3LPos; line = d3LIdx; } } void DiffTextWindow::convertLineCoordsToD3LCoords(int line, int pos, int& d3LIdx, int& d3LPos) { if(d->m_bWordWrap) { d3LPos = pos; d3LIdx = convertLineToDiff3LineIdx(line); int wrapLine = convertDiff3LineIdxToLine(d3LIdx); // First wrap line belonging to this d3LIdx while(wrapLine < line) { d3LPos += d->m_diff3WrapLineVector[wrapLine].wrapLineLength; ++wrapLine; } } else { d3LPos = pos; d3LIdx = line; } } void DiffTextWindow::setSelection(int firstLine, int startPos, int lastLine, int endPos, int& l, int& p) { d->m_selection.reset(); if(lastLine >= getNofLines()) { lastLine = getNofLines() - 1; const Diff3Line* d3l = (*d->m_pDiff3LineVector)[convertLineToDiff3LineIdx(lastLine)]; int line = -1; if(d->m_winIdx == 1) line = d3l->lineA; if(d->m_winIdx == 2) line = d3l->lineB; if(d->m_winIdx == 3) line = d3l->lineC; if(line >= 0) endPos = d->m_pLineData[line].width(d->m_pOptions->m_tabSize); } if(d->m_bWordWrap && d->m_pDiff3LineVector != nullptr) { QString s1 = d->getString(firstLine); int firstWrapLine = convertDiff3LineIdxToLine(firstLine); int wrapStartPos = startPos; while(wrapStartPos > d->m_diff3WrapLineVector[firstWrapLine].wrapLineLength) { wrapStartPos -= d->m_diff3WrapLineVector[firstWrapLine].wrapLineLength; s1 = s1.mid(d->m_diff3WrapLineVector[firstWrapLine].wrapLineLength); ++firstWrapLine; } QString s2 = d->getString(lastLine); int lastWrapLine = convertDiff3LineIdxToLine(lastLine); int wrapEndPos = endPos; while(wrapEndPos > d->m_diff3WrapLineVector[lastWrapLine].wrapLineLength) { wrapEndPos -= d->m_diff3WrapLineVector[lastWrapLine].wrapLineLength; s2 = s2.mid(d->m_diff3WrapLineVector[lastWrapLine].wrapLineLength); ++lastWrapLine; } d->m_selection.start(firstWrapLine, convertToPosOnScreen(s1, wrapStartPos, d->m_pOptions->m_tabSize)); d->m_selection.end(lastWrapLine, convertToPosOnScreen(s2, wrapEndPos, d->m_pOptions->m_tabSize)); l = firstWrapLine; p = wrapStartPos; } else { if(d->m_pDiff3LineVector != nullptr){ d->m_selection.start(firstLine, convertToPosOnScreen(d->getString(firstLine), startPos, d->m_pOptions->m_tabSize)); d->m_selection.end(lastLine, convertToPosOnScreen(d->getString(lastLine), endPos, d->m_pOptions->m_tabSize)); l = firstLine; p = startPos; } } update(); } int DiffTextWindowData::convertLineOnScreenToLineInSource(int lineOnScreen, e_CoordType coordType, bool bFirstLine) { int line = -1; if(lineOnScreen >= 0) { if(coordType == eWrapCoords) return lineOnScreen; int d3lIdx = m_pDiffTextWindow->convertLineToDiff3LineIdx(lineOnScreen); if(!bFirstLine && d3lIdx >= (int)m_pDiff3LineVector->size()) d3lIdx = m_pDiff3LineVector->size() - 1; if(coordType == eD3LLineCoords) return d3lIdx; while(line < 0 && d3lIdx < (int)m_pDiff3LineVector->size() && d3lIdx >= 0) { const Diff3Line* d3l = (*m_pDiff3LineVector)[d3lIdx]; if(m_winIdx == 1) line = d3l->lineA; if(m_winIdx == 2) line = d3l->lineB; if(m_winIdx == 3) line = d3l->lineC; if(bFirstLine) ++d3lIdx; else --d3lIdx; } if(coordType == eFileCoords) return line; } return line; } void DiffTextWindow::getSelectionRange(int* pFirstLine, int* pLastLine, e_CoordType coordType) { if(pFirstLine) *pFirstLine = d->convertLineOnScreenToLineInSource(d->m_selection.beginLine(), coordType, true); if(pLastLine) *pLastLine = d->convertLineOnScreenToLineInSource(d->m_selection.endLine(), coordType, false); } void DiffTextWindow::convertSelectionToD3LCoords() { if(d->m_pDiff3LineVector == nullptr || !d->m_bPaintingAllowed || !isVisible() || d->m_selection.isEmpty()) { return; } // convert the d->m_selection to unwrapped coordinates: Later restore to new coords int firstD3LIdx, firstD3LPos; QString s = d->getLineString(d->m_selection.beginLine()); int firstPosInText = convertToPosInText(s, d->m_selection.beginPos(), d->m_pOptions->m_tabSize); convertLineCoordsToD3LCoords(d->m_selection.beginLine(), firstPosInText, firstD3LIdx, firstD3LPos); int lastD3LIdx, lastD3LPos; s = d->getLineString(d->m_selection.endLine()); int lastPosInText = convertToPosInText(s, d->m_selection.endPos(), d->m_pOptions->m_tabSize); convertLineCoordsToD3LCoords(d->m_selection.endLine(), lastPosInText, lastD3LIdx, lastD3LPos); d->m_selection.start(firstD3LIdx, firstD3LPos); d->m_selection.end(lastD3LIdx, lastD3LPos); } int s_maxNofRunnables = 0; class RecalcWordWrapRunnable : public QRunnable { DiffTextWindow* m_pDTW; // DiffTextWindowData* m_pDTWData; // TODO unused? int m_visibleTextWidth; int m_cacheIdx; public: RecalcWordWrapRunnable(DiffTextWindow* p, DiffTextWindowData* pData, int visibleTextWidth, int cacheIdx) : m_pDTW(p), /* m_pDTWData(pData),*/ m_visibleTextWidth(visibleTextWidth), m_cacheIdx(cacheIdx) { Q_UNUSED(pData) // TODO really unused? setAutoDelete(true); //++s_runnableCount; // in Qt>=5.3 only s_runnableCount.fetchAndAddOrdered(1); } void run() { m_pDTW->recalcWordWrapHelper(0, m_visibleTextWidth, m_cacheIdx); // int newValue = --s_runnableCount; // in Qt>=5.3 only int newValue = s_runnableCount.fetchAndAddOrdered(-1) - 1; g_pProgressDialog->setCurrent(s_maxNofRunnables - getAtomic(s_runnableCount)); if(newValue == 0) { QWidget* p = m_pDTW; while(p) { p = p->parentWidget(); if(KDiff3App* pKDiff3App = dynamic_cast(p)) { QMetaObject::invokeMethod(pKDiff3App, "slotFinishRecalcWordWrap", Qt::QueuedConnection); break; } } } } }; static QList s_runnables; bool startRunnables() { if(s_runnables.count() == 0) { return false; } else { g_pProgressDialog->setStayHidden(true); g_pProgressDialog->push(); g_pProgressDialog->setMaxNofSteps(s_runnables.count()); s_maxNofRunnables = s_runnables.count(); g_pProgressDialog->setCurrent(0); for(int i = 0; i < s_runnables.count(); ++i) { QThreadPool::globalInstance()->start(s_runnables[i]); } s_runnables.clear(); return true; } } static const int s_linesPerRunnable = 2000; void DiffTextWindow::recalcWordWrap(bool bWordWrap, int wrapLineVectorSize, int visibleTextWidth) { if(d->m_pDiff3LineVector == nullptr || !isVisible()) { d->m_bWordWrap = bWordWrap; if(!bWordWrap) d->m_diff3WrapLineVector.resize(0); return; } d->m_bWordWrap = bWordWrap; if(bWordWrap) { d->m_lineNumberWidth = d->m_pOptions->m_bShowLineNumbers ? (int)log10((double)qMax(d->m_size, 1)) + 1 : 0; d->m_diff3WrapLineVector.resize(wrapLineVectorSize); if(wrapLineVectorSize == 0) d->m_wrapLineCacheList.clear(); if(wrapLineVectorSize == 0) { d->m_bPaintingAllowed = false; for(int i = 0, j = 0; i < d->m_pDiff3LineVector->size(); i += s_linesPerRunnable, ++j) //int i=0; { d->m_wrapLineCacheList.append(QVector()); s_runnables.push_back(new RecalcWordWrapRunnable(this, d, visibleTextWidth, j)); } //recalcWordWrap( bWordWrap, wrapLineVectorSize, visibleTextWidth, 0 ); } else { recalcWordWrapHelper(wrapLineVectorSize, visibleTextWidth, 0); d->m_bPaintingAllowed = true; } } else { if(wrapLineVectorSize == 0 && getAtomic(d->m_maxTextWidth) < 0) { d->m_diff3WrapLineVector.resize(0); d->m_wrapLineCacheList.clear(); d->m_bPaintingAllowed = false; for(int i = 0, j = 0; i < d->m_pDiff3LineVector->size(); i += s_linesPerRunnable, ++j) { s_runnables.push_back(new RecalcWordWrapRunnable(this, d, visibleTextWidth, j)); } } else { d->m_bPaintingAllowed = true; } } } void DiffTextWindow::recalcWordWrapHelper(int wrapLineVectorSize, int visibleTextWidth, int cacheListIdx) { if(d->m_bWordWrap) { if(g_pProgressDialog->wasCancelled()) return; if(visibleTextWidth < 0) visibleTextWidth = getVisibleTextAreaWidth(); else visibleTextWidth -= d->leftInfoWidth() * fontMetrics().width('0'); int i; int wrapLineIdx = 0; int size = d->m_pDiff3LineVector->size(); int firstD3LineIdx = wrapLineVectorSize > 0 ? 0 : cacheListIdx * s_linesPerRunnable; int endIdx = wrapLineVectorSize > 0 ? size : qMin(firstD3LineIdx + s_linesPerRunnable, size); QVector& wrapLineCache = d->m_wrapLineCacheList[cacheListIdx]; int cacheListIdx2 = 0; QTextLayout textLayout(QString(), font(), this); for(i = firstD3LineIdx; i < endIdx; ++i) { if(g_pProgressDialog->wasCancelled()) return; int linesNeeded = 0; if(wrapLineVectorSize == 0) { QString s = d->getString(i); textLayout.clearLayout(); textLayout.setText(s); d->prepareTextLayout(textLayout, true, visibleTextWidth); linesNeeded = textLayout.lineCount(); for(int l = 0; l < linesNeeded; ++l) { QTextLine line = textLayout.lineAt(l); wrapLineCache.push_back(DiffTextWindowData::WrapLineCacheData(i, line.textStart(), line.textLength())); } } else if(wrapLineVectorSize > 0 && cacheListIdx2 < d->m_wrapLineCacheList.count()) { DiffTextWindowData::WrapLineCacheData* pWrapLineCache = d->m_wrapLineCacheList[cacheListIdx2].data(); int cacheIdx = 0; int clc = d->m_wrapLineCacheList.count() - 1; int cllc = d->m_wrapLineCacheList.last().count(); int curCount = d->m_wrapLineCacheList[cacheListIdx2].count() - 1; int l = 0; while((cacheListIdx2 < clc || (cacheListIdx2 == clc && cacheIdx < cllc)) && pWrapLineCache->m_d3LineIdx <= i) { if(pWrapLineCache->m_d3LineIdx == i) { Diff3WrapLine* pDiff3WrapLine = &d->m_diff3WrapLineVector[wrapLineIdx + l]; pDiff3WrapLine->wrapLineOffset = pWrapLineCache->m_textStart; pDiff3WrapLine->wrapLineLength = pWrapLineCache->m_textLength; ++l; } if(cacheIdx < curCount) { ++cacheIdx; ++pWrapLineCache; } else { ++cacheListIdx2; if(cacheListIdx2 >= d->m_wrapLineCacheList.count()) break; pWrapLineCache = d->m_wrapLineCacheList[cacheListIdx2].data(); curCount = d->m_wrapLineCacheList[cacheListIdx2].count(); cacheIdx = 0; } } linesNeeded = l; } Diff3Line& d3l = *(*d->m_pDiff3LineVector)[i]; if(d3l.linesNeededForDisplay < linesNeeded) { assert(wrapLineVectorSize == 0); d3l.linesNeededForDisplay = linesNeeded; } if(wrapLineVectorSize > 0) { int j; for(j = 0; j < d3l.linesNeededForDisplay; ++j, ++wrapLineIdx) { Diff3WrapLine& d3wl = d->m_diff3WrapLineVector[wrapLineIdx]; d3wl.diff3LineIndex = i; d3wl.pD3L = (*d->m_pDiff3LineVector)[i]; if(j >= linesNeeded) { d3wl.wrapLineOffset = 0; d3wl.wrapLineLength = 0; } } } } if(wrapLineVectorSize > 0) { d->m_firstLine = min2(d->m_firstLine, wrapLineVectorSize - 1); d->m_horizScrollOffset = 0; d->m_pDiffTextWindowFrame->setFirstLine(d->m_firstLine); } } else // no word wrap, just calc the maximum text width { if(g_pProgressDialog->wasCancelled()) return; int size = d->m_pDiff3LineVector->size(); int firstD3LineIdx = cacheListIdx * s_linesPerRunnable; int endIdx = qMin(firstD3LineIdx + s_linesPerRunnable, size); int maxTextWidth = getAtomic(d->m_maxTextWidth); // current value QTextLayout textLayout(QString(), font(), this); for(int i = firstD3LineIdx; i < endIdx; ++i) { if(g_pProgressDialog->wasCancelled()) return; textLayout.clearLayout(); textLayout.setText(d->getString(i)); d->prepareTextLayout(textLayout, true); if(textLayout.maximumWidth() > maxTextWidth) maxTextWidth = textLayout.maximumWidth(); } for(;;) { int prevMaxTextWidth = d->m_maxTextWidth.fetchAndStoreOrdered(maxTextWidth); if(prevMaxTextWidth <= maxTextWidth) break; maxTextWidth = prevMaxTextWidth; } } if(!d->m_selection.isEmpty() && (!d->m_bWordWrap || wrapLineVectorSize > 0)) { // Assume unwrapped coordinates //( Why? ->Conversion to unwrapped coords happened a few lines above in this method. // Also see KDiff3App::recalcWordWrap() on the role of wrapLineVectorSize) // Wrap them now. // convert the d->m_selection to unwrapped coordinates. int firstLine, firstPos; convertD3LCoordsToLineCoords(d->m_selection.beginLine(), d->m_selection.beginPos(), firstLine, firstPos); int lastLine, lastPos; convertD3LCoordsToLineCoords(d->m_selection.endLine(), d->m_selection.endPos(), lastLine, lastPos); d->m_selection.start(firstLine, convertToPosOnScreen(d->getLineString(firstLine), firstPos, d->m_pOptions->m_tabSize)); d->m_selection.end(lastLine, convertToPosOnScreen(d->getLineString(lastLine), lastPos, d->m_pOptions->m_tabSize)); } } class DiffTextWindowFrameData { public: DiffTextWindow* m_pDiffTextWindow; QLineEdit* m_pFileSelection; QPushButton* m_pBrowseButton; Options* m_pOptions; QLabel* m_pLabel; QLabel* m_pTopLine; QLabel* m_pEncoding; QLabel* m_pLineEndStyle; QWidget* m_pTopLineWidget; int m_winIdx; }; DiffTextWindowFrame::DiffTextWindowFrame(QWidget* pParent, QStatusBar* pStatusBar, Options* pOptions, int winIdx, SourceData* psd) : QWidget(pParent) { d = new DiffTextWindowFrameData; d->m_winIdx = winIdx; setAutoFillBackground(true); d->m_pOptions = pOptions; d->m_pTopLineWidget = new QWidget(this); d->m_pFileSelection = new QLineEdit(d->m_pTopLineWidget); d->m_pBrowseButton = new QPushButton("...", d->m_pTopLineWidget); d->m_pBrowseButton->setFixedWidth(30); connect(d->m_pBrowseButton, &QPushButton::clicked, this, &DiffTextWindowFrame::slotBrowseButtonClicked); connect(d->m_pFileSelection, &QLineEdit::returnPressed, this, &DiffTextWindowFrame::slotReturnPressed); d->m_pLabel = new QLabel("A:", d->m_pTopLineWidget); d->m_pTopLine = new QLabel(d->m_pTopLineWidget); d->m_pDiffTextWindow = nullptr; d->m_pDiffTextWindow = new DiffTextWindow(this, pStatusBar, pOptions, winIdx); QVBoxLayout* pVTopLayout = new QVBoxLayout(d->m_pTopLineWidget); pVTopLayout->setMargin(2); pVTopLayout->setSpacing(0); QHBoxLayout* pHL = new QHBoxLayout(); QHBoxLayout* pHL2 = new QHBoxLayout(); pVTopLayout->addLayout(pHL); pVTopLayout->addLayout(pHL2); // Upper line: pHL->setMargin(0); pHL->setSpacing(2); pHL->addWidget(d->m_pLabel, 0); pHL->addWidget(d->m_pFileSelection, 1); pHL->addWidget(d->m_pBrowseButton, 0); pHL->addWidget(d->m_pTopLine, 0); // Lower line pHL2->setMargin(0); pHL2->setSpacing(2); pHL2->addWidget(d->m_pTopLine, 0); d->m_pEncoding = new EncodingLabel(i18n("Encoding:"), this, psd, pOptions); d->m_pLineEndStyle = new QLabel(i18n("Line end style:")); pHL2->addWidget(d->m_pEncoding); pHL2->addWidget(d->m_pLineEndStyle); QVBoxLayout* pVL = new QVBoxLayout(this); pVL->setMargin(0); pVL->setSpacing(0); pVL->addWidget(d->m_pTopLineWidget, 0); pVL->addWidget(d->m_pDiffTextWindow, 1); d->m_pDiffTextWindow->installEventFilter(this); d->m_pFileSelection->installEventFilter(this); d->m_pBrowseButton->installEventFilter(this); init(); } DiffTextWindowFrame::~DiffTextWindowFrame() { delete d; } void DiffTextWindowFrame::init() { DiffTextWindow* pDTW = d->m_pDiffTextWindow; if(pDTW) { QString s = QDir::toNativeSeparators(pDTW->d->m_filename); d->m_pFileSelection->setText(s); QString winId = pDTW->d->m_winIdx == 1 ? (pDTW->d->m_bTriple ? "A (Base)" : "A") : (pDTW->d->m_winIdx == 2 ? "B" : "C"); d->m_pLabel->setText(winId + ":"); d->m_pEncoding->setText(i18n("Encoding:") + " " + (pDTW->d->m_pTextCodec != nullptr ? pDTW->d->m_pTextCodec->name() : QString())); d->m_pLineEndStyle->setText(i18n("Line end style:") + " " + (pDTW->d->m_eLineEndStyle == eLineEndStyleDos ? i18n("DOS") : i18n("Unix"))); } } // Search for the first visible line (search loop needed when no line exist for this file.) int DiffTextWindow::calcTopLineInFile(int firstLine) { int l = -1; for(int i = convertLineToDiff3LineIdx(firstLine); i < (int)d->m_pDiff3LineVector->size(); ++i) { const Diff3Line* d3l = (*d->m_pDiff3LineVector)[i]; l = d3l->getLineInFile(d->m_winIdx); if(l != -1) break; } return l; } void DiffTextWindowFrame::setFirstLine(int firstLine) { DiffTextWindow* pDTW = d->m_pDiffTextWindow; if(pDTW && pDTW->d->m_pDiff3LineVector) { QString s = i18n("Top line"); int lineNumberWidth = (int)log10((double)qMax(pDTW->d->m_size, 1)) + 1; int l = pDTW->calcTopLineInFile(firstLine); int w = d->m_pTopLine->fontMetrics().width( s + " " + QString().fill('0', lineNumberWidth)); d->m_pTopLine->setMinimumWidth(w); if(l == -1) s = i18n("End"); else s += " " + QString::number(l + 1); d->m_pTopLine->setText(s); d->m_pTopLine->repaint(); } } DiffTextWindow* DiffTextWindowFrame::getDiffTextWindow() { return d->m_pDiffTextWindow; } bool DiffTextWindowFrame::eventFilter(QObject* o, QEvent* e) { DiffTextWindow* pDTW = d->m_pDiffTextWindow; if(e->type() == QEvent::FocusIn || e->type() == QEvent::FocusOut) { QColor c1 = d->m_pOptions->m_bgColor; QColor c2; if(d->m_winIdx == 1) c2 = d->m_pOptions->m_colorA; else if(d->m_winIdx == 2) c2 = d->m_pOptions->m_colorB; else if(d->m_winIdx == 3) c2 = d->m_pOptions->m_colorC; QPalette p = d->m_pTopLineWidget->palette(); if(e->type() == QEvent::FocusOut) std::swap(c1, c2); p.setColor(QPalette::Window, c2); setPalette(p); p.setColor(QPalette::WindowText, c1); d->m_pLabel->setPalette(p); d->m_pTopLine->setPalette(p); d->m_pEncoding->setPalette(p); d->m_pLineEndStyle->setPalette(p); } if(o == d->m_pFileSelection && e->type() == QEvent::Drop) { QDropEvent* d = static_cast(e); if(d->mimeData()->hasUrls()) { QList lst = d->mimeData()->urls(); if(lst.count() > 0) { static_cast(o)->setText(lst[0].toString()); static_cast(o)->setFocus(); emit fileNameChanged(lst[0].toString(), pDTW->d->m_winIdx); return true; } } } return false; } void DiffTextWindowFrame::slotReturnPressed() { DiffTextWindow* pDTW = d->m_pDiffTextWindow; if(pDTW->d->m_filename != d->m_pFileSelection->text()) { emit fileNameChanged(d->m_pFileSelection->text(), pDTW->d->m_winIdx); } } void DiffTextWindowFrame::slotBrowseButtonClicked() { QString current = d->m_pFileSelection->text(); QUrl newURL = QFileDialog::getOpenFileUrl(this, i18n("Open File"), QUrl::fromUserInput(current, QString(), QUrl::AssumeLocalFile)); if(!newURL.isEmpty()) { DiffTextWindow* pDTW = d->m_pDiffTextWindow; emit fileNameChanged(newURL.url(), pDTW->d->m_winIdx); } } void DiffTextWindowFrame::sendEncodingChangedSignal(QTextCodec* c) { emit encodingChanged(c); } EncodingLabel::EncodingLabel(const QString& text, DiffTextWindowFrame* pDiffTextWindowFrame, SourceData* pSD, Options* pOptions) : QLabel(text) { m_pDiffTextWindowFrame = pDiffTextWindowFrame; m_pOptions = pOptions; m_pSourceData = pSD; m_pContextEncodingMenu = nullptr; setMouseTracking(true); } void EncodingLabel::mouseMoveEvent(QMouseEvent*) { // When there is no data to display or it came from clipboard, // we will be use UTF-8 only, // in that case there is no possibility to change the encoding in the SourceData // so, we should hide the HandCursor and display usual ArrowCursor if(m_pSourceData->isFromBuffer() || m_pSourceData->isEmpty()) setCursor(QCursor(Qt::ArrowCursor)); else setCursor(QCursor(Qt::PointingHandCursor)); } void EncodingLabel::mousePressEvent(QMouseEvent*) { if(!(m_pSourceData->isFromBuffer() || m_pSourceData->isEmpty())) { delete m_pContextEncodingMenu; m_pContextEncodingMenu = new QMenu(this); QMenu* pContextEncodingSubMenu = new QMenu(m_pContextEncodingMenu); int currentTextCodecEnum = m_pSourceData->getEncoding()->mibEnum(); // the codec that will be checked in the context menu QList mibs = QTextCodec::availableMibs(); QList codecEnumList; // Adding "main" encodings insertCodec(i18n("Unicode, 8 bit"), QTextCodec::codecForName("UTF-8"), codecEnumList, m_pContextEncodingMenu, currentTextCodecEnum); if(QTextCodec::codecForName("System")) { insertCodec(QString(), QTextCodec::codecForName("System"), codecEnumList, m_pContextEncodingMenu, currentTextCodecEnum); } // Adding recent encodings if(m_pOptions != nullptr) { QStringList& recentEncodings = m_pOptions->m_recentEncodings; foreach(const QString& s, recentEncodings) { insertCodec("", QTextCodec::codecForName(s.toLatin1()), codecEnumList, m_pContextEncodingMenu, currentTextCodecEnum); } } // Submenu to add the rest of available encodings pContextEncodingSubMenu->setTitle(i18n("Other")); foreach(int i, mibs) { QTextCodec* c = QTextCodec::codecForMib(i); if(c != nullptr) insertCodec("", c, codecEnumList, pContextEncodingSubMenu, currentTextCodecEnum); } m_pContextEncodingMenu->addMenu(pContextEncodingSubMenu); m_pContextEncodingMenu->exec(QCursor::pos()); } } void EncodingLabel::insertCodec(const QString& visibleCodecName, QTextCodec* pCodec, QList& codecEnumList, QMenu* pMenu, int currentTextCodecEnum) { int CodecMIBEnum = pCodec->mibEnum(); if(pCodec != nullptr && !codecEnumList.contains(CodecMIBEnum)) { QAction* pAction = new QAction(pMenu); // menu takes ownership, so deleting the menu deletes the action too. pAction->setText(visibleCodecName.isEmpty() ? QString(pCodec->name()) : visibleCodecName + " (" + pCodec->name() + ")"); pAction->setData(CodecMIBEnum); pAction->setCheckable(true); if(currentTextCodecEnum == CodecMIBEnum) pAction->setChecked(true); pMenu->addAction(pAction); connect(pAction, SIGNAL(triggered()), this, SLOT(slotEncodingChanged())); codecEnumList.append(CodecMIBEnum); } } void EncodingLabel::slotEncodingChanged() { QAction* pAction = qobject_cast(sender()); if(pAction) { QTextCodec* pCodec = QTextCodec::codecForMib(pAction->data().toInt()); if(pCodec != nullptr) { QString s(pCodec->name()); QStringList& recentEncodings = m_pOptions->m_recentEncodings; if(!recentEncodings.contains(s) && s != "UTF-8" && s != "System") { int itemsToRemove = recentEncodings.size() - m_maxRecentEncodings + 1; for(int i = 0; i < itemsToRemove; ++i) { recentEncodings.removeFirst(); } recentEncodings.append(s); } } m_pDiffTextWindowFrame->sendEncodingChangedSignal(pCodec); } } diff --git a/src/directorymergewindow.cpp b/src/directorymergewindow.cpp index c55578d..f7692c0 100644 --- a/src/directorymergewindow.cpp +++ b/src/directorymergewindow.cpp @@ -1,3739 +1,3739 @@ /*************************************************************************** directorymergewindow.cpp ----------------- begin : Sat Oct 19 2002 copyright : (C) 2002-2011 by Joachim Eibl email : joachim.eibl at gmx.de ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifdef _WIN32 #include #endif #include "directorymergewindow.h" #include "guiutils.h" #include "options.h" #include "progress.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static bool conflictingFileTypes(MergeFileInfos& mfi); static QPixmap getOnePixmap(e_Age eAge, bool bLink, bool bDir); class StatusInfo : public QDialog { QTextEdit* m_pTextEdit; public: explicit StatusInfo(QWidget* pParent) : QDialog(pParent) { QVBoxLayout* pVLayout = new QVBoxLayout(this); m_pTextEdit = new QTextEdit(this); pVLayout->addWidget(m_pTextEdit); setObjectName("StatusInfo"); setWindowFlags(Qt::Dialog); m_pTextEdit->setWordWrapMode(QTextOption::NoWrap); m_pTextEdit->setReadOnly(true); QPushButton* pClose = new QPushButton(tr("Close")); connect(pClose, &QPushButton::clicked, this, &QDialog::accept); pVLayout->addWidget(pClose); } bool isEmpty() { return m_pTextEdit->toPlainText().isEmpty(); } void addText(const QString& s) { m_pTextEdit->append(s); } void clear() { m_pTextEdit->clear(); } void setVisible(bool bVisible) { if(bVisible) { m_pTextEdit->moveCursor(QTextCursor::End); m_pTextEdit->moveCursor(QTextCursor::StartOfLine); m_pTextEdit->ensureCursorVisible(); } QDialog::setVisible(bVisible); if(bVisible) setWindowState(windowState() | Qt::WindowMaximized); } }; class TempRemover { public: TempRemover(const QString& origName, FileAccess& fa); ~TempRemover(); QString name() { return m_name; } bool success() { return m_bSuccess; } private: QString m_name; bool m_bTemp; bool m_bSuccess; }; TempRemover::TempRemover(const QString& origName, FileAccess& fa) { if(fa.isLocal()) { m_name = origName; m_bTemp = false; m_bSuccess = true; } else { m_name = FileAccess::tempFileName(); m_bSuccess = fa.copyFile(m_name); m_bTemp = m_bSuccess; } } TempRemover::~TempRemover() { if(m_bTemp && !m_name.isEmpty()) FileAccess::removeTempFile(m_name); } enum Columns { s_NameCol = 0, s_ACol = 1, s_BCol = 2, s_CCol = 3, s_OpCol = 4, s_OpStatusCol = 5, s_UnsolvedCol = 6, // Nr of unsolved conflicts (for 3 input files) s_SolvedCol = 7, // Nr of auto-solvable conflicts (for 3 input files) s_NonWhiteCol = 8, // Nr of nonwhite deltas (for 2 input files) s_WhiteCol = 9 // Nr of white deltas (for 2 input files) }; enum e_OperationStatus { eOpStatusNone, eOpStatusDone, eOpStatusError, eOpStatusSkipped, eOpStatusNotSaved, eOpStatusInProgress, eOpStatusToDo }; class MergeFileInfos { public: MergeFileInfos() { m_bEqualAB = false; m_bEqualAC = false; m_bEqualBC = false; m_pParent = nullptr; m_bOperationComplete = false; m_bSimOpComplete = false; m_eMergeOperation = eNoOperation; m_eOpStatus = eOpStatusNone; m_ageA = eNotThere; m_ageB = eNotThere; m_ageC = eNotThere; m_bConflictingAges = false; m_pFileInfoA = nullptr; m_pFileInfoB = nullptr; m_pFileInfoC = nullptr; } ~MergeFileInfos() { //for( int i=0; i( const MergeFileInfos& ); QString subPath() const { return m_pFileInfoA && m_pFileInfoA->exists() ? m_pFileInfoA->filePath() : m_pFileInfoB && m_pFileInfoB->exists() ? m_pFileInfoB->filePath() : m_pFileInfoC && m_pFileInfoC->exists() ? m_pFileInfoC->filePath() : QString(""); } QString fileName() const { return m_pFileInfoA && m_pFileInfoA->exists() ? m_pFileInfoA->fileName() : m_pFileInfoB && m_pFileInfoB->exists() ? m_pFileInfoB->fileName() : m_pFileInfoC && m_pFileInfoC->exists() ? m_pFileInfoC->fileName() : QString(""); } bool dirA() const { return m_pFileInfoA ? m_pFileInfoA->isDir() : false; } bool dirB() const { return m_pFileInfoB ? m_pFileInfoB->isDir() : false; } bool dirC() const { return m_pFileInfoC ? m_pFileInfoC->isDir() : false; } bool isLinkA() const { return m_pFileInfoA ? m_pFileInfoA->isSymLink() : false; } bool isLinkB() const { return m_pFileInfoB ? m_pFileInfoB->isSymLink() : false; } bool isLinkC() const { return m_pFileInfoC ? m_pFileInfoC->isSymLink() : false; } bool existsInA() const { return m_pFileInfoA != nullptr; } bool existsInB() const { return m_pFileInfoB != nullptr; } bool existsInC() const { return m_pFileInfoC != nullptr; } MergeFileInfos* m_pParent; FileAccess* m_pFileInfoA; FileAccess* m_pFileInfoB; FileAccess* m_pFileInfoC; TotalDiffStatus m_totalDiffStatus; QList m_children; e_MergeOperation m_eMergeOperation : 5; e_OperationStatus m_eOpStatus : 4; e_Age m_ageA : 3; e_Age m_ageB : 3; e_Age m_ageC : 3; bool m_bOperationComplete : 1; bool m_bSimOpComplete : 1; bool m_bEqualAB : 1; bool m_bEqualAC : 1; bool m_bEqualBC : 1; bool m_bConflictingAges : 1; // Equal age but files are not! }; static Qt::CaseSensitivity s_eCaseSensitivity = Qt::CaseSensitive; class DirectoryMergeWindow::Data : public QAbstractItemModel { public: DirectoryMergeWindow* q; explicit Data(DirectoryMergeWindow* pDMW) { q = pDMW; m_pOptions = nullptr; m_pIconLoader = nullptr; m_pDirectoryMergeInfo = nullptr; m_bSimulatedMergeStarted = false; m_bRealMergeStarted = false; m_bError = false; m_bSyncMode = false; m_pStatusInfo = new StatusInfo(q); m_pStatusInfo->hide(); m_bScanning = false; m_bCaseSensitive = true; m_bUnfoldSubdirs = false; m_bSkipDirStatus = false; m_pRoot = new MergeFileInfos; } ~Data() { delete m_pRoot; } // Implement QAbstractItemModel QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; //Qt::ItemFlags flags ( const QModelIndex & index ) const QModelIndex parent(const QModelIndex& index) const { MergeFileInfos* pMFI = getMFI(index); if(pMFI == nullptr || pMFI == m_pRoot || pMFI->m_pParent == m_pRoot) return QModelIndex(); else { MergeFileInfos* pParentsParent = pMFI->m_pParent->m_pParent; return createIndex(pParentsParent->m_children.indexOf(pMFI->m_pParent), 0, pMFI->m_pParent); } } int rowCount(const QModelIndex& parent = QModelIndex()) const { MergeFileInfos* pParentMFI = getMFI(parent); if(pParentMFI != nullptr) return pParentMFI->m_children.count(); else return m_pRoot->m_children.count(); } int columnCount(const QModelIndex& /*parent*/) const { return 10; } QModelIndex index(int row, int column, const QModelIndex& parent) const { MergeFileInfos* pParentMFI = getMFI(parent); if(pParentMFI == nullptr && row < m_pRoot->m_children.count()) return createIndex(row, column, m_pRoot->m_children[row]); else if(pParentMFI != nullptr && row < pParentMFI->m_children.count()) return createIndex(row, column, pParentMFI->m_children[row]); else return QModelIndex(); } QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; void sort(int column, Qt::SortOrder order); // private data and helper methods MergeFileInfos* getMFI(const QModelIndex& mi) const { if(mi.isValid()) return (MergeFileInfos*)mi.internalPointer(); else return nullptr; } MergeFileInfos* m_pRoot; QString fullNameA(const MergeFileInfos& mfi) { return mfi.existsInA() ? mfi.m_pFileInfoA->absoluteFilePath() : m_dirA.absoluteFilePath() + "/" + mfi.subPath(); } QString fullNameB(const MergeFileInfos& mfi) { return mfi.existsInB() ? mfi.m_pFileInfoB->absoluteFilePath() : m_dirB.absoluteFilePath() + "/" + mfi.subPath(); } QString fullNameC(const MergeFileInfos& mfi) { return mfi.existsInC() ? mfi.m_pFileInfoC->absoluteFilePath() : m_dirC.absoluteFilePath() + "/" + mfi.subPath(); } QString fullNameDest(const MergeFileInfos& mfi) { if(m_dirDestInternal.prettyAbsPath() == m_dirC.prettyAbsPath()) return fullNameC(mfi); else if(m_dirDestInternal.prettyAbsPath() == m_dirB.prettyAbsPath()) return fullNameB(mfi); else return m_dirDestInternal.absoluteFilePath() + "/" + mfi.subPath(); } FileAccess m_dirA; FileAccess m_dirB; FileAccess m_dirC; FileAccess m_dirDest; FileAccess m_dirDestInternal; Options* m_pOptions; void calcDirStatus(bool bThreeDirs, const QModelIndex& mi, int& nofFiles, int& nofDirs, int& nofEqualFiles, int& nofManualMerges); void mergeContinue(bool bStart, bool bVerbose); void prepareListView(ProgressProxy& pp); void calcSuggestedOperation(const QModelIndex& mi, e_MergeOperation eDefaultOperation); void setAllMergeOperations(e_MergeOperation eDefaultOperation); friend class MergeFileInfos; bool canContinue(); QModelIndex treeIterator(QModelIndex mi, bool bVisitChildren = true, bool bFindInvisible = false); void prepareMergeStart(const QModelIndex& miBegin, const QModelIndex& miEnd, bool bVerbose); bool executeMergeOperation(MergeFileInfos& mfi, bool& bSingleFileMerge); void scanDirectory(const QString& dirName, t_DirectoryList& dirList); void scanLocalDirectory(const QString& dirName, t_DirectoryList& dirList); bool fastFileComparison(FileAccess& fi1, FileAccess& fi2, bool& bError, QString& status); void compareFilesAndCalcAges(MergeFileInfos& mfi); void setMergeOperation(const QModelIndex& mi, e_MergeOperation eMergeOp, bool bRecursive = true); bool isDir(const QModelIndex& mi); QString getFileName(const QModelIndex& mi); bool copyFLD(const QString& srcName, const QString& destName); bool deleteFLD(const QString& name, bool bCreateBackup); bool makeDir(const QString& name, bool bQuiet = false); bool renameFLD(const QString& srcName, const QString& destName); bool mergeFLD(const QString& nameA, const QString& nameB, const QString& nameC, const QString& nameDest, bool& bSingleFileMerge); t_DirectoryList m_dirListA; t_DirectoryList m_dirListB; t_DirectoryList m_dirListC; QString m_dirMergeStateFilename; class FileKey { public: const FileAccess* m_pFA; explicit FileKey(const FileAccess& fa) : m_pFA(&fa) {} int getParents(const FileAccess* pFA, const FileAccess* v[]) const { int s = 0; for(s = 0; pFA->parent() != nullptr; pFA = pFA->parent(), ++s) v[s] = pFA; return s; } // This is essentially the same as // int r = filePath().compare( fa.filePath() ) // if ( r<0 ) return true; // if ( r==0 ) return m_col < fa.m_col; // return false; bool operator<(const FileKey& fk) const { const FileAccess* v1[100]; const FileAccess* v2[100]; int v1Size = getParents(m_pFA, v1); int v2Size = getParents(fk.m_pFA, v2); for(int i = 0; i < v1Size && i < v2Size; ++i) { int r = v1[v1Size - i - 1]->fileName().compare(v2[v2Size - i - 1]->fileName(), s_eCaseSensitivity); if(r < 0) return true; else if(r > 0) return false; } if(v1Size < v2Size) return true; return false; } }; typedef QMap t_fileMergeMap; t_fileMergeMap m_fileMergeMap; bool m_bFollowDirLinks; bool m_bFollowFileLinks; bool m_bSimulatedMergeStarted; bool m_bRealMergeStarted; bool m_bError; bool m_bSyncMode; bool m_bDirectoryMerge; // if true, then merge is the default operation, otherwise it's diff. bool m_bCaseSensitive; bool m_bUnfoldSubdirs; bool m_bSkipDirStatus; bool m_bScanning; // true while in init() KIconLoader* m_pIconLoader; DirectoryMergeInfo* m_pDirectoryMergeInfo; StatusInfo* m_pStatusInfo; typedef std::list MergeItemList; // linked list MergeItemList m_mergeItemList; MergeItemList::iterator m_currentIndexForOperation; QModelIndex m_selection1Index; QModelIndex m_selection2Index; QModelIndex m_selection3Index; void selectItemAndColumn(const QModelIndex& mi, bool bContextMenu); friend class DirMergeItem; QAction* m_pDirStartOperation; QAction* m_pDirRunOperationForCurrentItem; QAction* m_pDirCompareCurrent; QAction* m_pDirMergeCurrent; QAction* m_pDirRescan; QAction* m_pDirChooseAEverywhere; QAction* m_pDirChooseBEverywhere; QAction* m_pDirChooseCEverywhere; QAction* m_pDirAutoChoiceEverywhere; QAction* m_pDirDoNothingEverywhere; QAction* m_pDirFoldAll; QAction* m_pDirUnfoldAll; KToggleAction* m_pDirShowIdenticalFiles; KToggleAction* m_pDirShowDifferentFiles; KToggleAction* m_pDirShowFilesOnlyInA; KToggleAction* m_pDirShowFilesOnlyInB; KToggleAction* m_pDirShowFilesOnlyInC; KToggleAction* m_pDirSynchronizeDirectories; KToggleAction* m_pDirChooseNewerFiles; QAction* m_pDirCompareExplicit; QAction* m_pDirMergeExplicit; QAction* m_pDirCurrentDoNothing; QAction* m_pDirCurrentChooseA; QAction* m_pDirCurrentChooseB; QAction* m_pDirCurrentChooseC; QAction* m_pDirCurrentMerge; QAction* m_pDirCurrentDelete; QAction* m_pDirCurrentSyncDoNothing; QAction* m_pDirCurrentSyncCopyAToB; QAction* m_pDirCurrentSyncCopyBToA; QAction* m_pDirCurrentSyncDeleteA; QAction* m_pDirCurrentSyncDeleteB; QAction* m_pDirCurrentSyncDeleteAAndB; QAction* m_pDirCurrentSyncMergeToA; QAction* m_pDirCurrentSyncMergeToB; QAction* m_pDirCurrentSyncMergeToAAndB; QAction* m_pDirSaveMergeState; QAction* m_pDirLoadMergeState; bool init(FileAccess& dirA, FileAccess& dirB, FileAccess& dirC, FileAccess& dirDest, bool bDirectoryMerge, bool bReload); void setOpStatus(const QModelIndex& mi, e_OperationStatus eOpStatus) { if(MergeFileInfos* pMFI = getMFI(mi)) { pMFI->m_eOpStatus = eOpStatus; emit dataChanged(mi, mi); } } }; QVariant DirectoryMergeWindow::Data::data(const QModelIndex& index, int role) const { MergeFileInfos* pMFI = getMFI(index); if(pMFI) { if(role == Qt::DisplayRole) { switch(index.column()) { case s_NameCol: return QFileInfo(pMFI->subPath()).fileName(); case s_ACol: return "A"; case s_BCol: return "B"; case s_CCol: return "C"; //case s_OpCol: return i18n("Operation"); //case s_OpStatusCol: return i18n("Status"); case s_UnsolvedCol: return i18n("Unsolved"); case s_SolvedCol: return i18n("Solved"); case s_NonWhiteCol: return i18n("Nonwhite"); case s_WhiteCol: return i18n("White"); //default : return QVariant(); } if(s_OpCol == index.column()) { bool bDir = pMFI->dirA() || pMFI->dirB() || pMFI->dirC(); switch(pMFI->m_eMergeOperation) { case eNoOperation: return ""; break; case eCopyAToB: return i18n("Copy A to B"); break; case eCopyBToA: return i18n("Copy B to A"); break; case eDeleteA: return i18n("Delete A"); break; case eDeleteB: return i18n("Delete B"); break; case eDeleteAB: return i18n("Delete A & B"); break; case eMergeToA: return i18n("Merge to A"); break; case eMergeToB: return i18n("Merge to B"); break; case eMergeToAB: return i18n("Merge to A & B"); break; case eCopyAToDest: return "A"; break; case eCopyBToDest: return "B"; break; case eCopyCToDest: return "C"; break; case eDeleteFromDest: return i18n("Delete (if exists)"); break; case eMergeABCToDest: return bDir ? i18n("Merge") : i18n("Merge (manual)"); break; case eMergeABToDest: return bDir ? i18n("Merge") : i18n("Merge (manual)"); break; case eConflictingFileTypes: return i18n("Error: Conflicting File Types"); break; case eChangedAndDeleted: return i18n("Error: Changed and Deleted"); break; case eConflictingAges: return i18n("Error: Dates are equal but files are not."); break; default: assert(false); break; } } if(s_OpStatusCol == index.column()) { switch(pMFI->m_eOpStatus) { case eOpStatusNone: return ""; case eOpStatusDone: return i18n("Done"); case eOpStatusError: return i18n("Error"); case eOpStatusSkipped: return i18n("Skipped."); case eOpStatusNotSaved: return i18n("Not saved."); case eOpStatusInProgress: return i18n("In progress..."); case eOpStatusToDo: return i18n("To do."); } } } else if(role == Qt::DecorationRole) { if(s_NameCol == index.column()) { return getOnePixmap(eAgeEnd, pMFI->isLinkA() || pMFI->isLinkB() || pMFI->isLinkC(), pMFI->dirA() || pMFI->dirB() || pMFI->dirC()); } if(s_ACol == index.column()) { return getOnePixmap(pMFI->m_ageA, pMFI->isLinkA(), pMFI->dirA()); } if(s_BCol == index.column()) { return getOnePixmap(pMFI->m_ageB, pMFI->isLinkB(), pMFI->dirB()); } if(s_CCol == index.column()) { return getOnePixmap(pMFI->m_ageC, pMFI->isLinkC(), pMFI->dirC()); } } else if(role == Qt::TextAlignmentRole) { if(s_UnsolvedCol == index.column() || s_SolvedCol == index.column() || s_NonWhiteCol == index.column() || s_WhiteCol == index.column()) return Qt::AlignRight; } } return QVariant(); } QVariant DirectoryMergeWindow::Data::headerData(int section, Qt::Orientation orientation, int role) const { if(orientation == Qt::Horizontal && section >= 0 && section < columnCount(QModelIndex()) && role == Qt::DisplayRole) { switch(section) { case s_NameCol: return i18n("Name"); case s_ACol: return "A"; case s_BCol: return "B"; case s_CCol: return "C"; case s_OpCol: return i18n("Operation"); case s_OpStatusCol: return i18n("Status"); case s_UnsolvedCol: return i18n("Unsolved"); case s_SolvedCol: return i18n("Solved"); case s_NonWhiteCol: return i18n("Nonwhite"); case s_WhiteCol: return i18n("White"); default: return QVariant(); } } return QVariant(); } // Previously Q3ListViewItem::paintCell(p,cg,column,width,align); class DirectoryMergeWindow::DirMergeItemDelegate : public QStyledItemDelegate { DirectoryMergeWindow* m_pDMW; DirectoryMergeWindow::Data* d; public: explicit DirMergeItemDelegate(DirectoryMergeWindow* pParent) : QStyledItemDelegate(pParent), m_pDMW(pParent), d(pParent->d) { } void paint(QPainter* p, const QStyleOptionViewItem& option, const QModelIndex& index) const { int column = index.column(); if(column == s_ACol || column == s_BCol || column == s_CCol) { QVariant value = index.data(Qt::DecorationRole); QPixmap icon; if(value.isValid()) { if(value.type() == QVariant::Icon) { icon = qvariant_cast(value).pixmap(16, 16); //icon = qvariant_cast(value); //decorationRect = QRect(QPoint(0, 0), icon.actualSize(option.decorationSize, iconMode, iconState)); } else { icon = qvariant_cast(value); //decorationRect = QRect(QPoint(0, 0), option.decorationSize).intersected(pixmap.rect()); } } int x = option.rect.left(); int y = option.rect.top(); //QPixmap icon = value.value(); //pixmap(column); if(!icon.isNull()) { int yOffset = (sizeHint(option, index).height() - icon.height()) / 2; p->drawPixmap(x + 2, y + yOffset, icon); int i = index == d->m_selection1Index ? 1 : index == d->m_selection2Index ? 2 : index == d->m_selection3Index ? 3 : 0; if(i != 0) { Options* pOpts = d->m_pOptions; QColor c(i == 1 ? pOpts->m_colorA : i == 2 ? pOpts->m_colorB : pOpts->m_colorC); p->setPen(c); // highlight() ); p->drawRect(x + 2, y + yOffset, icon.width(), icon.height()); p->setPen(QPen(c, 0, Qt::DotLine)); p->drawRect(x + 1, y + yOffset - 1, icon.width() + 2, icon.height() + 2); p->setPen(Qt::white); QString s(QChar('A' + i - 1)); p->drawText(x + 2 + (icon.width() - p->fontMetrics().width(s)) / 2, y + yOffset + (icon.height() + p->fontMetrics().ascent()) / 2 - 1, s); } else { p->setPen(m_pDMW->palette().background().color()); p->drawRect(x + 1, y + yOffset - 1, icon.width() + 2, icon.height() + 2); } return; } } QStyleOptionViewItem option2 = option; if(column >= s_UnsolvedCol) { option2.displayAlignment = Qt::AlignRight; } QStyledItemDelegate::paint(p, option2, index); } QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { QSize sz = QStyledItemDelegate::sizeHint(option, index); return sz.expandedTo(QSize(0, 18)); } }; DirectoryMergeWindow::DirectoryMergeWindow(QWidget* pParent, Options* pOptions, KIconLoader* pIconLoader) : QTreeView(pParent) { d = new Data(this); setModel(d); setItemDelegate(new DirMergeItemDelegate(this)); connect(this, &DirectoryMergeWindow::doubleClicked, this, &DirectoryMergeWindow::onDoubleClick); connect(this, &DirectoryMergeWindow::expanded, this, &DirectoryMergeWindow::onExpanded); d->m_pOptions = pOptions; d->m_pIconLoader = pIconLoader; setSortingEnabled(true); } DirectoryMergeWindow::~DirectoryMergeWindow() { delete d; } QString DirectoryMergeWindow::getDirNameA() { return d->m_dirA.prettyAbsPath(); } QString DirectoryMergeWindow::getDirNameB() { return d->m_dirB.prettyAbsPath(); } QString DirectoryMergeWindow::getDirNameC() { return d->m_dirC.prettyAbsPath(); } QString DirectoryMergeWindow::getDirNameDest() { return d->m_dirDest.prettyAbsPath(); } void DirectoryMergeWindow::setDirectoryMergeInfo(DirectoryMergeInfo* p) { d->m_pDirectoryMergeInfo = p; } bool DirectoryMergeWindow::isDirectoryMergeInProgress() { return d->m_bRealMergeStarted; } bool DirectoryMergeWindow::isSyncMode() { return d->m_bSyncMode; } bool DirectoryMergeWindow::isScanning() { return d->m_bScanning; } bool DirectoryMergeWindow::Data::fastFileComparison( FileAccess& fi1, FileAccess& fi2, bool& bError, QString& status) { ProgressProxy pp; status = ""; bool bEqual = false; bError = true; if(!m_bFollowFileLinks) { if(fi1.isSymLink() != fi2.isSymLink()) { status = i18n("Mix of links and normal files."); return bEqual; } else if(fi1.isSymLink() && fi2.isSymLink()) { bError = false; bEqual = fi1.readLink() == fi2.readLink(); status = i18n("Link: "); return bEqual; } } if(fi1.size() != fi2.size()) { bEqual = false; status = i18n("Size. "); return bEqual; } else if(m_pOptions->m_bDmTrustSize) { bEqual = true; return bEqual; } if(m_pOptions->m_bDmTrustDate) { bEqual = (fi1.lastModified() == fi2.lastModified() && fi1.size() == fi2.size()); bError = false; status = i18n("Date & Size: "); return bEqual; } if(m_pOptions->m_bDmTrustDateFallbackToBinary) { bEqual = (fi1.lastModified() == fi2.lastModified() && fi1.size() == fi2.size()); if(bEqual) { bError = false; status = i18n("Date & Size: "); return bEqual; } } QString fileName1 = fi1.absoluteFilePath(); QString fileName2 = fi2.absoluteFilePath(); TempRemover tr1(fileName1, fi1); if(!tr1.success()) { status = i18n("Creating temp copy of %1 failed.", fileName1); return bEqual; } TempRemover tr2(fileName2, fi2); if(!tr2.success()) { status = i18n("Creating temp copy of %1 failed.", fileName2); return bEqual; } std::vector buf1(100000); std::vector buf2(buf1.size()); QFile file1(tr1.name()); if(!file1.open(QIODevice::ReadOnly)) { status = i18n("Opening %1 failed.", fileName1); return bEqual; } QFile file2(tr2.name()); if(!file2.open(QIODevice::ReadOnly)) { status = i18n("Opening %1 failed.", fileName2); return bEqual; } pp.setInformation(i18n("Comparing file..."), 0, false); typedef qint64 t_FileSize; t_FileSize fullSize = file1.size(); t_FileSize sizeLeft = fullSize; pp.setMaxNofSteps(fullSize / buf1.size()); while(sizeLeft > 0 && !pp.wasCancelled()) { int len = min2(sizeLeft, (t_FileSize)buf1.size()); if(len != file1.read(&buf1[0], len)) { status = i18n("Error reading from %1", fileName1); return bEqual; } if(len != file2.read(&buf2[0], len)) { status = i18n("Error reading from %1", fileName2); return bEqual; } if(memcmp(&buf1[0], &buf2[0], len) != 0) { bError = false; return bEqual; } sizeLeft -= len; //pp.setCurrent(double(fullSize-sizeLeft)/fullSize, false ); pp.step(); } // If the program really arrives here, then the files are really equal. bError = false; bEqual = true; return bEqual; } int DirectoryMergeWindow::totalColumnWidth() { int w = 0; for(int i = 0; i < s_OpStatusCol; ++i) { w += columnWidth(i); } return w; } void DirectoryMergeWindow::reload() { if(isDirectoryMergeInProgress()) { int result = KMessageBox::warningYesNo(this, i18n("You are currently doing a directory merge. Are you sure, you want to abort the merge and rescan the directory?"), i18n("Warning"), KGuiItem(i18n("Rescan")), KGuiItem(i18n("Continue Merging"))); if(result != KMessageBox::Yes) return; } init(d->m_dirA, d->m_dirB, d->m_dirC, d->m_dirDest, d->m_bDirectoryMerge, true); //fix file visibilities after reload or menu will be out of sync with display if changed from defaults. updateFileVisibilities(); } // Copy pm2 onto pm1, but preserve the alpha value from pm1 where pm2 is transparent. static QPixmap pixCombiner(const QPixmap* pm1, const QPixmap* pm2) { QImage img1 = pm1->toImage().convertToFormat(QImage::Format_ARGB32); QImage img2 = pm2->toImage().convertToFormat(QImage::Format_ARGB32); for(int y = 0; y < img1.height(); y++) { quint32* line1 = reinterpret_cast(img1.scanLine(y)); quint32* line2 = reinterpret_cast(img2.scanLine(y)); for(int x = 0; x < img1.width(); x++) { if(qAlpha(line2[x]) > 0) line1[x] = (line2[x] | 0xff000000); } } return QPixmap::fromImage(img1); } // like pixCombiner but let the pm1 color shine through static QPixmap pixCombiner2(const QPixmap* pm1, const QPixmap* pm2) { QPixmap pix = *pm1; QPainter p(&pix); p.setOpacity(0.5); p.drawPixmap(0, 0, *pm2); p.end(); return pix; } void DirectoryMergeWindow::Data::calcDirStatus(bool bThreeDirs, const QModelIndex& mi, int& nofFiles, int& nofDirs, int& nofEqualFiles, int& nofManualMerges) { MergeFileInfos* pMFI = getMFI(mi); if(pMFI->dirA() || pMFI->dirB() || pMFI->dirC()) { ++nofDirs; } else { ++nofFiles; if(pMFI->m_bEqualAB && (!bThreeDirs || pMFI->m_bEqualAC)) { ++nofEqualFiles; } else { if(pMFI->m_eMergeOperation == eMergeABCToDest || pMFI->m_eMergeOperation == eMergeABToDest) ++nofManualMerges; } } for(int childIdx = 0; childIdx < rowCount(mi); ++childIdx) calcDirStatus(bThreeDirs, index(childIdx, 0, mi), nofFiles, nofDirs, nofEqualFiles, nofManualMerges); } struct t_ItemInfo { bool bExpanded; bool bOperationComplete; QString status; e_MergeOperation eMergeOperation; }; bool DirectoryMergeWindow::init( FileAccess& dirA, FileAccess& dirB, FileAccess& dirC, FileAccess& dirDest, bool bDirectoryMerge, bool bReload) { return d->init(dirA, dirB, dirC, dirDest, bDirectoryMerge, bReload); } bool DirectoryMergeWindow::Data::init( FileAccess& dirA, FileAccess& dirB, FileAccess& dirC, FileAccess& dirDest, bool bDirectoryMerge, bool bReload) { if(m_pOptions->m_bDmFullAnalysis) { // A full analysis uses the same ressources that a normal text-diff/merge uses. // So make sure that the user saves his data first. bool bCanContinue = false; emit q->checkIfCanContinue(&bCanContinue); if(!bCanContinue) return false; emit q->startDiffMerge("", "", "", "", "", "", "", nullptr); // hide main window } q->show(); q->setUpdatesEnabled(true); std::map expandedDirsMap; if(bReload) { // Remember expanded items TODO //QTreeWidgetItemIterator it( this ); //while ( *it ) //{ // DirMergeItem* pDMI = static_cast( *it ); // t_ItemInfo& ii = expandedDirsMap[ pDMI->m_pMFI->subPath() ]; // ii.bExpanded = pDMI->isExpanded(); // ii.bOperationComplete = pDMI->m_pMFI->m_bOperationComplete; // ii.status = pDMI->text( s_OpStatusCol ); // ii.eMergeOperation = pDMI->m_pMFI->m_eMergeOperation; // ++it; //} } ProgressProxy pp; m_bFollowDirLinks = m_pOptions->m_bDmFollowDirLinks; m_bFollowFileLinks = m_pOptions->m_bDmFollowFileLinks; m_bSimulatedMergeStarted = false; m_bRealMergeStarted = false; m_bError = false; m_bDirectoryMerge = bDirectoryMerge; m_selection1Index = QModelIndex(); m_selection2Index = QModelIndex(); m_selection3Index = QModelIndex(); m_bCaseSensitive = m_pOptions->m_bDmCaseSensitiveFilenameComparison; m_bUnfoldSubdirs = m_pOptions->m_bDmUnfoldSubdirs; m_bSkipDirStatus = m_pOptions->m_bDmSkipDirStatus; beginResetModel(); m_pRoot->m_children.clear(); m_mergeItemList.clear(); endResetModel(); m_currentIndexForOperation = m_mergeItemList.end(); m_dirA = dirA; m_dirB = dirB; m_dirC = dirC; m_dirDest = dirDest; if(!bReload) { m_pDirShowIdenticalFiles->setChecked(true); m_pDirShowDifferentFiles->setChecked(true); m_pDirShowFilesOnlyInA->setChecked(true); m_pDirShowFilesOnlyInB->setChecked(true); m_pDirShowFilesOnlyInC->setChecked(true); } // Check if all input directories exist and are valid. The dest dir is not tested now. // The test will happen only when we are going to write to it. if(!m_dirA.isDir() || !m_dirB.isDir() || (m_dirC.isValid() && !m_dirC.isDir())) { QString text(i18n("Opening of directories failed:")); text += "\n\n"; if(!dirA.isDir()) { text += i18n("Dir A \"%1\" does not exist or is not a directory.\n", m_dirA.prettyAbsPath()); } if(!dirB.isDir()) { text += i18n("Dir B \"%1\" does not exist or is not a directory.\n", m_dirB.prettyAbsPath()); } if(m_dirC.isValid() && !m_dirC.isDir()) { text += i18n("Dir C \"%1\" does not exist or is not a directory.\n", m_dirC.prettyAbsPath()); } KMessageBox::sorry(q, text, i18n("Directory Open Error")); return false; } if(m_dirC.isValid() && (m_dirDest.prettyAbsPath() == m_dirA.prettyAbsPath() || m_dirDest.prettyAbsPath() == m_dirB.prettyAbsPath())) { KMessageBox::error(q, i18n("The destination directory must not be the same as A or B when " "three directories are merged.\nCheck again before continuing."), i18n("Parameter Warning")); return false; } m_bScanning = true; emit q->statusBarMessage(i18n("Scanning directories...")); m_bSyncMode = m_pOptions->m_bDmSyncMode && !m_dirC.isValid() && !m_dirDest.isValid(); if(m_dirDest.isValid()) m_dirDestInternal = m_dirDest; else m_dirDestInternal = m_dirC.isValid() ? m_dirC : m_dirB; QString origCurrentDirectory = QDir::currentPath(); m_fileMergeMap.clear(); s_eCaseSensitivity = m_bCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive; t_DirectoryList::iterator i; // calc how many directories will be read: double nofScans = (m_dirA.isValid() ? 1 : 0) + (m_dirB.isValid() ? 1 : 0) + (m_dirC.isValid() ? 1 : 0); int currentScan = 0; //TODO setColumnWidthMode(s_UnsolvedCol, Q3ListView::Manual); // setColumnWidthMode(s_SolvedCol, Q3ListView::Manual); // setColumnWidthMode(s_WhiteCol, Q3ListView::Manual); // setColumnWidthMode(s_NonWhiteCol, Q3ListView::Manual); q->setColumnHidden(s_CCol, !m_dirC.isValid()); q->setColumnHidden(s_WhiteCol, !m_pOptions->m_bDmFullAnalysis); q->setColumnHidden(s_NonWhiteCol, !m_pOptions->m_bDmFullAnalysis); q->setColumnHidden(s_UnsolvedCol, !m_pOptions->m_bDmFullAnalysis); q->setColumnHidden(s_SolvedCol, !(m_pOptions->m_bDmFullAnalysis && m_dirC.isValid())); bool bListDirSuccessA = true; bool bListDirSuccessB = true; bool bListDirSuccessC = true; m_dirListA.clear(); m_dirListB.clear(); m_dirListC.clear(); if(m_dirA.isValid()) { pp.setInformation(i18n("Reading Directory A")); pp.setSubRangeTransformation(currentScan / nofScans, (currentScan + 1) / nofScans); ++currentScan; bListDirSuccessA = m_dirA.listDir(&m_dirListA, m_pOptions->m_bDmRecursiveDirs, m_pOptions->m_bDmFindHidden, m_pOptions->m_DmFilePattern, m_pOptions->m_DmFileAntiPattern, m_pOptions->m_DmDirAntiPattern, m_pOptions->m_bDmFollowDirLinks, m_pOptions->m_bDmUseCvsIgnore); for(i = m_dirListA.begin(); i != m_dirListA.end(); ++i) { MergeFileInfos& mfi = m_fileMergeMap[FileKey(*i)]; //std::cout <filePath()<m_bDmRecursiveDirs, m_pOptions->m_bDmFindHidden, m_pOptions->m_DmFilePattern, m_pOptions->m_DmFileAntiPattern, m_pOptions->m_DmDirAntiPattern, m_pOptions->m_bDmFollowDirLinks, m_pOptions->m_bDmUseCvsIgnore); for(i = m_dirListB.begin(); i != m_dirListB.end(); ++i) { MergeFileInfos& mfi = m_fileMergeMap[FileKey(*i)]; mfi.m_pFileInfoB = &(*i); if(mfi.m_pFileInfoA && mfi.m_pFileInfoA->fileName() == mfi.m_pFileInfoB->fileName()) mfi.m_pFileInfoB->setSharedName(mfi.m_pFileInfoA->fileName()); // Reduce memory by sharing the name. } } e_MergeOperation eDefaultMergeOp; if(m_dirC.isValid()) { pp.setInformation(i18n("Reading Directory C")); pp.setSubRangeTransformation(currentScan / nofScans, (currentScan + 1) / nofScans); ++currentScan; bListDirSuccessC = m_dirC.listDir(&m_dirListC, m_pOptions->m_bDmRecursiveDirs, m_pOptions->m_bDmFindHidden, m_pOptions->m_DmFilePattern, m_pOptions->m_DmFileAntiPattern, m_pOptions->m_DmDirAntiPattern, m_pOptions->m_bDmFollowDirLinks, m_pOptions->m_bDmUseCvsIgnore); for(i = m_dirListC.begin(); i != m_dirListC.end(); ++i) { MergeFileInfos& mfi = m_fileMergeMap[FileKey(*i)]; mfi.m_pFileInfoC = &(*i); } eDefaultMergeOp = eMergeABCToDest; } else eDefaultMergeOp = m_bSyncMode ? eMergeToAB : eMergeABToDest; bool bContinue = true; if(!bListDirSuccessA || !bListDirSuccessB || !bListDirSuccessC) { QString s = i18n("Some subdirectories were not readable in"); if(!bListDirSuccessA) s += "\nA: " + m_dirA.prettyAbsPath(); if(!bListDirSuccessB) s += "\nB: " + m_dirB.prettyAbsPath(); if(!bListDirSuccessC) s += "\nC: " + m_dirC.prettyAbsPath(); s += "\n"; s += i18n("Check the permissions of the subdirectories."); bContinue = KMessageBox::Continue == KMessageBox::warningContinueCancel(q, s); } if(bContinue) { prepareListView(pp); q->updateFileVisibilities(); for(int childIdx = 0; childIdx < rowCount(); ++childIdx) { QModelIndex mi = index(childIdx, 0, QModelIndex()); calcSuggestedOperation(mi, eDefaultMergeOp); } } q->sortByColumn(0, Qt::AscendingOrder); for(int i = 0; i < columnCount(QModelIndex()); ++i) q->resizeColumnToContents(i); // Try to improve the view a little bit. QWidget* pParent = q->parentWidget(); QSplitter* pSplitter = static_cast(pParent); if(pSplitter != nullptr) { QList sizes = pSplitter->sizes(); int total = sizes[0] + sizes[1]; if(total < 10) total = 100; sizes[0] = total * 6 / 10; sizes[1] = total - sizes[0]; pSplitter->setSizes(sizes); } QDir::setCurrent(origCurrentDirectory); m_bScanning = false; emit q->statusBarMessage(i18n("Ready.")); if(bContinue && !m_bSkipDirStatus) { // Generate a status report int nofFiles = 0; int nofDirs = 0; int nofEqualFiles = 0; int nofManualMerges = 0; //TODO for(int childIdx = 0; childIdx < rowCount(); ++childIdx) calcDirStatus(m_dirC.isValid(), index(childIdx, 0, QModelIndex()), nofFiles, nofDirs, nofEqualFiles, nofManualMerges); QString s; s = i18n("Directory Comparison Status") + "\n\n" + i18n("Number of subdirectories:") + " " + QString::number(nofDirs) + "\n" + i18n("Number of equal files:") + " " + QString::number(nofEqualFiles) + "\n" + i18n("Number of different files:") + " " + QString::number(nofFiles - nofEqualFiles); if(m_dirC.isValid()) s += "\n" + i18n("Number of manual merges:") + " " + QString::number(nofManualMerges); KMessageBox::information(q, s); // //TODO //if ( topLevelItemCount()>0 ) //{ // topLevelItem(0)->setSelected(true); // setCurrentItem( topLevelItem(0) ); //} } if(bReload) { // Remember expanded items //TODO //QTreeWidgetItemIterator it( this ); //while ( *it ) //{ // DirMergeItem* pDMI = static_cast( *it ); // std::map::iterator i = expandedDirsMap.find( pDMI->m_pMFI->subPath() ); // if ( i!=expandedDirsMap.end() ) // { // t_ItemInfo& ii = i->second; // pDMI->setExpanded( ii.bExpanded ); // //pDMI->m_pMFI->setMergeOperation( ii.eMergeOperation, false ); unsafe, might have changed // pDMI->m_pMFI->m_bOperationComplete = ii.bOperationComplete; // pDMI->setText( s_OpStatusCol, ii.status ); // } // ++it; //} } else if(m_bUnfoldSubdirs) { m_pDirUnfoldAll->trigger(); } return true; } void DirectoryMergeWindow::onExpanded() { resizeColumnToContents(s_NameCol); } void DirectoryMergeWindow::slotChooseAEverywhere() { d->setAllMergeOperations(eCopyAToDest); } void DirectoryMergeWindow::slotChooseBEverywhere() { d->setAllMergeOperations(eCopyBToDest); } void DirectoryMergeWindow::slotChooseCEverywhere() { d->setAllMergeOperations(eCopyCToDest); } void DirectoryMergeWindow::slotAutoChooseEverywhere() { e_MergeOperation eDefaultMergeOp = d->m_dirC.isValid() ? eMergeABCToDest : d->m_bSyncMode ? eMergeToAB : eMergeABToDest; d->setAllMergeOperations(eDefaultMergeOp); } void DirectoryMergeWindow::slotNoOpEverywhere() { d->setAllMergeOperations(eNoOperation); } void DirectoryMergeWindow::slotFoldAllSubdirs() { collapseAll(); } void DirectoryMergeWindow::slotUnfoldAllSubdirs() { expandAll(); } // Merge current item (merge mode) void DirectoryMergeWindow::slotCurrentDoNothing() { d->setMergeOperation(currentIndex(), eNoOperation); } void DirectoryMergeWindow::slotCurrentChooseA() { d->setMergeOperation(currentIndex(), d->m_bSyncMode ? eCopyAToB : eCopyAToDest); } void DirectoryMergeWindow::slotCurrentChooseB() { d->setMergeOperation(currentIndex(), d->m_bSyncMode ? eCopyBToA : eCopyBToDest); } void DirectoryMergeWindow::slotCurrentChooseC() { d->setMergeOperation(currentIndex(), eCopyCToDest); } void DirectoryMergeWindow::slotCurrentMerge() { bool bThreeDirs = d->m_dirC.isValid(); d->setMergeOperation(currentIndex(), bThreeDirs ? eMergeABCToDest : eMergeABToDest); } void DirectoryMergeWindow::slotCurrentDelete() { d->setMergeOperation(currentIndex(), eDeleteFromDest); } // Sync current item void DirectoryMergeWindow::slotCurrentCopyAToB() { d->setMergeOperation(currentIndex(), eCopyAToB); } void DirectoryMergeWindow::slotCurrentCopyBToA() { d->setMergeOperation(currentIndex(), eCopyBToA); } void DirectoryMergeWindow::slotCurrentDeleteA() { d->setMergeOperation(currentIndex(), eDeleteA); } void DirectoryMergeWindow::slotCurrentDeleteB() { d->setMergeOperation(currentIndex(), eDeleteB); } void DirectoryMergeWindow::slotCurrentDeleteAAndB() { d->setMergeOperation(currentIndex(), eDeleteAB); } void DirectoryMergeWindow::slotCurrentMergeToA() { d->setMergeOperation(currentIndex(), eMergeToA); } void DirectoryMergeWindow::slotCurrentMergeToB() { d->setMergeOperation(currentIndex(), eMergeToB); } void DirectoryMergeWindow::slotCurrentMergeToAAndB() { d->setMergeOperation(currentIndex(), eMergeToAB); } void DirectoryMergeWindow::keyPressEvent(QKeyEvent* e) { if((e->QInputEvent::modifiers() & Qt::ControlModifier) != 0) { bool bThreeDirs = d->m_dirC.isValid(); MergeFileInfos* pMFI = d->getMFI(currentIndex()); if(pMFI == nullptr) return; bool bMergeMode = bThreeDirs || !d->m_bSyncMode; bool bFTConflict = pMFI == nullptr ? false : conflictingFileTypes(*pMFI); if(bMergeMode) { switch(e->key()) { case Qt::Key_1: if(pMFI->existsInA()) { slotCurrentChooseA(); } return; case Qt::Key_2: if(pMFI->existsInB()) { slotCurrentChooseB(); } return; case Qt::Key_3: if(pMFI->existsInC()) { slotCurrentChooseC(); } return; case Qt::Key_Space: slotCurrentDoNothing(); return; case Qt::Key_4: if(!bFTConflict) { slotCurrentMerge(); } return; case Qt::Key_Delete: slotCurrentDelete(); return; default: break; } } else { switch(e->key()) { case Qt::Key_1: if(pMFI->existsInA()) { slotCurrentCopyAToB(); } return; case Qt::Key_2: if(pMFI->existsInB()) { slotCurrentCopyBToA(); } return; case Qt::Key_Space: slotCurrentDoNothing(); return; case Qt::Key_4: if(!bFTConflict) { slotCurrentMergeToAAndB(); } return; case Qt::Key_Delete: if(pMFI->existsInA() && pMFI->existsInB()) slotCurrentDeleteAAndB(); else if(pMFI->existsInA()) slotCurrentDeleteA(); else if(pMFI->existsInB()) slotCurrentDeleteB(); return; default: break; } } } else if(e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) { onDoubleClick(currentIndex()); return; } QTreeView::keyPressEvent(e); } void DirectoryMergeWindow::focusInEvent(QFocusEvent*) { updateAvailabilities(); } void DirectoryMergeWindow::focusOutEvent(QFocusEvent*) { updateAvailabilities(); } void DirectoryMergeWindow::Data::setAllMergeOperations(e_MergeOperation eDefaultOperation) { if(KMessageBox::Yes == KMessageBox::warningYesNo(q, i18n("This affects all merge operations."), i18n("Changing All Merge Operations"), KStandardGuiItem::cont(), KStandardGuiItem::cancel())) { for(int i = 0; i < rowCount(); ++i) { calcSuggestedOperation(index(i, 0, QModelIndex()), eDefaultOperation); } } } void DirectoryMergeWindow::Data::compareFilesAndCalcAges(MergeFileInfos& mfi) { std::map dateMap; if(mfi.existsInA()) { dateMap[mfi.m_pFileInfoA->lastModified()] = 0; } if(mfi.existsInB()) { dateMap[mfi.m_pFileInfoB->lastModified()] = 1; } if(mfi.existsInC()) { dateMap[mfi.m_pFileInfoC->lastModified()] = 2; } if(m_pOptions->m_bDmFullAnalysis) { if((mfi.existsInA() && mfi.dirA()) || (mfi.existsInB() && mfi.dirB()) || (mfi.existsInC() && mfi.dirC())) { // If any input is a directory, don't start any comparison. mfi.m_bEqualAB = mfi.existsInA() && mfi.existsInB(); mfi.m_bEqualAC = mfi.existsInA() && mfi.existsInC(); mfi.m_bEqualBC = mfi.existsInB() && mfi.existsInC(); } else { emit q->startDiffMerge( mfi.existsInA() ? mfi.m_pFileInfoA->absoluteFilePath() : QString(""), mfi.existsInB() ? mfi.m_pFileInfoB->absoluteFilePath() : QString(""), mfi.existsInC() ? mfi.m_pFileInfoC->absoluteFilePath() : QString(""), "", "", "", "", &mfi.m_totalDiffStatus); int nofNonwhiteConflicts = mfi.m_totalDiffStatus.nofUnsolvedConflicts + mfi.m_totalDiffStatus.nofSolvedConflicts - mfi.m_totalDiffStatus.nofWhitespaceConflicts; if(m_pOptions->m_bDmWhiteSpaceEqual && nofNonwhiteConflicts == 0) { mfi.m_bEqualAB = mfi.existsInA() && mfi.existsInB(); mfi.m_bEqualAC = mfi.existsInA() && mfi.existsInC(); mfi.m_bEqualBC = mfi.existsInB() && mfi.existsInC(); } else { mfi.m_bEqualAB = mfi.m_totalDiffStatus.bBinaryAEqB; mfi.m_bEqualBC = mfi.m_totalDiffStatus.bBinaryBEqC; mfi.m_bEqualAC = mfi.m_totalDiffStatus.bBinaryAEqC; } } } else { bool bError; QString eqStatus; if(mfi.existsInA() && mfi.existsInB()) { if(mfi.dirA()) mfi.m_bEqualAB = true; else mfi.m_bEqualAB = fastFileComparison(*mfi.m_pFileInfoA, *mfi.m_pFileInfoB, bError, eqStatus); } if(mfi.existsInA() && mfi.existsInC()) { if(mfi.dirA()) mfi.m_bEqualAC = true; else mfi.m_bEqualAC = fastFileComparison(*mfi.m_pFileInfoA, *mfi.m_pFileInfoC, bError, eqStatus); } if(mfi.existsInB() && mfi.existsInC()) { if(mfi.m_bEqualAB && mfi.m_bEqualAC) mfi.m_bEqualBC = true; else { if(mfi.dirB()) mfi.m_bEqualBC = true; else mfi.m_bEqualBC = fastFileComparison(*mfi.m_pFileInfoB, *mfi.m_pFileInfoC, bError, eqStatus); } } } if(mfi.isLinkA() != mfi.isLinkB()) mfi.m_bEqualAB = false; if(mfi.isLinkA() != mfi.isLinkC()) mfi.m_bEqualAC = false; if(mfi.isLinkB() != mfi.isLinkC()) mfi.m_bEqualBC = false; if(mfi.dirA() != mfi.dirB()) mfi.m_bEqualAB = false; if(mfi.dirA() != mfi.dirC()) mfi.m_bEqualAC = false; if(mfi.dirB() != mfi.dirC()) mfi.m_bEqualBC = false; assert(eNew == 0 && eMiddle == 1 && eOld == 2); // The map automatically sorts the keys. int age = eNew; std::map::reverse_iterator i; for(i = dateMap.rbegin(); i != dateMap.rend(); ++i) { int n = i->second; if(n == 0 && mfi.m_ageA == eNotThere) { mfi.m_ageA = (e_Age)age; ++age; if(mfi.m_bEqualAB) { mfi.m_ageB = mfi.m_ageA; ++age; } if(mfi.m_bEqualAC) { mfi.m_ageC = mfi.m_ageA; ++age; } } else if(n == 1 && mfi.m_ageB == eNotThere) { mfi.m_ageB = (e_Age)age; ++age; if(mfi.m_bEqualAB) { mfi.m_ageA = mfi.m_ageB; ++age; } if(mfi.m_bEqualBC) { mfi.m_ageC = mfi.m_ageB; ++age; } } else if(n == 2 && mfi.m_ageC == eNotThere) { mfi.m_ageC = (e_Age)age; ++age; if(mfi.m_bEqualAC) { mfi.m_ageA = mfi.m_ageC; ++age; } if(mfi.m_bEqualBC) { mfi.m_ageB = mfi.m_ageC; ++age; } } } // The checks below are necessary when the dates of the file are equal but the // files are not. One wouldn't expect this to happen, yet it happens sometimes. if(mfi.existsInC() && mfi.m_ageC == eNotThere) { mfi.m_ageC = (e_Age)age; ++age; mfi.m_bConflictingAges = true; } if(mfi.existsInB() && mfi.m_ageB == eNotThere) { mfi.m_ageB = (e_Age)age; ++age; mfi.m_bConflictingAges = true; } if(mfi.existsInA() && mfi.m_ageA == eNotThere) { mfi.m_ageA = (e_Age)age; ++age; mfi.m_bConflictingAges = true; } if(mfi.m_ageA != eOld && mfi.m_ageB != eOld && mfi.m_ageC != eOld) { if(mfi.m_ageA == eMiddle) mfi.m_ageA = eOld; if(mfi.m_ageB == eMiddle) mfi.m_ageB = eOld; if(mfi.m_ageC == eMiddle) mfi.m_ageC = eOld; } } static QPixmap* s_pm_dir; static QPixmap* s_pm_file; static QPixmap* pmNotThere; static QPixmap* pmNew; static QPixmap* pmOld; static QPixmap* pmMiddle; static QPixmap* pmLink; static QPixmap* pmDirLink; static QPixmap* pmFileLink; static QPixmap* pmNewLink; static QPixmap* pmOldLink; static QPixmap* pmMiddleLink; static QPixmap* pmNewDir; static QPixmap* pmMiddleDir; static QPixmap* pmOldDir; static QPixmap* pmNewDirLink; static QPixmap* pmMiddleDirLink; static QPixmap* pmOldDirLink; static QPixmap colorToPixmap(QColor c) { QPixmap pm(16, 16); QPainter p(&pm); p.setPen(Qt::black); p.setBrush(c); p.drawRect(0, 0, pm.width(), pm.height()); return pm; } static void initPixmaps(QColor newest, QColor oldest, QColor middle, QColor notThere) { if(pmNew == nullptr) { pmNotThere = new QPixmap; pmNew = new QPixmap; pmOld = new QPixmap; pmMiddle = new QPixmap; #include "xpm/link_arrow.xpm" pmLink = new QPixmap(link_arrow); pmDirLink = new QPixmap; pmFileLink = new QPixmap; pmNewLink = new QPixmap; pmOldLink = new QPixmap; pmMiddleLink = new QPixmap; pmNewDir = new QPixmap; pmMiddleDir = new QPixmap; pmOldDir = new QPixmap; pmNewDirLink = new QPixmap; pmMiddleDirLink = new QPixmap; pmOldDirLink = new QPixmap; } *pmNotThere = colorToPixmap(notThere); *pmNew = colorToPixmap(newest); *pmOld = colorToPixmap(oldest); *pmMiddle = colorToPixmap(middle); *pmDirLink = pixCombiner(s_pm_dir, pmLink); *pmFileLink = pixCombiner(s_pm_file, pmLink); *pmNewLink = pixCombiner(pmNew, pmLink); *pmOldLink = pixCombiner(pmOld, pmLink); *pmMiddleLink = pixCombiner(pmMiddle, pmLink); *pmNewDir = pixCombiner2(pmNew, s_pm_dir); *pmMiddleDir = pixCombiner2(pmMiddle, s_pm_dir); *pmOldDir = pixCombiner2(pmOld, s_pm_dir); *pmNewDirLink = pixCombiner(pmNewDir, pmLink); *pmMiddleDirLink = pixCombiner(pmMiddleDir, pmLink); *pmOldDirLink = pixCombiner(pmOldDir, pmLink); } static QPixmap getOnePixmap(e_Age eAge, bool bLink, bool bDir) { static QPixmap* ageToPm[] = {pmNew, pmMiddle, pmOld, pmNotThere, s_pm_file}; static QPixmap* ageToPmLink[] = {pmNewLink, pmMiddleLink, pmOldLink, pmNotThere, pmFileLink}; static QPixmap* ageToPmDir[] = {pmNewDir, pmMiddleDir, pmOldDir, pmNotThere, s_pm_dir}; static QPixmap* ageToPmDirLink[] = {pmNewDirLink, pmMiddleDirLink, pmOldDirLink, pmNotThere, pmDirLink}; QPixmap** ppPm = bDir ? (bLink ? ageToPmDirLink : ageToPmDir) : (bLink ? ageToPmLink : ageToPm); return *ppPm[eAge]; } static void setPixmaps(MergeFileInfos& mfi, bool) { if(mfi.dirA() || mfi.dirB() || mfi.dirC()) { mfi.m_ageA = eNotThere; mfi.m_ageB = eNotThere; mfi.m_ageC = eNotThere; int age = eNew; if(mfi.existsInC()) { mfi.m_ageC = (e_Age)age; if(mfi.m_bEqualAC) mfi.m_ageA = (e_Age)age; if(mfi.m_bEqualBC) mfi.m_ageB = (e_Age)age; ++age; } if(mfi.existsInB() && mfi.m_ageB == eNotThere) { mfi.m_ageB = (e_Age)age; if(mfi.m_bEqualAB) mfi.m_ageA = (e_Age)age; ++age; } if(mfi.existsInA() && mfi.m_ageA == eNotThere) { mfi.m_ageA = (e_Age)age; } if(mfi.m_ageA != eOld && mfi.m_ageB != eOld && mfi.m_ageC != eOld) { if(mfi.m_ageA == eMiddle) mfi.m_ageA = eOld; if(mfi.m_ageB == eMiddle) mfi.m_ageB = eOld; if(mfi.m_ageC == eMiddle) mfi.m_ageC = eOld; } } } static QModelIndex nextSibling(const QModelIndex& mi) { QModelIndex miParent = mi.parent(); int currentIdx = mi.row(); if(currentIdx + 1 < mi.model()->rowCount(miParent)) return mi.model()->index(mi.row() + 1, 0, miParent); // next child of parent return QModelIndex(); } // Iterate through the complete tree. Start by specifying QListView::firstChild(). QModelIndex DirectoryMergeWindow::Data::treeIterator(QModelIndex mi, bool bVisitChildren, bool bFindInvisible) { if(mi.isValid()) { do { if(bVisitChildren && mi.model()->rowCount(mi) != 0) mi = mi.model()->index(0, 0, mi); else { QModelIndex miNextSibling = nextSibling(mi); if(miNextSibling.isValid()) mi = miNextSibling; else { mi = mi.parent(); while(mi.isValid()) { QModelIndex miNextSibling = nextSibling(mi); if(miNextSibling.isValid()) { mi = miNextSibling; break; } else { mi = mi.parent(); } } } } } while(mi.isValid() && q->isRowHidden(mi.row(), mi.parent()) && !bFindInvisible); } return mi; } void DirectoryMergeWindow::Data::prepareListView(ProgressProxy& pp) { static bool bFirstTime = true; if(bFirstTime) { #include "xpm/file.xpm" #include "xpm/folder.xpm" // FIXME specify correct icon loader group s_pm_dir = new QPixmap(m_pIconLoader->loadIcon("folder", KIconLoader::NoGroup, KIconLoader::Small)); if(s_pm_dir->size() != QSize(16, 16)) { delete s_pm_dir; s_pm_dir = new QPixmap(folder_pm); } s_pm_file = new QPixmap(file_pm); bFirstTime = false; } //TODO clear(); initPixmaps(m_pOptions->m_newestFileColor, m_pOptions->m_oldestFileColor, m_pOptions->m_midAgeFileColor, m_pOptions->m_missingFileColor); q->setRootIsDecorated(true); bool bCheckC = m_dirC.isValid(); t_fileMergeMap::iterator j; int nrOfFiles = m_fileMergeMap.size(); int currentIdx = 1; QTime t; t.start(); pp.setMaxNofSteps(nrOfFiles); for(j = m_fileMergeMap.begin(); j != m_fileMergeMap.end(); ++j) { MergeFileInfos& mfi = j.value(); // const QString& fileName = j->first; const QString& fileName = mfi.subPath(); pp.setInformation( i18n("Processing ") + QString::number(currentIdx) + " / " + QString::number(nrOfFiles) + "\n" + fileName, currentIdx, false); if(pp.wasCancelled()) break; ++currentIdx; // The comparisons and calculations for each file take place here. compareFilesAndCalcAges(mfi); // Get dirname from fileName: Search for "/" from end: int pos = fileName.lastIndexOf('/'); QString dirPart; QString filePart; if(pos == -1) { // Top dir filePart = fileName; } else { dirPart = fileName.left(pos); filePart = fileName.mid(pos + 1); } if(dirPart.isEmpty()) // Top level { m_pRoot->m_children.push_back(&mfi); //new DirMergeItem( this, filePart, &mfi ); mfi.m_pParent = m_pRoot; } else { FileAccess* pFA = mfi.m_pFileInfoA ? mfi.m_pFileInfoA : mfi.m_pFileInfoB ? mfi.m_pFileInfoB : mfi.m_pFileInfoC; MergeFileInfos& dirMfi = pFA->parent() ? m_fileMergeMap[FileKey(*pFA->parent())] : *m_pRoot; // parent dirMfi.m_children.push_back(&mfi); //new DirMergeItem( dirMfi.m_pDMI, filePart, &mfi ); mfi.m_pParent = &dirMfi; // // Equality for parent dirs is set in updateFileVisibilities() } setPixmaps(mfi, bCheckC); } beginResetModel(); endResetModel(); } static bool conflictingFileTypes(MergeFileInfos& mfi) { // Now check if file/dir-types fit. if(mfi.isLinkA() || mfi.isLinkB() || mfi.isLinkC()) { if((mfi.existsInA() && !mfi.isLinkA()) || (mfi.existsInB() && !mfi.isLinkB()) || (mfi.existsInC() && !mfi.isLinkC())) { return true; } } if(mfi.dirA() || mfi.dirB() || mfi.dirC()) { if((mfi.existsInA() && !mfi.dirA()) || (mfi.existsInB() && !mfi.dirB()) || (mfi.existsInC() && !mfi.dirC())) { return true; } } return false; } void DirectoryMergeWindow::Data::calcSuggestedOperation(const QModelIndex& mi, e_MergeOperation eDefaultMergeOp) { MergeFileInfos* pMFI = getMFI(mi); if(pMFI == nullptr) return; MergeFileInfos& mfi = *pMFI; bool bCheckC = m_dirC.isValid(); bool bCopyNewer = m_pOptions->m_bDmCopyNewer; bool bOtherDest = !((m_dirDestInternal.absoluteFilePath() == m_dirA.absoluteFilePath()) || (m_dirDestInternal.absoluteFilePath() == m_dirB.absoluteFilePath()) || (bCheckC && m_dirDestInternal.absoluteFilePath() == m_dirC.absoluteFilePath())); if(eDefaultMergeOp == eMergeABCToDest && !bCheckC) { eDefaultMergeOp = eMergeABToDest; } if(eDefaultMergeOp == eMergeToAB && bCheckC) { assert(false); } if(eDefaultMergeOp == eMergeToA || eDefaultMergeOp == eMergeToB || eDefaultMergeOp == eMergeABCToDest || eDefaultMergeOp == eMergeABToDest || eDefaultMergeOp == eMergeToAB) { if(!bCheckC) { if(mfi.m_bEqualAB) { setMergeOperation(mi, bOtherDest ? eCopyBToDest : eNoOperation); } else if(mfi.existsInA() && mfi.existsInB()) { if(!bCopyNewer || mfi.dirA()) setMergeOperation(mi, eDefaultMergeOp); else if(bCopyNewer && mfi.m_bConflictingAges) { setMergeOperation(mi, eConflictingAges); } else { if(mfi.m_ageA == eNew) setMergeOperation(mi, eDefaultMergeOp == eMergeToAB ? eCopyAToB : eCopyAToDest); else setMergeOperation(mi, eDefaultMergeOp == eMergeToAB ? eCopyBToA : eCopyBToDest); } } else if(!mfi.existsInA() && mfi.existsInB()) { if(eDefaultMergeOp == eMergeABToDest) setMergeOperation(mi, eCopyBToDest); else if(eDefaultMergeOp == eMergeToB) setMergeOperation(mi, eNoOperation); else setMergeOperation(mi, eCopyBToA); } else if(mfi.existsInA() && !mfi.existsInB()) { if(eDefaultMergeOp == eMergeABToDest) setMergeOperation(mi, eCopyAToDest); else if(eDefaultMergeOp == eMergeToA) setMergeOperation(mi, eNoOperation); else setMergeOperation(mi, eCopyAToB); } else //if ( !mfi.existsInA() && !mfi.existsInB() ) { setMergeOperation(mi, eNoOperation); assert(false); } } else { if(mfi.m_bEqualAB && mfi.m_bEqualAC) { setMergeOperation(mi, bOtherDest ? eCopyCToDest : eNoOperation); } else if(mfi.existsInA() && mfi.existsInB() && mfi.existsInC()) { if(mfi.m_bEqualAB) setMergeOperation(mi, eCopyCToDest); else if(mfi.m_bEqualAC) setMergeOperation(mi, eCopyBToDest); else if(mfi.m_bEqualBC) setMergeOperation(mi, eCopyCToDest); else setMergeOperation(mi, eMergeABCToDest); } else if(mfi.existsInA() && mfi.existsInB() && !mfi.existsInC()) { if(mfi.m_bEqualAB) setMergeOperation(mi, eDeleteFromDest); else setMergeOperation(mi, eChangedAndDeleted); } else if(mfi.existsInA() && !mfi.existsInB() && mfi.existsInC()) { if(mfi.m_bEqualAC) setMergeOperation(mi, eDeleteFromDest); else setMergeOperation(mi, eChangedAndDeleted); } else if(!mfi.existsInA() && mfi.existsInB() && mfi.existsInC()) { if(mfi.m_bEqualBC) setMergeOperation(mi, eCopyCToDest); else setMergeOperation(mi, eMergeABCToDest); } else if(!mfi.existsInA() && !mfi.existsInB() && mfi.existsInC()) { setMergeOperation(mi, eCopyCToDest); } else if(!mfi.existsInA() && mfi.existsInB() && !mfi.existsInC()) { setMergeOperation(mi, eCopyBToDest); } else if(mfi.existsInA() && !mfi.existsInB() && !mfi.existsInC()) { setMergeOperation(mi, eDeleteFromDest); } else //if ( !mfi.existsInA() && !mfi.existsInB() && !mfi.existsInC() ) { setMergeOperation(mi, eNoOperation); assert(false); } } // Now check if file/dir-types fit. if(conflictingFileTypes(mfi)) { setMergeOperation(mi, eConflictingFileTypes); } } else { e_MergeOperation eMO = eDefaultMergeOp; switch(eDefaultMergeOp) { case eConflictingFileTypes: case eChangedAndDeleted: case eConflictingAges: case eDeleteA: case eDeleteB: case eDeleteAB: case eDeleteFromDest: case eNoOperation: break; case eCopyAToB: if(!mfi.existsInA()) { eMO = eDeleteB; } break; case eCopyBToA: if(!mfi.existsInB()) { eMO = eDeleteA; } break; case eCopyAToDest: if(!mfi.existsInA()) { eMO = eDeleteFromDest; } break; case eCopyBToDest: if(!mfi.existsInB()) { eMO = eDeleteFromDest; } break; case eCopyCToDest: if(!mfi.existsInC()) { eMO = eDeleteFromDest; } break; case eMergeToA: case eMergeToB: case eMergeToAB: case eMergeABCToDest: case eMergeABToDest: default: assert(false); } setMergeOperation(mi, eMO); } } void DirectoryMergeWindow::onDoubleClick(const QModelIndex& mi) { if(!mi.isValid()) return; d->m_bSimulatedMergeStarted = false; if(d->m_bDirectoryMerge) mergeCurrentFile(); else compareCurrentFile(); } void DirectoryMergeWindow::currentChanged(const QModelIndex& current, const QModelIndex& previous) { QTreeView::currentChanged(current, previous); MergeFileInfos* pMFI = d->getMFI(current); if(pMFI == nullptr) return; d->m_pDirectoryMergeInfo->setInfo(d->m_dirA, d->m_dirB, d->m_dirC, d->m_dirDestInternal, *pMFI); } void DirectoryMergeWindow::mousePressEvent(QMouseEvent* e) { QTreeView::mousePressEvent(e); QModelIndex mi = indexAt(e->pos()); int c = mi.column(); QPoint p = e->globalPos(); MergeFileInfos* pMFI = d->getMFI(mi); if(pMFI == nullptr) return; MergeFileInfos& mfi = *pMFI; if(c == s_OpCol) { bool bThreeDirs = d->m_dirC.isValid(); QMenu m(this); if(bThreeDirs) { m.addAction(d->m_pDirCurrentDoNothing); int count = 0; if(mfi.existsInA()) { m.addAction(d->m_pDirCurrentChooseA); ++count; } if(mfi.existsInB()) { m.addAction(d->m_pDirCurrentChooseB); ++count; } if(mfi.existsInC()) { m.addAction(d->m_pDirCurrentChooseC); ++count; } if(!conflictingFileTypes(mfi) && count > 1) m.addAction(d->m_pDirCurrentMerge); m.addAction(d->m_pDirCurrentDelete); } else if(d->m_bSyncMode) { m.addAction(d->m_pDirCurrentSyncDoNothing); if(mfi.existsInA()) m.addAction(d->m_pDirCurrentSyncCopyAToB); if(mfi.existsInB()) m.addAction(d->m_pDirCurrentSyncCopyBToA); if(mfi.existsInA()) m.addAction(d->m_pDirCurrentSyncDeleteA); if(mfi.existsInB()) m.addAction(d->m_pDirCurrentSyncDeleteB); if(mfi.existsInA() && mfi.existsInB()) { m.addAction(d->m_pDirCurrentSyncDeleteAAndB); if(!conflictingFileTypes(mfi)) { m.addAction(d->m_pDirCurrentSyncMergeToA); m.addAction(d->m_pDirCurrentSyncMergeToB); m.addAction(d->m_pDirCurrentSyncMergeToAAndB); } } } else { m.addAction(d->m_pDirCurrentDoNothing); if(mfi.existsInA()) { m.addAction(d->m_pDirCurrentChooseA); } if(mfi.existsInB()) { m.addAction(d->m_pDirCurrentChooseB); } if(!conflictingFileTypes(mfi) && mfi.existsInA() && mfi.existsInB()) m.addAction(d->m_pDirCurrentMerge); m.addAction(d->m_pDirCurrentDelete); } m.exec(p); } else if(c == s_ACol || c == s_BCol || c == s_CCol) { QString itemPath; if(c == s_ACol && mfi.existsInA()) { itemPath = d->fullNameA(mfi); } else if(c == s_BCol && mfi.existsInB()) { itemPath = d->fullNameB(mfi); } else if(c == s_CCol && mfi.existsInC()) { itemPath = d->fullNameC(mfi); } if(!itemPath.isEmpty()) { d->selectItemAndColumn(mi, e->button() == Qt::RightButton); } } } void DirectoryMergeWindow::contextMenuEvent(QContextMenuEvent* e) { QModelIndex mi = indexAt(e->pos()); int c = mi.column(); QPoint p = e->globalPos(); MergeFileInfos* pMFI = d->getMFI(mi); if(pMFI == nullptr) return; if(c == s_ACol || c == s_BCol || c == s_CCol) { QString itemPath; if(c == s_ACol && pMFI->existsInA()) { itemPath = d->fullNameA(*pMFI); } else if(c == s_BCol && pMFI->existsInB()) { itemPath = d->fullNameB(*pMFI); } else if(c == s_CCol && pMFI->existsInC()) { itemPath = d->fullNameC(*pMFI); } if(!itemPath.isEmpty()) { d->selectItemAndColumn(mi, true); QMenu m(this); m.addAction(d->m_pDirCompareExplicit); m.addAction(d->m_pDirMergeExplicit); #ifndef _WIN32 m.exec(p); #else void showShellContextMenu(const QString&, QPoint, QWidget*, QMenu*); showShellContextMenu(itemPath, p, this, &m); #endif } } } QString DirectoryMergeWindow::Data::getFileName(const QModelIndex& mi) { MergeFileInfos* pMFI = getMFI(mi); if(pMFI != nullptr) { return mi.column() == s_ACol ? pMFI->m_pFileInfoA->absoluteFilePath() : mi.column() == s_BCol ? pMFI->m_pFileInfoB->absoluteFilePath() : mi.column() == s_CCol ? pMFI->m_pFileInfoC->absoluteFilePath() : QString(""); } return ""; } bool DirectoryMergeWindow::Data::isDir(const QModelIndex& mi) { MergeFileInfos* pMFI = getMFI(mi); if(pMFI != nullptr) { return mi.column() == s_ACol ? pMFI->dirA() : mi.column() == s_BCol ? pMFI->dirB() : pMFI->dirC(); } return false; } void DirectoryMergeWindow::Data::selectItemAndColumn(const QModelIndex& mi, bool bContextMenu) { if(bContextMenu && (mi == m_selection1Index || mi == m_selection2Index || mi == m_selection3Index)) return; QModelIndex old1 = m_selection1Index; QModelIndex old2 = m_selection2Index; QModelIndex old3 = m_selection3Index; bool bReset = false; if(m_selection1Index.isValid()) { if(isDir(m_selection1Index) != isDir(mi)) bReset = true; } if(bReset || m_selection3Index.isValid() || mi == m_selection1Index || mi == m_selection2Index || mi == m_selection3Index) { // restart m_selection1Index = QModelIndex(); m_selection2Index = QModelIndex(); m_selection3Index = QModelIndex(); } else if(!m_selection1Index.isValid()) { m_selection1Index = mi; m_selection2Index = QModelIndex(); m_selection3Index = QModelIndex(); } else if(!m_selection2Index.isValid()) { m_selection2Index = mi; m_selection3Index = QModelIndex(); } else if(!m_selection3Index.isValid()) { m_selection3Index = mi; } if(old1.isValid()) dataChanged(old1, old1); if(old2.isValid()) dataChanged(old2, old2); if(old3.isValid()) dataChanged(old3, old3); if(m_selection1Index.isValid()) dataChanged(m_selection1Index, m_selection1Index); if(m_selection2Index.isValid()) dataChanged(m_selection2Index, m_selection2Index); if(m_selection3Index.isValid()) dataChanged(m_selection3Index, m_selection3Index); emit q->updateAvailabilities(); } //TODO //void DirMergeItem::init(MergeFileInfos* pMFI) //{ // pMFI->m_pDMI = this; // m_pMFI = pMFI; // TotalDiffStatus& tds = pMFI->m_totalDiffStatus; // if ( m_pMFI->dirA() || m_pMFI->dirB() || m_pMFI->dirC() ) // { // } // else // { // setText( s_UnsolvedCol, QString::number( tds.nofUnsolvedConflicts ) ); // setText( s_SolvedCol, QString::number( tds.nofSolvedConflicts ) ); // setText( s_NonWhiteCol, QString::number( tds.nofUnsolvedConflicts + tds.nofSolvedConflicts - tds.nofWhitespaceConflicts ) ); // setText( s_WhiteCol, QString::number( tds.nofWhitespaceConflicts ) ); // } // setSizeHint( s_ACol, QSize(17,17) ); // Iconsize // setSizeHint( s_BCol, QSize(17,17) ); // Iconsize // setSizeHint( s_CCol, QSize(17,17) ); // Iconsize //} class MfiCompare { Qt::SortOrder mOrder; public: explicit MfiCompare(Qt::SortOrder order) { mOrder = order; } bool operator()(MergeFileInfos* pMFI1, MergeFileInfos* pMFI2) { bool bDir1 = pMFI1->dirA() || pMFI1->dirB() || pMFI1->dirC(); bool bDir2 = pMFI2->dirA() || pMFI2->dirB() || pMFI2->dirC(); if(bDir1 == bDir2) { if(mOrder == Qt::AscendingOrder) { return pMFI1->fileName().compare(pMFI2->fileName(), Qt::CaseInsensitive) < 0; } else { return pMFI1->fileName().compare(pMFI2->fileName(), Qt::CaseInsensitive) > 0; } } else return bDir1; } }; static void sortHelper(MergeFileInfos* pMFI, Qt::SortOrder order) { std::sort(pMFI->m_children.begin(), pMFI->m_children.end(), MfiCompare(order)); for(int i = 0; i < pMFI->m_children.count(); ++i) sortHelper(pMFI->m_children[i], order); } void DirectoryMergeWindow::Data::sort(int column, Qt::SortOrder order) { Q_UNUSED(column); beginResetModel(); sortHelper(m_pRoot, order); endResetModel(); } // //DirMergeItem::~DirMergeItem() //{ // m_pMFI->m_pDMI = 0; //} void DirectoryMergeWindow::Data::setMergeOperation(const QModelIndex& mi, e_MergeOperation eMOp, bool bRecursive) { MergeFileInfos* pMFI = getMFI(mi); if(pMFI == nullptr) return; MergeFileInfos& mfi = *pMFI; if(eMOp != mfi.m_eMergeOperation) { mfi.m_bOperationComplete = false; setOpStatus(mi, eOpStatusNone); } mfi.m_eMergeOperation = eMOp; if(bRecursive) { e_MergeOperation eChildrenMergeOp = mfi.m_eMergeOperation; if(eChildrenMergeOp == eConflictingFileTypes) eChildrenMergeOp = eMergeABCToDest; for(int childIdx = 0; childIdx < mfi.m_children.count(); ++childIdx) { calcSuggestedOperation(index(childIdx, 0, mi), eChildrenMergeOp); } } } void DirectoryMergeWindow::compareCurrentFile() { if(!d->canContinue()) return; if(d->m_bRealMergeStarted) { KMessageBox::sorry(this, i18n("This operation is currently not possible."), i18n("Operation Not Possible")); return; } if(MergeFileInfos* pMFI = d->getMFI(currentIndex())) { if(!(pMFI->dirA() || pMFI->dirB() || pMFI->dirC())) { emit startDiffMerge( pMFI->existsInA() ? pMFI->m_pFileInfoA->absoluteFilePath() : QString(""), pMFI->existsInB() ? pMFI->m_pFileInfoB->absoluteFilePath() : QString(""), pMFI->existsInC() ? pMFI->m_pFileInfoC->absoluteFilePath() : QString(""), "", "", "", "", nullptr); } } emit updateAvailabilities(); } void DirectoryMergeWindow::slotCompareExplicitlySelectedFiles() { if(!d->isDir(d->m_selection1Index) && !d->canContinue()) return; if(d->m_bRealMergeStarted) { KMessageBox::sorry(this, i18n("This operation is currently not possible."), i18n("Operation Not Possible")); return; } emit startDiffMerge( d->getFileName(d->m_selection1Index), d->getFileName(d->m_selection2Index), d->getFileName(d->m_selection3Index), "", "", "", "", nullptr); d->m_selection1Index = QModelIndex(); d->m_selection2Index = QModelIndex(); d->m_selection3Index = QModelIndex(); emit updateAvailabilities(); update(); } void DirectoryMergeWindow::slotMergeExplicitlySelectedFiles() { if(!d->isDir(d->m_selection1Index) && !d->canContinue()) return; if(d->m_bRealMergeStarted) { KMessageBox::sorry(this, i18n("This operation is currently not possible."), i18n("Operation Not Possible")); return; } QString fn1 = d->getFileName(d->m_selection1Index); QString fn2 = d->getFileName(d->m_selection2Index); QString fn3 = d->getFileName(d->m_selection3Index); emit startDiffMerge(fn1, fn2, fn3, fn3.isEmpty() ? fn2 : fn3, "", "", "", nullptr); d->m_selection1Index = QModelIndex(); d->m_selection2Index = QModelIndex(); d->m_selection3Index = QModelIndex(); emit updateAvailabilities(); update(); } bool DirectoryMergeWindow::isFileSelected() { if(MergeFileInfos* pMFI = d->getMFI(currentIndex())) { return !(pMFI->dirA() || pMFI->dirB() || pMFI->dirC() || conflictingFileTypes(*pMFI)); } return false; } void DirectoryMergeWindow::mergeResultSaved(const QString& fileName) { QModelIndex mi = (d->m_mergeItemList.empty() || d->m_currentIndexForOperation == d->m_mergeItemList.end()) ? QModelIndex() : *d->m_currentIndexForOperation; MergeFileInfos* pMFI = d->getMFI(mi); if(pMFI == nullptr) { // This can happen if the same file is saved and modified and saved again. Nothing to do then. return; } if(fileName == d->fullNameDest(*pMFI)) { MergeFileInfos& mfi = *pMFI; if(mfi.m_eMergeOperation == eMergeToAB) { bool bSuccess = d->copyFLD(d->fullNameB(mfi), d->fullNameA(mfi)); if(!bSuccess) { KMessageBox::error(this, i18n("An error occurred while copying.\n"), i18n("Error")); d->m_pStatusInfo->setWindowTitle(i18n("Merge Error")); d->m_pStatusInfo->exec(); //if ( m_pStatusInfo->firstChild()!=0 ) // m_pStatusInfo->ensureItemVisible( m_pStatusInfo->last() ); d->m_bError = true; d->setOpStatus(mi, eOpStatusError); mfi.m_eMergeOperation = eCopyBToA; return; } } d->setOpStatus(mi, eOpStatusDone); pMFI->m_bOperationComplete = true; if(d->m_mergeItemList.size() == 1) { d->m_mergeItemList.clear(); d->m_bRealMergeStarted = false; } } emit updateAvailabilities(); } bool DirectoryMergeWindow::Data::canContinue() { bool bCanContinue = false; q->checkIfCanContinue(&bCanContinue); if(bCanContinue && !m_bError) { QModelIndex mi = (m_mergeItemList.empty() || m_currentIndexForOperation == m_mergeItemList.end()) ? QModelIndex() : *m_currentIndexForOperation; MergeFileInfos* pMFI = getMFI(mi); if(pMFI && !pMFI->m_bOperationComplete) { setOpStatus(mi, eOpStatusNotSaved); pMFI->m_bOperationComplete = true; if(m_mergeItemList.size() == 1) { m_mergeItemList.clear(); m_bRealMergeStarted = false; } } } return bCanContinue; } bool DirectoryMergeWindow::Data::executeMergeOperation(MergeFileInfos& mfi, bool& bSingleFileMerge) { bool bCreateBackups = m_pOptions->m_bDmCreateBakFiles; // First decide destname QString destName; switch(mfi.m_eMergeOperation) { case eNoOperation: break; case eDeleteAB: break; case eMergeToAB: // let the user save in B. In mergeResultSaved() the file will be copied to A. case eMergeToB: case eDeleteB: case eCopyAToB: destName = fullNameB(mfi); break; case eMergeToA: case eDeleteA: case eCopyBToA: destName = fullNameA(mfi); break; case eMergeABToDest: case eMergeABCToDest: case eCopyAToDest: case eCopyBToDest: case eCopyCToDest: case eDeleteFromDest: destName = fullNameDest(mfi); break; default: KMessageBox::error(q, i18n("Unknown merge operation. (This must never happen!)"), i18n("Error")); assert(false); } bool bSuccess = false; bSingleFileMerge = false; switch(mfi.m_eMergeOperation) { case eNoOperation: bSuccess = true; break; case eCopyAToDest: case eCopyAToB: bSuccess = copyFLD(fullNameA(mfi), destName); break; case eCopyBToDest: case eCopyBToA: bSuccess = copyFLD(fullNameB(mfi), destName); break; case eCopyCToDest: bSuccess = copyFLD(fullNameC(mfi), destName); break; case eDeleteFromDest: case eDeleteA: case eDeleteB: bSuccess = deleteFLD(destName, bCreateBackups); break; case eDeleteAB: bSuccess = deleteFLD(fullNameA(mfi), bCreateBackups) && deleteFLD(fullNameB(mfi), bCreateBackups); break; case eMergeABToDest: case eMergeToA: case eMergeToAB: case eMergeToB: bSuccess = mergeFLD(fullNameA(mfi), fullNameB(mfi), "", destName, bSingleFileMerge); break; case eMergeABCToDest: bSuccess = mergeFLD( mfi.existsInA() ? fullNameA(mfi) : QString(""), mfi.existsInB() ? fullNameB(mfi) : QString(""), mfi.existsInC() ? fullNameC(mfi) : QString(""), destName, bSingleFileMerge); break; default: KMessageBox::error(q, i18n("Unknown merge operation."), i18n("Error")); assert(false); } return bSuccess; } // Check if the merge can start, and prepare the m_mergeItemList which then contains all // items that must be merged. void DirectoryMergeWindow::Data::prepareMergeStart(const QModelIndex& miBegin, const QModelIndex& miEnd, bool bVerbose) { if(bVerbose) { int status = KMessageBox::warningYesNoCancel(q, i18n("The merge is about to begin.\n\n" "Choose \"Do it\" if you have read the instructions and know what you are doing.\n" "Choosing \"Simulate it\" will tell you what would happen.\n\n" "Be aware that this program still has beta status " "and there is NO WARRANTY whatsoever! Make backups of your vital data!"), i18n("Starting Merge"), KGuiItem(i18n("Do It")), KGuiItem(i18n("Simulate It"))); if(status == KMessageBox::Yes) m_bRealMergeStarted = true; else if(status == KMessageBox::No) m_bSimulatedMergeStarted = true; else return; } else { m_bRealMergeStarted = true; } m_mergeItemList.clear(); if(!miBegin.isValid()) return; for(QModelIndex mi = miBegin; mi != miEnd; mi = treeIterator(mi)) { MergeFileInfos* pMFI = getMFI(mi); if(pMFI && !pMFI->m_bOperationComplete) { m_mergeItemList.push_back(mi); QString errorText; if(pMFI->m_eMergeOperation == eConflictingFileTypes) { errorText = i18n("The highlighted item has a different type in the different directories. Select what to do."); } if(pMFI->m_eMergeOperation == eConflictingAges) { errorText = i18n("The modification dates of the file are equal but the files are not. Select what to do."); } if(pMFI->m_eMergeOperation == eChangedAndDeleted) { errorText = i18n("The highlighted item was changed in one directory and deleted in the other. Select what to do."); } if(!errorText.isEmpty()) { q->scrollTo(mi, QAbstractItemView::EnsureVisible); q->setCurrentIndex(mi); KMessageBox::error(q, errorText, i18n("Error")); m_mergeItemList.clear(); m_bRealMergeStarted = false; return; } } } m_currentIndexForOperation = m_mergeItemList.begin(); return; } void DirectoryMergeWindow::slotRunOperationForCurrentItem() { if(!d->canContinue()) return; bool bVerbose = false; if(d->m_mergeItemList.empty()) { QModelIndex miBegin = currentIndex(); QModelIndex miEnd = d->treeIterator(miBegin, false, false); // find next visible sibling (no children) d->prepareMergeStart(miBegin, miEnd, bVerbose); d->mergeContinue(true, bVerbose); } else d->mergeContinue(false, bVerbose); } void DirectoryMergeWindow::slotRunOperationForAllItems() { if(!d->canContinue()) return; bool bVerbose = true; if(d->m_mergeItemList.empty()) { QModelIndex miBegin = d->rowCount() > 0 ? d->index(0, 0, QModelIndex()) : QModelIndex(); d->prepareMergeStart(miBegin, QModelIndex(), bVerbose); d->mergeContinue(true, bVerbose); } else d->mergeContinue(false, bVerbose); } void DirectoryMergeWindow::mergeCurrentFile() { if(!d->canContinue()) return; if(d->m_bRealMergeStarted) { KMessageBox::sorry(this, i18n("This operation is currently not possible because directory merge is currently running."), i18n("Operation Not Possible")); return; } if(isFileSelected()) { MergeFileInfos* pMFI = d->getMFI(currentIndex()); if(pMFI != nullptr) { MergeFileInfos& mfi = *pMFI; d->m_mergeItemList.clear(); d->m_mergeItemList.push_back(currentIndex()); d->m_currentIndexForOperation = d->m_mergeItemList.begin(); bool bDummy = false; d->mergeFLD( mfi.existsInA() ? mfi.m_pFileInfoA->absoluteFilePath() : QString(""), mfi.existsInB() ? mfi.m_pFileInfoB->absoluteFilePath() : QString(""), mfi.existsInC() ? mfi.m_pFileInfoC->absoluteFilePath() : QString(""), d->fullNameDest(mfi), bDummy); } } emit updateAvailabilities(); } // When bStart is true then m_currentIndexForOperation must still be processed. // When bVerbose is true then a messagebox will tell when the merge is complete. void DirectoryMergeWindow::Data::mergeContinue(bool bStart, bool bVerbose) { ProgressProxy pp; if(m_mergeItemList.empty()) return; int nrOfItems = 0; int nrOfCompletedItems = 0; int nrOfCompletedSimItems = 0; // Count the number of completed items (for the progress bar). for(MergeItemList::iterator i = m_mergeItemList.begin(); i != m_mergeItemList.end(); ++i) { MergeFileInfos* pMFI = getMFI(*i); ++nrOfItems; if(pMFI->m_bOperationComplete) ++nrOfCompletedItems; if(pMFI->m_bSimOpComplete) ++nrOfCompletedSimItems; } m_pStatusInfo->hide(); m_pStatusInfo->clear(); QModelIndex miCurrent = m_currentIndexForOperation == m_mergeItemList.end() ? QModelIndex() : *m_currentIndexForOperation; bool bContinueWithCurrentItem = bStart; // true for first item, else false bool bSkipItem = false; if(!bStart && m_bError && miCurrent.isValid()) { int status = KMessageBox::warningYesNoCancel(q, i18n("There was an error in the last step.\n" "Do you want to continue with the item that caused the error or do you want to skip this item?"), i18n("Continue merge after an error"), KGuiItem(i18n("Continue With Last Item")), KGuiItem(i18n("Skip Item"))); if(status == KMessageBox::Yes) bContinueWithCurrentItem = true; else if(status == KMessageBox::No) bSkipItem = true; else return; m_bError = false; } pp.setMaxNofSteps(nrOfItems); bool bSuccess = true; bool bSingleFileMerge = false; bool bSim = m_bSimulatedMergeStarted; while(bSuccess) { MergeFileInfos* pMFI = getMFI(miCurrent); if(pMFI == nullptr) { m_mergeItemList.clear(); m_bRealMergeStarted = false; break; } if(pMFI != nullptr && !bContinueWithCurrentItem) { if(bSim) { if(rowCount(miCurrent) == 0) { pMFI->m_bSimOpComplete = true; } } else { if(rowCount(miCurrent) == 0) { if(!pMFI->m_bOperationComplete) { setOpStatus(miCurrent, bSkipItem ? eOpStatusSkipped : eOpStatusDone); pMFI->m_bOperationComplete = true; bSkipItem = false; } } else { setOpStatus(miCurrent, eOpStatusInProgress); } } } if(!bContinueWithCurrentItem) { // Depth first QModelIndex miPrev = miCurrent; ++m_currentIndexForOperation; miCurrent = m_currentIndexForOperation == m_mergeItemList.end() ? QModelIndex() : *m_currentIndexForOperation; if((!miCurrent.isValid() || miCurrent.parent() != miPrev.parent()) && miPrev.parent().isValid()) { // Check if the parent may be set to "Done" QModelIndex miParent = miPrev.parent(); bool bDone = true; while(bDone && miParent.isValid()) { for(int childIdx = 0; childIdx < rowCount(miParent); ++childIdx) { MergeFileInfos* pMFI = getMFI(index(childIdx, 0, miParent)); if((!bSim && !pMFI->m_bOperationComplete) || (bSim && pMFI->m_bSimOpComplete)) { bDone = false; break; } } if(bDone) { MergeFileInfos* pMFI = getMFI(miParent); if(bSim) pMFI->m_bSimOpComplete = bDone; else { setOpStatus(miParent, eOpStatusDone); pMFI->m_bOperationComplete = bDone; } } miParent = miParent.parent(); } } } if(!miCurrent.isValid()) // end? { if(m_bRealMergeStarted) { if(bVerbose) { KMessageBox::information(q, i18n("Merge operation complete."), i18n("Merge Complete")); } m_bRealMergeStarted = false; m_pStatusInfo->setWindowTitle(i18n("Merge Complete")); } if(m_bSimulatedMergeStarted) { m_bSimulatedMergeStarted = false; QModelIndex mi = rowCount() > 0 ? index(0, 0, QModelIndex()) : QModelIndex(); for(; mi.isValid(); mi = treeIterator(mi)) { getMFI(mi)->m_bSimOpComplete = false; } m_pStatusInfo->setWindowTitle(i18n("Simulated merge complete: Check if you agree with the proposed operations.")); m_pStatusInfo->exec(); } m_mergeItemList.clear(); m_bRealMergeStarted = false; return; } MergeFileInfos& mfi = *getMFI(miCurrent); pp.setInformation(mfi.subPath(), bSim ? nrOfCompletedSimItems : nrOfCompletedItems, false // bRedrawUpdate ); bSuccess = executeMergeOperation(mfi, bSingleFileMerge); // Here the real operation happens. if(bSuccess) { if(bSim) ++nrOfCompletedSimItems; else ++nrOfCompletedItems; bContinueWithCurrentItem = false; } if(pp.wasCancelled()) break; } // end while //g_pProgressDialog->hide(); q->setCurrentIndex(miCurrent); q->scrollTo(miCurrent, EnsureVisible); if(!bSuccess && !bSingleFileMerge) { KMessageBox::error(q, i18n("An error occurred. Press OK to see detailed information.\n"), i18n("Error")); m_pStatusInfo->setWindowTitle(i18n("Merge Error")); m_pStatusInfo->exec(); //if ( m_pStatusInfo->firstChild()!=0 ) // m_pStatusInfo->ensureItemVisible( m_pStatusInfo->last() ); m_bError = true; setOpStatus(miCurrent, eOpStatusError); } else { m_bError = false; } emit q->updateAvailabilities(); if(m_currentIndexForOperation == m_mergeItemList.end()) { m_mergeItemList.clear(); m_bRealMergeStarted = false; } } bool DirectoryMergeWindow::Data::deleteFLD(const QString& name, bool bCreateBackup) { FileAccess fi(name, true); if(!fi.exists()) return true; if(bCreateBackup) { bool bSuccess = renameFLD(name, name + ".orig"); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error: While deleting %1: Creating backup failed.", name)); return false; } } else { if(fi.isDir() && !fi.isSymLink()) m_pStatusInfo->addText(i18n("delete directory recursively( %1 )", name)); else m_pStatusInfo->addText(i18n("delete( %1 )", name)); if(m_bSimulatedMergeStarted) { return true; } if(fi.isDir() && !fi.isSymLink()) // recursive directory delete only for real dirs, not symlinks { t_DirectoryList dirList; bool bSuccess = fi.listDir(&dirList, false, true, "*", "", "", false, false); // not recursive, find hidden files if(!bSuccess) { // No Permission to read directory or other error. m_pStatusInfo->addText(i18n("Error: delete dir operation failed while trying to read the directory.")); return false; } t_DirectoryList::iterator it; // create list iterator for(it = dirList.begin(); it != dirList.end(); ++it) // for each file... { FileAccess& fi2 = *it; if(fi2.fileName() == "." || fi2.fileName() == "..") continue; bSuccess = deleteFLD(fi2.absoluteFilePath(), false); if(!bSuccess) break; } if(bSuccess) { bSuccess = FileAccess::removeDir(name); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error: rmdir( %1 ) operation failed.", name)); return false; } } } else { bool bSuccess = FileAccess::removeFile(name); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error: delete operation failed.")); return false; } } } return true; } bool DirectoryMergeWindow::Data::mergeFLD(const QString& nameA, const QString& nameB, const QString& nameC, const QString& nameDest, bool& bSingleFileMerge) { FileAccess fi(nameA); if(fi.isDir()) { return makeDir(nameDest); } // Make sure that the dir exists, into which we will save the file later. int pos = nameDest.lastIndexOf('/'); if(pos > 0) { QString parentName = nameDest.left(pos); bool bSuccess = makeDir(parentName, true /*quiet*/); if(!bSuccess) return false; } m_pStatusInfo->addText(i18n("manual merge( %1, %2, %3 -> %4)", nameA, nameB, nameC, nameDest)); if(m_bSimulatedMergeStarted) { m_pStatusInfo->addText(i18n(" Note: After a manual merge the user should continue by pressing F7.")); return true; } bSingleFileMerge = true; setOpStatus(*m_currentIndexForOperation, eOpStatusInProgress); q->scrollTo(*m_currentIndexForOperation, EnsureVisible); emit q->startDiffMerge(nameA, nameB, nameC, nameDest, "", "", "", nullptr); return false; } bool DirectoryMergeWindow::Data::copyFLD(const QString& srcName, const QString& destName) { if(srcName == destName) return true; FileAccess fi(srcName); FileAccess faDest(destName, true); if(faDest.exists() && !(fi.isDir() && faDest.isDir() && (fi.isSymLink() == faDest.isSymLink()))) { bool bSuccess = deleteFLD(destName, m_pOptions->m_bDmCreateBakFiles); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error: copy( %1 -> %2 ) failed." "Deleting existing destination failed.", srcName, destName)); return false; } } if(fi.isSymLink() && ((fi.isDir() && !m_bFollowDirLinks) || (!fi.isDir() && !m_bFollowFileLinks))) { m_pStatusInfo->addText(i18n("copyLink( %1 -> %2 )", srcName, destName)); -#if defined(_WIN32) || defined(Q_OS_OS2) +#if defined(_WIN32) // What are links? #else if(m_bSimulatedMergeStarted) { return true; } FileAccess destFi(destName); if(!destFi.isLocal() || !fi.isLocal()) { m_pStatusInfo->addText(i18n("Error: copyLink failed: Remote links are not yet supported.")); return false; } QString linkTarget = fi.readLink(); bool bSuccess = FileAccess::symLink(linkTarget, destName); if(!bSuccess) m_pStatusInfo->addText(i18n("Error: copyLink failed.")); return bSuccess; #endif } if(fi.isDir()) { if(faDest.exists()) return true; else { bool bSuccess = makeDir(destName); return bSuccess; } } int pos = destName.lastIndexOf('/'); if(pos > 0) { QString parentName = destName.left(pos); bool bSuccess = makeDir(parentName, true /*quiet*/); if(!bSuccess) return false; } m_pStatusInfo->addText(i18n("copy( %1 -> %2 )", srcName, destName)); if(m_bSimulatedMergeStarted) { return true; } FileAccess faSrc(srcName); bool bSuccess = faSrc.copyFile(destName); if(!bSuccess) m_pStatusInfo->addText(faSrc.getStatusText()); return bSuccess; } // Rename is not an operation that can be selected by the user. // It will only be used to create backups. // Hence it will delete an existing destination without making a backup (of the old backup.) bool DirectoryMergeWindow::Data::renameFLD(const QString& srcName, const QString& destName) { if(srcName == destName) return true; if(FileAccess(destName, true).exists()) { bool bSuccess = deleteFLD(destName, false /*no backup*/); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error during rename( %1 -> %2 ): " "Cannot delete existing destination.", srcName, destName)); return false; } } m_pStatusInfo->addText(i18n("rename( %1 -> %2 )", srcName, destName)); if(m_bSimulatedMergeStarted) { return true; } bool bSuccess = FileAccess(srcName).rename(destName); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error: Rename failed.")); return false; } return true; } bool DirectoryMergeWindow::Data::makeDir(const QString& name, bool bQuiet) { FileAccess fi(name, true); if(fi.exists() && fi.isDir()) return true; if(fi.exists() && !fi.isDir()) { bool bSuccess = deleteFLD(name, true); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error during makeDir of %1. " "Cannot delete existing file.", name)); return false; } } int pos = name.lastIndexOf('/'); if(pos > 0) { QString parentName = name.left(pos); bool bSuccess = makeDir(parentName, true); if(!bSuccess) return false; } if(!bQuiet) m_pStatusInfo->addText(i18n("makeDir( %1 )", name)); if(m_bSimulatedMergeStarted) { return true; } bool bSuccess = FileAccess::makeDir(name); if(bSuccess == false) { m_pStatusInfo->addText(i18n("Error while creating directory.")); return false; } return true; } DirectoryMergeInfo::DirectoryMergeInfo(QWidget* pParent) : QFrame(pParent) { QVBoxLayout* topLayout = new QVBoxLayout(this); topLayout->setMargin(0); QGridLayout* grid = new QGridLayout(); topLayout->addLayout(grid); grid->setColumnStretch(1, 10); int line = 0; m_pA = new QLabel("A", this); grid->addWidget(m_pA, line, 0); m_pInfoA = new QLabel(this); grid->addWidget(m_pInfoA, line, 1); ++line; m_pB = new QLabel("B", this); grid->addWidget(m_pB, line, 0); m_pInfoB = new QLabel(this); grid->addWidget(m_pInfoB, line, 1); ++line; m_pC = new QLabel("C", this); grid->addWidget(m_pC, line, 0); m_pInfoC = new QLabel(this); grid->addWidget(m_pInfoC, line, 1); ++line; m_pDest = new QLabel(i18n("Dest"), this); grid->addWidget(m_pDest, line, 0); m_pInfoDest = new QLabel(this); grid->addWidget(m_pInfoDest, line, 1); ++line; m_pInfoList = new QTreeWidget(this); topLayout->addWidget(m_pInfoList); m_pInfoList->setHeaderLabels(QStringList() << i18n("Dir") << i18n("Type") << i18n("Size") << i18n("Attr") << i18n("Last Modification") << i18n("Link-Destination")); setMinimumSize(100, 100); m_pInfoList->installEventFilter(this); m_pInfoList->setRootIsDecorated(false); } bool DirectoryMergeInfo::eventFilter(QObject* o, QEvent* e) { if(e->type() == QEvent::FocusIn && o == m_pInfoList) emit gotFocus(); return false; } static void addListViewItem(QTreeWidget* pListView, const QString& dir, const QString& basePath, FileAccess* fi) { if(basePath.isEmpty()) { return; } else { if(fi != nullptr && fi->exists()) { QString dateString = fi->lastModified().toString("yyyy-MM-dd hh:mm:ss"); new QTreeWidgetItem( pListView, QStringList() << dir << QString(fi->isDir() ? i18n("Dir") : i18n("File")) + (fi->isSymLink() ? "-Link" : "") << QString::number(fi->size()) << QString(fi->isReadable() ? "r" : " ") + (fi->isWritable() ? "w" : " ") #ifdef _WIN32 /*Future: Use GetFileAttributes()*/ << #else + (fi->isExecutable() ? "x" : " ") << #endif dateString << QString(fi->isSymLink() ? (" -> " + fi->readLink()) : QString(""))); } else { new QTreeWidgetItem( pListView, QStringList() << dir << i18n("not available") << "" << "" << "" << ""); } } } void DirectoryMergeInfo::setInfo( const FileAccess& dirA, const FileAccess& dirB, const FileAccess& dirC, const FileAccess& dirDest, MergeFileInfos& mfi) { bool bHideDest = false; if(dirA.absoluteFilePath() == dirDest.absoluteFilePath()) { m_pA->setText(i18n("A (Dest): ")); bHideDest = true; } else m_pA->setText(!dirC.isValid() ? QString("A: ") : i18n("A (Base): ")); m_pInfoA->setText(dirA.prettyAbsPath()); if(dirB.absoluteFilePath() == dirDest.absoluteFilePath()) { m_pB->setText(i18n("B (Dest): ")); bHideDest = true; } else m_pB->setText("B: "); m_pInfoB->setText(dirB.prettyAbsPath()); if(dirC.absoluteFilePath() == dirDest.absoluteFilePath()) { m_pC->setText(i18n("C (Dest): ")); bHideDest = true; } else m_pC->setText("C: "); m_pInfoC->setText(dirC.prettyAbsPath()); m_pDest->setText(i18n("Dest: ")); m_pInfoDest->setText(dirDest.prettyAbsPath()); if(!dirC.isValid()) { m_pC->hide(); m_pInfoC->hide(); } else { m_pC->show(); m_pInfoC->show(); } if(!dirDest.isValid() || bHideDest) { m_pDest->hide(); m_pInfoDest->hide(); } else { m_pDest->show(); m_pInfoDest->show(); } m_pInfoList->clear(); addListViewItem(m_pInfoList, "A", dirA.prettyAbsPath(), mfi.m_pFileInfoA); addListViewItem(m_pInfoList, "B", dirB.prettyAbsPath(), mfi.m_pFileInfoB); addListViewItem(m_pInfoList, "C", dirC.prettyAbsPath(), mfi.m_pFileInfoC); if(!bHideDest) { FileAccess fiDest(dirDest.prettyAbsPath() + "/" + mfi.subPath(), true); addListViewItem(m_pInfoList, i18n("Dest"), dirDest.prettyAbsPath(), &fiDest); } for(int i = 0; i < m_pInfoList->columnCount(); ++i) m_pInfoList->resizeColumnToContents(i); } QTextStream& operator<<(QTextStream& ts, MergeFileInfos& mfi) { ts << "{\n"; ValueMap vm; vm.writeEntry("SubPath", mfi.subPath()); vm.writeEntry("ExistsInA", mfi.existsInA()); vm.writeEntry("ExistsInB", mfi.existsInB()); vm.writeEntry("ExistsInC", mfi.existsInC()); vm.writeEntry("EqualAB", mfi.m_bEqualAB); vm.writeEntry("EqualAC", mfi.m_bEqualAC); vm.writeEntry("EqualBC", mfi.m_bEqualBC); //DirMergeItem* m_pDMI; //MergeFileInfos* m_pParent; vm.writeEntry("MergeOperation", (int)mfi.m_eMergeOperation); vm.writeEntry("DirA", mfi.dirA()); vm.writeEntry("DirB", mfi.dirB()); vm.writeEntry("DirC", mfi.dirC()); vm.writeEntry("LinkA", mfi.isLinkA()); vm.writeEntry("LinkB", mfi.isLinkB()); vm.writeEntry("LinkC", mfi.isLinkC()); vm.writeEntry("OperationComplete", mfi.m_bOperationComplete); //bool m_bSimOpComplete ); vm.writeEntry("AgeA", (int)mfi.m_ageA); vm.writeEntry("AgeB", (int)mfi.m_ageB); vm.writeEntry("AgeC", (int)mfi.m_ageC); vm.writeEntry("ConflictingAges", mfi.m_bConflictingAges); // Equal age but files are not! //FileAccess m_fileInfoA; //FileAccess m_fileInfoB; //FileAccess m_fileInfoC; //TotalDiffStatus m_totalDiffStatus; vm.save(ts); ts << "}\n"; return ts; } void DirectoryMergeWindow::slotSaveMergeState() { //slotStatusMsg(i18n("Saving Directory Merge State ...")); //QString s = KFileDialog::getSaveUrl( QDir::currentPath(), 0, this, i18n("Save As...") ).url(); QString s = QFileDialog::getSaveFileName(this, i18n("Save Directory Merge State As..."), QDir::currentPath(), nullptr); if(!s.isEmpty()) { d->m_dirMergeStateFilename = s; QFile file(d->m_dirMergeStateFilename); bool bSuccess = file.open(QIODevice::WriteOnly); if(bSuccess) { QTextStream ts(&file); QModelIndex mi(d->index(0, 0, QModelIndex())); while(mi.isValid()) { MergeFileInfos* pMFI = d->getMFI(mi); ts << *pMFI; mi = d->treeIterator(mi, true, true); } } } //slotStatusMsg(i18n("Ready.")); } void DirectoryMergeWindow::slotLoadMergeState() { } void DirectoryMergeWindow::updateFileVisibilities() { bool bShowIdentical = d->m_pDirShowIdenticalFiles->isChecked(); bool bShowDifferent = d->m_pDirShowDifferentFiles->isChecked(); bool bShowOnlyInA = d->m_pDirShowFilesOnlyInA->isChecked(); bool bShowOnlyInB = d->m_pDirShowFilesOnlyInB->isChecked(); bool bShowOnlyInC = d->m_pDirShowFilesOnlyInC->isChecked(); bool bThreeDirs = d->m_dirC.isValid(); d->m_selection1Index = QModelIndex(); d->m_selection2Index = QModelIndex(); d->m_selection3Index = QModelIndex(); // in first run set all dirs to equal and determine if they are not equal. // on second run don't change the equal-status anymore; it is needed to // set the visibility (when bShowIdentical is false). for(int loop = 0; loop < 2; ++loop) { QModelIndex mi = d->rowCount() > 0 ? d->index(0, 0, QModelIndex()) : QModelIndex(); while(mi.isValid()) { MergeFileInfos* pMFI = d->getMFI(mi); bool bDir = pMFI->dirA() || pMFI->dirB() || pMFI->dirC(); if(loop == 0 && bDir) { bool bChange = false; if(!pMFI->m_bEqualAB && pMFI->dirA() == pMFI->dirB() && pMFI->isLinkA() == pMFI->isLinkB()) { pMFI->m_bEqualAB = true; bChange = true; } if(!pMFI->m_bEqualBC && pMFI->dirC() == pMFI->dirB() && pMFI->isLinkC() == pMFI->isLinkB()) { pMFI->m_bEqualBC = true; bChange = true; } if(!pMFI->m_bEqualAC && pMFI->dirA() == pMFI->dirC() && pMFI->isLinkA() == pMFI->isLinkC()) { pMFI->m_bEqualAC = true; bChange = true; } if(bChange) setPixmaps(*pMFI, bThreeDirs); } bool bExistsEverywhere = pMFI->existsInA() && pMFI->existsInB() && (pMFI->existsInC() || !bThreeDirs); int existCount = int(pMFI->existsInA()) + int(pMFI->existsInB()) + int(pMFI->existsInC()); bool bVisible = (bShowIdentical && bExistsEverywhere && pMFI->m_bEqualAB && (pMFI->m_bEqualAC || !bThreeDirs)) || ((bShowDifferent || bDir) && existCount >= 2 && (!pMFI->m_bEqualAB || !(pMFI->m_bEqualAC || !bThreeDirs))) || (bShowOnlyInA && pMFI->existsInA() && !pMFI->existsInB() && !pMFI->existsInC()) || (bShowOnlyInB && !pMFI->existsInA() && pMFI->existsInB() && !pMFI->existsInC()) || (bShowOnlyInC && !pMFI->existsInA() && !pMFI->existsInB() && pMFI->existsInC()); QString fileName = pMFI->fileName(); bVisible = bVisible && ((bDir && !wildcardMultiMatch(d->m_pOptions->m_DmDirAntiPattern, fileName, d->m_bCaseSensitive)) || (wildcardMultiMatch(d->m_pOptions->m_DmFilePattern, fileName, d->m_bCaseSensitive) && !wildcardMultiMatch(d->m_pOptions->m_DmFileAntiPattern, fileName, d->m_bCaseSensitive))); setRowHidden(mi.row(), mi.parent(), !bVisible); bool bEqual = bThreeDirs ? pMFI->m_bEqualAB && pMFI->m_bEqualAC : pMFI->m_bEqualAB; if(!bEqual && bVisible && loop == 0) // Set all parents to "not equal" { MergeFileInfos* p2 = pMFI->m_pParent; while(p2 != nullptr) { bool bChange = false; if(!pMFI->m_bEqualAB && p2->m_bEqualAB) { p2->m_bEqualAB = false; bChange = true; } if(!pMFI->m_bEqualAC && p2->m_bEqualAC) { p2->m_bEqualAC = false; bChange = true; } if(!pMFI->m_bEqualBC && p2->m_bEqualBC) { p2->m_bEqualBC = false; bChange = true; } if(bChange) setPixmaps(*p2, bThreeDirs); else break; p2 = p2->m_pParent; } } mi = d->treeIterator(mi, true, true); } } } void DirectoryMergeWindow::slotShowIdenticalFiles() { d->m_pOptions->m_bDmShowIdenticalFiles = d->m_pDirShowIdenticalFiles->isChecked(); updateFileVisibilities(); } void DirectoryMergeWindow::slotShowDifferentFiles() { updateFileVisibilities(); } void DirectoryMergeWindow::slotShowFilesOnlyInA() { updateFileVisibilities(); } void DirectoryMergeWindow::slotShowFilesOnlyInB() { updateFileVisibilities(); } void DirectoryMergeWindow::slotShowFilesOnlyInC() { updateFileVisibilities(); } void DirectoryMergeWindow::slotSynchronizeDirectories() {} void DirectoryMergeWindow::slotChooseNewerFiles() {} void DirectoryMergeWindow::initDirectoryMergeActions(QObject* pKDiff3App, KActionCollection* ac) { #include "xpm/showequalfiles.xpm" #include "xpm/showfilesonlyina.xpm" #include "xpm/showfilesonlyinb.xpm" #include "xpm/showfilesonlyinc.xpm" #include "xpm/startmerge.xpm" DirectoryMergeWindow* p = this; d->m_pDirStartOperation = KDiff3::createAction(i18n("Start/Continue Directory Merge"), QKeySequence(Qt::Key_F7), p, SLOT(slotRunOperationForAllItems()), ac, "dir_start_operation"); d->m_pDirRunOperationForCurrentItem = KDiff3::createAction(i18n("Run Operation for Current Item"), QKeySequence(Qt::Key_F6), p, SLOT(slotRunOperationForCurrentItem()), ac, "dir_run_operation_for_current_item"); d->m_pDirCompareCurrent = KDiff3::createAction(i18n("Compare Selected File"), p, SLOT(compareCurrentFile()), ac, "dir_compare_current"); d->m_pDirMergeCurrent = KDiff3::createAction(i18n("Merge Current File"), QIcon(QPixmap(startmerge)), i18n("Merge\nFile"), pKDiff3App, SLOT(slotMergeCurrentFile()), ac, "merge_current"); d->m_pDirFoldAll = KDiff3::createAction(i18n("Fold All Subdirs"), p, SLOT(collapseAll()), ac, "dir_fold_all"); d->m_pDirUnfoldAll = KDiff3::createAction(i18n("Unfold All Subdirs"), p, SLOT(expandAll()), ac, "dir_unfold_all"); d->m_pDirRescan = KDiff3::createAction(i18n("Rescan"), QKeySequence(Qt::SHIFT + Qt::Key_F5), p, SLOT(reload()), ac, "dir_rescan"); d->m_pDirSaveMergeState = nullptr; //KDiff3::createAction< QAction >(i18n("Save Directory Merge State ..."), 0, p, SLOT(slotSaveMergeState()), ac, "dir_save_merge_state"); d->m_pDirLoadMergeState = nullptr; //KDiff3::createAction< QAction >(i18n("Load Directory Merge State ..."), 0, p, SLOT(slotLoadMergeState()), ac, "dir_load_merge_state"); d->m_pDirChooseAEverywhere = KDiff3::createAction(i18n("Choose A for All Items"), p, SLOT(slotChooseAEverywhere()), ac, "dir_choose_a_everywhere"); d->m_pDirChooseBEverywhere = KDiff3::createAction(i18n("Choose B for All Items"), p, SLOT(slotChooseBEverywhere()), ac, "dir_choose_b_everywhere"); d->m_pDirChooseCEverywhere = KDiff3::createAction(i18n("Choose C for All Items"), p, SLOT(slotChooseCEverywhere()), ac, "dir_choose_c_everywhere"); d->m_pDirAutoChoiceEverywhere = KDiff3::createAction(i18n("Auto-Choose Operation for All Items"), p, SLOT(slotAutoChooseEverywhere()), ac, "dir_autochoose_everywhere"); d->m_pDirDoNothingEverywhere = KDiff3::createAction(i18n("No Operation for All Items"), p, SLOT(slotNoOpEverywhere()), ac, "dir_nothing_everywhere"); // d->m_pDirSynchronizeDirectories = KDiff3::createAction< KToggleAction >(i18n("Synchronize Directories"), 0, this, SLOT(slotSynchronizeDirectories()), ac, "dir_synchronize_directories"); // d->m_pDirChooseNewerFiles = KDiff3::createAction< KToggleAction >(i18n("Copy Newer Files Instead of Merging"), 0, this, SLOT(slotChooseNewerFiles()), ac, "dir_choose_newer_files"); d->m_pDirShowIdenticalFiles = KDiff3::createAction(i18n("Show Identical Files"), QIcon(QPixmap(showequalfiles)), i18n("Identical\nFiles"), this, SLOT(slotShowIdenticalFiles()), ac, "dir_show_identical_files"); d->m_pDirShowDifferentFiles = KDiff3::createAction(i18n("Show Different Files"), this, SLOT(slotShowDifferentFiles()), ac, "dir_show_different_files"); d->m_pDirShowFilesOnlyInA = KDiff3::createAction(i18n("Show Files only in A"), QIcon(QPixmap(showfilesonlyina)), i18n("Files\nonly in A"), this, SLOT(slotShowFilesOnlyInA()), ac, "dir_show_files_only_in_a"); d->m_pDirShowFilesOnlyInB = KDiff3::createAction(i18n("Show Files only in B"), QIcon(QPixmap(showfilesonlyinb)), i18n("Files\nonly in B"), this, SLOT(slotShowFilesOnlyInB()), ac, "dir_show_files_only_in_b"); d->m_pDirShowFilesOnlyInC = KDiff3::createAction(i18n("Show Files only in C"), QIcon(QPixmap(showfilesonlyinc)), i18n("Files\nonly in C"), this, SLOT(slotShowFilesOnlyInC()), ac, "dir_show_files_only_in_c"); d->m_pDirShowIdenticalFiles->setChecked(d->m_pOptions->m_bDmShowIdenticalFiles); d->m_pDirCompareExplicit = KDiff3::createAction(i18n("Compare Explicitly Selected Files"), p, SLOT(slotCompareExplicitlySelectedFiles()), ac, "dir_compare_explicitly_selected_files"); d->m_pDirMergeExplicit = KDiff3::createAction(i18n("Merge Explicitly Selected Files"), p, SLOT(slotMergeExplicitlySelectedFiles()), ac, "dir_merge_explicitly_selected_files"); d->m_pDirCurrentDoNothing = KDiff3::createAction(i18n("Do Nothing"), p, SLOT(slotCurrentDoNothing()), ac, "dir_current_do_nothing"); d->m_pDirCurrentChooseA = KDiff3::createAction(i18n("A"), p, SLOT(slotCurrentChooseA()), ac, "dir_current_choose_a"); d->m_pDirCurrentChooseB = KDiff3::createAction(i18n("B"), p, SLOT(slotCurrentChooseB()), ac, "dir_current_choose_b"); d->m_pDirCurrentChooseC = KDiff3::createAction(i18n("C"), p, SLOT(slotCurrentChooseC()), ac, "dir_current_choose_c"); d->m_pDirCurrentMerge = KDiff3::createAction(i18n("Merge"), p, SLOT(slotCurrentMerge()), ac, "dir_current_merge"); d->m_pDirCurrentDelete = KDiff3::createAction(i18n("Delete (if exists)"), p, SLOT(slotCurrentDelete()), ac, "dir_current_delete"); d->m_pDirCurrentSyncDoNothing = KDiff3::createAction(i18n("Do Nothing"), p, SLOT(slotCurrentDoNothing()), ac, "dir_current_sync_do_nothing"); d->m_pDirCurrentSyncCopyAToB = KDiff3::createAction(i18n("Copy A to B"), p, SLOT(slotCurrentCopyAToB()), ac, "dir_current_sync_copy_a_to_b"); d->m_pDirCurrentSyncCopyBToA = KDiff3::createAction(i18n("Copy B to A"), p, SLOT(slotCurrentCopyBToA()), ac, "dir_current_sync_copy_b_to_a"); d->m_pDirCurrentSyncDeleteA = KDiff3::createAction(i18n("Delete A"), p, SLOT(slotCurrentDeleteA()), ac, "dir_current_sync_delete_a"); d->m_pDirCurrentSyncDeleteB = KDiff3::createAction(i18n("Delete B"), p, SLOT(slotCurrentDeleteB()), ac, "dir_current_sync_delete_b"); d->m_pDirCurrentSyncDeleteAAndB = KDiff3::createAction(i18n("Delete A && B"), p, SLOT(slotCurrentDeleteAAndB()), ac, "dir_current_sync_delete_a_and_b"); d->m_pDirCurrentSyncMergeToA = KDiff3::createAction(i18n("Merge to A"), p, SLOT(slotCurrentMergeToA()), ac, "dir_current_sync_merge_to_a"); d->m_pDirCurrentSyncMergeToB = KDiff3::createAction(i18n("Merge to B"), p, SLOT(slotCurrentMergeToB()), ac, "dir_current_sync_merge_to_b"); d->m_pDirCurrentSyncMergeToAAndB = KDiff3::createAction(i18n("Merge to A && B"), p, SLOT(slotCurrentMergeToAAndB()), ac, "dir_current_sync_merge_to_a_and_b"); } void DirectoryMergeWindow::updateAvailabilities(bool bDirCompare, bool bDiffWindowVisible, KToggleAction* chooseA, KToggleAction* chooseB, KToggleAction* chooseC) { d->m_pDirStartOperation->setEnabled(bDirCompare); d->m_pDirRunOperationForCurrentItem->setEnabled(bDirCompare); d->m_pDirFoldAll->setEnabled(bDirCompare); d->m_pDirUnfoldAll->setEnabled(bDirCompare); d->m_pDirCompareCurrent->setEnabled(bDirCompare && isVisible() && isFileSelected()); d->m_pDirMergeCurrent->setEnabled((bDirCompare && isVisible() && isFileSelected()) || bDiffWindowVisible); d->m_pDirRescan->setEnabled(bDirCompare); d->m_pDirAutoChoiceEverywhere->setEnabled(bDirCompare && isVisible()); d->m_pDirDoNothingEverywhere->setEnabled(bDirCompare && isVisible()); d->m_pDirChooseAEverywhere->setEnabled(bDirCompare && isVisible()); d->m_pDirChooseBEverywhere->setEnabled(bDirCompare && isVisible()); d->m_pDirChooseCEverywhere->setEnabled(bDirCompare && isVisible()); bool bThreeDirs = d->m_dirC.isValid(); MergeFileInfos* pMFI = d->getMFI(currentIndex()); bool bItemActive = bDirCompare && isVisible() && pMFI != nullptr; // && hasFocus(); bool bMergeMode = bThreeDirs || !d->m_bSyncMode; bool bFTConflict = pMFI == nullptr ? false : conflictingFileTypes(*pMFI); bool bDirWindowHasFocus = isVisible() && hasFocus(); d->m_pDirShowIdenticalFiles->setEnabled(bDirCompare && isVisible()); d->m_pDirShowDifferentFiles->setEnabled(bDirCompare && isVisible()); d->m_pDirShowFilesOnlyInA->setEnabled(bDirCompare && isVisible()); d->m_pDirShowFilesOnlyInB->setEnabled(bDirCompare && isVisible()); d->m_pDirShowFilesOnlyInC->setEnabled(bDirCompare && isVisible() && bThreeDirs); d->m_pDirCompareExplicit->setEnabled(bDirCompare && isVisible() && d->m_selection2Index.isValid()); d->m_pDirMergeExplicit->setEnabled(bDirCompare && isVisible() && d->m_selection2Index.isValid()); d->m_pDirCurrentDoNothing->setEnabled(bItemActive && bMergeMode); d->m_pDirCurrentChooseA->setEnabled(bItemActive && bMergeMode && pMFI->existsInA()); d->m_pDirCurrentChooseB->setEnabled(bItemActive && bMergeMode && pMFI->existsInB()); d->m_pDirCurrentChooseC->setEnabled(bItemActive && bMergeMode && pMFI->existsInC()); d->m_pDirCurrentMerge->setEnabled(bItemActive && bMergeMode && !bFTConflict); d->m_pDirCurrentDelete->setEnabled(bItemActive && bMergeMode); if(bDirWindowHasFocus) { chooseA->setEnabled(bItemActive && pMFI->existsInA()); chooseB->setEnabled(bItemActive && pMFI->existsInB()); chooseC->setEnabled(bItemActive && pMFI->existsInC()); chooseA->setChecked(false); chooseB->setChecked(false); chooseC->setChecked(false); } d->m_pDirCurrentSyncDoNothing->setEnabled(bItemActive && !bMergeMode); d->m_pDirCurrentSyncCopyAToB->setEnabled(bItemActive && !bMergeMode && pMFI->existsInA()); d->m_pDirCurrentSyncCopyBToA->setEnabled(bItemActive && !bMergeMode && pMFI->existsInB()); d->m_pDirCurrentSyncDeleteA->setEnabled(bItemActive && !bMergeMode && pMFI->existsInA()); d->m_pDirCurrentSyncDeleteB->setEnabled(bItemActive && !bMergeMode && pMFI->existsInB()); d->m_pDirCurrentSyncDeleteAAndB->setEnabled(bItemActive && !bMergeMode && pMFI->existsInA() && pMFI->existsInB()); d->m_pDirCurrentSyncMergeToA->setEnabled(bItemActive && !bMergeMode && !bFTConflict); d->m_pDirCurrentSyncMergeToB->setEnabled(bItemActive && !bMergeMode && !bFTConflict); d->m_pDirCurrentSyncMergeToAAndB->setEnabled(bItemActive && !bMergeMode && !bFTConflict); } //#include "directorymergewindow.moc" diff --git a/src/fileaccess.cpp b/src/fileaccess.cpp index 9c468e9..f7b7285 100644 --- a/src/fileaccess.cpp +++ b/src/fileaccess.cpp @@ -1,1784 +1,1784 @@ /*************************************************************************** * Copyright (C) 2003-2011 by Joachim Eibl * * joachim.eibl at gmx.de * * * * 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 "fileaccess.h" #include "common.h" #include "progress.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #include #include #include #else #include // Needed for creating symbolic links via symlink(). #include #endif class FileAccess::Data { public: Data() { reset(); } void reset() { m_url = QUrl(); m_bValidData = false; m_name = QString(); //m_creationTime = QDateTime(); //m_accessTime = QDateTime(); m_bReadable = false; m_bExecutable = false; m_linkTarget = ""; //m_fileType = -1; m_bLocal = true; m_pParent = nullptr; } QUrl m_url; bool m_bLocal; bool m_bValidData; //QDateTime m_accessTime; //QDateTime m_creationTime; bool m_bReadable; bool m_bExecutable; //long m_fileType; // for testing only FileAccess* m_pParent; QString m_linkTarget; //QString m_user; //QString m_group; QString m_name; QString m_localCopy; QString m_statusText; // Might contain an error string, when the last operation didn't succeed. }; FileAccess::FileAccess(const QString& name, bool bWantToWrite) { m_pData = nullptr; m_bUseData = false; setFile(name, bWantToWrite); } FileAccess::FileAccess() { m_bUseData = false; m_bExists = false; m_bFile = false; m_bDir = false; m_bSymLink = false; m_bWritable = false; m_bHidden = false; m_pParent = nullptr; m_size = 0; } FileAccess::FileAccess(const FileAccess& other) { m_pData = nullptr; m_bUseData = false; *this = other; } void FileAccess::createData() { if(d() == nullptr) { FileAccess* pParent = m_pParent; // backup because in union with m_pData m_pData = new Data(); m_bUseData = true; m_pData->m_pParent = pParent; } } FileAccess& FileAccess::operator=(const FileAccess& other) { m_size = other.m_size; m_filePath = other.m_filePath; m_modificationTime = other.m_modificationTime; m_bSymLink = other.m_bSymLink; m_bFile = other.m_bFile; m_bDir = other.m_bDir; m_bExists = other.m_bExists; m_bWritable = other.m_bWritable; m_bHidden = other.m_bHidden; if(other.m_bUseData) { createData(); *m_pData = *other.m_pData; } else { if(m_bUseData) { delete m_pData; } m_bUseData = false; m_pParent = other.parent(); // should be 0 anyway } return *this; } FileAccess::~FileAccess() { if(m_bUseData) { if(!d()->m_localCopy.isEmpty()) { removeTempFile(d()->m_localCopy); } delete m_pData; } } static QString nicePath(const QFileInfo& fi) { QString fp = fi.filePath(); if(fp.length() > 2 && fp[0] == '.' && fp[1] == '/') { return fp.mid(2); } return fp; } // Two kinds of optimization are applied here: // 1. Speed: don't ask for data as long as it is not needed or cheap to get. // When opening a file it is early enough to ask for details. // 2. Memory usage: Don't store data that is not needed, and avoid redundancy. // For recursive directory trees don't store the full path if a parent is available. // Store urls only if files are not local. void FileAccess::setFile(const QFileInfo& fi, FileAccess* pParent) { m_filePath = pParent == nullptr ? fi.absoluteFilePath() : nicePath(fi.filePath()); // remove "./" at start m_bSymLink = fi.isSymLink(); if(m_bSymLink || (!m_bExists && m_filePath.contains("@@"))) { createData(); } if(m_bUseData) d()->m_pParent = pParent; else m_pParent = pParent; if(parent() || d()) // if a parent is specified then we arrive here because of listing a directory { m_bFile = fi.isFile(); m_bDir = fi.isDir(); m_bExists = fi.exists(); m_size = fi.size(); m_modificationTime = fi.lastModified(); m_bHidden = fi.isHidden(); #if defined(Q_WS_WIN) m_bWritable = pParent == 0 || fi.isWritable(); // in certain situations this might become a problem though #else m_bWritable = fi.isWritable(); #endif } if(d() != nullptr) { #if defined(Q_WS_WIN) // On some windows machines in a network this takes very long. // and it's not so important anyway. d()->m_bReadable = true; d()->m_bExecutable = false; #else d()->m_bReadable = fi.isReadable(); d()->m_bExecutable = fi.isExecutable(); #endif //d()->m_creationTime = fi.created(); //d()->m_modificationTime = fi.lastModified(); //d()->m_accessTime = fi.lastRead(); d()->m_name = fi.fileName(); if(m_bSymLink) { #ifdef _WIN32 d()->m_linkTarget = fi.readLink(); #else // TODO: Update for Qt5. // Unfortunately Qt4 readLink always returns an absolute path, even if the link is relative char s[PATH_MAX + 1]; int len = readlink(QFile::encodeName(fi.absoluteFilePath()).constData(), s, PATH_MAX); if(len > 0) { s[len] = '\0'; d()->m_linkTarget = QFile::decodeName(s); } else { d()->m_linkTarget = fi.readLink(); } #endif } d()->m_bLocal = true; d()->m_bValidData = true; d()->m_url = QUrl::fromLocalFile(fi.filePath()); if(d()->m_url.isRelative()) { d()->m_url.setPath(absoluteFilePath()); } if(!m_bExists && absoluteFilePath().contains("@@")) { // Try reading a clearcase file d()->m_localCopy = FileAccess::tempFileName(); QString cmd = "cleartool get -to \"" + d()->m_localCopy + "\" \"" + absoluteFilePath() + "\""; QProcess process; process.start(cmd); process.waitForFinished(-1); //::system( cmd.local8Bit() ); QFile::setPermissions(d()->m_localCopy, QFile::ReadUser | QFile::WriteUser); // Clearcase creates a write protected file, allow delete. QFileInfo fi(d()->m_localCopy); #if defined(Q_WS_WIN) d()->m_bReadable = true; //fi.isReadable(); m_bWritable = true; //fi.isWritable(); d()->m_bExecutable = false; //fi.isExecutable(); #else d()->m_bReadable = fi.isReadable(); d()->m_bExecutable = fi.isExecutable(); #endif //d()->m_creationTime = fi.created(); //d()->m_accessTime = fi.lastRead(); m_bExists = fi.exists(); m_size = fi.size(); } } } void FileAccess::setFile(const QString& name, bool bWantToWrite) { m_bExists = false; m_bFile = false; m_bDir = false; m_bSymLink = false; m_size = 0; m_modificationTime = QDateTime(); if(d() != nullptr) { d()->reset(); d()->m_pParent = nullptr; } else m_pParent = nullptr; // Note: Checking if the filename-string is empty is necessary for Win95/98/ME. // The isFile() / isDir() queries would cause the program to crash. // (This is a Win95-bug which has been corrected only in WinNT/2000/XP.) if(!name.isEmpty()) { QUrl url = QUrl::fromUserInput(name, QString(), QUrl::AssumeLocalFile); // FileAccess tries to detect if the given name is an URL or a local file. // This is a problem if the filename looks like an URL (i.e. contains a colon ':'). // e.g. "file:f.txt" is a valid filename. // Most of the time it is sufficient to check if the file exists locally. // 2 Problems remain: // 1. When the local file exists and the remote location is wanted nevertheless. (unlikely) // 2. When the local file doesn't exist and should be written to. bool bExistsLocal = QDir().exists(name); if(url.isLocalFile() || url.isRelative() || !url.isValid() || bExistsLocal) // assuming that invalid means relative { QString localName = name; #if defined(Q_WS_WIN) if(localName.startsWith("/tmp/")) { // git on Cygwin will put files in /tmp // A workaround for the a native kdiff3 binary to find them... QString cygwinBin = getenv("CYGWIN_BIN"); if(!cygwinBin.isEmpty()) { localName = QString("%1\\..%2").arg(cygwinBin).arg(name); } } #endif if(!bExistsLocal && url.isLocalFile() && name.left(5).toLower() == "file:") { localName = url.path(); // I want the path without preceding "file:" } QFileInfo fi(localName); setFile(fi, nullptr); } else { createData(); d()->m_url = url; d()->m_name = d()->m_url.fileName(); d()->m_bLocal = false; FileAccessJobHandler jh(this); // A friend, which writes to the parameters of this class! jh.stat(2 /*all details*/, bWantToWrite); // returns bSuccess, ignored m_filePath = name; d()->m_bValidData = true; // After running stat() the variables are initialised // and valid even if the file doesn't exist and the stat // query failed. } } } void FileAccess::addPath(const QString& txt) { if(d() != nullptr && d()->m_url.isValid()) { QUrl url = d()->m_url.adjusted(QUrl::StripTrailingSlash); d()->m_url.setPath(url.path() + '/' + txt); setFile(d()->m_url.url()); // reinitialise } else { QString slash = (txt.isEmpty() || txt[0] == '/') ? "" : "/"; setFile(absoluteFilePath() + slash + txt); } } /* Filetype: S_IFMT 0170000 bitmask for the file type bitfields S_IFSOCK 0140000 socket S_IFLNK 0120000 symbolic link S_IFREG 0100000 regular file S_IFBLK 0060000 block device S_IFDIR 0040000 directory S_IFCHR 0020000 character device S_IFIFO 0010000 fifo S_ISUID 0004000 set UID bit S_ISGID 0002000 set GID bit (see below) S_ISVTX 0001000 sticky bit (see below) Access: S_IRWXU 00700 mask for file owner permissions S_IRUSR 00400 owner has read permission S_IWUSR 00200 owner has write permission S_IXUSR 00100 owner has execute permission S_IRWXG 00070 mask for group permissions S_IRGRP 00040 group has read permission S_IWGRP 00020 group has write permission S_IXGRP 00010 group has execute permission S_IRWXO 00007 mask for permissions for others (not in group) S_IROTH 00004 others have read permission S_IWOTH 00002 others have write permisson S_IXOTH 00001 others have execute permission */ //TODO: Change this to kf5/kde auto test #ifdef KREPLACEMENTS_H void FileAccess::setUdsEntry(const KIO::UDSEntry&) { } // not needed if KDE is not available #else void FileAccess::setUdsEntry(const KIO::UDSEntry& e) { long acc = 0; long fileType = 0; QVector fields = e.fields(); for(QVector::ConstIterator ei = fields.constBegin(); ei != fields.constEnd(); ++ei) { uint f = *ei; switch(f) { case KIO::UDSEntry::UDS_SIZE: m_size = e.numberValue(f); break; //case KIO::UDSEntry::UDS_USER : d()->m_user = e.stringValue(f); break; //case KIO::UDSEntry::UDS_GROUP : d()->m_group = e.stringValue(f); break; case KIO::UDSEntry::UDS_NAME: m_filePath = e.stringValue(f); break; // During listDir the relative path is given here. case KIO::UDSEntry::UDS_MODIFICATION_TIME: m_modificationTime.setTime_t(e.numberValue(f)); break; //case KIO::UDSEntry::UDS_ACCESS_TIME : d()->m_accessTime.setTime_t( e.numberValue(f) ); break; //case KIO::UDSEntry::UDS_CREATION_TIME : d()->m_creationTime.setTime_t( e.numberValue(f) ); break; case KIO::UDSEntry::UDS_LINK_DEST: d()->m_linkTarget = e.stringValue(f); break; case KIO::UDSEntry::UDS_ACCESS: { acc = e.numberValue(f); d()->m_bReadable = (acc & S_IRUSR) != 0; m_bWritable = (acc & S_IWUSR) != 0; d()->m_bExecutable = (acc & S_IXUSR) != 0; break; } case KIO::UDSEntry::UDS_FILE_TYPE: { fileType = e.numberValue(f); m_bDir = (fileType & S_IFMT) == S_IFDIR; m_bFile = (fileType & S_IFMT) == S_IFREG; m_bSymLink = (fileType & S_IFMT) == S_IFLNK; m_bExists = fileType != 0; //d()->m_fileType = fileType; break; } case KIO::UDSEntry::UDS_URL: // m_url = QUrlFix( e.stringValue(f) ); break; case KIO::UDSEntry::UDS_MIME_TYPE: break; case KIO::UDSEntry::UDS_GUESSED_MIME_TYPE: break; case KIO::UDSEntry::UDS_XML_PROPERTIES: break; default: break; } } m_bExists = acc != 0 || fileType != 0; d()->m_bLocal = false; d()->m_bValidData = true; m_bSymLink = !d()->m_linkTarget.isEmpty(); if(d()->m_name.isEmpty()) { int pos = m_filePath.lastIndexOf('/') + 1; d()->m_name = m_filePath.mid(pos); } m_bHidden = d()->m_name[0] == '.'; } #endif bool FileAccess::isValid() const { return d() == nullptr ? !m_filePath.isEmpty() : d()->m_bValidData; } bool FileAccess::isFile() const { if(parent() || d()) return m_bFile; else return QFileInfo(absoluteFilePath()).isFile(); } bool FileAccess::isDir() const { if(parent() || d()) return m_bDir; else return QFileInfo(absoluteFilePath()).isDir(); } bool FileAccess::isSymLink() const { return m_bSymLink; } bool FileAccess::exists() const { if(parent() || d()) return m_bExists; else return QFileInfo::exists(absoluteFilePath()); } qint64 FileAccess::size() const { if(parent() || d()) return m_size; else return QFileInfo(absoluteFilePath()).size(); } QUrl FileAccess::url() const { if(d() != nullptr) return d()->m_url; else { QUrl url = QUrl::fromLocalFile(m_filePath); if(url.isRelative()) { url.setPath(absoluteFilePath()); } return url; } } bool FileAccess::isLocal() const { return d() == nullptr || d()->m_bLocal; } bool FileAccess::isReadable() const { #if defined(Q_WS_WIN) // On some windows machines in a network this takes very long to find out and it's not so important anyway. return true; #else if(d() != nullptr) return d()->m_bReadable; else return QFileInfo(absoluteFilePath()).isReadable(); #endif } bool FileAccess::isWritable() const { if(parent() || d()) return m_bWritable; else return QFileInfo(absoluteFilePath()).isWritable(); } bool FileAccess::isExecutable() const { #if defined(Q_WS_WIN) // On some windows machines in a network this takes very long to find out and it's not so important anyway. return true; #else if(d() != nullptr) return d()->m_bExecutable; else return QFileInfo(absoluteFilePath()).isExecutable(); #endif } bool FileAccess::isHidden() const { if(parent() || d()) return m_bHidden; else return QFileInfo(absoluteFilePath()).isHidden(); } QString FileAccess::readLink() const { if(d() != nullptr) return d()->m_linkTarget; else return QString(); } QString FileAccess::absoluteFilePath() const { if(parent() != nullptr) return parent()->absoluteFilePath() + "/" + m_filePath; else { if(m_filePath.isEmpty()) return QString(); if(!isLocal()) return m_filePath; // return complete url QFileInfo fi(m_filePath); if(fi.isAbsolute()) return m_filePath; else return fi.absoluteFilePath(); // Probably never reached } } // Full abs path // Just the name-part of the path, without parent directories QString FileAccess::fileName() const { if(d() != nullptr) return d()->m_name; else if(parent()) return m_filePath; else return QFileInfo(m_filePath).fileName(); } void FileAccess::setSharedName(const QString& name) { if(name == m_filePath) m_filePath = name; // reduce memory because string is only used once. } QString FileAccess::filePath() const { if(parent() && parent()->parent()) return parent()->filePath() + "/" + m_filePath; else return m_filePath; // The path-string that was used during construction } FileAccess* FileAccess::parent() const { if(m_bUseData) return d()->m_pParent; else return m_pParent; } FileAccess::Data* FileAccess::d() { if(m_bUseData) return m_pData; else return nullptr; } const FileAccess::Data* FileAccess::d() const { if(m_bUseData) return m_pData; else return nullptr; } QString FileAccess::prettyAbsPath() const { return isLocal() ? absoluteFilePath() : d()->m_url.toDisplayString(); } /* QDateTime FileAccess::created() const { if ( d()!=0 ) { if ( isLocal() && d()->m_creationTime.isNull() ) const_cast(this)->d()->m_creationTime = QFileInfo( absoluteFilePath() ).created(); return ( d()->m_creationTime.isValid() ? d()->m_creationTime : lastModified() ); } else { QDateTime created = QFileInfo( absoluteFilePath() ).created(); return created.isValid() ? created : lastModified(); } } */ QDateTime FileAccess::lastModified() const { if(isLocal() && m_modificationTime.isNull()) const_cast(this)->m_modificationTime = QFileInfo(absoluteFilePath()).lastModified(); return m_modificationTime; } /* QDateTime FileAccess::lastRead() const { QDateTime accessTime = d()!=0 ? d()->m_accessTime : QFileInfo( absoluteFilePath() ).lastRead(); return ( accessTime.isValid() ? accessTime : lastModified() ); } */ static bool interruptableReadFile(QFile& f, void* pDestBuffer, unsigned long maxLength) { ProgressProxy pp; const unsigned long maxChunkSize = 100000; unsigned long i = 0; pp.setMaxNofSteps(maxLength / maxChunkSize + 1); while(i < maxLength) { unsigned long nextLength = min2(maxLength - i, maxChunkSize); unsigned long reallyRead = f.read((char*)pDestBuffer + i, nextLength); if(reallyRead != nextLength) { return false; } i += reallyRead; pp.setCurrent(double(i) / maxLength); if(pp.wasCancelled()) return false; } return true; } bool FileAccess::readFile(void* pDestBuffer, unsigned long maxLength) { if(d() != nullptr && !d()->m_localCopy.isEmpty()) { QFile f(d()->m_localCopy); if(f.open(QIODevice::ReadOnly)) return interruptableReadFile(f, pDestBuffer, maxLength); // maxLength == f.read( (char*)pDestBuffer, maxLength ); } else if(isLocal()) { QFile f(absoluteFilePath()); if(f.open(QIODevice::ReadOnly)) return interruptableReadFile(f, pDestBuffer, maxLength); //maxLength == f.read( (char*)pDestBuffer, maxLength ); } else { FileAccessJobHandler jh(this); return jh.get(pDestBuffer, maxLength); } return false; } bool FileAccess::writeFile(const void* pSrcBuffer, unsigned long length) { ProgressProxy pp; if(isLocal()) { QFile f(absoluteFilePath()); if(f.open(QIODevice::WriteOnly)) { const unsigned long maxChunkSize = 100000; pp.setMaxNofSteps(length / maxChunkSize + 1); unsigned long i = 0; while(i < length) { unsigned long nextLength = min2(length - i, maxChunkSize); unsigned long reallyWritten = f.write((char*)pSrcBuffer + i, nextLength); if(reallyWritten != nextLength) { return false; } i += reallyWritten; pp.step(); if(pp.wasCancelled()) return false; } f.close(); #ifndef _WIN32 if(isExecutable()) // value is true if the old file was executable { // Preserve attributes f.setPermissions(f.permissions() | QFile::ExeUser); //struct stat srcFileStatus; //int statResult = ::stat( filePath().toLocal8Bit().constData(), &srcFileStatus ); //if (statResult==0) //{ // ::chmod ( filePath().toLocal8Bit().constData(), srcFileStatus.st_mode | S_IXUSR ); //} } #endif return true; } } else { FileAccessJobHandler jh(this); return jh.put(pSrcBuffer, length, true /*overwrite*/); } return false; } bool FileAccess::copyFile(const QString& dest) { FileAccessJobHandler jh(this); return jh.copyFile(dest); // Handles local and remote copying. } bool FileAccess::rename(const QString& dest) { FileAccessJobHandler jh(this); return jh.rename(dest); } bool FileAccess::removeFile() { if(isLocal()) { return QDir().remove(absoluteFilePath()); } else { FileAccessJobHandler jh(this); return jh.removeFile(url()); } } bool FileAccess::removeFile(const QString& name) // static { return FileAccess(name).removeFile(); } bool FileAccess::listDir(t_DirectoryList* pDirList, bool bRecursive, bool bFindHidden, const QString& filePattern, const QString& fileAntiPattern, const QString& dirAntiPattern, bool bFollowDirLinks, bool bUseCvsIgnore) { FileAccessJobHandler jh(this); return jh.listDir(pDirList, bRecursive, bFindHidden, filePattern, fileAntiPattern, dirAntiPattern, bFollowDirLinks, bUseCvsIgnore); } QString FileAccess::tempFileName() { QTemporaryFile tmpFile; tmpFile.open(); //tmpFile.setAutoDelete( true ); // We only want the name. Delete the precreated file immediately. QString name = tmpFile.fileName() + ".2"; tmpFile.close(); return name; } bool FileAccess::removeTempFile(const QString& name) // static { if(name.endsWith(".2")) FileAccess(name.left(name.length() - 2)).removeFile(); return FileAccess(name).removeFile(); } bool FileAccess::makeDir(const QString& dirName) { FileAccessJobHandler fh(nullptr); return fh.mkDir(dirName); } bool FileAccess::removeDir(const QString& dirName) { FileAccessJobHandler fh(nullptr); return fh.rmDir(dirName); } -#if defined(_WIN32) || defined(Q_OS_OS2) +#if defined(_WIN32) bool FileAccess::symLink(const QString& /*linkTarget*/, const QString& /*linkLocation*/) { return false; } #else bool FileAccess::symLink(const QString& linkTarget, const QString& linkLocation) { return 0 == ::symlink(linkTarget.toLocal8Bit().constData(), linkLocation.toLocal8Bit().constData()); //FileAccessJobHandler fh(0); //return fh.symLink( linkTarget, linkLocation ); } #endif bool FileAccess::exists(const QString& name) { FileAccess fa(name); return fa.exists(); } // If the size couldn't be determined by stat() then the file is copied to a local temp file. qint64 FileAccess::sizeForReading() { if(!isLocal() && m_size == 0) { // Size couldn't be determined. Copy the file to a local temp place. QString localCopy = tempFileName(); bool bSuccess = copyFile(localCopy); if(bSuccess) { QFileInfo fi(localCopy); m_size = fi.size(); d()->m_localCopy = localCopy; return m_size; } else { return 0; } } else return size(); } QString FileAccess::getStatusText() { return d() == nullptr ? QString() : d()->m_statusText; } void FileAccess::setStatusText(const QString& s) { if(!s.isEmpty() || d() != nullptr) { createData(); d()->m_statusText = s; } } QString FileAccess::cleanPath(const QString& path) // static { QUrl url = QUrl::fromUserInput(path, QString(""), QUrl::AssumeLocalFile); if(url.isLocalFile() || !url.isValid()) { return QDir().cleanPath(path); } else { return path; } } bool FileAccess::createBackup(const QString& bakExtension) { if(exists()) { createData(); setFile(absoluteFilePath()); // make sure Data is initialized // First rename the existing file to the bak-file. If a bak-file file exists, delete that. QString bakName = absoluteFilePath() + bakExtension; FileAccess bakFile(bakName, true /*bWantToWrite*/); if(bakFile.exists()) { bool bSuccess = bakFile.removeFile(); if(!bSuccess) { setStatusText(i18n("While trying to make a backup, deleting an older backup failed. \nFilename: ") + bakName); return false; } } bool bSuccess = rename(bakName); if(!bSuccess) { setStatusText(i18n("While trying to make a backup, renaming failed. \nFilenames: ") + absoluteFilePath() + " -> " + bakName); return false; } } return true; } FileAccessJobHandler::FileAccessJobHandler(FileAccess* pFileAccess) { m_pFileAccess = pFileAccess; m_bSuccess = false; } bool FileAccessJobHandler::stat(int detail, bool bWantToWrite) { m_bSuccess = false; m_pFileAccess->setStatusText(QString()); KIO::StatJob* pStatJob = KIO::stat(m_pFileAccess->url(), bWantToWrite ? KIO::StatJob::DestinationSide : KIO::StatJob::SourceSide, detail, KIO::HideProgressInfo); connect(pStatJob, &KIO::StatJob::result, this, &FileAccessJobHandler::slotStatResult); ProgressProxy::enterEventLoop(pStatJob, i18n("Getting file status: %1", m_pFileAccess->prettyAbsPath())); return m_bSuccess; } void FileAccessJobHandler::slotStatResult(KJob* pJob) { if(pJob->error()) { //pJob->uiDelegate()->showErrorMessage(); m_pFileAccess->m_bExists = false; m_bSuccess = true; } else { m_bSuccess = true; m_pFileAccess->d()->m_bValidData = true; const KIO::UDSEntry e = static_cast(pJob)->statResult(); m_pFileAccess->setUdsEntry(e); } ProgressProxy::exitEventLoop(); } bool FileAccessJobHandler::get(void* pDestBuffer, long maxLength) { ProgressProxyExtender pp; // Implicitly used in slotPercent() if(maxLength > 0 && !pp.wasCancelled()) { KIO::TransferJob* pJob = KIO::get(m_pFileAccess->url(), KIO::NoReload); m_transferredBytes = 0; m_pTransferBuffer = (char*)pDestBuffer; m_maxLength = maxLength; m_bSuccess = false; m_pFileAccess->setStatusText(QString()); connect(pJob, &KIO::TransferJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); connect(pJob, &KIO::TransferJob::data, this, &FileAccessJobHandler::slotGetData); connect(pJob, SIGNAL(percent(KJob*, unsigned long)), &pp, SLOT(slotPercent(KJob*, unsigned long))); ProgressProxy::enterEventLoop(pJob, i18n("Reading file: %1", m_pFileAccess->prettyAbsPath())); return m_bSuccess; } else return true; } void FileAccessJobHandler::slotGetData(KJob* pJob, const QByteArray& newData) { if(pJob->error()) { pJob->uiDelegate()->showErrorMessage(); } else { qint64 length = min2(qint64(newData.size()), m_maxLength - m_transferredBytes); ::memcpy(m_pTransferBuffer + m_transferredBytes, newData.data(), newData.size()); m_transferredBytes += length; } } bool FileAccessJobHandler::put(const void* pSrcBuffer, long maxLength, bool bOverwrite, bool bResume, int permissions) { ProgressProxyExtender pp; // Implicitly used in slotPercent() if(maxLength > 0) { KIO::TransferJob* pJob = KIO::put(m_pFileAccess->url(), permissions, KIO::HideProgressInfo | (bOverwrite ? KIO::Overwrite : KIO::DefaultFlags) | (bResume ? KIO::Resume : KIO::DefaultFlags)); m_transferredBytes = 0; m_pTransferBuffer = (char*)pSrcBuffer; m_maxLength = maxLength; m_bSuccess = false; m_pFileAccess->setStatusText(QString()); connect(pJob, &KIO::TransferJob::result, this, &FileAccessJobHandler::slotPutJobResult); connect(pJob, &KIO::TransferJob::dataReq, this, &FileAccessJobHandler::slotPutData); connect(pJob, SIGNAL(percent(KJob*, unsigned long)), &pp, SLOT(slotPercent(KJob*, unsigned long))); ProgressProxy::enterEventLoop(pJob, i18n("Writing file: %1", m_pFileAccess->prettyAbsPath())); return m_bSuccess; } else return true; } void FileAccessJobHandler::slotPutData(KIO::Job* pJob, QByteArray& data) { if(pJob->error()) { pJob->uiDelegate()->showErrorMessage(); } else { qint64 maxChunkSize = 100000; qint64 length = min2(maxChunkSize, m_maxLength - m_transferredBytes); data.resize(length); if(data.size() == length) { if(length > 0) { ::memcpy(data.data(), m_pTransferBuffer + m_transferredBytes, data.size()); m_transferredBytes += length; } } else { KMessageBox::error(ProgressProxy::getDialog(), i18n("Out of memory")); data.resize(0); m_bSuccess = false; } } } void FileAccessJobHandler::slotPutJobResult(KJob* pJob) { if(pJob->error()) { pJob->uiDelegate()->showErrorMessage(); } else { m_bSuccess = (m_transferredBytes == m_maxLength); // Special success condition } ProgressProxy::exitEventLoop(); // Close the dialog, return from exec() } bool FileAccessJobHandler::mkDir(const QString& dirName) { QUrl dirURL = QUrl::fromUserInput(dirName, QString(""), QUrl::AssumeLocalFile); if(dirName.isEmpty()) return false; else if(dirURL.isLocalFile() || dirURL.isRelative()) { return QDir().mkdir(dirURL.path()); } else { m_bSuccess = false; KIO::SimpleJob* pJob = KIO::mkdir(dirURL); connect(pJob, &KIO::SimpleJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); ProgressProxy::enterEventLoop(pJob, i18n("Making directory: %1", dirName)); return m_bSuccess; } } bool FileAccessJobHandler::rmDir(const QString& dirName) { QUrl dirURL = QUrl::fromUserInput(dirName, QString(""), QUrl::AssumeLocalFile); if(dirName.isEmpty()) return false; else if(dirURL.isLocalFile()) { return QDir().rmdir(dirURL.path()); } else { m_bSuccess = false; KIO::SimpleJob* pJob = KIO::rmdir(dirURL); connect(pJob, &KIO::SimpleJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); ProgressProxy::enterEventLoop(pJob, i18n("Removing directory: %1", dirName)); return m_bSuccess; } } bool FileAccessJobHandler::removeFile(const QUrl& fileName) { if(fileName.isEmpty()) return false; else { m_bSuccess = false; KIO::SimpleJob* pJob = KIO::file_delete(fileName, KIO::HideProgressInfo); connect(pJob, &KIO::SimpleJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); ProgressProxy::enterEventLoop(pJob, i18n("Removing file: %1", fileName.toDisplayString())); return m_bSuccess; } } bool FileAccessJobHandler::symLink(const QUrl& linkTarget, const QUrl& linkLocation) { if(linkTarget.isEmpty() || linkLocation.isEmpty()) return false; else { m_bSuccess = false; KIO::CopyJob* pJob = KIO::link(linkTarget, linkLocation, KIO::HideProgressInfo); connect(pJob, &KIO::CopyJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); ProgressProxy::enterEventLoop(pJob, i18n("Creating symbolic link: %1 -> %2", linkLocation.toDisplayString(), linkTarget.toDisplayString())); return m_bSuccess; } } bool FileAccessJobHandler::rename(const QString& dest) { if(dest.isEmpty()) return false; QUrl kurl = QUrl::fromUserInput(dest, QString(""), QUrl::AssumeLocalFile); if(kurl.isRelative()) kurl = QUrl::fromUserInput(QDir().absoluteFilePath(dest), QString(""), QUrl::AssumeLocalFile); // assuming that invalid means relative if(m_pFileAccess->isLocal() && kurl.isLocalFile()) { return QDir().rename(m_pFileAccess->absoluteFilePath(), kurl.path()); } else { ProgressProxyExtender pp; int permissions = -1; m_bSuccess = false; KIO::FileCopyJob* pJob = KIO::file_move(m_pFileAccess->url(), kurl, permissions, KIO::HideProgressInfo); connect(pJob, &KIO::FileCopyJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); connect(pJob, SIGNAL(percent(KJob*, unsigned long)), &pp, SLOT(slotPercent(KJob*, unsigned long))); ProgressProxy::enterEventLoop(pJob, i18n("Renaming file: %1 -> %2", m_pFileAccess->prettyAbsPath(), dest)); return m_bSuccess; } } void FileAccessJobHandler::slotSimpleJobResult(KJob* pJob) { if(pJob->error()) { pJob->uiDelegate()->showErrorMessage(); } else { m_bSuccess = true; } ProgressProxy::exitEventLoop(); // Close the dialog, return from exec() } // Copy local or remote files. bool FileAccessJobHandler::copyFile(const QString& dest) { ProgressProxyExtender pp; QUrl destUrl = QUrl::fromUserInput(dest, QString(""), QUrl::AssumeLocalFile); m_pFileAccess->setStatusText(QString()); if(!m_pFileAccess->isLocal() || !destUrl.isLocalFile()) // if either url is nonlocal { int permissions = (m_pFileAccess->isExecutable() ? 0111 : 0) + (m_pFileAccess->isWritable() ? 0222 : 0) + (m_pFileAccess->isReadable() ? 0444 : 0); m_bSuccess = false; KIO::FileCopyJob* pJob = KIO::file_copy(m_pFileAccess->url(), destUrl, permissions, KIO::HideProgressInfo); connect(pJob, &KIO::FileCopyJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); connect(pJob, SIGNAL(percent(KJob*, unsigned long)), &pp, SLOT(slotPercent(KJob*, unsigned long))); ProgressProxy::enterEventLoop(pJob, i18n("Copying file: %1 -> %2", m_pFileAccess->prettyAbsPath(), dest)); return m_bSuccess; // Note that the KIO-slave preserves the original date, if this is supported. } // Both files are local: QString srcName = m_pFileAccess->absoluteFilePath(); QString destName = dest; QFile srcFile(srcName); QFile destFile(destName); bool bReadSuccess = srcFile.open(QIODevice::ReadOnly); if(bReadSuccess == false) { m_pFileAccess->setStatusText(i18n("Error during file copy operation: Opening file for reading failed. Filename: %1", srcName)); return false; } bool bWriteSuccess = destFile.open(QIODevice::WriteOnly); if(bWriteSuccess == false) { m_pFileAccess->setStatusText(i18n("Error during file copy operation: Opening file for writing failed. Filename: %1", destName)); return false; } std::vector buffer(100000); qint64 bufSize = buffer.size(); qint64 srcSize = srcFile.size(); while(srcSize > 0 && !pp.wasCancelled()) { qint64 readSize = srcFile.read(&buffer[0], min2(srcSize, bufSize)); if(readSize == -1 || readSize == 0) { m_pFileAccess->setStatusText(i18n("Error during file copy operation: Reading failed. Filename: %1", srcName)); return false; } srcSize -= readSize; while(readSize > 0) { qint64 writeSize = destFile.write(&buffer[0], readSize); if(writeSize == -1 || writeSize == 0) { m_pFileAccess->setStatusText(i18n("Error during file copy operation: Writing failed. Filename: %1", destName)); return false; } readSize -= writeSize; } destFile.flush(); pp.setCurrent((double)(srcFile.size() - srcSize) / srcFile.size(), false); } srcFile.close(); destFile.close(); // Update the times of the destFile #ifdef _WIN32 struct _stat srcFileStatus; int statResult = ::_stat(srcName.toLocal8Bit().constData(), &srcFileStatus); if(statResult == 0) { _utimbuf destTimes; destTimes.actime = srcFileStatus.st_atime; /* time of last access */ destTimes.modtime = srcFileStatus.st_mtime; /* time of last modification */ _utime(destName.toLocal8Bit().constData(), &destTimes); _chmod(destName.toLocal8Bit().constData(), srcFileStatus.st_mode); } #else struct stat srcFileStatus; int statResult = ::stat(srcName.toLocal8Bit().constData(), &srcFileStatus); if(statResult == 0) { utimbuf destTimes; destTimes.actime = srcFileStatus.st_atime; /* time of last access */ destTimes.modtime = srcFileStatus.st_mtime; /* time of last modification */ utime(destName.toLocal8Bit().constData(), &destTimes); chmod(destName.toLocal8Bit().constData(), srcFileStatus.st_mode); } #endif return true; } bool wildcardMultiMatch(const QString& wildcard, const QString& testString, bool bCaseSensitive) { static QHash s_patternMap; QStringList sl = wildcard.split(";"); for(QStringList::Iterator it = sl.begin(); it != sl.end(); ++it) { QHash::iterator patIt = s_patternMap.find(*it); if(patIt == s_patternMap.end()) { QRegExp pattern(*it, bCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive, QRegExp::Wildcard); patIt = s_patternMap.insert(*it, pattern); } if(patIt.value().exactMatch(testString)) return true; } return false; } // class CvsIgnoreList from Cervisia cvsdir.cpp // Copyright (C) 1999-2002 Bernd Gehrmann // with elements from class StringMatcher // Copyright (c) 2003 Andre Woebbeking // Modifications for KDiff3 by Joachim Eibl class CvsIgnoreList { public: CvsIgnoreList() {} void init(FileAccess& dir, bool bUseLocalCvsIgnore); bool matches(const QString& fileName, bool bCaseSensitive) const; private: void addEntriesFromString(const QString& str); void addEntriesFromFile(const QString& name); void addEntry(const QString& entry); QStringList m_exactPatterns; QStringList m_startPatterns; QStringList m_endPatterns; QStringList m_generalPatterns; }; void CvsIgnoreList::init(FileAccess& dir, bool bUseLocalCvsIgnore) { static const char* ignorestr = ". .. core RCSLOG tags TAGS RCS SCCS .make.state " ".nse_depinfo #* .#* cvslog.* ,* CVS CVS.adm .del-* *.a *.olb *.o *.obj " "*.so *.Z *~ *.old *.elc *.ln *.bak *.BAK *.orig *.rej *.exe _$* *$"; addEntriesFromString(QString::fromLatin1(ignorestr)); addEntriesFromFile(QDir::homePath() + "/.cvsignore"); addEntriesFromString(QString::fromLocal8Bit(::getenv("CVSIGNORE"))); if(bUseLocalCvsIgnore) { FileAccess file(dir); file.addPath(".cvsignore"); int size = file.exists() ? file.sizeForReading() : 0; if(size > 0) { char* buf = new char[size]; if(buf != nullptr) { file.readFile(buf, size); int pos1 = 0; for(int pos = 0; pos <= size; ++pos) { if(pos == size || buf[pos] == ' ' || buf[pos] == '\t' || buf[pos] == '\n' || buf[pos] == '\r') { if(pos > pos1) { addEntry(QString::fromLatin1(&buf[pos1], pos - pos1)); } ++pos1; } } delete[] buf; } } } } void CvsIgnoreList::addEntriesFromString(const QString& str) { int posLast(0); int pos; while((pos = str.indexOf(' ', posLast)) >= 0) { if(pos > posLast) addEntry(str.mid(posLast, pos - posLast)); posLast = pos + 1; } if(posLast < static_cast(str.length())) addEntry(str.mid(posLast)); } void CvsIgnoreList::addEntriesFromFile(const QString& name) { QFile file(name); if(file.open(QIODevice::ReadOnly)) { QTextStream stream(&file); while(!stream.atEnd()) { addEntriesFromString(stream.readLine()); } } } void CvsIgnoreList::addEntry(const QString& pattern) { if(pattern != QString("!")) { if(pattern.isEmpty()) return; // The general match is general but slow. // Special tests for '*' and '?' at the beginning or end of a pattern // allow fast checks. // Count number of '*' and '?' unsigned int nofMetaCharacters = 0; const QChar* pos; pos = pattern.unicode(); const QChar* posEnd; posEnd = pos + pattern.length(); while(pos < posEnd) { if(*pos == QChar('*') || *pos == QChar('?')) ++nofMetaCharacters; ++pos; } if(nofMetaCharacters == 0) { m_exactPatterns.append(pattern); } else if(nofMetaCharacters == 1) { if(pattern.at(0) == QChar('*')) { m_endPatterns.append(pattern.right(pattern.length() - 1)); } else if(pattern.at(pattern.length() - 1) == QChar('*')) { m_startPatterns.append(pattern.left(pattern.length() - 1)); } else { m_generalPatterns.append(pattern.toLocal8Bit()); } } else { m_generalPatterns.append(pattern.toLocal8Bit()); } } else { m_exactPatterns.clear(); m_startPatterns.clear(); m_endPatterns.clear(); m_generalPatterns.clear(); } } bool CvsIgnoreList::matches(const QString& text, bool bCaseSensitive) const { if(m_exactPatterns.indexOf(text) >= 0) { return true; } QStringList::ConstIterator it; QStringList::ConstIterator itEnd; for(it = m_startPatterns.begin(), itEnd = m_startPatterns.end(); it != itEnd; ++it) { if(text.startsWith(*it)) { return true; } } for(it = m_endPatterns.begin(), itEnd = m_endPatterns.end(); it != itEnd; ++it) { if(text.mid(text.length() - (*it).length()) == *it) //(text.endsWith(*it)) { return true; } } /* for (QValueList::const_iterator it(m_generalPatterns.begin()), itEnd(m_generalPatterns.end()); it != itEnd; ++it) { if (::fnmatch(*it, text.local8Bit(), FNM_PATHNAME) == 0) { return true; } } */ for(it = m_generalPatterns.begin(); it != m_generalPatterns.end(); ++it) { QRegExp pattern(*it, bCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive, QRegExp::Wildcard); if(pattern.exactMatch(text)) return true; } return false; } static bool cvsIgnoreExists(t_DirectoryList* pDirList) { t_DirectoryList::iterator i; for(i = pDirList->begin(); i != pDirList->end(); ++i) { if(i->fileName() == ".cvsignore") return true; } return false; } bool FileAccessJobHandler::listDir(t_DirectoryList* pDirList, bool bRecursive, bool bFindHidden, const QString& filePattern, const QString& fileAntiPattern, const QString& dirAntiPattern, bool bFollowDirLinks, bool bUseCvsIgnore) { ProgressProxyExtender pp; m_pDirList = pDirList; m_pDirList->clear(); m_bFindHidden = bFindHidden; m_bRecursive = bRecursive; m_bFollowDirLinks = bFollowDirLinks; // Only relevant if bRecursive==true. m_fileAntiPattern = fileAntiPattern; m_filePattern = filePattern; m_dirAntiPattern = dirAntiPattern; if(pp.wasCancelled()) return true; // Cancelled is not an error. pp.setInformation(i18n("Reading directory: ") + m_pFileAccess->absoluteFilePath(), 0, false); if(m_pFileAccess->isLocal()) { QString currentPath = QDir::currentPath(); m_bSuccess = QDir::setCurrent(m_pFileAccess->absoluteFilePath()); if(m_bSuccess) { #ifndef _WIN32 m_bSuccess = true; QDir dir("."); dir.setSorting(QDir::Name | QDir::DirsFirst); dir.setFilter(QDir::Files | QDir::Dirs | /* from KDE3 QDir::TypeMaskDirs | */ QDir::Hidden | QDir::System); QFileInfoList fiList = dir.entryInfoList(); if(fiList.isEmpty()) { // No Permission to read directory or other error. m_bSuccess = false; } else { foreach(const QFileInfo& fi, fiList) // for each file... { if(fi.fileName() == "." || fi.fileName() == "..") continue; FileAccess fa; fa.setFile(fi, m_pFileAccess); pDirList->push_back(fa); } } #else QString pattern = "*.*"; WIN32_FIND_DATA findData; Qt::HANDLE searchHandle = FindFirstFileW((const wchar_t*)pattern.utf16(), &findData); if(searchHandle != INVALID_HANDLE_VALUE) { QString absPath = m_pFileAccess->absoluteFilePath(); QString relPath = m_pFileAccess->filePath(); bool bFirst = true; while(!pp.wasCancelled()) { if(!bFirst) { if(!FindNextFileW(searchHandle, &findData)) break; } bFirst = false; FileAccess fa; fa.m_filePath = QString::fromUtf16((const ushort*)findData.cFileName); if(fa.m_filePath != "." && fa.m_filePath != "..") { fa.m_size = (qint64(findData.nFileSizeHigh) << 32) + findData.nFileSizeLow; FILETIME ft; SYSTEMTIME t; FileTimeToLocalFileTime(&findData.ftLastWriteTime, &ft); FileTimeToSystemTime(&ft, &t); fa.m_modificationTime = QDateTime(QDate(t.wYear, t.wMonth, t.wDay), QTime(t.wHour, t.wMinute, t.wSecond)); //FileTimeToLocalFileTime( &findData.ftLastAccessTime, &ft ); FileTimeToSystemTime(&ft,&t); //fa.m_accessTime = QDateTime( QDate(t.wYear, t.wMonth, t.wDay), QTime(t.wHour, t.wMinute, t.wSecond) ); //FileTimeToLocalFileTime( &findData.ftCreationTime, &ft ); FileTimeToSystemTime(&ft,&t); //fa.m_creationTime = QDateTime( QDate(t.wYear, t.wMonth, t.wDay), QTime(t.wHour, t.wMinute, t.wSecond) ); int a = findData.dwFileAttributes; fa.m_bWritable = (a & FILE_ATTRIBUTE_READONLY) == 0; fa.m_bDir = (a & FILE_ATTRIBUTE_DIRECTORY) != 0; fa.m_bFile = !fa.m_bDir; fa.m_bHidden = (a & FILE_ATTRIBUTE_HIDDEN) != 0; //fa.m_bExecutable = false; // Useless on windows fa.m_bExists = true; //fa.m_bReadable = true; //fa.m_bLocal = true; //fa.m_bValidData = true; fa.m_bSymLink = false; //fa.m_fileType = 0; //fa.m_filePath = fa.m_name; //fa.m_absoluteFilePath = absPath + "/" + fa.m_name; //fa.m_url.setPath( fa.m_absoluteFilePath ); if(fa.d()) fa.m_pData->m_pParent = m_pFileAccess; else fa.m_pParent = m_pFileAccess; pDirList->push_back(fa); } } FindClose(searchHandle); } else { QDir::setCurrent(currentPath); // restore current path return false; } #endif } QDir::setCurrent(currentPath); // restore current path } else { KIO::ListJob* pListJob = nullptr; pListJob = KIO::listDir(m_pFileAccess->url(), KIO::HideProgressInfo, true /*bFindHidden*/); m_bSuccess = false; if(pListJob != nullptr) { connect(pListJob, &KIO::ListJob::entries, this, &FileAccessJobHandler::slotListDirProcessNewEntries); connect(pListJob, &KIO::ListJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); connect(pListJob, &KIO::ListJob::infoMessage, &pp, &ProgressProxyExtender::slotListDirInfoMessage); // This line makes the transfer via fish unreliable.:-( //connect( pListJob, SIGNAL(percent(KJob*,unsigned long)), &pp, SLOT(slotPercent(KJob*, unsigned long))); ProgressProxy::enterEventLoop(pListJob, i18n("Listing directory: %1", m_pFileAccess->prettyAbsPath())); } } CvsIgnoreList cvsIgnoreList; if(bUseCvsIgnore) { cvsIgnoreList.init(*m_pFileAccess, cvsIgnoreExists(pDirList)); } -#if defined(_WIN32) || defined(Q_OS_OS2) +#if defined(_WIN32) bool bCaseSensitive = false; #else bool bCaseSensitive = true; #endif // Now remove all entries that should be ignored: t_DirectoryList::iterator i; for(i = pDirList->begin(); i != pDirList->end();) { t_DirectoryList::iterator i2 = i; ++i2; QString fn = i->fileName(); if((!bFindHidden && i->isHidden()) || (i->isFile() && (!wildcardMultiMatch(filePattern, fn, bCaseSensitive) || wildcardMultiMatch(fileAntiPattern, fn, bCaseSensitive))) || (i->isDir() && wildcardMultiMatch(dirAntiPattern, fn, bCaseSensitive)) || cvsIgnoreList.matches(fn, bCaseSensitive)) { // Remove it pDirList->erase(i); i = i2; } else { ++i; } } if(bRecursive) { t_DirectoryList subDirsList; t_DirectoryList::iterator i; for(i = m_pDirList->begin(); i != m_pDirList->end(); ++i) { if(i->isDir() && (!i->isSymLink() || m_bFollowDirLinks)) { t_DirectoryList dirList; i->listDir(&dirList, bRecursive, bFindHidden, filePattern, fileAntiPattern, dirAntiPattern, bFollowDirLinks, bUseCvsIgnore); t_DirectoryList::iterator j; for(j = dirList.begin(); j != dirList.end(); ++j) { if(j->parent() == nullptr) j->m_filePath = i->fileName() + "/" + j->m_filePath; } // append data onto the main list subDirsList.splice(subDirsList.end(), dirList); } } m_pDirList->splice(m_pDirList->end(), subDirsList); } return m_bSuccess; } void FileAccessJobHandler::slotListDirProcessNewEntries(KIO::Job*, const KIO::UDSEntryList& l) { //This function is called for non-local urls. Don't use QUrl::fromLocalFile here as it does not handle these. QUrl parentUrl = QUrl::fromUserInput(m_pFileAccess->absoluteFilePath(), QString(""), QUrl::AssumeLocalFile); KIO::UDSEntryList::ConstIterator i; for(i = l.begin(); i != l.end(); ++i) { const KIO::UDSEntry& e = *i; FileAccess fa; fa.createData(); fa.m_pData->m_pParent = m_pFileAccess; fa.setUdsEntry(e); if(fa.fileName() != "." && fa.fileName() != "..") { fa.d()->m_url = parentUrl; QUrl url = fa.d()->m_url.adjusted(QUrl::StripTrailingSlash); fa.d()->m_url.setPath(url.path() + "/" + fa.fileName()); //fa.d()->m_absoluteFilePath = fa.url().url(); m_pDirList->push_back(fa); } } } void ProgressProxyExtender::slotListDirInfoMessage(KJob*, const QString& msg) { setInformation(msg, 0); } void ProgressProxyExtender::slotPercent(KJob*, unsigned long percent) { setCurrent(percent); } //#include "fileaccess.moc" diff --git a/src/kdiff3.cpp b/src/kdiff3.cpp index 9285532..879d583 100644 --- a/src/kdiff3.cpp +++ b/src/kdiff3.cpp @@ -1,1099 +1,1099 @@ /*************************************************************************** kdiff3.cpp - description ------------------- begin : Don Jul 11 12:31:29 CEST 2002 copyright : (C) 2002-2007 by Joachim Eibl email : joachim.eibl at gmx.de ***************************************************************************/ /*************************************************************************** * * * 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 "mergeresultwindow.h" #include // include files for QT #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // include files for KDE #include #include #include #include //#include #include #include #include #include // application specific includes #include "directorymergewindow.h" #include "fileaccess.h" #include "guiutils.h" // namespace KDiff3 #include "kdiff3.h" #include "kdiff3_part.h" #include "kdiff3_shell.h" #include "optiondialog.h" #include "progress.h" #include "smalldialogs.h" #define ID_STATUS_MSG 1 #define MAIN_TOOLBAR_NAME "mainToolBar" KActionCollection* KDiff3App::actionCollection() { if(m_pKDiff3Shell == nullptr) return m_pKDiff3Part->actionCollection(); else return m_pKDiff3Shell->actionCollection(); } QStatusBar* KDiff3App::statusBar() { if(m_pKDiff3Shell == nullptr) return nullptr; else return m_pKDiff3Shell->statusBar(); } KToolBar* KDiff3App::toolBar(const char* toolBarId) { if(m_pKDiff3Shell == nullptr) return nullptr; else return m_pKDiff3Shell->toolBar(toolBarId); //return m_pKDiff3Shell->toolBar(); } bool KDiff3App::isPart() { return m_pKDiff3Shell == nullptr; } bool KDiff3App::isFileSaved() { return m_bFileSaved; } bool KDiff3App::isDirComparison() { return m_bDirCompare; } KDiff3App::KDiff3App(QWidget* pParent, const char* /*name*/, KDiff3Part* pKDiff3Part) : QSplitter(pParent) //previously KMainWindow { setObjectName("KDiff3App"); m_pKDiff3Part = pKDiff3Part; m_pKDiff3Shell = qobject_cast(pParent); setWindowTitle("KDiff3"); setOpaqueResize(false); // faster resizing setUpdatesEnabled(false); // set Disabled to same color as enabled to prevent flicker in DirectoryMergeWindow QPalette pal; pal.setBrush(QPalette::Base, pal.brush(QPalette::Active, QPalette::Base)); pal.setColor(QPalette::Text, pal.color(QPalette::Active, QPalette::Text)); setPalette(pal); m_pMainSplitter = nullptr; m_pDirectoryMergeSplitter = nullptr; m_pDirectoryMergeWindow = nullptr; m_pCornerWidget = nullptr; m_pMainWidget = nullptr; m_pDiffTextWindow1 = nullptr; m_pDiffTextWindow2 = nullptr; m_pDiffTextWindow3 = nullptr; m_pDiffTextWindowFrame1 = nullptr; m_pDiffTextWindowFrame2 = nullptr; m_pDiffTextWindowFrame3 = nullptr; m_pDiffWindowSplitter = nullptr; m_pOverview = nullptr; m_bTripleDiff = false; m_pMergeResultWindow = nullptr; m_pMergeWindowFrame = nullptr; m_bOutputModified = false; m_bFileSaved = false; m_bTimerBlock = false; m_pHScrollBar = nullptr; m_pDiffVScrollBar = nullptr; m_pMergeVScrollBar = nullptr; viewToolBar = nullptr; m_bRecalcWordWrapPosted = false; m_bFinishMainInit = false; m_pEventLoopForPrinting = nullptr; m_bLoadFiles = false; // Needed before any file operations via FileAccess happen. if(!g_pProgressDialog) { g_pProgressDialog = new ProgressDialog(this, statusBar()); g_pProgressDialog->setStayHidden(true); } // All default values must be set before calling readOptions(). m_pOptionDialog = new OptionDialog(m_pKDiff3Shell != nullptr, this); connect(m_pOptionDialog, &OptionDialog::applyDone, this, &KDiff3App::slotRefresh); // This is just a convenience variable to make code that accesses options more readable m_pOptions = &m_pOptionDialog->m_options; m_pOptionDialog->readOptions(KSharedConfig::openConfig()); // Option handling: Only when pParent==0 (no parent) int argCount = KDiff3Shell::getParser()->optionNames().count() + KDiff3Shell::getParser()->positionalArguments().count(); bool hasArgs = !isPart() && argCount > 0; if(hasArgs) { QString s; QString title; if(KDiff3Shell::getParser()->isSet("confighelp")) { s = m_pOptionDialog->calcOptionHelp(); title = i18n("Current Configuration:"); } else { s = m_pOptionDialog->parseOptions(KDiff3Shell::getParser()->values("cs")); title = i18n("Config Option Error:"); } if(!s.isEmpty()) { //KMessageBox::information(0, s,i18n("KDiff3-Usage")); QDialog* pDialog = new QDialog(this); pDialog->setAttribute(Qt::WA_DeleteOnClose); pDialog->setModal(true); pDialog->setWindowTitle(title); QVBoxLayout* pVBoxLayout = new QVBoxLayout(pDialog); QTextEdit* pTextEdit = new QTextEdit(pDialog); pTextEdit->setText(s); pTextEdit->setReadOnly(true); pTextEdit->setWordWrapMode(QTextOption::NoWrap); pVBoxLayout->addWidget(pTextEdit); pDialog->resize(600, 400); pDialog->exec(); -#if !defined(_WIN32) && !defined(Q_OS_OS2) +#if !defined(_WIN32) // A windows program has no console printf("%s\n", title.toLatin1().constData()); printf("%s\n", s.toLatin1().constData()); #endif exit(1); } } m_sd1.setOptions(m_pOptions); m_sd2.setOptions(m_pOptions); m_sd3.setOptions(m_pOptions); m_bAutoFlag = false; //disable --auto option git hard codes this unwanted flag. m_bAutoMode = m_bAutoFlag || m_pOptions->m_bAutoSaveAndQuitOnMergeWithoutConflicts; if(hasArgs) { m_outputFilename = KDiff3Shell::getParser()->value("output"); if(m_outputFilename.isEmpty()) m_outputFilename = KDiff3Shell::getParser()->value("out"); if(!m_outputFilename.isEmpty()) m_outputFilename = FileAccess(m_outputFilename, true).absoluteFilePath(); if(m_bAutoMode && m_outputFilename.isEmpty()) { if(m_bAutoFlag) { //KMessageBox::information(this, i18n("Option --auto used, but no output file specified.")); fprintf(stderr, "%s\n", (const char*)i18n("Option --auto used, but no output file specified.").toLatin1()); } m_bAutoMode = false; } if(m_outputFilename.isEmpty() && KDiff3Shell::getParser()->isSet("merge")) { m_outputFilename = "unnamed.txt"; m_bDefaultFilename = true; } else { m_bDefaultFilename = false; } g_bAutoSolve = !KDiff3Shell::getParser()->isSet("qall"); // Note that this is effective only once. QStringList args = KDiff3Shell::getParser()->positionalArguments(); m_sd1.setFilename(KDiff3Shell::getParser()->value("base")); if(m_sd1.isEmpty()) { if(args.count() > 0) m_sd1.setFilename(args[0]); // args->arg(0) if(args.count() > 1) m_sd2.setFilename(args[1]); if(args.count() > 2) m_sd3.setFilename(args[2]); } else { if(args.count() > 0) m_sd2.setFilename(args[0]); if(args.count() > 1) m_sd3.setFilename(args[1]); } //never properly defined and redundant QStringList aliasList; //KDiff3Shell::getParser()->values( "fname" ); QStringList::Iterator ali = aliasList.begin(); QString an1 = KDiff3Shell::getParser()->value("L1"); if(!an1.isEmpty()) { m_sd1.setAliasName(an1); } else if(ali != aliasList.end()) { m_sd1.setAliasName(*ali); ++ali; } QString an2 = KDiff3Shell::getParser()->value("L2"); if(!an2.isEmpty()) { m_sd2.setAliasName(an2); } else if(ali != aliasList.end()) { m_sd2.setAliasName(*ali); ++ali; } QString an3 = KDiff3Shell::getParser()->value("L3"); if(!an3.isEmpty()) { m_sd3.setAliasName(an3); } else if(ali != aliasList.end()) { m_sd3.setAliasName(*ali); ++ali; } } else { m_bDefaultFilename = false; g_bAutoSolve = false; } g_pProgressDialog->setStayHidden(m_bAutoMode); /////////////////////////////////////////////////////////////////// // call inits to invoke all other construction parts initActions(actionCollection()); initStatusBar(); m_pFindDialog = new FindDialog(this); connect(m_pFindDialog, &FindDialog::findNext, this, &KDiff3App::slotEditFindNext); autoAdvance->setChecked(m_pOptions->m_bAutoAdvance); showWhiteSpaceCharacters->setChecked(m_pOptions->m_bShowWhiteSpaceCharacters); showWhiteSpace->setChecked(m_pOptions->m_bShowWhiteSpace); showWhiteSpaceCharacters->setEnabled(m_pOptions->m_bShowWhiteSpace); showLineNumbers->setChecked(m_pOptions->m_bShowLineNumbers); wordWrap->setChecked(m_pOptions->m_bWordWrap); if(!isPart()) { // TODO // viewToolBar->setChecked( m_pOptions->m_bShowToolBar ); // slotViewToolBar(); viewStatusBar->setChecked(m_pOptions->m_bShowStatusBar); slotViewStatusBar(); /* vrusu - commented out as new KToolBar does not hase setParPos TODO figure out how to restore bar position if( toolBar(MAIN_TOOLBAR_NAME)!=0 ) toolBar(MAIN_TOOLBAR_NAME)->setBarPos( (KToolBar::BarPosition) m_pOptions->m_toolBarPos );*/ /* QSize size = m_pOptions->m_geometry; QPoint pos = m_pOptions->m_position; if(!size.isEmpty()) { m_pKDiff3Shell->resize( size ); QRect visibleRect = QRect( pos, size ) & QApplication::desktop()->rect(); if ( visibleRect.width()>100 && visibleRect.height()>100 ) m_pKDiff3Shell->move( pos ); }*/ } slotRefresh(); m_pMainSplitter = this; //new QSplitter(this); m_pMainSplitter->setOrientation(Qt::Vertical); // setCentralWidget( m_pMainSplitter ); m_pDirectoryMergeSplitter = new QSplitter(m_pMainSplitter); m_pDirectoryMergeSplitter->setObjectName("DirectoryMergeSplitter"); m_pMainSplitter->addWidget(m_pDirectoryMergeSplitter); m_pDirectoryMergeSplitter->setOrientation(Qt::Horizontal); m_pDirectoryMergeWindow = new DirectoryMergeWindow(m_pDirectoryMergeSplitter, m_pOptions, KIconLoader::global()); m_pDirectoryMergeSplitter->addWidget(m_pDirectoryMergeWindow); m_pDirectoryMergeInfo = new DirectoryMergeInfo(m_pDirectoryMergeSplitter); m_pDirectoryMergeWindow->setDirectoryMergeInfo(m_pDirectoryMergeInfo); m_pDirectoryMergeSplitter->addWidget(m_pDirectoryMergeInfo); connect(m_pDirectoryMergeWindow, &DirectoryMergeWindow::startDiffMerge, this, &KDiff3App::slotFileOpen2); connect(m_pDirectoryMergeWindow->selectionModel(), &QItemSelectionModel::selectionChanged, this, &KDiff3App::slotUpdateAvailabilities); connect(m_pDirectoryMergeWindow->selectionModel(), &QItemSelectionModel::currentChanged, this, &KDiff3App::slotUpdateAvailabilities); connect(m_pDirectoryMergeWindow, &DirectoryMergeWindow::checkIfCanContinue, this, &KDiff3App::slotCheckIfCanContinue); connect(m_pDirectoryMergeWindow, SIGNAL(updateAvailabilities()), this, SLOT(slotUpdateAvailabilities())); connect(m_pDirectoryMergeWindow, &DirectoryMergeWindow::statusBarMessage, this, &KDiff3App::slotStatusMsg); m_pDirectoryMergeWindow->initDirectoryMergeActions(this, actionCollection()); delete KDiff3Shell::getParser(); //if( args != 0 ) args->clear(); // Free up some memory. if(m_pKDiff3Shell == nullptr) { completeInit(); } } void KDiff3App::completeInit(const QString& fn1, const QString& fn2, const QString& fn3) { if(m_pKDiff3Shell != nullptr) { QSize size = m_pOptions->m_geometry; QPoint pos = m_pOptions->m_position; if(!size.isEmpty()) { m_pKDiff3Shell->resize(size); QRect visibleRect = QRect(pos, size) & QApplication::desktop()->rect(); if(visibleRect.width() > 100 && visibleRect.height() > 100) m_pKDiff3Shell->move(pos); if(!m_bAutoMode) { if(m_pOptions->m_bMaximised) m_pKDiff3Shell->showMaximized(); else m_pKDiff3Shell->show(); } } } if(!fn1.isEmpty()) { m_sd1.setFilename(fn1); } if(!fn2.isEmpty()) { m_sd2.setFilename(fn2); } if(!fn3.isEmpty()) { m_sd3.setFilename(fn3); } bool bSuccess = improveFilenames(false); if(m_bAutoFlag && m_bAutoMode && m_bDirCompare) { fprintf(stderr, "%s\n", (const char*)i18n("Option --auto ignored for directory comparison.").toLatin1()); m_bAutoMode = false; } if(!m_bDirCompare) { m_pDirectoryMergeSplitter->hide(); mainInit(); if(m_bAutoMode) { SourceData* pSD = nullptr; if(m_sd3.isEmpty()) { if(m_totalDiffStatus.bBinaryAEqB) { pSD = &m_sd1; } } else { if(m_totalDiffStatus.bBinaryBEqC) { pSD = &m_sd3; // B==C (assume A is old) } else if(m_totalDiffStatus.bBinaryAEqB) { pSD = &m_sd3; // assuming C has changed } else if(m_totalDiffStatus.bBinaryAEqC) { pSD = &m_sd2; // assuming B has changed } } if(pSD != nullptr) { // Save this file directly, not via the merge result window. bool bSuccess = false; FileAccess fa(m_outputFilename); if(m_pOptions->m_bDmCreateBakFiles && fa.exists()) { QString newName = m_outputFilename + ".orig"; if(FileAccess::exists(newName)) FileAccess::removeFile(newName); if(!FileAccess::exists(newName)) fa.rename(newName); } bSuccess = pSD->saveNormalDataAs(m_outputFilename); if(bSuccess) ::exit(0); else KMessageBox::error(this, i18n("Saving failed.")); } else if(m_pMergeResultWindow->getNrOfUnsolvedConflicts() == 0) { bool bSuccess = m_pMergeResultWindow->saveDocument(m_pMergeResultWindowTitle->getFileName(), m_pMergeResultWindowTitle->getEncoding(), m_pMergeResultWindowTitle->getLineEndStyle()); if(bSuccess) ::exit(0); } } } m_bAutoMode = false; if(m_pKDiff3Shell) { if(m_pOptions->m_bMaximised) m_pKDiff3Shell->showMaximized(); else m_pKDiff3Shell->show(); } g_pProgressDialog->setStayHidden(false); if(statusBar() != nullptr) statusBar()->setSizeGripEnabled(true); slotClipboardChanged(); // For initialisation. slotUpdateAvailabilities(); if(!m_bDirCompare && m_pKDiff3Shell != nullptr) { bool bFileOpenError = false; if((!m_sd1.isEmpty() && !m_sd1.hasData()) || (!m_sd2.isEmpty() && !m_sd2.hasData()) || (!m_sd3.isEmpty() && !m_sd3.hasData())) { QString text(i18n("Opening of these files failed:")); text += "\n\n"; if(!m_sd1.isEmpty() && !m_sd1.hasData()) text += " - " + m_sd1.getAliasName() + "\n"; if(!m_sd2.isEmpty() && !m_sd2.hasData()) text += " - " + m_sd2.getAliasName() + "\n"; if(!m_sd3.isEmpty() && !m_sd3.hasData()) text += " - " + m_sd3.getAliasName() + "\n"; KMessageBox::sorry(this, text, i18n("File Open Error")); bFileOpenError = true; } if(m_sd1.isEmpty() || m_sd2.isEmpty() || bFileOpenError) slotFileOpen(); } else if(!bSuccess) // Directory open failed { slotFileOpen(); } } KDiff3App::~KDiff3App() { } /** * Helper function used to create actions into the ac collection */ void KDiff3App::initActions(KActionCollection* ac) { if(ac == nullptr){ KMessageBox::error(nullptr, "actionCollection==0"); exit(-1);//we cann't recover from this. } fileOpen = KStandardAction::open(this, SLOT(slotFileOpen()), ac); fileOpen->setStatusTip(i18n("Opens documents for comparison...")); fileReload = KDiff3::createAction(i18n("Reload"), QKeySequence(QKeySequence::Refresh), this, SLOT(slotReload()), ac, "file_reload"); fileSave = KStandardAction::save(this, SLOT(slotFileSave()), ac); fileSave->setStatusTip(i18n("Saves the merge result. All conflicts must be solved!")); fileSaveAs = KStandardAction::saveAs(this, SLOT(slotFileSaveAs()), ac); fileSaveAs->setStatusTip(i18n("Saves the current document as...")); #ifndef QT_NO_PRINTER filePrint = KStandardAction::print(this, SLOT(slotFilePrint()), ac); filePrint->setStatusTip(i18n("Print the differences")); #endif fileQuit = KStandardAction::quit(this, SLOT(slotFileQuit()), ac); fileQuit->setStatusTip(i18n("Quits the application")); editCut = KStandardAction::cut(this, SLOT(slotEditCut()), ac); editCut->setStatusTip(i18n("Cuts the selected section and puts it to the clipboard")); editCopy = KStandardAction::copy(this, SLOT(slotEditCopy()), ac); editCopy->setStatusTip(i18n("Copies the selected section to the clipboard")); editPaste = KStandardAction::paste(this, SLOT(slotEditPaste()), ac); editPaste->setStatusTip(i18n("Pastes the clipboard contents to current position")); editSelectAll = KStandardAction::selectAll(this, SLOT(slotEditSelectAll()), ac); editSelectAll->setStatusTip(i18n("Select everything in current window")); editFind = KStandardAction::find(this, SLOT(slotEditFind()), ac); editFind->setStatusTip(i18n("Search for a string")); editFindNext = KStandardAction::findNext(this, SLOT(slotEditFindNext()), ac); editFindNext->setStatusTip(i18n("Search again for the string")); /* FIXME figure out how to implement this action viewToolBar = KStandardAction::showToolbar(this, SLOT(slotViewToolBar()), ac); viewToolBar->setStatusTip(i18n("Enables/disables the toolbar")); */ viewStatusBar = KStandardAction::showStatusbar(this, SLOT(slotViewStatusBar()), ac); viewStatusBar->setStatusTip(i18n("Enables/disables the statusbar")); KStandardAction::keyBindings(this, SLOT(slotConfigureKeys()), ac); QAction* pAction = KStandardAction::preferences(this, SLOT(slotConfigure()), ac); if(isPart()) pAction->setText(i18n("Configure KDiff3...")); #include "xpm/autoadvance.xpm" #include "xpm/currentpos.xpm" #include "xpm/down1arrow.xpm" #include "xpm/down2arrow.xpm" #include "xpm/downend.xpm" #include "xpm/iconA.xpm" #include "xpm/iconB.xpm" #include "xpm/iconC.xpm" #include "xpm/nextunsolved.xpm" #include "xpm/prevunsolved.xpm" #include "xpm/showlinenumbers.xpm" #include "xpm/showwhitespace.xpm" #include "xpm/showwhitespacechars.xpm" #include "xpm/up1arrow.xpm" #include "xpm/up2arrow.xpm" #include "xpm/upend.xpm" //#include "reload.xpm" goCurrent = KDiff3::createAction(i18n("Go to Current Delta"), QIcon(QPixmap(currentpos)), i18n("Current\nDelta"), QKeySequence(Qt::CTRL + Qt::Key_Space), this, SLOT(slotGoCurrent()), ac, "go_current"); goTop = KDiff3::createAction(i18n("Go to First Delta"), QIcon(QPixmap(upend)), i18n("First\nDelta"), this, SLOT(slotGoTop()), ac, "go_top"); goBottom = KDiff3::createAction(i18n("Go to Last Delta"), QIcon(QPixmap(downend)), i18n("Last\nDelta"), this, SLOT(slotGoBottom()), ac, "go_bottom"); QString omitsWhitespace = ".\n" + i18n("(Skips white space differences when \"Show White Space\" is disabled.)"); QString includeWhitespace = ".\n" + i18n("(Does not skip white space differences even when \"Show White Space\" is disabled.)"); goPrevDelta = KDiff3::createAction(i18n("Go to Previous Delta"), QIcon(QPixmap(up1arrow)), i18n("Prev\nDelta"), QKeySequence(Qt::CTRL + Qt::Key_Up), this, SLOT(slotGoPrevDelta()), ac, "go_prev_delta"); goPrevDelta->setToolTip(goPrevDelta->text() + omitsWhitespace); goNextDelta = KDiff3::createAction(i18n("Go to Next Delta"), QIcon(QPixmap(down1arrow)), i18n("Next\nDelta"), QKeySequence(Qt::CTRL + Qt::Key_Down), this, SLOT(slotGoNextDelta()), ac, "go_next_delta"); goNextDelta->setToolTip(goNextDelta->text() + omitsWhitespace); goPrevConflict = KDiff3::createAction(i18n("Go to Previous Conflict"), QIcon(QPixmap(up2arrow)), i18n("Prev\nConflict"), QKeySequence(Qt::CTRL + Qt::Key_PageUp), this, SLOT(slotGoPrevConflict()), ac, "go_prev_conflict"); goPrevConflict->setToolTip(goPrevConflict->text() + omitsWhitespace); goNextConflict = KDiff3::createAction(i18n("Go to Next Conflict"), QIcon(QPixmap(down2arrow)), i18n("Next\nConflict"), QKeySequence(Qt::CTRL + Qt::Key_PageDown), this, SLOT(slotGoNextConflict()), ac, "go_next_conflict"); goNextConflict->setToolTip(goNextConflict->text() + omitsWhitespace); goPrevUnsolvedConflict = KDiff3::createAction(i18n("Go to Previous Unsolved Conflict"), QIcon(QPixmap(prevunsolved)), i18n("Prev\nUnsolved"), this, SLOT(slotGoPrevUnsolvedConflict()), ac, "go_prev_unsolved_conflict"); goPrevUnsolvedConflict->setToolTip(goPrevUnsolvedConflict->text() + includeWhitespace); goNextUnsolvedConflict = KDiff3::createAction(i18n("Go to Next Unsolved Conflict"), QIcon(QPixmap(nextunsolved)), i18n("Next\nUnsolved"), this, SLOT(slotGoNextUnsolvedConflict()), ac, "go_next_unsolved_conflict"); goNextUnsolvedConflict->setToolTip(goNextUnsolvedConflict->text() + includeWhitespace); chooseA = KDiff3::createAction(i18n("Select Line(s) From A"), QIcon(QPixmap(iconA)), i18n("Choose\nA"), QKeySequence(Qt::CTRL + Qt::Key_1), this, SLOT(slotChooseA()), ac, "merge_choose_a"); chooseB = KDiff3::createAction(i18n("Select Line(s) From B"), QIcon(QPixmap(iconB)), i18n("Choose\nB"), QKeySequence(Qt::CTRL + Qt::Key_2), this, SLOT(slotChooseB()), ac, "merge_choose_b"); chooseC = KDiff3::createAction(i18n("Select Line(s) From C"), QIcon(QPixmap(iconC)), i18n("Choose\nC"), QKeySequence(Qt::CTRL + Qt::Key_3), this, SLOT(slotChooseC()), ac, "merge_choose_c"); autoAdvance = KDiff3::createAction(i18n("Automatically Go to Next Unsolved Conflict After Source Selection"), QIcon(QPixmap(autoadvance)), i18n("Auto\nNext"), this, SLOT(slotAutoAdvanceToggled()), ac, "merge_autoadvance"); showWhiteSpaceCharacters = KDiff3::createAction(i18n("Show Space && Tabulator Characters"), QIcon(QPixmap(showwhitespacechars)), i18n("White\nCharacters"), this, SLOT(slotShowWhiteSpaceToggled()), ac, "diff_show_whitespace_characters"); showWhiteSpace = KDiff3::createAction(i18n("Show White Space"), QIcon(QPixmap(showwhitespace)), i18n("White\nDeltas"), this, SLOT(slotShowWhiteSpaceToggled()), ac, "diff_show_whitespace"); showLineNumbers = KDiff3::createAction(i18n("Show Line Numbers"), QIcon(QPixmap(showlinenumbers)), i18n("Line\nNumbers"), this, SLOT(slotShowLineNumbersToggled()), ac, "diff_showlinenumbers"); chooseAEverywhere = KDiff3::createAction(i18n("Choose A Everywhere"), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_1), this, SLOT(slotChooseAEverywhere()), ac, "merge_choose_a_everywhere"); chooseBEverywhere = KDiff3::createAction(i18n("Choose B Everywhere"), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_2), this, SLOT(slotChooseBEverywhere()), ac, "merge_choose_b_everywhere"); chooseCEverywhere = KDiff3::createAction(i18n("Choose C Everywhere"), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_3), this, SLOT(slotChooseCEverywhere()), ac, "merge_choose_c_everywhere"); chooseAForUnsolvedConflicts = KDiff3::createAction(i18n("Choose A for All Unsolved Conflicts"), this, SLOT(slotChooseAForUnsolvedConflicts()), ac, "merge_choose_a_for_unsolved_conflicts"); chooseBForUnsolvedConflicts = KDiff3::createAction(i18n("Choose B for All Unsolved Conflicts"), this, SLOT(slotChooseBForUnsolvedConflicts()), ac, "merge_choose_b_for_unsolved_conflicts"); chooseCForUnsolvedConflicts = KDiff3::createAction(i18n("Choose C for All Unsolved Conflicts"), this, SLOT(slotChooseCForUnsolvedConflicts()), ac, "merge_choose_c_for_unsolved_conflicts"); chooseAForUnsolvedWhiteSpaceConflicts = KDiff3::createAction(i18n("Choose A for All Unsolved Whitespace Conflicts"), this, SLOT(slotChooseAForUnsolvedWhiteSpaceConflicts()), ac, "merge_choose_a_for_unsolved_whitespace_conflicts"); chooseBForUnsolvedWhiteSpaceConflicts = KDiff3::createAction(i18n("Choose B for All Unsolved Whitespace Conflicts"), this, SLOT(slotChooseBForUnsolvedWhiteSpaceConflicts()), ac, "merge_choose_b_for_unsolved_whitespace_conflicts"); chooseCForUnsolvedWhiteSpaceConflicts = KDiff3::createAction(i18n("Choose C for All Unsolved Whitespace Conflicts"), this, SLOT(slotChooseCForUnsolvedWhiteSpaceConflicts()), ac, "merge_choose_c_for_unsolved_whitespace_conflicts"); autoSolve = KDiff3::createAction(i18n("Automatically Solve Simple Conflicts"), this, SLOT(slotAutoSolve()), ac, "merge_autosolve"); unsolve = KDiff3::createAction(i18n("Set Deltas to Conflicts"), this, SLOT(slotUnsolve()), ac, "merge_autounsolve"); mergeRegExp = KDiff3::createAction(i18n("Run Regular Expression Auto Merge"), this, SLOT(slotRegExpAutoMerge()), ac, "merge_regexp_automerge"); mergeHistory = KDiff3::createAction(i18n("Automatically Solve History Conflicts"), this, SLOT(slotMergeHistory()), ac, "merge_versioncontrol_history"); splitDiff = KDiff3::createAction(i18n("Split Diff At Selection"), this, SLOT(slotSplitDiff()), ac, "merge_splitdiff"); joinDiffs = KDiff3::createAction(i18n("Join Selected Diffs"), this, SLOT(slotJoinDiffs()), ac, "merge_joindiffs"); showWindowA = KDiff3::createAction(i18n("Show Window A"), this, SLOT(slotShowWindowAToggled()), ac, "win_show_a"); showWindowB = KDiff3::createAction(i18n("Show Window B"), this, SLOT(slotShowWindowBToggled()), ac, "win_show_b"); showWindowC = KDiff3::createAction(i18n("Show Window C"), this, SLOT(slotShowWindowCToggled()), ac, "win_show_c"); winFocusNext = KDiff3::createAction(i18n("Focus Next Window"), QKeySequence(Qt::ALT + Qt::Key_Right), this, SLOT(slotWinFocusNext()), ac, "win_focus_next"); overviewModeNormal = KDiff3::createAction(i18n("Normal Overview"), this, SLOT(slotOverviewNormal()), ac, "diff_overview_normal"); overviewModeAB = KDiff3::createAction(i18n("A vs. B Overview"), this, SLOT(slotOverviewAB()), ac, "diff_overview_ab"); overviewModeAC = KDiff3::createAction(i18n("A vs. C Overview"), this, SLOT(slotOverviewAC()), ac, "diff_overview_ac"); overviewModeBC = KDiff3::createAction(i18n("B vs. C Overview"), this, SLOT(slotOverviewBC()), ac, "diff_overview_bc"); wordWrap = KDiff3::createAction(i18n("Word Wrap Diff Windows"), this, SLOT(slotWordWrapToggled()), ac, "diff_wordwrap"); addManualDiffHelp = KDiff3::createAction(i18n("Add Manual Diff Alignment"), QKeySequence(Qt::CTRL + Qt::Key_Y), this, SLOT(slotAddManualDiffHelp()), ac, "diff_add_manual_diff_help"); clearManualDiffHelpList = KDiff3::createAction(i18n("Clear All Manual Diff Alignments"), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Y), this, SLOT(slotClearManualDiffHelpList()), ac, "diff_clear_manual_diff_help_list"); -#if defined(_WIN32) || defined(Q_OS_OS2) +#if defined(_WIN32)//TODO:Huh?! KDiff3::createAction(i18n("Focus Next Window"), QKeySequence(Qt::CTRL + Qt::Key_Tab), this, SLOT(slotWinFocusNext()), ac, "win_focus_next"); #endif winFocusPrev = KDiff3::createAction(i18n("Focus Prev Window"), QKeySequence(Qt::ALT + Qt::Key_Left), this, SLOT(slotWinFocusPrev()), ac, "win_focus_prev"); winToggleSplitOrientation = KDiff3::createAction(i18n("Toggle Split Orientation"), this, SLOT(slotWinToggleSplitterOrientation()), ac, "win_toggle_split_orientation"); dirShowBoth = KDiff3::createAction(i18n("Dir && Text Split Screen View"), this, SLOT(slotDirShowBoth()), ac, "win_dir_show_both"); dirShowBoth->setChecked(true); dirViewToggle = KDiff3::createAction(i18n("Toggle Between Dir && Text View"), this, SLOT(slotDirViewToggle()), ac, "win_dir_view_toggle"); m_pMergeEditorPopupMenu = new QMenu(this); /* chooseA->plug( m_pMergeEditorPopupMenu ); chooseB->plug( m_pMergeEditorPopupMenu ); chooseC->plug( m_pMergeEditorPopupMenu );*/ m_pMergeEditorPopupMenu->addAction(chooseA); m_pMergeEditorPopupMenu->addAction(chooseB); m_pMergeEditorPopupMenu->addAction(chooseC); } void KDiff3App::showPopupMenu(const QPoint& point) { m_pMergeEditorPopupMenu->popup(point); } void KDiff3App::initStatusBar() { /////////////////////////////////////////////////////////////////// // STATUSBAR if(statusBar() != nullptr) statusBar()->showMessage(i18n("Ready.")); } void KDiff3App::saveOptions(KSharedConfigPtr config) { if(!m_bAutoMode) { if(!isPart()) { m_pOptions->m_bMaximised = m_pKDiff3Shell->isMaximized(); if(!m_pKDiff3Shell->isMaximized() && m_pKDiff3Shell->isVisible()) { m_pOptions->m_geometry = m_pKDiff3Shell->size(); m_pOptions->m_position = m_pKDiff3Shell->pos(); } /* TODO change this option as now KToolbar uses QToolbar positioning style if ( toolBar(MAIN_TOOLBAR_NAME)!=0 ) m_pOptionDialog->m_toolBarPos = (int) toolBar(MAIN_TOOLBAR_NAME)->allowedAreas();*/ } m_pOptionDialog->saveOptions(config); } } bool KDiff3App::queryClose() { saveOptions(KSharedConfig::openConfig()); if(m_bOutputModified) { int result = KMessageBox::warningYesNoCancel(this, i18n("The merge result hasn't been saved."), i18n("Warning"), KGuiItem(i18n("Save && Quit")), KGuiItem(i18n("Quit Without Saving"))); if(result == KMessageBox::Cancel) return false; else if(result == KMessageBox::Yes) { slotFileSave(); if(m_bOutputModified) { KMessageBox::sorry(this, i18n("Saving the merge result failed."), i18n("Warning")); return false; } } } m_bOutputModified = false; if(m_pDirectoryMergeWindow->isDirectoryMergeInProgress()) { int result = KMessageBox::warningYesNo(this, i18n("You are currently doing a directory merge. Are you sure, you want to abort?"), i18n("Warning"), KStandardGuiItem::quit(), KStandardGuiItem::cont() /* i18n("Continue Merging") */); if(result != KMessageBox::Yes) return false; } return true; } ///////////////////////////////////////////////////////////////////// // SLOT IMPLEMENTATION ///////////////////////////////////////////////////////////////////// void KDiff3App::slotFileSave() { if(m_bDefaultFilename) { slotFileSaveAs(); } else { slotStatusMsg(i18n("Saving file...")); bool bSuccess = m_pMergeResultWindow->saveDocument(m_outputFilename, m_pMergeResultWindowTitle->getEncoding(), m_pMergeResultWindowTitle->getLineEndStyle()); if(bSuccess) { m_bFileSaved = true; m_bOutputModified = false; if(m_bDirCompare) m_pDirectoryMergeWindow->mergeResultSaved(m_outputFilename); } slotStatusMsg(i18n("Ready.")); } } void KDiff3App::slotFileSaveAs() { slotStatusMsg(i18n("Saving file with a new filename...")); QString s = QFileDialog::getSaveFileUrl(this, i18n("Save As..."), QUrl::fromLocalFile(QDir::currentPath()), nullptr).url(QUrl::PreferLocalFile); if(!s.isEmpty()) { m_outputFilename = s; m_pMergeResultWindowTitle->setFileName(m_outputFilename); bool bSuccess = m_pMergeResultWindow->saveDocument(m_outputFilename, m_pMergeResultWindowTitle->getEncoding(), m_pMergeResultWindowTitle->getLineEndStyle()); if(bSuccess) { m_bOutputModified = false; if(m_bDirCompare) m_pDirectoryMergeWindow->mergeResultSaved(m_outputFilename); } //setWindowTitle(url.fileName(),doc->isModified()); m_bDefaultFilename = false; } slotStatusMsg(i18n("Ready.")); } void printDiffTextWindow(MyPainter& painter, const QRect& view, const QString& headerText, DiffTextWindow* pDiffTextWindow, int line, int linesPerPage, QColor fgColor) { QRect clipRect = view; clipRect.setTop(0); painter.setClipRect(clipRect); painter.translate(view.left(), 0); QFontMetrics fm = painter.fontMetrics(); //if ( fm.width(headerText) > view.width() ) { // A simple wrapline algorithm int l = 0; for(int p = 0; p < headerText.length();) { QString s = headerText.mid(p); int i; for(i = 2; i < s.length(); ++i) if(fm.width(s, i) > view.width()) { --i; break; } //QString s2 = s.left(i); painter.drawText(0, l * fm.height() + fm.ascent(), s.left(i)); p += i; ++l; } painter.setPen(fgColor); painter.drawLine(0, view.top() - 2, view.width(), view.top() - 2); } painter.translate(0, view.top()); pDiffTextWindow->print(painter, view, line, linesPerPage); painter.resetMatrix(); } void KDiff3App::slotFilePrint() { if(m_pDiffTextWindow1 == nullptr) return; #ifdef QT_NO_PRINTER slotStatusMsg(i18n("Printing not implemented.")); #endif #ifndef QT_NO_PRINTER QPrinter printer; QPrintDialog printDialog(&printer, this); int firstSelectionD3LIdx = -1; int lastSelectionD3LIdx = -1; m_pDiffTextWindow1->getSelectionRange(&firstSelectionD3LIdx, &lastSelectionD3LIdx, eD3LLineCoords); if(firstSelectionD3LIdx < 0 && m_pDiffTextWindow2) { m_pDiffTextWindow2->getSelectionRange(&firstSelectionD3LIdx, &lastSelectionD3LIdx, eD3LLineCoords); } if(firstSelectionD3LIdx < 0 && m_pDiffTextWindow3) { m_pDiffTextWindow3->getSelectionRange(&firstSelectionD3LIdx, &lastSelectionD3LIdx, eD3LLineCoords); } if(firstSelectionD3LIdx >= 0) { printDialog.addEnabledOption(QPrintDialog::PrintSelection); //printer.setOptionEnabled(QPrinter::PrintSelection,true); printDialog.setPrintRange(QAbstractPrintDialog::Selection); } if(firstSelectionD3LIdx == -1) printDialog.setPrintRange(QAbstractPrintDialog::AllPages); //printDialog.setMinMax(0,0); printDialog.setFromTo(0, 0); int currentFirstLine = m_pDiffTextWindow1->getFirstLine(); int currentFirstD3LIdx = m_pDiffTextWindow1->convertLineToDiff3LineIdx(currentFirstLine); // do some printer initialization printer.setFullPage(false); // initialize the printer using the print dialog if(printDialog.exec() == QDialog::Accepted) { slotStatusMsg(i18n("Printing...")); // create a painter to paint on the printer object MyPainter painter(&printer, m_pOptions->m_bRightToLeftLanguage, width(), fontMetrics().width('W')); QPaintDevice* pPaintDevice = painter.device(); int dpiy = pPaintDevice->logicalDpiY(); int columnDistance = (int)((0.5 / 2.54) * dpiy); // 0.5 cm between the columns int columns = m_bTripleDiff ? 3 : 2; int columnWidth = (pPaintDevice->width() - (columns - 1) * columnDistance) / columns; QFont f = m_pOptions->m_font; f.setPointSizeF(f.pointSizeF() - 1); // Print with slightly smaller font. painter.setFont(f); QFontMetrics fm = painter.fontMetrics(); QString topLineText = i18n("Top line"); //int headerWidth = fm.width( m_sd1.getAliasName() + ", "+topLineText+": 01234567" ); int headerLines = fm.width(m_sd1.getAliasName() + ", " + topLineText + ": 01234567") / columnWidth + 1; int headerMargin = headerLines * fm.height() + 3; // Text + one horizontal line int footerMargin = fm.height() + 3; QRect view(0, headerMargin, pPaintDevice->width(), pPaintDevice->height() - (headerMargin + footerMargin)); QRect view1(0 * (columnWidth + columnDistance), view.top(), columnWidth, view.height()); QRect view2(1 * (columnWidth + columnDistance), view.top(), columnWidth, view.height()); QRect view3(2 * (columnWidth + columnDistance), view.top(), columnWidth, view.height()); int linesPerPage = view.height() / fm.lineSpacing(); QEventLoop eventLoopForPrinting; m_pEventLoopForPrinting = &eventLoopForPrinting; if(m_pOptions->m_bWordWrap) { // For printing the lines are wrapped differently (this invalidates the first line) recalcWordWrap(columnWidth); m_pEventLoopForPrinting->exec(); } int totalNofLines = max2(m_pDiffTextWindow1->getNofLines(), m_pDiffTextWindow2->getNofLines()); if(m_bTripleDiff && m_pDiffTextWindow3) totalNofLines = max2(totalNofLines, m_pDiffTextWindow3->getNofLines()); QList pageList; // = printer.pageList(); bool bPrintCurrentPage = false; bool bFirstPrintedPage = false; bool bPrintSelection = false; int totalNofPages = (totalNofLines + linesPerPage - 1) / linesPerPage; int line = -1; int selectionEndLine = -1; if(printer.printRange() == QPrinter::AllPages) { pageList.clear(); for(int i = 0; i < totalNofPages; ++i) { pageList.push_back(i + 1); } } else if(printer.printRange() == QPrinter::PageRange) { pageList.clear(); for(int i = printer.fromPage(); i <= printer.toPage(); ++i) { pageList.push_back(i); } } if(printer.printRange() == QPrinter::Selection) { bPrintSelection = true; if(firstSelectionD3LIdx >= 0) { line = m_pDiffTextWindow1->convertDiff3LineIdxToLine(firstSelectionD3LIdx); selectionEndLine = m_pDiffTextWindow1->convertDiff3LineIdxToLine(lastSelectionD3LIdx + 1); totalNofPages = (selectionEndLine - line + linesPerPage - 1) / linesPerPage; } } int page = 1; ProgressProxy pp; pp.setMaxNofSteps(totalNofPages); QList::iterator pageListIt = pageList.begin(); for(;;) { pp.setInformation(i18n("Printing page %1 of %2").arg(page).arg(totalNofPages), false); pp.setCurrent(page - 1); if(pp.wasCancelled()) { printer.abort(); break; } if(!bPrintSelection) { if(pageListIt == pageList.end()) break; page = *pageListIt; line = (page - 1) * linesPerPage; if(page == 10000) { // This means "Print the current page" bPrintCurrentPage = true; // Detect the first visible line in the window. line = m_pDiffTextWindow1->convertDiff3LineIdxToLine(currentFirstD3LIdx); } } else { if(line >= selectionEndLine) { break; } else { if(selectionEndLine - line < linesPerPage) linesPerPage = selectionEndLine - line; } } if(line >= 0 && line < totalNofLines) { if(bFirstPrintedPage) printer.newPage(); painter.setClipping(true); painter.setPen(m_pOptions->m_colorA); QString headerText1 = m_sd1.getAliasName() + ", " + topLineText + ": " + QString::number(m_pDiffTextWindow1->calcTopLineInFile(line) + 1); printDiffTextWindow(painter, view1, headerText1, m_pDiffTextWindow1, line, linesPerPage, m_pOptions->m_fgColor); painter.setPen(m_pOptions->m_colorB); QString headerText2 = m_sd2.getAliasName() + ", " + topLineText + ": " + QString::number(m_pDiffTextWindow2->calcTopLineInFile(line) + 1); printDiffTextWindow(painter, view2, headerText2, m_pDiffTextWindow2, line, linesPerPage, m_pOptions->m_fgColor); if(m_bTripleDiff && m_pDiffTextWindow3) { painter.setPen(m_pOptions->m_colorC); QString headerText3 = m_sd3.getAliasName() + ", " + topLineText + ": " + QString::number(m_pDiffTextWindow3->calcTopLineInFile(line) + 1); printDiffTextWindow(painter, view3, headerText3, m_pDiffTextWindow3, line, linesPerPage, m_pOptions->m_fgColor); } painter.setClipping(false); painter.setPen(m_pOptions->m_fgColor); painter.drawLine(0, view.bottom() + 3, view.width(), view.bottom() + 3); QString s = bPrintCurrentPage ? QString("") : QString::number(page) + "/" + QString::number(totalNofPages); if(bPrintSelection) s += " (" + i18n("Selection") + ")"; painter.drawText((view.right() - painter.fontMetrics().width(s)) / 2, view.bottom() + painter.fontMetrics().ascent() + 5, s); bFirstPrintedPage = true; } if(bPrintSelection) { line += linesPerPage; ++page; } else { ++pageListIt; } } painter.end(); if(m_pOptions->m_bWordWrap) { recalcWordWrap(); m_pEventLoopForPrinting->exec(); m_pDiffVScrollBar->setValue(m_pDiffTextWindow1->convertDiff3LineIdxToLine(currentFirstD3LIdx)); } slotStatusMsg(i18n("Printing completed.")); } else { slotStatusMsg(i18n("Printing aborted.")); } #endif } void KDiff3App::slotFileQuit() { slotStatusMsg(i18n("Exiting...")); if(!queryClose()) return; // Don't quit QApplication::exit(isFileSaved() || isDirComparison() ? 0 : 1); } void KDiff3App::slotViewToolBar() { assert(viewToolBar != nullptr); slotStatusMsg(i18n("Toggling toolbar...")); m_pOptions->m_bShowToolBar = viewToolBar->isChecked(); /////////////////////////////////////////////////////////////////// // turn Toolbar on or off if(toolBar(MAIN_TOOLBAR_NAME) != nullptr) { if(!m_pOptions->m_bShowToolBar) { toolBar(MAIN_TOOLBAR_NAME)->hide(); } else { toolBar(MAIN_TOOLBAR_NAME)->show(); } } slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotViewStatusBar() { slotStatusMsg(i18n("Toggle the statusbar...")); m_pOptions->m_bShowStatusBar = viewStatusBar->isChecked(); /////////////////////////////////////////////////////////////////// //turn Statusbar on or off if(statusBar() != nullptr) { if(!viewStatusBar->isChecked()) { statusBar()->hide(); } else { statusBar()->show(); } } slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotStatusMsg(const QString& text) { /////////////////////////////////////////////////////////////////// // change status message permanently if(statusBar() != nullptr) { statusBar()->clearMessage(); statusBar()->showMessage(text); } } //#include "kdiff3.moc" diff --git a/src/kreplacements/kreplacements.cpp b/src/kreplacements/kreplacements.cpp index 7d9244a..c5a491d 100644 --- a/src/kreplacements/kreplacements.cpp +++ b/src/kreplacements/kreplacements.cpp @@ -1,1192 +1,1192 @@ /*************************************************************************** kreplacements.cpp - description ------------------- begin : Sat Aug 3 2002 copyright : (C) 2002-2006 by Joachim Eibl email : joachim.eibl at gmx.de ***************************************************************************/ /*************************************************************************** * * * 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 "kreplacements.h" #include "common.h" #include #include //namespace #include #include #include #include #ifndef QT_NO_COLORDIALOG #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include static QString s_copyright; static QString s_email; static QString s_description; static QString s_appName; static QString s_version; static QString s_homepage; static KAboutData* s_pAboutData; #ifdef _WIN32 #include #include #include #endif static void showHelp() { - #if defined(_WIN32) || defined(Q_OS_OS2) + #if defined(_WIN32) QString exePath = QCoreApplication::applicationDirPath(); QFileInfo helpFile( exePath + "\\doc\\en\\index.html" ); if ( ! helpFile.exists() ) { helpFile.setFile( exePath + "\\..\\doc\\en\\index.html" ); } if ( ! helpFile.exists() ) { helpFile.setFile( exePath + "\\doc\\index.html" ); } if ( ! helpFile.exists() ) { helpFile.setFile( exePath + "\\..\\doc\\index.html" ); } if ( ! helpFile.exists() ) { QMessageBox::warning( 0, "KDiff3 documentation not found", "Couldn't find the documentation. \n\n" "The documentation can also be found at the homepage:\n\n " " http://kdiff3.sourceforge.net/"); return; } #ifndef Q_OS_OS2 char buf[256]; HINSTANCE hi = FindExecutableA( helpFile.fileName().toLatin1(), helpFile.absolutePath().toLatin1(), buf ); if ( (quintptr)hi<=32 ) { #endif static QTextBrowser* pBrowser = 0; if (pBrowser==0) { pBrowser = new QTextBrowser( 0 ); pBrowser->setMinimumSize( 600, 400 ); } pBrowser->setSource(QUrl("file:///"+helpFile.filePath())); pBrowser->show(); #ifndef Q_OS_OS2 } else { QFileInfo prog( buf ); QProcess::startDetached ( prog.filePath(), QStringList( "file:///"+helpFile.absoluteFilePath() ) ); } #endif #else static QTextBrowser* pBrowser = 0; if (pBrowser==0) { pBrowser = new QTextBrowser( 0 ); pBrowser->setMinimumSize( 600, 400 ); } pBrowser->setSource(QUrl("file://usr/local/share/doc/kdiff3/en/index.html")); pBrowser->show(); #endif } QString getTranslationDir(const QString& locale) { - #if defined(_WIN32) || defined(Q_OS_OS2) + #if defined(_WIN32) QUNUSED(locale); QString exePath; exePath = QCoreApplication::applicationDirPath(); return exePath+"/translations"; #elif defined(__APPLE__) QUNUSED(locale); QString exePath = QCoreApplication::applicationDirPath(); return exePath+"/../Resources/translations"; #else return (QString)"/usr/share/locale/" + locale + "/LC_MESSAGES"; #endif } // static void KMessageBox::error( QWidget* parent, const QString& text, const QString& caption ) { QMessageBox::critical( parent, caption, text ); } int KMessageBox::warningContinueCancel( QWidget* parent, const QString& text, const QString& caption, const QString& button1 ) { return 0 == QMessageBox::warning( parent, caption, text, button1, "Cancel" ) ? Continue : Cancel; } void KMessageBox::sorry( QWidget* parent, const QString& text, const QString& caption ) { QMessageBox::information( parent, caption, text ); } void KMessageBox::information( QWidget* parent, const QString& text, const QString& caption ) { QMessageBox::information( parent, caption, text ); } int KMessageBox::warningYesNo( QWidget* parent, const QString& text, const QString& caption, const QString& button1, const QString& button2 ) { return 0 == QMessageBox::warning( parent, caption, text, button1, button2, QString::null, 1, 1 ) ? Yes : No; } int KMessageBox::warningYesNoCancel( QWidget* parent, const QString& text, const QString& caption, const QString& button1, const QString& button2 ) { int val = QMessageBox::warning( parent, caption, text, button1, button2, i18n("Cancel") ); if ( val==0 ) return Yes; if ( val==1 ) return No; else return Cancel; } KPageDialog::KPageDialog( QWidget* parent ) : QDialog( parent ) { setModal(true); QVBoxLayout *pMainLayout = new QVBoxLayout(this); m_pTabWidget = new QTabWidget(); pMainLayout->addWidget(m_pTabWidget,1); QHBoxLayout* pButtonLayout = new QHBoxLayout(); pMainLayout->addLayout( pButtonLayout ); pButtonLayout->addStretch(1); QPushButton* pOk = new QPushButton( i18n("Ok") ); connect( pOk, SIGNAL( clicked() ), this, SIGNAL(okClicked()) ); pButtonLayout->addWidget( pOk ); QPushButton* pHelp = new QPushButton( i18n("Help") ); connect( pHelp, SIGNAL( clicked() ), this, SLOT(slotHelpClicked())); pButtonLayout->addWidget( pHelp ); QPushButton* pDefaults = new QPushButton( i18n("Defaults") ); connect( pDefaults, SIGNAL( clicked() ), this, SIGNAL(defaultClicked()) ); pButtonLayout->addWidget( pDefaults ); QPushButton* pCancel = new QPushButton( i18n("Cancel") ); connect( pCancel, SIGNAL( clicked() ), this, SLOT(reject())); pButtonLayout->addWidget( pCancel ); } KPageDialog::~KPageDialog() { } void KPageDialog::incrementInitialSize ( const QSize& ) { } void KPageDialog::setHelp(const QString&, const QString& ) { } void KPageDialog::slotHelpClicked() { showHelp(); } int KPageDialog::BarIcon(const QString& /*iconName*/, int ) { return 0; // Not used for replacement. } QFrame* KPageDialog::addPage( const QString& name, const QString& /*info*/, int ) { QFrame* p = new QFrame(); p->setObjectName( name ); m_pTabWidget->addTab( p, name ); return p; } void KPageDialog::addPage( KPageWidgetItem * p ) { m_pTabWidget->addTab( p->m_pWidget, p->m_title ); } int KPageDialog::spacingHint() { return 3; } KUrl KFileDialog::getSaveUrl( const QString &startDir, const QString &filter, QWidget *parent, const QString &caption) { QString s = QFileDialog::getSaveFileName(parent, caption, startDir, filter, 0/*, QFileDialog::DontUseNativeDialog*/); return KUrl(s); } KUrl KFileDialog::getOpenUrl( const QString & startDir, const QString & filter, QWidget * parent, const QString & caption ) { QString s = QFileDialog::getOpenFileName(parent, caption, startDir, filter ); return KUrl(s); } KUrl KFileDialog::getExistingDirectoryUrl( const QString & startDir, QWidget * parent, const QString & caption) { QString s = QFileDialog::getExistingDirectory(parent, caption, startDir); return KUrl(s); } QString KFileDialog::getSaveFileName (const QString &startDir, const QString &filter, QWidget *parent, const QString &caption) { return QFileDialog::getSaveFileName( parent, caption, startDir, filter ); } KToolBar::BarPosition KToolBar::barPos() { if ( m_pMainWindow->toolBarArea(this)==Qt::LeftToolBarArea ) return Left; if ( m_pMainWindow->toolBarArea(this)==Qt::RightToolBarArea ) return Right; if ( m_pMainWindow->toolBarArea(this)==Qt::BottomToolBarArea ) return Bottom; if ( m_pMainWindow->toolBarArea(this)==Qt::TopToolBarArea ) return Top; return Top; } void KToolBar::setBarPos(BarPosition bp) { if ( bp == Left ) m_pMainWindow->addToolBar ( Qt::LeftToolBarArea, this ); else if ( bp == Right ) m_pMainWindow->addToolBar ( Qt::RightToolBarArea, this ); else if ( bp == Bottom ) m_pMainWindow->addToolBar ( Qt::BottomToolBarArea, this ); else if ( bp == Top ) m_pMainWindow->addToolBar ( Qt::TopToolBarArea, this ); } KToolBar::KToolBar( QMainWindow* parent ) : QToolBar( parent ) { m_pMainWindow = parent; } KMainWindow::KMainWindow( QWidget* parent ) : QMainWindow( parent ), m_actionCollection(this) { fileMenu = menuBar()->addMenu( i18n("&File") ); editMenu = menuBar()->addMenu(i18n("&Edit") ); directoryMenu = menuBar()->addMenu(i18n("&Directory") ); dirCurrentItemMenu = 0; dirCurrentSyncItemMenu = 0; movementMenu = menuBar()->addMenu(i18n("&Movement") ); diffMenu = menuBar()->addMenu(i18n("D&iffview") ); mergeMenu = menuBar()->addMenu(i18n("&Merge") ); windowsMenu = menuBar()->addMenu(i18n("&Window") ); settingsMenu = menuBar()->addMenu(i18n("&Settings") ); helpMenu = menuBar()->addMenu(i18n("&Help") ); m_pToolBar = new KToolBar(this); addToolBar( m_pToolBar ); memberList = new QList; memberList->append(this); raise(); } KToolBar* KMainWindow::toolBar(const QString&) { return m_pToolBar; } KActionCollection* KMainWindow::actionCollection() { return &m_actionCollection; } void KMainWindow::createGUI() { KStandardAction::help(this, SLOT(appHelpActivated()), actionCollection()); KStandardAction::about(this, SLOT(slotAbout()), actionCollection()); KStandardAction::aboutQt(actionCollection()); } void KMainWindow::slotAbout() { QDialog d; QVBoxLayout* l = new QVBoxLayout( &d ); QTabWidget* pTabWidget = new QTabWidget; l->addWidget( pTabWidget ); QPushButton* pOkButton = new QPushButton(i18n("Ok")); connect( pOkButton, SIGNAL(clicked()), &d, SLOT(accept())); l->addWidget( pOkButton ); d.setWindowTitle("About " + s_appName); QTextBrowser* tb1 = new QTextBrowser(); tb1->setWordWrapMode( QTextOption::NoWrap ); tb1->setText( s_appName + " Version " + s_version + "\n\n" + s_description + "\n\n" + s_copyright + "\n\nHomepage: " + s_homepage + "\n\nLicence: GNU GPL Version 2" ); pTabWidget->addTab(tb1,i18n("&About")); std::list::iterator i; QString s2; for( i=s_pAboutData->m_authorList.begin(); i!=s_pAboutData->m_authorList.end(); ++i ) { if ( !i->m_name.isEmpty() ) s2 += i->m_name + "\n"; if ( !i->m_task.isEmpty() ) s2 += " " + i->m_task + "\n"; if ( !i->m_email.isEmpty() ) s2 += " " + i->m_email + "\n"; if ( !i->m_weblink.isEmpty() ) s2 += " " + i->m_weblink + "\n"; s2 += "\n"; } QTextBrowser* tb2 = new QTextBrowser(); tb2->setWordWrapMode( QTextOption::NoWrap ); tb2->setText(s2); pTabWidget->addTab(tb2,i18n("A&uthor")); QString s3; for( i=s_pAboutData->m_creditList.begin(); i!=s_pAboutData->m_creditList.end(); ++i ) { if ( !i->m_name.isEmpty() ) s3 += i->m_name + "\n"; if ( !i->m_task.isEmpty() ) s3 += " " + i->m_task + "\n"; if ( !i->m_email.isEmpty() ) s3 += " " + i->m_email + "\n"; if ( !i->m_weblink.isEmpty() ) s3 += " " + i->m_weblink + "\n"; s3 += "\n"; } QTextBrowser* tb3 = new QTextBrowser(); tb3->setWordWrapMode( QTextOption::NoWrap ); tb3->setText(s3); pTabWidget->addTab(tb3,i18n("&Thanks To")); d.resize(400,300); d.exec(); /* QMessageBox::information( this, "About " + s_appName, s_appName + " Version " + s_version + "\n\n" + s_description + "\n\n" + s_copyright + "\n\nHomepage: " + s_homepage + "\n\nLicence: GNU GPL Version 2" ); */ } void KMainWindow::appHelpActivated() { showHelp(); } QString KStandardDirs::findResource(const QString& resource, const QString& /*appName*/) { if (resource=="config") { -#if defined(_WIN32) || defined(Q_OS_OS2) +#if defined(_WIN32) QString exePath = QCoreApplication::applicationDirPath(); QString configPath = exePath + "/.kdiff3rc"; // This is useful for portable installations (e.g. on USB-Stick) if ( QFile::exists( configPath ) ) return configPath; #endif QString home = QDir::homePath(); return home + "/.kdiff3rc"; } return QString(); } KConfigGroupData::~KConfigGroupData() { QFile f(m_fileName); if ( f.open( QIODevice::WriteOnly | QIODevice::Text ) ) { // file opened successfully QTextStream t( &f ); // use a text stream save(t); f.close(); } } KConfigGroup::KConfigGroup() { d = new KConfigGroupData; } void KConfigGroup::readConfigFile( const QString& configFileName ) { if ( !configFileName.isEmpty() ) { d->m_fileName = configFileName; } else { d->m_fileName = KStandardDirs().findResource("config","kdiff3rc"); } QFile f( d->m_fileName ); if ( f.open(QIODevice::ReadOnly) ) { // file opened successfully QTextStream t( &f ); // use a text stream d->load(t); f.close(); } } KConfigGroup::~KConfigGroup() { } void KConfigGroup::setGroup(const QString&) { } KConfigGroup& KConfigGroup::group( const QString& ) { KApplication* pKApp = static_cast(QApplication::instance()); return *pKApp->config(); } static void initAction( QAction* pAction, QObject* receiver, const char* slot, KActionCollection* actionCollection, const QString& name, bool bToggle, bool bMenu) { QString n(name); KMainWindow* p = actionCollection->m_pMainWindow; if( slot!=0 ) { if (!bToggle) QObject::connect(pAction, SIGNAL(triggered()), receiver, slot); else { QObject::connect(pAction, SIGNAL(toggled(bool)), receiver, slot); } } if (bMenu) { if( n[0]=='g') p->movementMenu->addAction( pAction ); else if( n.left(16)=="dir_current_sync") { if ( p->dirCurrentItemMenu==0 ) { p->dirCurrentItemMenu = p->directoryMenu->addMenu( i18n("Current Item Merge Operation") ); p->dirCurrentSyncItemMenu = p->directoryMenu->addMenu( i18n("Current Item Sync Operation") ); } p->dirCurrentItemMenu->addAction( pAction ); } else if( n.left(11)=="dir_current") { if ( p->dirCurrentItemMenu==0 ) { p->dirCurrentItemMenu = p->directoryMenu->addMenu( i18n("Current Item Merge Operation") ); p->dirCurrentSyncItemMenu = p->directoryMenu->addMenu( i18n("Current Item Sync Operation") ); } p->dirCurrentSyncItemMenu->addAction( pAction ); } else if( n.left(4)=="diff") p->diffMenu->addAction( pAction ); else if( name[0]=='d') p->directoryMenu->addAction( pAction ); else if( name[0]=='f') p->fileMenu->addAction( pAction ); else if( name[0]=='w') p->windowsMenu->addAction( pAction ); else p->mergeMenu->addAction( pAction ); } } KAction::KAction(const QString& name, KActionCollection* actionCollection ) : QAction ( actionCollection->m_pMainWindow ) { initAction( this, 0,0, actionCollection, name, false, true ); } KAction::KAction( KActionCollection* actionCollection ) : QAction ( actionCollection->m_pMainWindow ) { } KAction::KAction(const QString& text, const QIcon& icon, int accel, QObject* receiver, const char* slot, KActionCollection* actionCollection, const char* name, bool bToggle, bool bMenu ) : QAction ( icon, text, actionCollection->m_pMainWindow ) { setObjectName(name); setShortcut( accel ); setCheckable( bToggle ); KMainWindow* p = actionCollection->m_pMainWindow; if ( !icon.isNull() && p ) p->m_pToolBar->addAction( this ); initAction(this, receiver,slot,actionCollection,name,bToggle,bMenu); } KAction::KAction(const QString& text, int accel, QObject* receiver, const char* slot, KActionCollection* actionCollection, const char* name, bool bToggle, bool bMenu ) : QAction ( text, actionCollection->m_pMainWindow ) { setObjectName(name); setShortcut( accel ); setCheckable( bToggle ); initAction(this,receiver,slot,actionCollection,name,bToggle,bMenu); } void KAction::setStatusText(const QString&) { } void KAction::plug(QMenu* menu) { menu->addAction( this ); } void KAction::setIcon( const QIcon& icon ) { QAction::setIcon(icon); if ( !icon.isNull() ) { KMainWindow* pMW = static_cast( parent() ); pMW->toolBar()->addAction(this); } } KToggleAction::KToggleAction( KActionCollection* actionCollection ) : KAction( actionCollection ) { setCheckable(true); } KToggleAction::KToggleAction(const QString& text, int accel, QObject* receiver, const char* slot, KActionCollection* actionCollection, const char* name, bool bMenu) : KAction( text, accel, receiver, slot, actionCollection, name, true, bMenu) { } KToggleAction::KToggleAction(const QString& text, const QIcon& icon, int accel, KActionCollection* actionCollection, const char* name, bool bMenu) : KAction( text, icon, accel, 0, 0, actionCollection, name, true, bMenu) { } void KToggleAction::setChecked(bool bChecked) { blockSignals( true ); QAction::setChecked( bChecked ); blockSignals( false ); } //static KAction* KStandardAction::open( QWidget* parent, const char* slot, KActionCollection* actionCollection) { #include "../xpm/fileopen.xpm" KMainWindow* p = actionCollection->m_pMainWindow; KAction* a = new KAction( i18n("Open"), QIcon(QPixmap(fileopen)), Qt::CTRL+Qt::Key_O, parent, slot, actionCollection, "open", false, false); if(p){ p->fileMenu->addAction( a ); } return a; } KAction* KStandardAction::save( QWidget* parent, const char* slot, KActionCollection* actionCollection ) { #include "../xpm/filesave.xpm" KMainWindow* p = actionCollection->m_pMainWindow; KAction* a = new KAction( i18n("Save"), QIcon(QPixmap(filesave)), Qt::CTRL+Qt::Key_S, parent, slot, actionCollection, "save", false, false); if(p){ p->fileMenu->addAction( a ); } return a; } KAction* KStandardAction::saveAs( QWidget* parent, const char* slot, KActionCollection* actionCollection) { KMainWindow* p = actionCollection->m_pMainWindow; KAction* a = new KAction( i18n("Save As..."), 0, parent, slot, actionCollection, "saveas", false, false); if(p) p->fileMenu->addAction( a ); return a; } KAction* KStandardAction::print( QWidget* parent, const char* slot, KActionCollection* actionCollection) { #include "../xpm/fileprint.xpm" KMainWindow* p = actionCollection->m_pMainWindow; KAction* a = new KAction( i18n("Print..."), QIcon(QPixmap(fileprint)),Qt::CTRL+Qt::Key_P, parent, slot, actionCollection, "print", false, false); if(p) p->fileMenu->addAction( a ); return a; } KAction* KStandardAction::quit( QWidget* parent, const char* slot, KActionCollection* actionCollection) { KMainWindow* p = actionCollection->m_pMainWindow; KAction* a = new KAction( i18n("Quit"), Qt::CTRL+Qt::Key_Q, parent, slot, actionCollection, "quit", false, false); if(p) p->fileMenu->addAction( a ); return a; } KAction* KStandardAction::cut( QWidget* parent, const char* slot, KActionCollection* actionCollection) { KMainWindow* p = actionCollection->m_pMainWindow; KAction* a = new KAction( i18n("Cut"), Qt::CTRL+Qt::Key_X, parent, slot, actionCollection, "cut", false, false ); if(p) p->editMenu->addAction( a ); return a; } KAction* KStandardAction::copy( QWidget* parent, const char* slot, KActionCollection* actionCollection) { KMainWindow* p = actionCollection->m_pMainWindow; KAction* a = new KAction( i18n("Copy"), Qt::CTRL+Qt::Key_C, parent, slot, actionCollection, "copy", false, false ); if(p) p->editMenu->addAction( a ); return a; } KAction* KStandardAction::paste( QWidget* parent, const char* slot, KActionCollection* actionCollection) { KMainWindow* p = actionCollection->m_pMainWindow; KAction* a = new KAction( i18n("Paste"), Qt::CTRL+Qt::Key_V, parent, slot, actionCollection, "paste", false, false ); if(p) p->editMenu->addAction( a ); return a; } KAction* KStandardAction::selectAll( QWidget* parent, const char* slot, KActionCollection* actionCollection) { KMainWindow* p = actionCollection->m_pMainWindow; KAction* a = new KAction( i18n("Select All"), Qt::CTRL+Qt::Key_A, parent, slot, actionCollection, "selectall", false, false ); if(p) p->editMenu->addAction( a ); return a; } KToggleAction* KStandardAction::showToolbar( QWidget* parent, const char* slot, KActionCollection* actionCollection) { KMainWindow* p = actionCollection->m_pMainWindow; KToggleAction* a = new KToggleAction( i18n("Show Toolbar"), 0, parent, slot, actionCollection, "showtoolbar", false ); if(p) p->settingsMenu->addAction( a ); return a; } KToggleAction* KStandardAction::showStatusbar( QWidget* parent, const char* slot, KActionCollection* actionCollection) { KMainWindow* p = actionCollection->m_pMainWindow; KToggleAction* a = new KToggleAction( i18n("Show &Status Bar"), 0, parent, slot, actionCollection, "showstatusbar", false ); if(p) p->settingsMenu->addAction( a ); return a; } KAction* KStandardAction::preferences( QWidget* parent, const char* slot, KActionCollection* actionCollection) { KMainWindow* p = actionCollection->m_pMainWindow; KAction* a = new KAction( i18n("&Configure %1...",QString("KDiff3")), 0, parent, slot, actionCollection, "settings", false, false ); if(p) p->settingsMenu->addAction( a ); return a; } KAction* KStandardAction::keyBindings( QWidget*, const char*, KActionCollection*) { return 0; } KAction* KStandardAction::about( QWidget* parent, const char* slot, KActionCollection* actionCollection) { KMainWindow* p = actionCollection->m_pMainWindow; KAction* a = new KAction( i18n("About")+" KDiff3", 0, parent, slot, actionCollection, "about_kdiff3", false, false ); if(p) p->helpMenu->addAction( a ); return a; } KAction* KStandardAction::aboutQt( KActionCollection* actionCollection ) { KMainWindow* p = actionCollection->m_pMainWindow; KAction* a = new KAction( i18n("About")+" Qt", 0, qApp, SLOT(aboutQt()), actionCollection, "about_qt", false, false ); if(p) p->helpMenu->addAction( a ); return a; } KAction* KStandardAction::help( QWidget* parent, const char* slot, KActionCollection* actionCollection) { KMainWindow* p = actionCollection->m_pMainWindow; KAction* a = new KAction( i18n("Help"), Qt::Key_F1, parent, slot, actionCollection, "help-contents", false, false ); if(p) p->helpMenu->addAction( a ); return a; } KAction* KStandardAction::find( QWidget* parent, const char* slot, KActionCollection* actionCollection) { KMainWindow* p = actionCollection->m_pMainWindow; KAction* a = new KAction( i18n("Find"), Qt::CTRL+Qt::Key_F, parent, slot, actionCollection, "edit-find", false, false ); if(p) p->editMenu->addAction( a ); return a; } KAction* KStandardAction::findNext( QWidget* parent, const char* slot, KActionCollection* actionCollection) { KMainWindow* p = actionCollection->m_pMainWindow; KAction* a = new KAction( i18n("Find Next"), Qt::Key_F3, parent, slot, actionCollection, "findNext", false, false ); if(p) p->editMenu->addAction( a ); return a; } KAction* KActionCollection::addAction(const QString& name ) { return new KAction( name, this ); } void KActionCollection::addAction( const QString& name, QAction* pAction ) { initAction( pAction, 0,0,this,name,false,true); } KColorButton::KColorButton(QWidget* parent) : QPushButton(parent) { connect( this, SIGNAL(clicked()), this, SLOT(slotClicked())); } QColor KColorButton::color() { return m_color; } void KColorButton::setColor( const QColor& color ) { m_color = color; update(); } void KColorButton::paintEvent( QPaintEvent* e ) { QPushButton::paintEvent(e); QPainter p(this); int w = width(); int h = height(); p.fillRect( 10, 5, w-20, h-10, m_color ); p.drawRect( 10, 5, w-20, h-10 ); } void KColorButton::slotClicked() { #ifndef QT_NO_COLORDIALOG // Under Windows ChooseColor() should be used. (Nicer if few colors exist.) QColor c = QColorDialog::getColor ( m_color, this ); if ( c.isValid() ) m_color = c; #endif update(); } #ifndef QT_NO_PRINTER KPrinter::KPrinter() { } QList KPrinter::pageList() { QList vl; int to = toPage(); for(int i=fromPage(); i<=to; ++i) { vl.push_back(i); } return vl; } void KPrinter::setCurrentPage(int) { } void KPrinter::setPageSelection(e_PageSelection) { } #endif QPixmap KIconLoader::loadIcon( const QString&, int, int ) { return QPixmap(); } KAboutData::KAboutData(const QByteArray &appName, const QByteArray & /*catalogName*/, const KLocalizedString & /*programName*/, const QByteArray &version, const KLocalizedString &shortDescription, LicenseKey /*licenseType*/, const KLocalizedString ©rightStatement, const KLocalizedString & /*text*/, const QByteArray &homePageAddress, const QByteArray &bugsEmailAddress) { s_copyright = copyrightStatement; s_email = bugsEmailAddress; s_appName = appName; s_description = shortDescription; s_version = version; s_homepage = homePageAddress; } KAboutData::KAboutData( const QString& /*name*/, const QString& /*appName*/, const QString& /*appName2*/, const QString& /*version*/ ) { } void KAboutData::addAuthor(const QString& name, const QString& task, const QString& email, const QString& weblink) { m_authorList.push_back( AboutDataEntry( name, task, email, weblink) ); } void KAboutData::addCredit(const QString& name, const QString& task, const QString& email, const QString& weblink) { m_creditList.push_back( AboutDataEntry( name, task, email, weblink) ); } /* Option structure: e.g.: { "m", 0, 0 }, { "merge", I18N_NOOP("Automatically merge the input."), 0 }, { "o", 0, 0 }, { "output file", I18N_NOOP("Output file. Implies -m. E.g.: -o newfile.txt"), 0 }, { "+[File1]", I18N_NOOP("file1 to open (base)"), 0 }, { "+[File2]", I18N_NOOP("file2 to open"), 0 }, { "+[File3]", I18N_NOOP("file3 to open"), 0 }, */ //////////////// static KCmdLineArgs s_cmdLineArgs; static int s_argc; static char** s_argv; struct KCmdLineOptionsItem { QString name; QString description; int def; }; static QList s_options; static std::vector s_vOption; static std::vector s_vArg; KCmdLineOptions& KCmdLineOptions::add( const QString& name, const QString& description ) { KCmdLineOptionsItem i; i.name = name; i.description = description; i.def = 0; s_options.push_back(i); return *this; } KCmdLineArgs* KCmdLineArgs::parsedArgs() // static { return &s_cmdLineArgs; } void KCmdLineArgs::init( int argc, char**argv, KAboutData* pAboutData ) // static { s_argc = argc; s_argv = argv; s_pAboutData = pAboutData; } void KCmdLineArgs::addCmdLineOptions( const KCmdLineOptions& /*options*/ ) // static { //s_pOptions = &options; } int KCmdLineArgs::count() { return s_vArg.size(); } QString KCmdLineArgs::arg(int idx) { return s_vArg[idx]; } void KCmdLineArgs::clear() { } QString KCmdLineArgs::getOption( const QString& s ) { // Find the option int j=0; for( j=0; j<(int)s_vOption.size(); ++j ) { QString optName = s_options[j].name; int pos = optName.indexOf( ' ' ); int len = pos==-1 ? optName.length() : pos; if( s == optName.left( len ) ) { return s_vOption[j].isEmpty() ? QString() : s_vOption[j].last(); } } assert(false); return QString(); } QStringList KCmdLineArgs::getOptionList( const QString& s ) { // Find the option int j=0; for( j=0; j<(int)s_vOption.size(); ++j ) { QString optName = s_options[j].name; int pos = optName.indexOf( ' ' ); int len = pos==-1 ? optName.length() : pos; if( s == optName.left( len ) ) { return s_vOption[j]; } } assert(false); return QStringList(); } bool KCmdLineArgs::isSet(const QString& s) { // Find the option int j=0; for( j=0; j<(int)s_vOption.size(); ++j ) { QString optName = s_options[j].name; if( s == optName ) { return ! s_vOption[j].isEmpty(); } } assert(false); return false; } /////////////////// KApplication* kapp; KApplication::KApplication() : QApplication( s_argc,s_argv ) { kapp = this; - #if ! ( defined(_WIN32) || defined(Q_OS_OS2) ) + #if ! ( defined(_WIN32) ) parseOptions(); #endif } void KApplication::parseOptions() { //setStyle( new QWindowsStyle ); // doesn't show checkmarks on checkable icons in menu int nofOptions=0; int nofArgs=0; int i=0; while( i < s_options.size() ) { if ( s_options[i].name[0]=='[' ) nofArgs++; else nofOptions++; ++i; } QStringList args = QCoreApplication::arguments(); // First find the option "-config" or "--config" to allow loading of options QString configFileName; for( i=1; i0 && ( (arg[1]=='-' && len2-2==len && optName.left(len) == arg.mid(2,len)) || (len2-1==len && optName.left(len) == arg.mid(1,len)) )) { if (s_options[j].description == 0) // alias, because without description. { ++j; optName = s_options[j].name; pos = optName.indexOf( ' ' ); } if (pos!=-1){ ++i; s_vOption[j].append( args[i] ); } //use param else { s_vOption[j].append("1"); } //set state break; } } if (j==nofOptions) { QString s; s = QString("Unknown option: ") + arg + "\n"; s += "If KDiff3 should ignore this option, run KDiff3 normally and edit\n" "the \"Command line options to ignore\" in the \"Integration Settings\".\n\n"; s += "KDiff3-Usage when starting via commandline: \n"; s += "- Comparing 2 files:\t\tkdiff3 file1 file2\n"; s += "- Merging 2 files: \t\tkdiff3 file1 file2 -o outputfile\n"; s += "- Comparing 3 files:\t\tkdiff3 file1 file2 file3\n"; s += "- Merging 3 files: \t\tkdiff3 file1 file2 file3 -o outputfile\n"; s += " Note that file1 will be treated as base of file2 and file3.\n"; s += "\n"; s += "If you start without arguments, then a dialog will appear\n"; s += "where you can select your files via a filebrowser.\n"; s += "\n"; s += "Options:\n"; j=0; int pos=s.length(); for( j=0; j1 ) s += "-"; } s += s_options[j].name; s += QString().fill(' ', minMaxLimiter( 20 - ((int)s.length()-pos), 3, 20 ) ); s += s_options[j].description; s +="\n"; pos=s.length(); } else { s += "-"; if ( s_options[j].name.length()>1 ) s += "-"; s += s_options[j].name; s += ", "; } } s += "\n"+i18n("For more documentation, see the help-menu or the subdirectory doc.")+"\n"; -#if defined(_WIN32) || defined(Q_OS_OS2) +#if defined(_WIN32) // A windows program has no console if ( 0==QMessageBox::information(0, i18n("KDiff3-Usage"), s, i18n("Ignore"),i18n("Exit") ) ) continue; #else fprintf(stderr, "%s\n", s.toLatin1().constData()); #endif ::exit(-1); } } else s_vArg.push_back( arg ); } } KConfigGroup* KApplication::config() { return &m_config; } bool KApplication::isRestored() { return false; } KApplication* KApplication::kApplication() { return kapp; } KIconLoader* KApplication::iconLoader() { return &m_iconLoader; } namespace KIO { SimpleJob* mkdir( KUrl ){return 0;} SimpleJob* rmdir( KUrl ){return 0;} SimpleJob* file_delete( KUrl, int ){return 0;} FileCopyJob* file_move( KUrl, KUrl, int, int ) {return 0;} FileCopyJob* file_copy( KUrl, KUrl, int, int ) {return 0;} CopyJob* link( KUrl, KUrl, bool ) {return 0;} ListJob* listRecursive( KUrl, bool, bool ){return 0;} ListJob* listDir( KUrl, bool, bool ){return 0;} StatJob* stat( KUrl, bool, int, int ){return 0;} TransferJob* get( KUrl, int ){return (TransferJob*)0;} TransferJob* put( KUrl, int, int ){return (TransferJob*)0;} }; KActionCollection* KParts::Part::actionCollection() { return 0; } KApplication* KParts::Part::instance() { return kapp; } KLibLoader* KLibLoader::self() { static KLibLoader ll; return ≪ } extern "C" void* init_libkdiff3part(); KLibFactory* KLibLoader::factory(QString const&) { return (KLibFactory*) init_libkdiff3part(); } QObject* KLibFactory::create(QObject* pParent, const QString& name, const QString& classname ) { KParts::Factory* f = qobject_cast(this); if (f!=0) return f->createPartObject( (QWidget*)pParent, name.toLatin1(), pParent, name.toLatin1(), classname.toLatin1(), QStringList() ); else return 0; } #include "kreplacements.moc" diff --git a/src/main.cpp b/src/main.cpp index 57de738..b6022a3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,293 +1,249 @@ /*************************************************************************** main.cpp - Where everything starts. ------------------- begin : Don Jul 11 12:31:29 CEST 2002 copyright : (C) 2002-2007 by Joachim Eibl email : joachim.eibl at gmx.de ***************************************************************************/ /*************************************************************************** * * * 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 "common.h" #include "kdiff3_shell.h" #include "version.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#include #include #ifdef _WIN32 #include #include #endif void initialiseCmdLineArgs(QCommandLineParser* cmdLineParser) { QString configFileName = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, "kdiff3rc"); QFile configFile(configFileName); QString ignorableOptionsLine = "-u;-query;-html;-abort"; if(configFile.open(QIODevice::ReadOnly)) { QTextStream ts(&configFile); while(!ts.atEnd()) { QString line = ts.readLine(); if(line.startsWith("IgnorableCmdLineOptions=")) { int pos = line.indexOf('='); if(pos >= 0) { ignorableOptionsLine = line.mid(pos + 1); } break; } } } //support our own old preferances this is obsolete QStringList sl = ignorableOptionsLine.split(','); if(!sl.isEmpty()) { QStringList ignorableOptions = sl.front().split(';'); for(QStringList::iterator i = ignorableOptions.begin(); i != ignorableOptions.end(); ++i) { (*i).remove('-'); if(!(*i).isEmpty()) { if(i->length() == 1) { cmdLineParser->addOption(QCommandLineOption(QStringList() << i->toLatin1() << QLatin1String("ignore"), i18n("Ignored. (User defined.)"))); } else { cmdLineParser->addOption(QCommandLineOption(QStringList() << i->toLatin1(), i18n("Ignored. (User defined.)"))); } } } } } #ifdef _WIN32 // This command checks the comm static bool isOptionUsed(const QString& s, int argc, char* argv[]) { for(int j = 0; j < argc; ++j) { if(QString("-" + s) == argv[j] || QString("--" + s) == argv[j]) { return true; } } return false; } #endif #if defined(KREPLACEMENTS_H) && !defined(QT_NO_TRANSLATION) class ContextFreeTranslator : public QTranslator { public: ContextFreeTranslator(QObject* pParent) : QTranslator(pParent) {} QString translate(const char* context, const char* sourceText, const char* disambiguation, int /*n*/) const /*override*/ { if(context != 0) return QTranslator::translate(0, sourceText, disambiguation); else return QString(); } }; #endif int main(int argc, char* argv[]) { QApplication app(argc, argv); // KAboutData and QCommandLineParser depend on this being setup. KCrash::initialize(); -#ifdef _WIN32 - /* KDiff3 can be used as replacement for the text-diff and merge tool provided by - Clearcase. This is experimental and so far has only been tested under Windows. - - There are two ways to use KDiff3 with clearcase - - The file lib/mgrs/map contains the list of compare/merge tasks on one side and - the tool on the other. Originally this contains only clearcase tools, but you can - edit this file and put kdiff3 there instead. (Recommended method) - - Exchange the original program with KDiff3: (Hackish, no fine control) - 1. In the Clearcase "bin"-directory rename "cleardiffmrg.exe" to "cleardiffmrg_orig.exe". - 2. Copy kdiff3.exe into that "bin"-directory and rename it to "cleardiffmrg.exe". - (Also copy the other files that are needed by KDiff3 there.) - Now when a file comparison or merge is done by Clearcase then of course KDiff3 will be - run instead. - If the commandline contains the option "-directory" then KDiff3 can't do it but will - run "cleardiffmrg_orig.exe" instead. - */ - - // Write all args into a temporary file. Uncomment this for debugging purposes. - /* - FILE* f = fopen(QDir::toNativeSeparators(QDir::homePath()+"//kdiff3_call_args.txt").toLatin1().data(),"w"); - for(int i=0; i< argc; ++i) - fprintf(f,"Arg %d: %s\n", i, argv[i]); - fclose(f); - - // Call orig cleardiffmrg.exe to see what result it returns. - int result=0; - result = ::_spawnvp(_P_WAIT , "C:\\Programme\\Rational\\ClearCase\\bin\\cleardiffmrg.exe", argv ); - fprintf(f,"Result: %d\n", result ); - fclose(f); - return result; - */ - - // KDiff3 can replace cleardiffmrg from clearcase. But not all functions. - if(isOptionUsed("directory", argc, argv)) - { - return ::_spawnvp(_P_WAIT, "cleardiffmrg_orig", argv); - } - -#endif -#ifdef Q_OS_OS2 - // expand wildcards on the command line - _wildcard(&argc, &argv); -#endif //QApplication::setColorSpec( QApplication::ManyColor ); // Grab all 216 colors const QByteArray& appName = QByteArray::fromRawData("kdiff3", 6); const QByteArray& appCatalog = appName; const QString i18nName = i18n("kdiff3"); QByteArray appVersion = QByteArray::fromRawData(VERSION, sizeof(VERSION)); if(sizeof(void*) == 8) appVersion += " (64 bit)"; else if(sizeof(void*) == 4) appVersion += " (32 bit)"; const QString description = i18n("Tool for Comparison and Merge of Files and Directories"); const QString copyright = i18n("(c) 2002-2014 Joachim Eibl"); const QString& homePage = QStringLiteral("http://kdiff3.sourceforge.net/"); const QString& bugsAddress = QStringLiteral("joachim.eibl" "@" "gmx.de"); KLocalizedString::setApplicationDomain(appCatalog); KAboutData aboutData(appName, i18nName, appVersion, description, KAboutLicense::GPL_V2, copyright, description, homePage, bugsAddress); KAboutData::setApplicationData(aboutData); QCommandLineParser* cmdLineParser = KDiff3Shell::getParser(); cmdLineParser->setApplicationDescription(aboutData.shortDescription()); cmdLineParser->addVersionOption(); cmdLineParser->addHelpOption(); initialiseCmdLineArgs(cmdLineParser); // ignorable command options cmdLineParser->addOption(QCommandLineOption(QStringList() << QLatin1String("m") << QLatin1String("merge"), i18n("Merge the input."))); cmdLineParser->addOption(QCommandLineOption(QStringList() << QLatin1String("b") << QLatin1String("base"), i18n("Explicit base file. For compatibility with certain tools."), QLatin1String("file"))); cmdLineParser->addOption(QCommandLineOption(QStringList() << QLatin1String("o") << QLatin1String("output"), i18n("Output file. Implies -m. E.g.: -o newfile.txt"), QLatin1String("file"))); cmdLineParser->addOption(QCommandLineOption(QStringList() << QLatin1String("out"), i18n("Output file, again. (For compatibility with certain tools.)"), QLatin1String("file"))); cmdLineParser->addOption(QCommandLineOption(QStringList() << QLatin1String("auto"), i18n("No GUI if all conflicts are auto-solvable. (Needs -o file)"))); cmdLineParser->addOption(QCommandLineOption(QStringList() << QLatin1String("qall"), i18n("Don't solve conflicts automatically."))); cmdLineParser->addOption(QCommandLineOption(QStringList() << QLatin1String("L1"), i18n("Visible name replacement for input file 1 (base)."), QLatin1String("alias1"))); cmdLineParser->addOption(QCommandLineOption(QStringList() << QLatin1String("L2"), i18n("Visible name replacement for input file 2."), QLatin1String("alias2"))); cmdLineParser->addOption(QCommandLineOption(QStringList() << QLatin1String("L3"), i18n("Visible name replacement for input file 3."), QLatin1String("alias3"))); cmdLineParser->addOption(QCommandLineOption(QStringList() << QLatin1String("L") << QLatin1String("fname alias"), i18n("Alternative visible name replacement. Supply this once for every input."))); cmdLineParser->addOption(QCommandLineOption(QStringList() << QLatin1String("cs"), i18n("Override a config setting. Use once for every setting. E.g.: --cs \"AutoAdvance=1\""), QLatin1String("string"))); cmdLineParser->addOption(QCommandLineOption(QStringList() << QLatin1String("confighelp"), i18n("Show list of config settings and current values."))); cmdLineParser->addOption(QCommandLineOption(QStringList() << QLatin1String("config"), i18n("Use a different config file."), QLatin1String("file"))); // other command options cmdLineParser->addPositionalArgument(QLatin1String("[File1]"), i18n("file1 to open (base, if not specified via --base)")); cmdLineParser->addPositionalArgument(QLatin1String("[File2]"), i18n("file2 to open")); cmdLineParser->addPositionalArgument(QLatin1String("[File3]"), i18n("file3 to open")); /* Don't use QCommandLineParser::process as it auto terminates the program if an option is not reconized. Further more errors are directed to the console alone if not running on windows. This makes for a bad user experiance when run from a graphical interface such as kde. Don't assume that this only happens when running from a commandline. */ if(!cmdLineParser->parse(QCoreApplication::arguments())) { QString errorMessage = cmdLineParser->errorText(); QString helpText = cmdLineParser->helpText(); QMessageBox::warning(nullptr, aboutData.displayName(), "

" + errorMessage + "

" + helpText + "
"); -#if !defined(_WIN32) && !defined(Q_OS_OS2) +#if !defined(_WIN32) fputs(qPrintable(errorMessage), stderr); fputs("\n\n", stderr); fputs(qPrintable(helpText + "\n"), stderr); fputs("\n", stderr); #endif exit(1); } if(cmdLineParser->isSet(QStringLiteral("version"))) { QMessageBox::information(nullptr, aboutData.displayName(), aboutData.displayName() + ' ' + aboutData.version()); -#if !defined(_WIN32) && !defined(Q_OS_OS2) +#if !defined(_WIN32) printf("%s %s\n", appName.constData(), appVersion.constData()); #endif exit(0); } if(cmdLineParser->isSet(QStringLiteral("help"))) { QMessageBox::warning(nullptr, aboutData.displayName(), "
" + cmdLineParser->helpText() + "
"); -#if !defined(_WIN32) && !defined(Q_OS_OS2) +#if !defined(_WIN32) fputs(qPrintable(cmdLineParser->helpText()), stdout); #endif exit(0); } //cmdLineParser->process( app ); //must be after process or parse call aboutData.setupCommandLine(cmdLineParser); /** * take component name and org. name from KAboutData */ app.setApplicationName(aboutData.componentName()); app.setApplicationDisplayName(aboutData.displayName()); app.setOrganizationDomain(aboutData.organizationDomain()); app.setApplicationVersion(aboutData.version()); #if defined(KREPLACEMENTS_H) && !defined(QT_NO_TRANSLATION) QString locale; locale = app.config()->readEntry("Language", "Auto"); int spacePos = locale.indexOf(' '); if(spacePos > 0) locale = locale.left(spacePos); ContextFreeTranslator kdiff3Translator(0); QTranslator qtTranslator(0); if(locale != "en_orig") { QString translationDir = getTranslationDir(locale); kdiff3Translator.load(QLocale::system(), QString("kdiff3_"), translationDir); app.installTranslator(&kdiff3Translator); qtTranslator.load(QLocale::system(), QString("qt_"), translationDir); app.installTranslator(&qtTranslator); } #endif KDiff3Shell* p = new KDiff3Shell(); p->show(); //p->setWindowState( p->windowState() | Qt::WindowActive ); // Patch for ubuntu: window not active on startup //app.installEventFilter( new CFilter ); int retVal = app.exec(); /* if (QApplication::clipboard()->text().size() == 0) QApplication::clipboard()->clear(); // Patch for Ubuntu: Fix issue with Qt clipboard*/ return retVal; } // Suppress warning with --enable-final #undef VERSION diff --git a/src/optiondialog.cpp b/src/optiondialog.cpp index 1c64b45..66616f5 100644 --- a/src/optiondialog.cpp +++ b/src/optiondialog.cpp @@ -1,2047 +1,2047 @@ /* * kdiff3 - Text Diff And Merge Tool * Copyright (C) 2002-2009 Joachim Eibl, joachim.eibl at gmx.de * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "diff.h" #include "optiondialog.h" #include "smalldialogs.h" #define KDIFF3_CONFIG_GROUP "KDiff3 Options" static QString s_historyEntryStartRegExpToolTip; static QString s_historyEntryStartSortKeyOrderToolTip; static QString s_autoMergeRegExpToolTip; static QString s_historyStartRegExpToolTip; void OptionDialog::addOptionItem(OptionItem* p) { m_optionItemList.push_back(p); } class OptionItem { public: OptionItem(OptionDialog* pOptionDialog, const QString& saveName) { assert(pOptionDialog != nullptr); pOptionDialog->addOptionItem(this); m_saveName = saveName; m_bPreserved = false; } virtual ~OptionItem() {} virtual void setToDefault() = 0; virtual void setToCurrent() = 0; virtual void apply() = 0; virtual void write(ValueMap*) = 0; virtual void read(ValueMap*) = 0; void doPreserve() { if(!m_bPreserved) { m_bPreserved = true; preserve(); } } void doUnpreserve() { if(m_bPreserved) { unpreserve(); } } QString getSaveName() { return m_saveName; } protected: virtual void preserve() = 0; virtual void unpreserve() = 0; bool m_bPreserved; QString m_saveName; }; template class OptionItemT : public OptionItem { public: OptionItemT(OptionDialog* pOptionDialog, const QString& saveName) : OptionItem(pOptionDialog, saveName) { } protected: virtual void preserve() { m_preservedVal = *m_pVar; } virtual void unpreserve() { *m_pVar = m_preservedVal; } T* m_pVar; T m_preservedVal; T m_defaultVal; }; class OptionCheckBox : public QCheckBox, public OptionItemT { public: OptionCheckBox(QString text, bool bDefaultVal, const QString& saveName, bool* pbVar, QWidget* pParent, OptionDialog* pOD) : QCheckBox(text, pParent), OptionItemT(pOD, saveName) { m_pVar = pbVar; m_defaultVal = bDefaultVal; } void setToDefault() { setChecked(m_defaultVal); } void setToCurrent() { setChecked(*m_pVar); } void apply() { *m_pVar = isChecked(); } void write(ValueMap* config) { config->writeEntry(m_saveName, *m_pVar); } void read(ValueMap* config) { *m_pVar = config->readBoolEntry(m_saveName, *m_pVar); } private: OptionCheckBox(const OptionCheckBox&); // private copy constructor without implementation }; class OptionRadioButton : public QRadioButton, public OptionItemT { public: OptionRadioButton(QString text, bool bDefaultVal, const QString& saveName, bool* pbVar, QWidget* pParent, OptionDialog* pOD) : QRadioButton(text, pParent), OptionItemT(pOD, saveName) { m_pVar = pbVar; m_defaultVal = bDefaultVal; } void setToDefault() { setChecked(m_defaultVal); } void setToCurrent() { setChecked(*m_pVar); } void apply() { *m_pVar = isChecked(); } void write(ValueMap* config) { config->writeEntry(m_saveName, *m_pVar); } void read(ValueMap* config) { *m_pVar = config->readBoolEntry(m_saveName, *m_pVar); } private: OptionRadioButton(const OptionRadioButton&); // private copy constructor without implementation }; template class OptionT : public OptionItemT { public: OptionT(const T& defaultVal, const QString& saveName, T* pVar, OptionDialog* pOD) : OptionItemT(pOD, saveName) { this->m_pVar = pVar; *this->m_pVar = defaultVal; } OptionT(const QString& saveName, T* pVar, OptionDialog* pOD) : OptionItemT(pOD, saveName) { this->m_pVar = pVar; } void setToDefault() {} void setToCurrent() {} void apply() {} void write(ValueMap* vm) { writeEntry(vm, this->m_saveName, *this->m_pVar); } void read(ValueMap* vm) { *this->m_pVar = vm->readEntry(this->m_saveName, *this->m_pVar); } private: OptionT(const OptionT&); // private copy constructor without implementation }; template void writeEntry(ValueMap* vm, const QString& saveName, const T& v) { vm->writeEntry(saveName, v); } static void writeEntry(ValueMap* vm, const QString& saveName, const QStringList& v) { vm->writeEntry(saveName, v); } //static void readEntry(ValueMap* vm, const QString& saveName, bool& v ) { v = vm->readBoolEntry( saveName, v ); } //static void readEntry(ValueMap* vm, const QString& saveName, int& v ) { v = vm->readNumEntry( saveName, v ); } //static void readEntry(ValueMap* vm, const QString& saveName, QSize& v ) { v = vm->readSizeEntry( saveName, &v ); } //static void readEntry(ValueMap* vm, const QString& saveName, QPoint& v ) { v = vm->readPointEntry( saveName, &v ); } //static void readEntry(ValueMap* vm, const QString& saveName, QStringList& v ){ v = vm->readListEntry( saveName, QStringList(), '|' ); } typedef OptionT OptionToggleAction; typedef OptionT OptionNum; typedef OptionT OptionPoint; typedef OptionT OptionSize; typedef OptionT OptionStringList; FontChooser::FontChooser(QWidget* pParent) : QGroupBox(pParent) { QVBoxLayout* pLayout = new QVBoxLayout(this); m_pLabel = new QLabel(QString(), this); pLayout->addWidget(m_pLabel); QChar visualTab(0x2192); QChar visualSpace((ushort)0xb7); m_pExampleTextEdit = new QPlainTextEdit(QString("The quick brown fox jumps over the river\n" "but the little red hen escapes with a shiver.\n" ":-)") + visualTab + visualSpace, this); m_pExampleTextEdit->setFont(m_font); m_pExampleTextEdit->setReadOnly(true); pLayout->addWidget(m_pExampleTextEdit); m_pSelectFont = new QPushButton(i18n("Change Font"), this); m_pSelectFont->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); connect(m_pSelectFont, SIGNAL(clicked()), this, SLOT(slotSelectFont())); pLayout->addWidget(m_pSelectFont); pLayout->setAlignment(m_pSelectFont, Qt::AlignRight); } QFont FontChooser::font() { return m_font; //QFont("courier",10); } void FontChooser::setFont(const QFont& font, bool) { m_font = font; m_pExampleTextEdit->setFont(m_font); m_pLabel->setText(i18n("Font: %1, %2, %3\n\nExample:", m_font.family(), m_font.styleName(), m_font.pointSize())); //update(); } void FontChooser::slotSelectFont() { bool bOk; m_font = QFontDialog::getFont(&bOk, m_font); m_pExampleTextEdit->setFont(m_font); m_pLabel->setText(i18n("Font: %1, %2, %3\n\nExample:", m_font.family(), m_font.styleName(), m_font.pointSize())); } class OptionFontChooser : public FontChooser, public OptionItemT { public: OptionFontChooser(const QFont& defaultVal, const QString& saveName, QFont* pVar, QWidget* pParent, OptionDialog* pOD) : FontChooser(pParent), OptionItemT(pOD, saveName) { m_pVar = pVar; *m_pVar = defaultVal; m_defaultVal = defaultVal; } void setToDefault() { setFont(m_defaultVal, false); } void setToCurrent() { setFont(*m_pVar, false); } void apply() { *m_pVar = font(); } void write(ValueMap* config) { config->writeEntry(m_saveName, *m_pVar); } void read(ValueMap* config) { *m_pVar = config->readFontEntry(m_saveName, m_pVar); } private: OptionFontChooser(const OptionToggleAction&); // private copy constructor without implementation }; class OptionColorButton : public KColorButton, public OptionItemT { public: OptionColorButton(QColor defaultVal, const QString& saveName, QColor* pVar, QWidget* pParent, OptionDialog* pOD) : KColorButton(pParent), OptionItemT(pOD, saveName) { m_pVar = pVar; m_defaultVal = defaultVal; } void setToDefault() { setColor(m_defaultVal); } void setToCurrent() { setColor(*m_pVar); } void apply() { *m_pVar = color(); } void write(ValueMap* config) { config->writeEntry(m_saveName, *m_pVar); } void read(ValueMap* config) { *m_pVar = config->readColorEntry(m_saveName, m_pVar); } private: OptionColorButton(const OptionColorButton&); // private copy constructor without implementation }; class OptionLineEdit : public QComboBox, public OptionItemT { public: OptionLineEdit(const QString& defaultVal, const QString& saveName, QString* pVar, QWidget* pParent, OptionDialog* pOD) : QComboBox(pParent), OptionItemT(pOD, saveName) { setMinimumWidth(50); setEditable(true); m_pVar = pVar; m_defaultVal = defaultVal; m_list.push_back(defaultVal); insertText(); } void setToDefault() { setEditText(m_defaultVal); } void setToCurrent() { setEditText(*m_pVar); } void apply() { *m_pVar = currentText(); insertText(); } void write(ValueMap* config) { config->writeEntry(m_saveName, m_list); } void read(ValueMap* config) { m_list = config->readListEntry(m_saveName, QStringList(m_defaultVal)); if(!m_list.empty()) *m_pVar = m_list.front(); clear(); insertItems(0, m_list); } private: void insertText() { // Check if the text exists. If yes remove it and push it in as first element QString current = currentText(); m_list.removeAll(current); m_list.push_front(current); clear(); if(m_list.size() > 10) m_list.erase(m_list.begin() + 10, m_list.end()); insertItems(0, m_list); } OptionLineEdit(const OptionLineEdit&); // private copy constructor without implementation QStringList m_list; }; #if defined QT_NO_VALIDATOR #error No validator #endif class OptionIntEdit : public QLineEdit, public OptionItemT { public: OptionIntEdit(int defaultVal, const QString& saveName, int* pVar, int rangeMin, int rangeMax, QWidget* pParent, OptionDialog* pOD) : QLineEdit(pParent), OptionItemT(pOD, saveName) { m_pVar = pVar; m_defaultVal = defaultVal; QIntValidator* v = new QIntValidator(this); v->setRange(rangeMin, rangeMax); setValidator(v); } void setToDefault() { QString s; s.setNum(m_defaultVal); setText(s); } void setToCurrent() { QString s; s.setNum(*m_pVar); setText(s); } void apply() { const QIntValidator* v = static_cast(validator()); *m_pVar = minMaxLimiter(text().toInt(), v->bottom(), v->top()); setText(QString::number(*m_pVar)); } void write(ValueMap* config) { config->writeEntry(m_saveName, *m_pVar); } void read(ValueMap* config) { *m_pVar = config->readNumEntry(m_saveName, *m_pVar); } private: OptionIntEdit(const OptionIntEdit&); // private copy constructor without implementation }; class OptionComboBox : public QComboBox, public OptionItem { public: OptionComboBox(int defaultVal, const QString& saveName, int* pVarNum, QWidget* pParent, OptionDialog* pOD) : QComboBox(pParent), OptionItem(pOD, saveName) { setMinimumWidth(50); m_pVarNum = pVarNum; m_pVarStr = nullptr; m_defaultVal = defaultVal; setEditable(false); } OptionComboBox(int defaultVal, const QString& saveName, QString* pVarStr, QWidget* pParent, OptionDialog* pOD) : QComboBox(pParent), OptionItem(pOD, saveName) { m_pVarNum = nullptr; m_pVarStr = pVarStr; m_defaultVal = defaultVal; setEditable(false); } void setToDefault() { setCurrentIndex(m_defaultVal); if(m_pVarStr != nullptr) { *m_pVarStr = currentText(); } } void setToCurrent() { if(m_pVarNum != nullptr) setCurrentIndex(*m_pVarNum); else setText(*m_pVarStr); } void apply() { if(m_pVarNum != nullptr) { *m_pVarNum = currentIndex(); } else { *m_pVarStr = currentText(); } } void write(ValueMap* config) { if(m_pVarStr != nullptr) config->writeEntry(m_saveName, *m_pVarStr); else config->writeEntry(m_saveName, *m_pVarNum); } void read(ValueMap* config) { if(m_pVarStr != nullptr) setText(config->readEntry(m_saveName, currentText())); else *m_pVarNum = config->readNumEntry(m_saveName, *m_pVarNum); } void preserve() { if(m_pVarStr != nullptr) { m_preservedStrVal = *m_pVarStr; } else { m_preservedNumVal = *m_pVarNum; } } void unpreserve() { if(m_pVarStr != nullptr) { *m_pVarStr = m_preservedStrVal; } else { *m_pVarNum = m_preservedNumVal; } } private: OptionComboBox(const OptionIntEdit&); // private copy constructor without implementation int* m_pVarNum; int m_preservedNumVal = 0; QString* m_pVarStr; QString m_preservedStrVal; int m_defaultVal; void setText(const QString& s) { // Find the string in the combobox-list, don't change the value if nothing fits. for(int i = 0; i < count(); ++i) { if(itemText(i) == s) { if(m_pVarNum != nullptr) *m_pVarNum = i; if(m_pVarStr != nullptr) *m_pVarStr = s; setCurrentIndex(i); return; } } } }; class OptionEncodingComboBox : public QComboBox, public OptionItem { Q_OBJECT QVector m_codecVec; QTextCodec** m_ppVarCodec; public: OptionEncodingComboBox(const QString& saveName, QTextCodec** ppVarCodec, QWidget* pParent, OptionDialog* pOD) : QComboBox(pParent), OptionItem(pOD, saveName) { m_ppVarCodec = ppVarCodec; insertCodec(i18n("Unicode, 8 bit"), QTextCodec::codecForName("UTF-8")); insertCodec(i18n("Unicode"), QTextCodec::codecForName("iso-10646-UCS-2")); insertCodec(i18n("Latin1"), QTextCodec::codecForName("iso 8859-1")); // First sort codec names: std::map names; QList mibs = QTextCodec::availableMibs(); foreach(int i, mibs) { QTextCodec* c = QTextCodec::codecForMib(i); if(c != nullptr) names[QString(c->name()).toUpper()] = c; } std::map::iterator it; for(it = names.begin(); it != names.end(); ++it) { insertCodec("", it->second); } this->setToolTip(i18n( "Change this if non-ASCII characters are not displayed correctly.")); } void insertCodec(const QString& visibleCodecName, QTextCodec* c) { if(c != nullptr) { for(int i = 0; i < m_codecVec.size(); ++i) { if(c == m_codecVec[i]) return; // don't insert any codec twice } addItem(visibleCodecName.isEmpty() ? QString(c->name()) : visibleCodecName + " (" + c->name() + ")", (int)m_codecVec.size()); m_codecVec.push_back(c); } } void setToDefault() { QString defaultName = QTextCodec::codecForLocale()->name(); for(int i = 0; i < count(); ++i) { if(defaultName == itemText(i) && m_codecVec[i] == QTextCodec::codecForLocale()) { setCurrentIndex(i); if(m_ppVarCodec != nullptr) { *m_ppVarCodec = m_codecVec[i]; } return; } } setCurrentIndex(0); if(m_ppVarCodec != nullptr) { *m_ppVarCodec = m_codecVec[0]; } } void setToCurrent() { if(m_ppVarCodec != nullptr) { for(int i = 0; i < m_codecVec.size(); ++i) { if(*m_ppVarCodec == m_codecVec[i]) { setCurrentIndex(i); break; } } } } void apply() { if(m_ppVarCodec != nullptr) { *m_ppVarCodec = m_codecVec[currentIndex()]; } } void write(ValueMap* config) { if(m_ppVarCodec != nullptr) config->writeEntry(m_saveName, QString((*m_ppVarCodec)->name())); } void read(ValueMap* config) { QString codecName = config->readEntry(m_saveName, QString(m_codecVec[currentIndex()]->name())); for(int i = 0; i < m_codecVec.size(); ++i) { if(codecName == m_codecVec[i]->name()) { setCurrentIndex(i); if(m_ppVarCodec != nullptr) *m_ppVarCodec = m_codecVec[i]; break; } } } protected: void preserve() { m_preservedVal = currentIndex(); } void unpreserve() { setCurrentIndex(m_preservedVal); } int m_preservedVal; }; OptionDialog::OptionDialog(bool bShowDirMergeSettings, QWidget* parent, char* name) : // KPageDialog( IconList, i18n("Configure"), Help|Default|Apply|Ok|Cancel, // Ok, parent, name, true /*modal*/, true ) KPageDialog(parent) { setFaceType(List); setWindowTitle(i18n("Configure")); setStandardButtons(QDialogButtonBox::Help | QDialogButtonBox::RestoreDefaults | QDialogButtonBox::Apply | QDialogButtonBox::Ok | QDialogButtonBox::Cancel); // TODO KF5 necessary? setDefaultButton( Ok ); setObjectName(name); setModal(true); //showButtonSeparator( true ); //setHelp( "kdiff3/index.html", QString::null ); setupFontPage(); setupColorPage(); setupEditPage(); setupDiffPage(); setupMergePage(); setupOtherOptions(); if(bShowDirMergeSettings) setupDirectoryMergePage(); setupRegionalPage(); setupIntegrationPage(); //setupKeysPage(); // Initialize all values in the dialog resetToDefaults(); slotApply(); connect(button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &OptionDialog::slotApply); connect(button(QDialogButtonBox::Ok), &QPushButton::clicked, this, &OptionDialog::slotOk); connect(button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, this, &OptionDialog::slotDefault); connect(button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &QDialog::reject); connect(button(QDialogButtonBox::Help), &QPushButton::clicked, this, &OptionDialog::helpRequested); //connect(this, &OptionDialog::applyClicked, this, &OptionDialog::slotApply); //helpClicked() is connected in KDiff3App::KDiff3App -- Really where? //connect(this, &OptionDialog::defaultClicked, this, &OptionDialog::slotDefault); } void OptionDialog::helpRequested() { KHelpClient::invokeHelp(QStringLiteral("kdiff3/index.html"), QString()); } OptionDialog::~OptionDialog(void) { } void OptionDialog::setupOtherOptions() { new OptionToggleAction(false, "AutoAdvance", &m_options.m_bAutoAdvance, this); new OptionToggleAction(true, "ShowWhiteSpaceCharacters", &m_options.m_bShowWhiteSpaceCharacters, this); new OptionToggleAction(true, "ShowWhiteSpace", &m_options.m_bShowWhiteSpace, this); new OptionToggleAction(false, "ShowLineNumbers", &m_options.m_bShowLineNumbers, this); new OptionToggleAction(true, "HorizDiffWindowSplitting", &m_options.m_bHorizDiffWindowSplitting, this); new OptionToggleAction(false, "WordWrap", &m_options.m_bWordWrap, this); new OptionToggleAction(true, "ShowIdenticalFiles", &m_options.m_bDmShowIdenticalFiles, this); new OptionToggleAction(true, "Show Toolbar", &m_options.m_bShowToolBar, this); new OptionToggleAction(true, "Show Statusbar", &m_options.m_bShowStatusBar, this); /* TODO manage toolbar positioning new OptionNum( (int)KToolBar::Top, "ToolBarPos", &m_toolBarPos, this ); */ new OptionSize(QSize(600, 400), "Geometry", &m_options.m_geometry, this); new OptionPoint(QPoint(0, 22), "Position", &m_options.m_position, this); new OptionToggleAction(false, "WindowStateMaximised", &m_options.m_bMaximised, this); new OptionStringList("RecentAFiles", &m_options.m_recentAFiles, this); new OptionStringList("RecentBFiles", &m_options.m_recentBFiles, this); new OptionStringList("RecentCFiles", &m_options.m_recentCFiles, this); new OptionStringList("RecentOutputFiles", &m_options.m_recentOutputFiles, this); new OptionStringList("RecentEncodings", &m_options.m_recentEncodings, this); } void OptionDialog::setupFontPage(void) { QFrame* page = new QFrame(); KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18n("Font")); pageItem->setHeader(i18n("Editor & Diff Output Font")); //not all themes have this icon if(QIcon::hasThemeIcon(QStringLiteral("font-select-symbolic"))) pageItem->setIcon(QIcon::fromTheme(QStringLiteral("font-select-symbolic"))); else pageItem->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-font"))); addPage(pageItem); QVBoxLayout* topLayout = new QVBoxLayout(page); topLayout->setMargin(5); //requires QT 5.2 or later. static const QFont defaultFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); ; static QFont defaultAppFont = QApplication::font(); OptionFontChooser* pAppFontChooser = new OptionFontChooser(defaultAppFont, "ApplicationFont", &m_options.m_appFont, page, this); topLayout->addWidget(pAppFontChooser); pAppFontChooser->setTitle(i18n("Application font")); OptionFontChooser* pFontChooser = new OptionFontChooser(defaultFont, "Font", &m_options.m_font, page, this); topLayout->addWidget(pFontChooser); pFontChooser->setTitle(i18n("File view font")); QGridLayout* gbox = new QGridLayout(); topLayout->addLayout(gbox); //int line=0; // This currently does not work (see rendering in class DiffTextWindow) //OptionCheckBox* pItalicDeltas = new OptionCheckBox( i18n("Italic font for deltas"), false, "ItalicForDeltas", &m_options.m_bItalicForDeltas, page, this ); //gbox->addWidget( pItalicDeltas, line, 0, 1, 2 ); //pItalicDeltas->setToolTip( i18n( // "Selects the italic version of the font for differences.\n" // "If the font doesn't support italic characters, then this does nothing.") // ); } void OptionDialog::setupColorPage(void) { QFrame* page = new QFrame(); KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18n("Color")); pageItem->setHeader(i18n("Colors Settings")); pageItem->setIcon(QIcon::fromTheme(QStringLiteral("colormanagement"))); addPage(pageItem); QVBoxLayout* topLayout = new QVBoxLayout(page); topLayout->setMargin(5); QGridLayout* gbox = new QGridLayout(); gbox->setColumnStretch(1, 5); topLayout->addLayout(gbox); QLabel* label; int line = 0; int depth = QPixmap::defaultDepth(); bool bLowColor = depth <= 8; label = new QLabel(i18n("Editor and Diff Views:"), page); gbox->addWidget(label, line, 0); QFont f(label->font()); f.setBold(true); label->setFont(f); ++line; OptionColorButton* pFgColor = new OptionColorButton(Qt::black, "FgColor", &m_options.m_fgColor, page, this); label = new QLabel(i18n("Foreground color:"), page); label->setBuddy(pFgColor); gbox->addWidget(label, line, 0); gbox->addWidget(pFgColor, line, 1); ++line; OptionColorButton* pBgColor = new OptionColorButton(Qt::white, "BgColor", &m_options.m_bgColor, page, this); label = new QLabel(i18n("Background color:"), page); label->setBuddy(pBgColor); gbox->addWidget(label, line, 0); gbox->addWidget(pBgColor, line, 1); ++line; OptionColorButton* pDiffBgColor = new OptionColorButton( bLowColor ? QColor(Qt::lightGray) : qRgb(224, 224, 224), "DiffBgColor", &m_options.m_diffBgColor, page, this); label = new QLabel(i18n("Diff background color:"), page); label->setBuddy(pDiffBgColor); gbox->addWidget(label, line, 0); gbox->addWidget(pDiffBgColor, line, 1); ++line; OptionColorButton* pColorA = new OptionColorButton( bLowColor ? qRgb(0, 0, 255) : qRgb(0, 0, 200) /*blue*/, "ColorA", &m_options.m_colorA, page, this); label = new QLabel(i18n("Color A:"), page); label->setBuddy(pColorA); gbox->addWidget(label, line, 0); gbox->addWidget(pColorA, line, 1); ++line; OptionColorButton* pColorB = new OptionColorButton( bLowColor ? qRgb(0, 128, 0) : qRgb(0, 150, 0) /*green*/, "ColorB", &m_options.m_colorB, page, this); label = new QLabel(i18n("Color B:"), page); label->setBuddy(pColorB); gbox->addWidget(label, line, 0); gbox->addWidget(pColorB, line, 1); ++line; OptionColorButton* pColorC = new OptionColorButton( bLowColor ? qRgb(128, 0, 128) : qRgb(150, 0, 150) /*magenta*/, "ColorC", &m_options.m_colorC, page, this); label = new QLabel(i18n("Color C:"), page); label->setBuddy(pColorC); gbox->addWidget(label, line, 0); gbox->addWidget(pColorC, line, 1); ++line; OptionColorButton* pColorForConflict = new OptionColorButton(Qt::red, "ColorForConflict", &m_options.m_colorForConflict, page, this); label = new QLabel(i18n("Conflict color:"), page); label->setBuddy(pColorForConflict); gbox->addWidget(label, line, 0); gbox->addWidget(pColorForConflict, line, 1); ++line; OptionColorButton* pColor = new OptionColorButton( bLowColor ? qRgb(192, 192, 192) : qRgb(220, 220, 100), "CurrentRangeBgColor", &m_options.m_currentRangeBgColor, page, this); label = new QLabel(i18n("Current range background color:"), page); label->setBuddy(pColor); gbox->addWidget(label, line, 0); gbox->addWidget(pColor, line, 1); ++line; pColor = new OptionColorButton( bLowColor ? qRgb(255, 255, 0) : qRgb(255, 255, 150), "CurrentRangeDiffBgColor", &m_options.m_currentRangeDiffBgColor, page, this); label = new QLabel(i18n("Current range diff background color:"), page); label->setBuddy(pColor); gbox->addWidget(label, line, 0); gbox->addWidget(pColor, line, 1); ++line; pColor = new OptionColorButton(qRgb(0xff, 0xd0, 0x80), "ManualAlignmentRangeColor", &m_options.m_manualHelpRangeColor, page, this); label = new QLabel(i18n("Color for manually aligned difference ranges:"), page); label->setBuddy(pColor); gbox->addWidget(label, line, 0); gbox->addWidget(pColor, line, 1); ++line; label = new QLabel(i18n("Directory Comparison View:"), page); gbox->addWidget(label, line, 0); label->setFont(f); ++line; pColor = new OptionColorButton(qRgb(0, 0xd0, 0), "NewestFileColor", &m_options.m_newestFileColor, page, this); label = new QLabel(i18n("Newest file color:"), page); label->setBuddy(pColor); gbox->addWidget(label, line, 0); gbox->addWidget(pColor, line, 1); QString dirColorTip = i18n("Changing this color will only be effective when starting the next directory comparison."); label->setToolTip(dirColorTip); ++line; pColor = new OptionColorButton(qRgb(0xf0, 0, 0), "OldestFileColor", &m_options.m_oldestFileColor, page, this); label = new QLabel(i18n("Oldest file color:"), page); label->setBuddy(pColor); gbox->addWidget(label, line, 0); gbox->addWidget(pColor, line, 1); label->setToolTip(dirColorTip); ++line; pColor = new OptionColorButton(qRgb(0xc0, 0xc0, 0), "MidAgeFileColor", &m_options.m_midAgeFileColor, page, this); label = new QLabel(i18n("Middle age file color:"), page); label->setBuddy(pColor); gbox->addWidget(label, line, 0); gbox->addWidget(pColor, line, 1); label->setToolTip(dirColorTip); ++line; pColor = new OptionColorButton(qRgb(0, 0, 0), "MissingFileColor", &m_options.m_missingFileColor, page, this); label = new QLabel(i18n("Color for missing files:"), page); label->setBuddy(pColor); gbox->addWidget(label, line, 0); gbox->addWidget(pColor, line, 1); label->setToolTip(dirColorTip); ++line; topLayout->addStretch(10); } void OptionDialog::setupEditPage(void) { QFrame* page = new QFrame(); KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18n("Editor")); pageItem->setHeader(i18n("Editor Behavior")); pageItem->setIcon(QIcon::fromTheme(QStringLiteral("accessories-text-editor"))); addPage(pageItem); QVBoxLayout* topLayout = new QVBoxLayout(page); topLayout->setMargin(5); QGridLayout* gbox = new QGridLayout(); gbox->setColumnStretch(1, 5); topLayout->addLayout(gbox); QLabel* label; int line = 0; OptionCheckBox* pReplaceTabs = new OptionCheckBox(i18n("Tab inserts spaces"), false, "ReplaceTabs", &m_options.m_bReplaceTabs, page, this); gbox->addWidget(pReplaceTabs, line, 0, 1, 2); pReplaceTabs->setToolTip(i18n( "On: Pressing tab generates the appropriate number of spaces.\n" "Off: A tab character will be inserted.")); ++line; OptionIntEdit* pTabSize = new OptionIntEdit(8, "TabSize", &m_options.m_tabSize, 1, 100, page, this); label = new QLabel(i18n("Tab size:"), page); label->setBuddy(pTabSize); gbox->addWidget(label, line, 0); gbox->addWidget(pTabSize, line, 1); ++line; OptionCheckBox* pAutoIndentation = new OptionCheckBox(i18n("Auto indentation"), true, "AutoIndentation", &m_options.m_bAutoIndentation, page, this); gbox->addWidget(pAutoIndentation, line, 0, 1, 2); pAutoIndentation->setToolTip(i18n( "On: The indentation of the previous line is used for a new line.\n")); ++line; OptionCheckBox* pAutoCopySelection = new OptionCheckBox(i18n("Auto copy selection"), false, "AutoCopySelection", &m_options.m_bAutoCopySelection, page, this); gbox->addWidget(pAutoCopySelection, line, 0, 1, 2); pAutoCopySelection->setToolTip(i18n( "On: Any selection is immediately written to the clipboard.\n" "Off: You must explicitely copy e.g. via Ctrl-C.")); ++line; label = new QLabel(i18n("Line end style:"), page); gbox->addWidget(label, line, 0); OptionComboBox* pLineEndStyle = new OptionComboBox(eLineEndStyleAutoDetect, "LineEndStyle", &m_options.m_lineEndStyle, page, this); gbox->addWidget(pLineEndStyle, line, 1); pLineEndStyle->insertItem(eLineEndStyleUnix, "Unix"); pLineEndStyle->insertItem(eLineEndStyleDos, "Dos/Windows"); pLineEndStyle->insertItem(eLineEndStyleAutoDetect, "Autodetect"); label->setToolTip(i18n( "Sets the line endings for when an edited file is saved.\n" "DOS/Windows: CR+LF; UNIX: LF; with CR=0D, LF=0A")); ++line; topLayout->addStretch(10); } void OptionDialog::setupDiffPage(void) { QFrame* page = new QFrame(); KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18n("Diff")); pageItem->setHeader(i18n("Diff Settings")); pageItem->setIcon(QIcon::fromTheme(QStringLiteral("text-x-patch"))); addPage(pageItem); QVBoxLayout* topLayout = new QVBoxLayout(page); topLayout->setMargin(5); QGridLayout* gbox = new QGridLayout(); gbox->setColumnStretch(1, 5); topLayout->addLayout(gbox); int line = 0; QLabel* label = nullptr; m_options.m_bPreserveCarriageReturn = false; /* OptionCheckBox* pPreserveCarriageReturn = new OptionCheckBox( i18n("Preserve carriage return"), false, "PreserveCarriageReturn", &m_options.m_bPreserveCarriageReturn, page, this ); gbox->addWidget( pPreserveCarriageReturn, line, 0, 1, 2 ); pPreserveCarriageReturn->setToolTip( i18n( "Show carriage return characters '\\r' if they exist.\n" "Helps to compare files that were modified under different operating systems.") ); ++line; */ QString treatAsWhiteSpace = " (" + i18n("Treat as white space.") + ")"; OptionCheckBox* pIgnoreNumbers = new OptionCheckBox(i18n("Ignore numbers") + treatAsWhiteSpace, false, "IgnoreNumbers", &m_options.m_bIgnoreNumbers, page, this); gbox->addWidget(pIgnoreNumbers, line, 0, 1, 2); pIgnoreNumbers->setToolTip(i18n( "Ignore number characters during line matching phase. (Similar to Ignore white space.)\n" "Might help to compare files with numeric data.")); ++line; OptionCheckBox* pIgnoreComments = new OptionCheckBox(i18n("Ignore C/C++ comments") + treatAsWhiteSpace, false, "IgnoreComments", &m_options.m_bIgnoreComments, page, this); gbox->addWidget(pIgnoreComments, line, 0, 1, 2); pIgnoreComments->setToolTip(i18n("Treat C/C++ comments like white space.")); ++line; OptionCheckBox* pIgnoreCase = new OptionCheckBox(i18n("Ignore case") + treatAsWhiteSpace, false, "IgnoreCase", &m_options.m_bIgnoreCase, page, this); gbox->addWidget(pIgnoreCase, line, 0, 1, 2); pIgnoreCase->setToolTip(i18n( "Treat case differences like white space changes. ('a'<=>'A')")); ++line; label = new QLabel(i18n("Preprocessor command:"), page); gbox->addWidget(label, line, 0); OptionLineEdit* pLE = new OptionLineEdit("", "PreProcessorCmd", &m_options.m_PreProcessorCmd, page, this); gbox->addWidget(pLE, line, 1); label->setToolTip(i18n("User defined pre-processing. (See the docs for details.)")); ++line; label = new QLabel(i18n("Line-matching preprocessor command:"), page); gbox->addWidget(label, line, 0); pLE = new OptionLineEdit("", "LineMatchingPreProcessorCmd", &m_options.m_LineMatchingPreProcessorCmd, page, this); gbox->addWidget(pLE, line, 1); label->setToolTip(i18n("This pre-processor is only used during line matching.\n(See the docs for details.)")); ++line; OptionCheckBox* pTryHard = new OptionCheckBox(i18n("Try hard (slower)"), true, "TryHard", &m_options.m_bTryHard, page, this); gbox->addWidget(pTryHard, line, 0, 1, 2); pTryHard->setToolTip(i18n( "Enables the --minimal option for the external diff.\n" "The analysis of big files will be much slower.")); ++line; OptionCheckBox* pDiff3AlignBC = new OptionCheckBox(i18n("Align B and C for 3 input files"), false, "Diff3AlignBC", &m_options.m_bDiff3AlignBC, page, this); gbox->addWidget(pDiff3AlignBC, line, 0, 1, 2); pDiff3AlignBC->setToolTip(i18n( "Try to align B and C when comparing or merging three input files.\n" "Not recommended for merging because merge might get more complicated.\n" "(Default is off.)")); ++line; topLayout->addStretch(10); } void OptionDialog::setupMergePage(void) { QFrame* page = new QFrame(); KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18n("Merge")); pageItem->setHeader(i18n("Merge Settings")); pageItem->setIcon(QIcon::fromTheme(QStringLiteral("merge"))); addPage(pageItem); QVBoxLayout* topLayout = new QVBoxLayout(page); topLayout->setMargin(5); QGridLayout* gbox = new QGridLayout(); gbox->setColumnStretch(1, 5); topLayout->addLayout(gbox); int line = 0; QLabel* label = nullptr; label = new QLabel(i18n("Auto advance delay (ms):"), page); gbox->addWidget(label, line, 0); OptionIntEdit* pAutoAdvanceDelay = new OptionIntEdit(500, "AutoAdvanceDelay", &m_options.m_autoAdvanceDelay, 0, 2000, page, this); gbox->addWidget(pAutoAdvanceDelay, line, 1); label->setToolTip(i18n( "When in Auto-Advance mode the result of the current selection is shown \n" "for the specified time, before jumping to the next conflict. Range: 0-2000 ms")); ++line; OptionCheckBox* pShowInfoDialogs = new OptionCheckBox(i18n("Show info dialogs"), true, "ShowInfoDialogs", &m_options.m_bShowInfoDialogs, page, this); gbox->addWidget(pShowInfoDialogs, line, 0, 1, 2); pShowInfoDialogs->setToolTip(i18n("Show a dialog with information about the number of conflicts.")); ++line; label = new QLabel(i18n("White space 2-file merge default:"), page); gbox->addWidget(label, line, 0); OptionComboBox* pWhiteSpace2FileMergeDefault = new OptionComboBox(0, "WhiteSpace2FileMergeDefault", &m_options.m_whiteSpace2FileMergeDefault, page, this); gbox->addWidget(pWhiteSpace2FileMergeDefault, line, 1); pWhiteSpace2FileMergeDefault->insertItem(0, i18n("Manual Choice")); pWhiteSpace2FileMergeDefault->insertItem(1, "A"); pWhiteSpace2FileMergeDefault->insertItem(2, "B"); label->setToolTip(i18n( "Allow the merge algorithm to automatically select an input for " "white-space-only changes.")); ++line; label = new QLabel(i18n("White space 3-file merge default:"), page); gbox->addWidget(label, line, 0); OptionComboBox* pWhiteSpace3FileMergeDefault = new OptionComboBox(0, "WhiteSpace3FileMergeDefault", &m_options.m_whiteSpace3FileMergeDefault, page, this); gbox->addWidget(pWhiteSpace3FileMergeDefault, line, 1); pWhiteSpace3FileMergeDefault->insertItem(0, i18n("Manual Choice")); pWhiteSpace3FileMergeDefault->insertItem(1, "A"); pWhiteSpace3FileMergeDefault->insertItem(2, "B"); pWhiteSpace3FileMergeDefault->insertItem(3, "C"); label->setToolTip(i18n( "Allow the merge algorithm to automatically select an input for " "white-space-only changes.")); ++line; QGroupBox* pGroupBox = new QGroupBox(i18n("Automatic Merge Regular Expression")); gbox->addWidget(pGroupBox, line, 0, 1, 2); ++line; { QGridLayout* gbox = new QGridLayout(pGroupBox); gbox->setColumnStretch(1, 10); int line = 0; label = new QLabel(i18n("Auto merge regular expression:"), page); gbox->addWidget(label, line, 0); m_pAutoMergeRegExpLineEdit = new OptionLineEdit(".*\\$(Version|Header|Date|Author).*\\$.*", "AutoMergeRegExp", &m_options.m_autoMergeRegExp, page, this); gbox->addWidget(m_pAutoMergeRegExpLineEdit, line, 1); s_autoMergeRegExpToolTip = i18n("Regular expression for lines where KDiff3 should automatically choose one source.\n" "When a line with a conflict matches the regular expression then\n" "- if available - C, otherwise B will be chosen."); label->setToolTip(s_autoMergeRegExpToolTip); ++line; OptionCheckBox* pAutoMergeRegExp = new OptionCheckBox(i18n("Run regular expression auto merge on merge start"), false, "RunRegExpAutoMergeOnMergeStart", &m_options.m_bRunRegExpAutoMergeOnMergeStart, page, this); gbox->addWidget(pAutoMergeRegExp, line, 0, 1, 2); pAutoMergeRegExp->setToolTip(i18n("Run the merge for auto merge regular expressions\n" "immediately when a merge starts.\n")); ++line; } pGroupBox = new QGroupBox(i18n("Version Control History Merging")); gbox->addWidget(pGroupBox, line, 0, 1, 2); ++line; { QGridLayout* gbox = new QGridLayout(pGroupBox); gbox->setColumnStretch(1, 10); int line = 0; label = new QLabel(i18n("History start regular expression:"), page); gbox->addWidget(label, line, 0); m_pHistoryStartRegExpLineEdit = new OptionLineEdit(".*\\$Log.*\\$.*", "HistoryStartRegExp", &m_options.m_historyStartRegExp, page, this); gbox->addWidget(m_pHistoryStartRegExpLineEdit, line, 1); s_historyStartRegExpToolTip = i18n("Regular expression for the start of the version control history entry.\n" "Usually this line contains the \"$Log$\" keyword.\n" "Default value: \".*\\$Log.*\\$.*\""); label->setToolTip(s_historyStartRegExpToolTip); ++line; label = new QLabel(i18n("History entry start regular expression:"), page); gbox->addWidget(label, line, 0); // Example line: "** \main\rolle_fsp_dev_008\1 17 Aug 2001 10:45:44 rolle" QString historyEntryStartDefault = "\\s*\\\\main\\\\(\\S+)\\s+" // Start with "\main\" "([0-9]+) " // day "(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) " //month "([0-9][0-9][0-9][0-9]) " // year "([0-9][0-9]:[0-9][0-9]:[0-9][0-9])\\s+(.*)"; // time, name m_pHistoryEntryStartRegExpLineEdit = new OptionLineEdit(historyEntryStartDefault, "HistoryEntryStartRegExp", &m_options.m_historyEntryStartRegExp, page, this); gbox->addWidget(m_pHistoryEntryStartRegExpLineEdit, line, 1); s_historyEntryStartRegExpToolTip = i18n("A version control history entry consists of several lines.\n" "Specify the regular expression to detect the first line (without the leading comment).\n" "Use parentheses to group the keys you want to use for sorting.\n" "If left empty, then KDiff3 assumes that empty lines separate history entries.\n" "See the documentation for details."); label->setToolTip(s_historyEntryStartRegExpToolTip); ++line; m_pHistoryMergeSorting = new OptionCheckBox(i18n("History merge sorting"), false, "HistoryMergeSorting", &m_options.m_bHistoryMergeSorting, page, this); gbox->addWidget(m_pHistoryMergeSorting, line, 0, 1, 2); m_pHistoryMergeSorting->setToolTip(i18n("Sort version control history by a key.")); ++line; //QString branch = newHistoryEntry.cap(1); //int day = newHistoryEntry.cap(2).toInt(); //int month = QString("Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec").find(newHistoryEntry.cap(3))/4 + 1; //int year = newHistoryEntry.cap(4).toInt(); //QString time = newHistoryEntry.cap(5); //QString name = newHistoryEntry.cap(6); QString defaultSortKeyOrder = "4,3,2,5,1,6"; //QDate(year,month,day).toString(Qt::ISODate) +" "+ time + " " + branch + " " + name; label = new QLabel(i18n("History entry start sort key order:"), page); gbox->addWidget(label, line, 0); m_pHistorySortKeyOrderLineEdit = new OptionLineEdit(defaultSortKeyOrder, "HistoryEntryStartSortKeyOrder", &m_options.m_historyEntryStartSortKeyOrder, page, this); gbox->addWidget(m_pHistorySortKeyOrderLineEdit, line, 1); s_historyEntryStartSortKeyOrderToolTip = i18n("Each pair of parentheses used in the regular expression for the history start entry\n" "groups a key that can be used for sorting.\n" "Specify the list of keys (that are numbered in order of occurrence\n" "starting with 1) using ',' as separator (e.g. \"4,5,6,1,2,3,7\").\n" "If left empty, then no sorting will be done.\n" "See the documentation for details."); label->setToolTip(s_historyEntryStartSortKeyOrderToolTip); m_pHistorySortKeyOrderLineEdit->setEnabled(false); connect(m_pHistoryMergeSorting, &OptionCheckBox::toggled, m_pHistorySortKeyOrderLineEdit, &OptionLineEdit::setEnabled); ++line; m_pHistoryAutoMerge = new OptionCheckBox(i18n("Merge version control history on merge start"), false, "RunHistoryAutoMergeOnMergeStart", &m_options.m_bRunHistoryAutoMergeOnMergeStart, page, this); gbox->addWidget(m_pHistoryAutoMerge, line, 0, 1, 2); m_pHistoryAutoMerge->setToolTip(i18n("Run version control history automerge on merge start.")); ++line; OptionIntEdit* pMaxNofHistoryEntries = new OptionIntEdit(-1, "MaxNofHistoryEntries", &m_options.m_maxNofHistoryEntries, -1, 1000, page, this); label = new QLabel(i18n("Max number of history entries:"), page); gbox->addWidget(label, line, 0); gbox->addWidget(pMaxNofHistoryEntries, line, 1); pMaxNofHistoryEntries->setToolTip(i18n("Cut off after specified number. Use -1 for infinite number of entries.")); ++line; } QPushButton* pButton = new QPushButton(i18n("Test your regular expressions"), page); gbox->addWidget(pButton, line, 0); connect(pButton, &QPushButton::clicked, this, &OptionDialog::slotHistoryMergeRegExpTester); ++line; label = new QLabel(i18n("Irrelevant merge command:"), page); gbox->addWidget(label, line, 0); OptionLineEdit* pLE = new OptionLineEdit("", "IrrelevantMergeCmd", &m_options.m_IrrelevantMergeCmd, page, this); gbox->addWidget(pLE, line, 1); label->setToolTip(i18n("If specified this script is run after automerge\n" "when no other relevant changes were detected.\n" "Called with the parameters: filename1 filename2 filename3")); ++line; OptionCheckBox* pAutoSaveAndQuit = new OptionCheckBox(i18n("Auto save and quit on merge without conflicts"), false, "AutoSaveAndQuitOnMergeWithoutConflicts", &m_options.m_bAutoSaveAndQuitOnMergeWithoutConflicts, page, this); gbox->addWidget(pAutoSaveAndQuit, line, 0, 1, 2); pAutoSaveAndQuit->setToolTip(i18n("If KDiff3 was started for a file-merge from the command line and all\n" "conflicts are solvable without user interaction then automatically save and quit.\n" "(Similar to command line option \"--auto\".)")); ++line; topLayout->addStretch(10); } void OptionDialog::setupDirectoryMergePage(void) { QFrame* page = new QFrame(); KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18n("Directory")); pageItem->setHeader(i18n("Directory")); pageItem->setIcon(QIcon::fromTheme(QStringLiteral("inode-directory"))); addPage(pageItem); QVBoxLayout* topLayout = new QVBoxLayout(page); topLayout->setMargin(5); QGridLayout* gbox = new QGridLayout(); gbox->setColumnStretch(1, 5); topLayout->addLayout(gbox); int line = 0; OptionCheckBox* pRecursiveDirs = new OptionCheckBox(i18n("Recursive directories"), true, "RecursiveDirs", &m_options.m_bDmRecursiveDirs, page, this); gbox->addWidget(pRecursiveDirs, line, 0, 1, 2); pRecursiveDirs->setToolTip(i18n("Whether to analyze subdirectories or not.")); ++line; QLabel* label = new QLabel(i18n("File pattern(s):"), page); gbox->addWidget(label, line, 0); OptionLineEdit* pFilePattern = new OptionLineEdit("*", "FilePattern", &m_options.m_DmFilePattern, page, this); gbox->addWidget(pFilePattern, line, 1); label->setToolTip(i18n( "Pattern(s) of files to be analyzed. \n" "Wildcards: '*' and '?'\n" "Several Patterns can be specified by using the separator: ';'")); ++line; label = new QLabel(i18n("File-anti-pattern(s):"), page); gbox->addWidget(label, line, 0); OptionLineEdit* pFileAntiPattern = new OptionLineEdit("*.orig;*.o;*.obj;*.rej;*.bak", "FileAntiPattern", &m_options.m_DmFileAntiPattern, page, this); gbox->addWidget(pFileAntiPattern, line, 1); label->setToolTip(i18n( "Pattern(s) of files to be excluded from analysis. \n" "Wildcards: '*' and '?'\n" "Several Patterns can be specified by using the separator: ';'")); ++line; label = new QLabel(i18n("Dir-anti-pattern(s):"), page); gbox->addWidget(label, line, 0); OptionLineEdit* pDirAntiPattern = new OptionLineEdit("CVS;.deps;.svn;.hg;.git", "DirAntiPattern", &m_options.m_DmDirAntiPattern, page, this); gbox->addWidget(pDirAntiPattern, line, 1); label->setToolTip(i18n( "Pattern(s) of directories to be excluded from analysis. \n" "Wildcards: '*' and '?'\n" "Several Patterns can be specified by using the separator: ';'")); ++line; OptionCheckBox* pUseCvsIgnore = new OptionCheckBox(i18n("Use .cvsignore"), false, "UseCvsIgnore", &m_options.m_bDmUseCvsIgnore, page, this); gbox->addWidget(pUseCvsIgnore, line, 0, 1, 2); pUseCvsIgnore->setToolTip(i18n( "Extends the antipattern to anything that would be ignored by CVS.\n" "Via local \".cvsignore\" files this can be directory specific.")); ++line; OptionCheckBox* pFindHidden = new OptionCheckBox(i18n("Find hidden files and directories"), true, "FindHidden", &m_options.m_bDmFindHidden, page, this); gbox->addWidget(pFindHidden, line, 0, 1, 2); -#if defined(_WIN32) || defined(Q_OS_OS2) +#if defined(_WIN32) pFindHidden->setToolTip(i18n("Finds files and directories with the hidden attribute.")); #else pFindHidden->setToolTip(i18n("Finds files and directories starting with '.'.")); #endif ++line; OptionCheckBox* pFollowFileLinks = new OptionCheckBox(i18n("Follow file links"), false, "FollowFileLinks", &m_options.m_bDmFollowFileLinks, page, this); gbox->addWidget(pFollowFileLinks, line, 0, 1, 2); pFollowFileLinks->setToolTip(i18n( "On: Compare the file the link points to.\n" "Off: Compare the links.")); ++line; OptionCheckBox* pFollowDirLinks = new OptionCheckBox(i18n("Follow directory links"), false, "FollowDirLinks", &m_options.m_bDmFollowDirLinks, page, this); gbox->addWidget(pFollowDirLinks, line, 0, 1, 2); pFollowDirLinks->setToolTip(i18n( "On: Compare the directory the link points to.\n" "Off: Compare the links.")); ++line; //OptionCheckBox* pShowOnlyDeltas = new OptionCheckBox( i18n("List only deltas"),false,"ListOnlyDeltas", &m_options.m_bDmShowOnlyDeltas, page, this ); //gbox->addWidget( pShowOnlyDeltas, line, 0, 1, 2 ); //pShowOnlyDeltas->setToolTip( i18n( // "Files and directories without change will not appear in the list.")); //++line; -#if defined(_WIN32) || defined(Q_OS_OS2) +#if defined(_WIN32) bool bCaseSensitiveFilenameComparison = false; #else bool bCaseSensitiveFilenameComparison = true; #endif OptionCheckBox* pCaseSensitiveFileNames = new OptionCheckBox(i18n("Case sensitive filename comparison"), bCaseSensitiveFilenameComparison, "CaseSensitiveFilenameComparison", &m_options.m_bDmCaseSensitiveFilenameComparison, page, this); gbox->addWidget(pCaseSensitiveFileNames, line, 0, 1, 2); pCaseSensitiveFileNames->setToolTip(i18n( "The directory comparison will compare files or directories when their names match.\n" "Set this option if the case of the names must match. (Default for Windows is off, otherwise on.)")); ++line; OptionCheckBox* pUnfoldSubdirs = new OptionCheckBox(i18n("Unfold all subdirectories on load"), false, "UnfoldSubdirs", &m_options.m_bDmUnfoldSubdirs, page, this); gbox->addWidget(pUnfoldSubdirs, line, 0, 1, 2); pUnfoldSubdirs->setToolTip(i18n( "On: Unfold all subdirectories when starting a directory diff.\n" "Off: Leave subdirectories folded.")); ++line; OptionCheckBox* pSkipDirStatus = new OptionCheckBox(i18n("Skip directory status report"), false, "SkipDirStatus", &m_options.m_bDmSkipDirStatus, page, this); gbox->addWidget(pSkipDirStatus, line, 0, 1, 2); pSkipDirStatus->setToolTip(i18n( "On: Do not show the Directory Comparison Status.\n" "Off: Show the status dialog on start.")); ++line; QGroupBox* pBG = new QGroupBox(i18n("File Comparison Mode")); gbox->addWidget(pBG, line, 0, 1, 2); QVBoxLayout* pBGLayout = new QVBoxLayout(pBG); OptionRadioButton* pBinaryComparison = new OptionRadioButton(i18n("Binary comparison"), true, "BinaryComparison", &m_options.m_bDmBinaryComparison, pBG, this); pBinaryComparison->setToolTip(i18n("Binary comparison of each file. (Default)")); pBGLayout->addWidget(pBinaryComparison); OptionRadioButton* pFullAnalysis = new OptionRadioButton(i18n("Full analysis"), false, "FullAnalysis", &m_options.m_bDmFullAnalysis, pBG, this); pFullAnalysis->setToolTip(i18n("Do a full analysis and show statistics information in extra columns.\n" "(Slower than a binary comparison, much slower for binary files.)")); pBGLayout->addWidget(pFullAnalysis); OptionRadioButton* pTrustDate = new OptionRadioButton(i18n("Trust the size and modification date (unsafe)"), false, "TrustDate", &m_options.m_bDmTrustDate, pBG, this); pTrustDate->setToolTip(i18n("Assume that files are equal if the modification date and file length are equal.\n" "Files with equal contents but different modification dates will appear as different.\n" "Useful for big directories or slow networks.")); pBGLayout->addWidget(pTrustDate); OptionRadioButton* pTrustDateFallbackToBinary = new OptionRadioButton(i18n("Trust the size and date, but use binary comparison if date does not match (unsafe)"), false, "TrustDateFallbackToBinary", &m_options.m_bDmTrustDateFallbackToBinary, pBG, this); pTrustDateFallbackToBinary->setToolTip(i18n("Assume that files are equal if the modification date and file length are equal.\n" "If the dates are not equal but the sizes are, use binary comparison.\n" "Useful for big directories or slow networks.")); pBGLayout->addWidget(pTrustDateFallbackToBinary); OptionRadioButton* pTrustSize = new OptionRadioButton(i18n("Trust the size (unsafe)"), false, "TrustSize", &m_options.m_bDmTrustSize, pBG, this); pTrustSize->setToolTip(i18n("Assume that files are equal if their file lengths are equal.\n" "Useful for big directories or slow networks when the date is modified during download.")); pBGLayout->addWidget(pTrustSize); ++line; // Some two Dir-options: Affects only the default actions. OptionCheckBox* pSyncMode = new OptionCheckBox(i18n("Synchronize directories"), false, "SyncMode", &m_options.m_bDmSyncMode, page, this); gbox->addWidget(pSyncMode, line, 0, 1, 2); pSyncMode->setToolTip(i18n( "Offers to store files in both directories so that\n" "both directories are the same afterwards.\n" "Works only when comparing two directories without specifying a destination.")); ++line; // Allow white-space only differences to be considered equal OptionCheckBox* pWhiteSpaceDiffsEqual = new OptionCheckBox(i18n("White space differences considered equal"), true, "WhiteSpaceEqual", &m_options.m_bDmWhiteSpaceEqual, page, this); gbox->addWidget(pWhiteSpaceDiffsEqual, line, 0, 1, 2); pWhiteSpaceDiffsEqual->setToolTip(i18n( "If files differ only by white space consider them equal.\n" "This is only active when full analysis is chosen.")); connect(pFullAnalysis, &OptionRadioButton::toggled, pWhiteSpaceDiffsEqual, &OptionCheckBox::setEnabled); pWhiteSpaceDiffsEqual->setEnabled(false); ++line; OptionCheckBox* pCopyNewer = new OptionCheckBox(i18n("Copy newer instead of merging (unsafe)"), false, "CopyNewer", &m_options.m_bDmCopyNewer, page, this); gbox->addWidget(pCopyNewer, line, 0, 1, 2); pCopyNewer->setToolTip(i18n( "Don't look inside, just take the newer file.\n" "(Use this only if you know what you are doing!)\n" "Only effective when comparing two directories.")); ++line; OptionCheckBox* pCreateBakFiles = new OptionCheckBox(i18n("Backup files (.orig)"), true, "CreateBakFiles", &m_options.m_bDmCreateBakFiles, page, this); gbox->addWidget(pCreateBakFiles, line, 0, 1, 2); pCreateBakFiles->setToolTip(i18n( "If a file would be saved over an old file, then the old file\n" "will be renamed with a '.orig' extension instead of being deleted.")); ++line; topLayout->addStretch(10); } /* static void insertCodecs(OptionComboBox* p) { std::multimap m; // Using the multimap for case-insensitive sorting. int i; for(i=0;;++i) { QTextCodec* pCodec = QTextCodec::codecForIndex ( i ); if ( pCodec != 0 ) m.insert( std::make_pair( QString(pCodec->mimeName()).toUpper(), pCodec->mimeName()) ); else break; } p->insertItem( i18n("Auto"), 0 ); std::multimap::iterator mi; for(mi=m.begin(), i=0; mi!=m.end(); ++mi, ++i) p->insertItem(mi->second, i+1); } */ /* // UTF8-Codec that saves a BOM // UTF8-Codec that saves a BOM class Utf8BOMCodec : public QTextCodec { QTextCodec* m_pUtf8Codec; class PublicTextCodec : public QTextCodec { public: QString publicConvertToUnicode ( const char * p, int len, ConverterState* pState ) const { return convertToUnicode( p, len, pState ); } QByteArray publicConvertFromUnicode ( const QChar * input, int number, ConverterState * pState ) const { return convertFromUnicode( input, number, pState ); } }; public: Utf8BOMCodec() { m_pUtf8Codec = QTextCodec::codecForName("UTF-8"); } QByteArray name () const { return "UTF-8-BOM"; } int mibEnum () const { return 2123; } QByteArray convertFromUnicode ( const QChar * input, int number, ConverterState * pState ) const { QByteArray r; if ( pState && pState->state_data[2]==0) // state_data[2] not used by QUtf8::convertFromUnicode (see qutfcodec.cpp) { r += "\xEF\xBB\xBF"; pState->state_data[2]=1; pState->flags |= QTextCodec::IgnoreHeader; } r += ((PublicTextCodec*)m_pUtf8Codec)->publicConvertFromUnicode( input, number, pState ); return r; } QString convertToUnicode ( const char * p, int len, ConverterState* pState ) const { return ((PublicTextCodec*)m_pUtf8Codec)->publicConvertToUnicode( p, len, pState ); } }; */ void OptionDialog::setupRegionalPage(void) { /* TODO: What is this line supposed to do besides leak memmory? Intruduced as is in .91 no explaination new Utf8BOMCodec(); */ QFrame* page = new QFrame(); KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18n("Regional Settings")); pageItem->setHeader(i18n("Regional Settings")); pageItem->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-locale"))); addPage(pageItem); QVBoxLayout* topLayout = new QVBoxLayout(page); topLayout->setMargin(5); QGridLayout* gbox = new QGridLayout(); gbox->setColumnStretch(1, 5); topLayout->addLayout(gbox); int line = 0; QLabel* label; #ifdef KREPLACEMENTS_H static const char* countryMap[] = { "af Afrikaans", "ar Arabic", "az Azerbaijani", "be Belarusian", "bg Bulgarian", "bn Bengali", "bo Tibetan", "br Breton", "bs Bosnian", "ca Catalan", "ca@valencia Catalan (Valencian)", "cs Czech", "cy Welsh", "da Danish", "de German", "el Greek", "en_GB British English", "eo Esperanto", "es Spanish", "et Estonian", "eu Basque", "fa Farsi (Persian)", "fi Finnish", "fo Faroese", "fr French", "ga Irish Gaelic", "gl Galician", "gu Gujarati", "he Hebrew", "hi Hindi", "hne Chhattisgarhi", "hr Croatian", "hsb Upper Sorbian", "hu Hungarian", "id Indonesian", "is Icelandic", "it Italian", "ja Japanese", "ka Georgian", "ko Korean", "ku Kurdish", "lo Lao", "lt Lithuanian", "lv Latvian", "mi Maori", "mk Macedonian", "ml Malayalam" "mn Mongolian", "ms Malay", "mt Maltese", "nb Norwegian Bookmal", "nds Low Saxon", "nl Dutch", "nn Norwegian Nynorsk", "nso Northern Sotho", "oc Occitan", "pl Polish", "pt Portuguese", "pt_BR Brazilian Portuguese", "ro Romanian", "ru Russian", "rw Kinyarwanda", "se Northern Sami", "sk Slovak", "sl Slovenian", "sq Albanian", "sr Serbian", "sr@Latn Serbian", "ss Swati", "sv Swedish", "ta Tamil", "tg Tajik", "th Thai", "tr Turkish", "uk Ukrainian", "uz Uzbek", "ven Venda", "vi Vietnamese", "wa Walloon", "xh Xhosa", "zh_CN Chinese Simplified", "zh_TW Chinese Traditional", "zu Zulu"}; label = new QLabel(i18n("Language (restart required)"), page); gbox->addWidget(label, line, 0); OptionComboBox* pLanguage = new OptionComboBox(0, "Language", &m_options.m_language, page, this); gbox->addWidget(pLanguage, line, 1); pLanguage->addItem("Auto"); // Must not translate, won't work otherwise! pLanguage->addItem("en_orig"); -#if !defined(_WIN32) && !defined(Q_OS_OS2) && !defined(__APPLE__) +#if !defined(_WIN32) && !defined(__APPLE__) // Read directory: Find all kdiff3_*.qm-files and insert the found files here QDir localeDir("/usr/share/locale"); // See also kreplacements.cpp: getTranslationDir() QStringList dirList = localeDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name); for(int i = 0; i < dirList.size(); ++i) { QString languageId = dirList[i]; if(!QFile::exists("/usr/share/locale/" + languageId + "/LC_MESSAGES/kdiff3.qm")) continue; #else // Read directory: Find all kdiff3_*.qm-files and insert the found files here QDir localeDir(getTranslationDir(QString())); QStringList fileList = localeDir.entryList(QStringList("kdiff3_*.qm"), QDir::Files, QDir::Name); for(int i = 0; i < fileList.size(); ++i) { QString fileName = fileList[i]; // Skip the "kdiff3_" and omit the .qm QString languageId = fileName.mid(7, fileName.length() - 10); #endif unsigned int countryIdx = 0; for(countryIdx = 0; countryIdx < sizeof(countryMap) / sizeof(countryMap[0]); ++countryIdx) { QString fullName = countryMap[countryIdx]; if(QString(languageId + " ") == fullName.left(languageId.length() + 1)) { languageId += " (" + fullName.mid(languageId.length() + 1) + ")"; } } pLanguage->addItem(languageId); } label->setToolTip(i18n( "Choose the language of the GUI strings or \"Auto\".\n" "For a change of language to take place, quit and restart KDiff3.")); ++line; /* label = new QLabel( i18n("Codec for file contents"), page ); gbox->addWidget( label, line, 0 ); OptionComboBox* pFileCodec = new OptionComboBox( 0, "FileCodec", &m_options.m_fileCodec, page, this ); gbox->addWidget( pFileCodec, line, 1 ); insertCodecs( pFileCodec ); label->setToolTip( i18n( "Choose the codec that should be used for your input files\n" "or \"Auto\" if unsure." ) ); ++line; */ #endif m_pSameEncoding = new OptionCheckBox(i18n("Use the same encoding for everything:"), true, "SameEncoding", &m_options.m_bSameEncoding, page, this); gbox->addWidget(m_pSameEncoding, line, 0, 1, 2); m_pSameEncoding->setToolTip(i18n( "Enable this allows to change all encodings by changing the first only.\n" "Disable this if different individual settings are needed.")); ++line; label = new QLabel(i18n("Note: Local Encoding is ") + "\"" + QTextCodec::codecForLocale()->name() + "\"", page); gbox->addWidget(label, line, 0); ++line; label = new QLabel(i18n("File Encoding for A:"), page); gbox->addWidget(label, line, 0); m_pEncodingAComboBox = new OptionEncodingComboBox("EncodingForA", &m_options.m_pEncodingA, page, this); gbox->addWidget(m_pEncodingAComboBox, line, 1); QString autoDetectToolTip = i18n( "If enabled then Unicode (UTF-16 or UTF-8) encoding will be detected.\n" "If the file is not Unicode then the selected encoding will be used as fallback.\n" "(Unicode detection depends on the first bytes of a file.)"); m_pAutoDetectUnicodeA = new OptionCheckBox(i18n("Auto Detect Unicode"), true, "AutoDetectUnicodeA", &m_options.m_bAutoDetectUnicodeA, page, this); gbox->addWidget(m_pAutoDetectUnicodeA, line, 2); m_pAutoDetectUnicodeA->setToolTip(autoDetectToolTip); ++line; label = new QLabel(i18n("File Encoding for B:"), page); gbox->addWidget(label, line, 0); m_pEncodingBComboBox = new OptionEncodingComboBox("EncodingForB", &m_options.m_pEncodingB, page, this); gbox->addWidget(m_pEncodingBComboBox, line, 1); m_pAutoDetectUnicodeB = new OptionCheckBox(i18n("Auto Detect Unicode"), true, "AutoDetectUnicodeB", &m_options.m_bAutoDetectUnicodeB, page, this); gbox->addWidget(m_pAutoDetectUnicodeB, line, 2); m_pAutoDetectUnicodeB->setToolTip(autoDetectToolTip); ++line; label = new QLabel(i18n("File Encoding for C:"), page); gbox->addWidget(label, line, 0); m_pEncodingCComboBox = new OptionEncodingComboBox("EncodingForC", &m_options.m_pEncodingC, page, this); gbox->addWidget(m_pEncodingCComboBox, line, 1); m_pAutoDetectUnicodeC = new OptionCheckBox(i18n("Auto Detect Unicode"), true, "AutoDetectUnicodeC", &m_options.m_bAutoDetectUnicodeC, page, this); gbox->addWidget(m_pAutoDetectUnicodeC, line, 2); m_pAutoDetectUnicodeC->setToolTip(autoDetectToolTip); ++line; label = new QLabel(i18n("File Encoding for Merge Output and Saving:"), page); gbox->addWidget(label, line, 0); m_pEncodingOutComboBox = new OptionEncodingComboBox("EncodingForOutput", &m_options.m_pEncodingOut, page, this); gbox->addWidget(m_pEncodingOutComboBox, line, 1); m_pAutoSelectOutEncoding = new OptionCheckBox(i18n("Auto Select"), true, "AutoSelectOutEncoding", &m_options.m_bAutoSelectOutEncoding, page, this); gbox->addWidget(m_pAutoSelectOutEncoding, line, 2); m_pAutoSelectOutEncoding->setToolTip(i18n( "If enabled then the encoding from the input files is used.\n" "In ambiguous cases a dialog will ask the user to choose the encoding for saving.")); ++line; label = new QLabel(i18n("File Encoding for Preprocessor Files:"), page); gbox->addWidget(label, line, 0); m_pEncodingPPComboBox = new OptionEncodingComboBox("EncodingForPP", &m_options.m_pEncodingPP, page, this); gbox->addWidget(m_pEncodingPPComboBox, line, 1); ++line; connect(m_pSameEncoding, &OptionCheckBox::toggled, this, &OptionDialog::slotEncodingChanged); connect(m_pEncodingAComboBox, static_cast(&OptionEncodingComboBox::activated), this, &OptionDialog::slotEncodingChanged); connect(m_pAutoDetectUnicodeA, &OptionCheckBox::toggled, this, &OptionDialog::slotEncodingChanged); connect(m_pAutoSelectOutEncoding, &OptionCheckBox::toggled, this, &OptionDialog::slotEncodingChanged); OptionCheckBox* pRightToLeftLanguage = new OptionCheckBox(i18n("Right To Left Language"), false, "RightToLeftLanguage", &m_options.m_bRightToLeftLanguage, page, this); gbox->addWidget(pRightToLeftLanguage, line, 0, 1, 2); pRightToLeftLanguage->setToolTip(i18n( "Some languages are read from right to left.\n" "This setting will change the viewer and editor accordingly.")); ++line; topLayout->addStretch(10); } #ifdef _WIN32 #include "ccInstHelper.cpp" #endif void OptionDialog::setupIntegrationPage(void) { QFrame* page = new QFrame(); KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18n("Integration")); pageItem->setHeader(i18n("Integration Settings")); pageItem->setIcon(QIcon::fromTheme(QStringLiteral("utilities-terminal"))); addPage(pageItem); QVBoxLayout* topLayout = new QVBoxLayout(page); topLayout->setMargin(5); QGridLayout* gbox = new QGridLayout(); gbox->setColumnStretch(2, 5); topLayout->addLayout(gbox); int line = 0; QLabel* label; label = new QLabel(i18n("Command line options to ignore:"), page); gbox->addWidget(label, line, 0); OptionLineEdit* pIgnorableCmdLineOptions = new OptionLineEdit("-u;-query;-html;-abort", "IgnorableCmdLineOptions", &m_options.m_ignorableCmdLineOptions, page, this); gbox->addWidget(pIgnorableCmdLineOptions, line, 1, 1, 2); label->setToolTip(i18n( "List of command line options that should be ignored when KDiff3 is used by other tools.\n" "Several values can be specified if separated via ';'\n" "This will suppress the \"Unknown option\" error.")); ++line; OptionCheckBox* pEscapeKeyQuits = new OptionCheckBox(i18n("Quit also via Escape key"), false, "EscapeKeyQuits", &m_options.m_bEscapeKeyQuits, page, this); gbox->addWidget(pEscapeKeyQuits, line, 0, 1, 2); pEscapeKeyQuits->setToolTip(i18n( "Fast method to exit.\n" "For those who are used to using the Escape key.")); ++line; #ifdef _WIN32 QPushButton* pIntegrateWithClearCase = new QPushButton(i18n("Integrate with ClearCase"), page); gbox->addWidget(pIntegrateWithClearCase, line, 0); pIntegrateWithClearCase->setToolTip(i18n( "Integrate with Rational ClearCase from IBM.\n" "Modifies the \"map\" file in ClearCase subdir \"lib/mgrs\"\n" "(Only enabled when ClearCase \"bin\" directory is in the path.)")); connect(pIntegrateWithClearCase, &QPushButton::clicked, this, &OptionDialog::slotIntegrateWithClearCase); pIntegrateWithClearCase->setEnabled(integrateWithClearCase("existsClearCase", "") != 0); QPushButton* pRemoveClearCaseIntegration = new QPushButton(i18n("Remove ClearCase Integration"), page); gbox->addWidget(pRemoveClearCaseIntegration, line, 1); pRemoveClearCaseIntegration->setToolTip(i18n( "Restore the old \"map\" file from before doing the ClearCase integration.")); connect(pRemoveClearCaseIntegration, &QPushButton::clicked, this, &OptionDialog::slotRemoveClearCaseIntegration); pRemoveClearCaseIntegration->setEnabled(integrateWithClearCase("existsClearCase", "") != 0); ++line; #endif topLayout->addStretch(10); } void OptionDialog::slotIntegrateWithClearCase() { #ifdef _WIN32 char kdiff3CommandPath[1000]; GetModuleFileNameA(0, kdiff3CommandPath, sizeof(kdiff3CommandPath) - 1); integrateWithClearCase("install", kdiff3CommandPath); #endif } void OptionDialog::slotRemoveClearCaseIntegration() { #ifdef _WIN32 char kdiff3CommandPath[1000]; GetModuleFileNameA(0, kdiff3CommandPath, sizeof(kdiff3CommandPath) - 1); integrateWithClearCase("uninstall", kdiff3CommandPath); #endif } void OptionDialog::slotEncodingChanged() { if(m_pSameEncoding->isChecked()) { m_pEncodingBComboBox->setEnabled(false); m_pEncodingBComboBox->setCurrentIndex(m_pEncodingAComboBox->currentIndex()); m_pEncodingCComboBox->setEnabled(false); m_pEncodingCComboBox->setCurrentIndex(m_pEncodingAComboBox->currentIndex()); m_pEncodingOutComboBox->setEnabled(false); m_pEncodingOutComboBox->setCurrentIndex(m_pEncodingAComboBox->currentIndex()); m_pEncodingPPComboBox->setEnabled(false); m_pEncodingPPComboBox->setCurrentIndex(m_pEncodingAComboBox->currentIndex()); m_pAutoDetectUnicodeB->setEnabled(false); m_pAutoDetectUnicodeB->setCheckState(m_pAutoDetectUnicodeA->checkState()); m_pAutoDetectUnicodeC->setEnabled(false); m_pAutoDetectUnicodeC->setCheckState(m_pAutoDetectUnicodeA->checkState()); m_pAutoSelectOutEncoding->setEnabled(false); m_pAutoSelectOutEncoding->setCheckState(m_pAutoDetectUnicodeA->checkState()); } else { m_pEncodingBComboBox->setEnabled(true); m_pEncodingCComboBox->setEnabled(true); m_pEncodingOutComboBox->setEnabled(true); m_pEncodingPPComboBox->setEnabled(true); m_pAutoDetectUnicodeB->setEnabled(true); m_pAutoDetectUnicodeC->setEnabled(true); m_pAutoSelectOutEncoding->setEnabled(true); m_pEncodingOutComboBox->setEnabled(m_pAutoSelectOutEncoding->checkState() == Qt::Unchecked); } } void OptionDialog::setupKeysPage(void) { //QVBox *page = addVBoxPage( i18n("Keys"), i18n("KeyDialog" ), // BarIcon("fonts", KIconLoader::SizeMedium ) ); //QVBoxLayout *topLayout = new QVBoxLayout( page, 0, spacingHint() ); // new KFontChooser( page,"font",false/*onlyFixed*/,QStringList(),false,6 ); //m_pKeyDialog=new KKeyDialog( false, 0 ); //topLayout->addWidget( m_pKeyDialog ); } void OptionDialog::slotOk(void) { slotApply(); accept(); } /** Copy the values from the widgets to the public variables.*/ void OptionDialog::slotApply(void) { std::list::iterator i; for(i = m_optionItemList.begin(); i != m_optionItemList.end(); ++i) { (*i)->apply(); } emit applyDone(); #ifdef _WIN32 QString locale = m_options.m_language; if(locale == "Auto" || locale.isEmpty()) locale = QLocale::system().name().left(2); int spacePos = locale.indexOf(' '); if(spacePos > 0) locale = locale.left(spacePos); QSettings settings("HKEY_CURRENT_USER\\Software\\KDiff3\\diff-ext", QSettings::NativeFormat); settings.setValue("Language", locale); #endif } /** Set the default values in the widgets only, while the public variables remain unchanged. */ void OptionDialog::slotDefault() { int result = KMessageBox::warningContinueCancel(this, i18n("This resets all options. Not only those of the current topic.")); if(result == KMessageBox::Cancel) return; else resetToDefaults(); } void OptionDialog::resetToDefaults() { std::list::iterator i; for(i = m_optionItemList.begin(); i != m_optionItemList.end(); ++i) { (*i)->setToDefault(); } slotEncodingChanged(); } /** Initialise the widgets using the values in the public varibles. */ void OptionDialog::setState() { std::list::iterator i; for(i = m_optionItemList.begin(); i != m_optionItemList.end(); ++i) { (*i)->setToCurrent(); } slotEncodingChanged(); } class ConfigValueMap : public ValueMap { private: KConfigGroup m_config; public: explicit ConfigValueMap(const KConfigGroup& config) : m_config(config) {} void writeEntry(const QString& s, const QFont& v) { m_config.writeEntry(s, v); } void writeEntry(const QString& s, const QColor& v) { m_config.writeEntry(s, v); } void writeEntry(const QString& s, const QSize& v) { m_config.writeEntry(s, v); } void writeEntry(const QString& s, const QPoint& v) { m_config.writeEntry(s, v); } void writeEntry(const QString& s, int v) { m_config.writeEntry(s, v); } void writeEntry(const QString& s, bool v) { m_config.writeEntry(s, v); } void writeEntry(const QString& s, const QString& v) { m_config.writeEntry(s, v); } void writeEntry(const QString& s, const char* v) { m_config.writeEntry(s, v); } QFont readFontEntry(const QString& s, const QFont* defaultVal) { return m_config.readEntry(s, *defaultVal); } QColor readColorEntry(const QString& s, const QColor* defaultVal) { return m_config.readEntry(s, *defaultVal); } QSize readSizeEntry(const QString& s, const QSize* defaultVal) { return m_config.readEntry(s, *defaultVal); } QPoint readPointEntry(const QString& s, const QPoint* defaultVal) { return m_config.readEntry(s, *defaultVal); } bool readBoolEntry(const QString& s, bool defaultVal) { return m_config.readEntry(s, defaultVal); } int readNumEntry(const QString& s, int defaultVal) { return m_config.readEntry(s, defaultVal); } QString readStringEntry(const QString& s, const QString& defaultVal) { return m_config.readEntry(s, defaultVal); } void writeEntry(const QString& s, const QStringList& v) { m_config.writeEntry(s, v); } QStringList readListEntry(const QString& s, const QStringList& def) { return m_config.readEntry(s, def); } }; void OptionDialog::saveOptions(KSharedConfigPtr config) { // No i18n()-Translations here! ConfigValueMap cvm(config->group(KDIFF3_CONFIG_GROUP)); std::list::iterator i; for(i = m_optionItemList.begin(); i != m_optionItemList.end(); ++i) { (*i)->doUnpreserve(); (*i)->write(&cvm); } } void OptionDialog::readOptions(KSharedConfigPtr config) { // No i18n()-Translations here! ConfigValueMap cvm(config->group(KDIFF3_CONFIG_GROUP)); std::list::iterator i; for(i = m_optionItemList.begin(); i != m_optionItemList.end(); ++i) { (*i)->read(&cvm); } setState(); } QString OptionDialog::parseOptions(const QStringList& optionList) { QString result; QStringList::const_iterator i; for(i = optionList.begin(); i != optionList.end(); ++i) { QString s = *i; int pos = s.indexOf('='); if(pos > 0) // seems not to have a tag { QString key = s.left(pos); QString val = s.mid(pos + 1); std::list::iterator j; bool bFound = false; for(j = m_optionItemList.begin(); j != m_optionItemList.end(); ++j) { if((*j)->getSaveName() == key) { (*j)->doPreserve(); ValueMap config; config.writeEntry(key, val); // Write the value as a string and (*j)->read(&config); // use the internal conversion from string to the needed value. bFound = true; break; } } if(!bFound) { result += "No config item named \"" + key + "\"\n"; } } else { result += "No '=' found in \"" + s + "\"\n"; } } return result; } QString OptionDialog::calcOptionHelp() { ValueMap config; std::list::iterator j; for(j = m_optionItemList.begin(); j != m_optionItemList.end(); ++j) { (*j)->write(&config); } return config.getAsString(); } void OptionDialog::slotHistoryMergeRegExpTester() { RegExpTester dlg(this, s_autoMergeRegExpToolTip, s_historyStartRegExpToolTip, s_historyEntryStartRegExpToolTip, s_historyEntryStartSortKeyOrderToolTip); dlg.init(m_pAutoMergeRegExpLineEdit->currentText(), m_pHistoryStartRegExpLineEdit->currentText(), m_pHistoryEntryStartRegExpLineEdit->currentText(), m_pHistorySortKeyOrderLineEdit->currentText()); if(dlg.exec()) { m_pAutoMergeRegExpLineEdit->setEditText(dlg.autoMergeRegExp()); m_pHistoryStartRegExpLineEdit->setEditText(dlg.historyStartRegExp()); m_pHistoryEntryStartRegExpLineEdit->setEditText(dlg.historyEntryStartRegExp()); m_pHistorySortKeyOrderLineEdit->setEditText(dlg.historySortKeyOrder()); } } #include "optiondialog.moc"