diff --git a/kdiff3fileitemactionplugin/kdiff3fileitemaction.h b/kdiff3fileitemactionplugin/kdiff3fileitemaction.h index 079c453..5cb4619 100644 --- a/kdiff3fileitemactionplugin/kdiff3fileitemaction.h +++ b/kdiff3fileitemactionplugin/kdiff3fileitemaction.h @@ -1,55 +1,55 @@ /* This file is part of the KDiff3 project Copyright (C) 2008 Joachim Eibl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef _KDIFF3FILEITEMACTIONPLUGIN_H_ #define _KDIFF3FILEITEMACTIONPLUGIN_H_ #include #include #include class QStringList; class KDiff3FileItemAction : public KAbstractFileItemActionPlugin { Q_OBJECT public: KDiff3FileItemAction (QObject* pParent, const QVariantList & args); - virtual ~KDiff3FileItemAction(); + ~KDiff3FileItemAction() override; // implement pure virtual method from KonqPopupMenuPlugin - virtual QList actions( const KFileItemListProperties& fileItemInfos, QWidget* pParentWidget ); + QList actions( const KFileItemListProperties& fileItemInfos, QWidget* pParentWidget ) override; private Q_SLOTS: void slotCompareWith(); void slotCompareTwoFiles(); void slotCompareThreeFiles(); void slotMergeWith(); void slotMergeThreeWay(); void slotSaveForLater(); void slotClearList(); void slotCompareWithHistoryItem(); void slotAbout(); private: QList m_list; QWidget* m_pParentWidget; //KFileItemListProperties m_fileItemInfos; }; #endif diff --git a/src/difftextwindow.cpp b/src/difftextwindow.cpp index 57032bd..3b3fed4 100644 --- a/src/difftextwindow.cpp +++ b/src/difftextwindow.cpp @@ -1,2167 +1,2167 @@ /*************************************************************************** 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 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(Q_OS_WIN) m_eLineEndStyle = eLineEndStyleDos; #else m_eLineEndStyle = eLineEndStyleUnix; #endif } DiffTextWindow* m_pDiffTextWindow; DiffTextWindowFrame* m_pDiffTextWindowFrame = nullptr; QTextCodec* m_pTextCodec; e_LineEndStyle m_eLineEndStyle; 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) { Q_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) { Q_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) { Qt::PenStyle wrapLinePenStyle = Qt::DotLine; p.setPen(QPen(m_pOptions->m_fgColor, 0, bWrapLine ? wrapLinePenStyle : Qt::SolidLine)); p.drawLine(xOffset + 1, yOffset, xOffset + 1, yOffset + fontHeight - 1); p.setPen(QPen(m_pOptions->m_fgColor, 0, Qt::SolidLine)); } } if(c != m_pOptions->m_fgColor && whatChanged2 == 0) //&& whatChanged==0 ) { if(m_pOptions->m_bShowWhiteSpace) { p.setBrushOrigin(0, 0); p.fillRect(xLeft, yOffset, fontWidth * 2 - 1, fontHeight, QBrush(c, Qt::Dense5Pattern)); } } else { p.fillRect(xLeft, yOffset, fontWidth * 2 - 1, fontHeight, c == m_pOptions->m_fgColor ? bgColor : c); } if(bFastSelectionRange) { p.fillRect(xOffset + fontWidth - 1, yOffset, 3, fontHeight, m_pOptions->m_fgColor); } // Check if line needs a manual diff help mark ManualDiffHelpList::const_iterator ci; for(ci = m_pManualDiffHelpList->begin(); ci != m_pManualDiffHelpList->end(); ++ci) { const ManualDiffHelpEntry& mdhe = *ci; int rangeLine1 = -1; int rangeLine2 = -1; 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); Q_ASSERT(m_winIdx >= 1 && m_winIdx <= 3); 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); } } 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]; Q_ASSERT(d->m_winIdx >= 1 && d->m_winIdx <= 3); 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; } 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(Q_OS_WIN) 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() + void run() override { 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) { Q_ASSERT(wrapLineVectorSize == 0); d3l.linesNeededForDisplay = linesNeeded; } if(wrapLineVectorSize > 0) { int j; for(j = 0; j < d3l.linesNeededForDisplay; ++j, ++wrapLineIdx) { Diff3WrapLine& d3wl = d->m_diff3WrapLineVector[wrapLineIdx]; d3wl.diff3LineIndex = i; d3wl.pD3L = (*d->m_pDiff3LineVector)[i]; if(j >= linesNeeded) { d3wl.wrapLineOffset = 0; d3wl.wrapLineLength = 0; } } } } if(wrapLineVectorSize > 0) { d->m_firstLine = 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/difftextwindow.h b/src/difftextwindow.h index d2dfd0a..e775479 100644 --- a/src/difftextwindow.h +++ b/src/difftextwindow.h @@ -1,166 +1,166 @@ /*************************************************************************** difftextwindow.h - 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. * * * ***************************************************************************/ #ifndef DIFFTEXTWINDOW_H #define DIFFTEXTWINDOW_H #include "diff.h" #include class QMenu; class QStatusBar; class Options; class DiffTextWindowData; class DiffTextWindowFrame; class EncodingLabel; class DiffTextWindow : public QWidget { Q_OBJECT public: DiffTextWindow( DiffTextWindowFrame* pParent, QStatusBar* pStatusBar, Options* pOptions, int winIdx ); - ~DiffTextWindow(); + ~DiffTextWindow() override; void init( const QString& fileName, QTextCodec* pCodec, e_LineEndStyle eLineEndStyle, const LineData* pLineData, int size, const Diff3LineVector* pDiff3LineVector, const ManualDiffHelpList* pManualDiffHelpList, bool bTriple ); void reset(); void convertToLinePos( int x, int y, int& line, int& pos ); QString getSelection(); int getFirstLine(); int calcTopLineInFile( int firstLine ); int getMaxTextWidth(); int getNofLines(); int getNofVisibleLines(); int getVisibleTextAreaWidth(); int convertLineToDiff3LineIdx( int line ); int convertDiff3LineIdxToLine( int d3lIdx ); void convertD3LCoordsToLineCoords( int d3LIdx, int d3LPos, int& line, int& pos ); void convertLineCoordsToD3LCoords( int line, int pos, int& d3LIdx, int& d3LPos ); void convertSelectionToD3LCoords(); bool findString( const QString& s, int& d3vLine, int& posInLine, bool bDirDown, bool bCaseSensitive ); void setSelection( int firstLine, int startPos, int lastLine, int endPos, int& l, int& p ); void getSelectionRange( int* firstLine, int* lastLine, e_CoordType coordType ); void setPaintingAllowed( bool bAllowPainting ); void recalcWordWrap( bool bWordWrap, int wrapLineVectorSize, int nofVisibleColumns); void recalcWordWrapHelper( int wrapLineVectorSize, int visibleTextWidth, int cacheListIdx); void print( MyPainter& painter, const QRect& r, int firstLine, int nofLinesPerPage ); Q_SIGNALS: void resizeHeightChangedSignal(int nofVisibleLines); void resizeWidthChangedSignal(int nofVisibleColumns); void scroll(int deltaX, int deltaY); void newSelection(); void selectionEnd(); void setFastSelectorLine( int line ); void gotFocus(); void lineClicked( int winIdx, int line ); public Q_SLOTS: void setFirstLine( int line ); void setHorizScrollOffset( int horizScrollOffset ); void resetSelection(); void setFastSelectorRange( int line1, int nofLines ); protected: - virtual void mousePressEvent ( QMouseEvent * ); - virtual void mouseReleaseEvent ( QMouseEvent * ); - virtual void mouseMoveEvent ( QMouseEvent * ); - virtual void mouseDoubleClickEvent ( QMouseEvent * e ); + void mousePressEvent ( QMouseEvent * ) override; + void mouseReleaseEvent ( QMouseEvent * ) override; + void mouseMoveEvent ( QMouseEvent * ) override; + void mouseDoubleClickEvent ( QMouseEvent * e ) override; - virtual void paintEvent( QPaintEvent* ); - virtual void dragEnterEvent( QDragEnterEvent* e ); - virtual void focusInEvent( QFocusEvent* e ); + void paintEvent( QPaintEvent* ) override; + void dragEnterEvent( QDragEnterEvent* e ) override; + void focusInEvent( QFocusEvent* e ) override; - virtual void resizeEvent( QResizeEvent* ); - virtual void timerEvent(QTimerEvent*); + void resizeEvent( QResizeEvent* ) override; + void timerEvent(QTimerEvent*) override; private: DiffTextWindowData* d; void showStatusLine( int line ); friend class DiffTextWindowFrame; }; class DiffTextWindowFrameData; class DiffTextWindowFrame : public QWidget { Q_OBJECT public: DiffTextWindowFrame( QWidget* pParent, QStatusBar* pStatusBar, Options* pOptions, int winIdx, SourceData* psd); - ~DiffTextWindowFrame(); + ~DiffTextWindowFrame() override; DiffTextWindow* getDiffTextWindow(); void init(); void setFirstLine(int firstLine); void sendEncodingChangedSignal(QTextCodec* c); Q_SIGNALS: void fileNameChanged(const QString&, int); void encodingChanged(QTextCodec*); protected: - bool eventFilter( QObject*, QEvent* ); + bool eventFilter( QObject*, QEvent* ) override; //void paintEvent(QPaintEvent*); private Q_SLOTS: void slotReturnPressed(); void slotBrowseButtonClicked(); private: DiffTextWindowFrameData* d; }; class EncodingLabel : public QLabel { Q_OBJECT public: EncodingLabel( const QString & text, DiffTextWindowFrame* pDiffTextWindowFrame, SourceData* psd, Options* pOptions); protected: - void mouseMoveEvent(QMouseEvent *ev); - void mousePressEvent(QMouseEvent *ev); + void mouseMoveEvent(QMouseEvent *ev) override; + void mousePressEvent(QMouseEvent *ev) override; private Q_SLOTS: void slotEncodingChanged(); private: DiffTextWindowFrame* m_pDiffTextWindowFrame; //To send "EncodingChanged" signal QMenu* m_pContextEncodingMenu; SourceData* m_pSourceData; //SourceData to get access to "isEmpty()" and "isFromBuffer()" functions static const int m_maxRecentEncodings = 5; Options* m_pOptions; void insertCodec( const QString& visibleCodecName, QTextCodec* pCodec, QList &CodecEnumList, QMenu* pMenu, int currentTextCodecEnum); }; bool startRunnables(); #endif diff --git a/src/directorymergewindow.cpp b/src/directorymergewindow.cpp index c9d73c5..61f4fa3 100644 --- a/src/directorymergewindow.cpp +++ b/src/directorymergewindow.cpp @@ -1,3736 +1,3736 @@ /*************************************************************************** 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 Q_OS_WIN #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 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) + void setVisible(bool bVisible) override { 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() + ~Data() override { delete m_pRoot; } // Implement QAbstractItemModel - QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; //Qt::ItemFlags flags ( const QModelIndex & index ) const - QModelIndex parent(const QModelIndex& index) const + QModelIndex parent(const QModelIndex& index) const override { 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 + int rowCount(const QModelIndex& parent = QModelIndex()) const override { 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 + int columnCount(const QModelIndex& /*parent*/) const override { return 10; } - QModelIndex index(int row, int column, const QModelIndex& parent) const + QModelIndex index(int row, int column, const QModelIndex& parent) const override { 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); + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + void sort(int column, Qt::SortOrder order) override; // 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: Q_ASSERT(true); 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 + void paint(QPainter* p, const QStyleOptionViewItem& option, const QModelIndex& index) const override { 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 sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override { 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; Q_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) { Q_ASSERT(true); } 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); } } 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); } } // 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: break; default: Q_ASSERT(true); break; } 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 Q_OS_WIN 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")); } 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")); } 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(Q_OS_WIN) // 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 Q_OS_WIN /*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/directorymergewindow.h b/src/directorymergewindow.h index 0fa6743..a997ff5 100644 --- a/src/directorymergewindow.h +++ b/src/directorymergewindow.h @@ -1,187 +1,187 @@ /*************************************************************************** directorymergewindow.h ------------------- begin : Sat Oct 19 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. * * * ***************************************************************************/ #ifndef DIRECTORY_MERGE_WINDOW_H #define DIRECTORY_MERGE_WINDOW_H #include #include #include #include #include "common.h" #include "fileaccess.h" #include "diff.h" //TotalDiffStatus class Options; class KIconLoader; class StatusInfo; class DirectoryMergeInfo; class OneDirectoryInfo; class QLabel; class QAction; class KToggleAction; class KActionCollection; class TotalDiffStatus; enum e_MergeOperation { eTitleId, eNoOperation, // Operations in sync mode (with only two directories): eCopyAToB, eCopyBToA, eDeleteA, eDeleteB, eDeleteAB, eMergeToA, eMergeToB, eMergeToAB, // Operations in merge mode (with two or three directories) eCopyAToDest, eCopyBToDest, eCopyCToDest, eDeleteFromDest, eMergeABCToDest, eMergeABToDest, eConflictingFileTypes, // Error eChangedAndDeleted, // Error eConflictingAges // Equal age but files are not! }; enum e_Age { eNew, eMiddle, eOld, eNotThere, eAgeEnd }; class MergeFileInfos; class DirectoryMergeWindow : public QTreeView { Q_OBJECT public: DirectoryMergeWindow( QWidget* pParent, Options* pOptions, KIconLoader* pIconLoader ); - ~DirectoryMergeWindow(); + ~DirectoryMergeWindow() override; void setDirectoryMergeInfo(DirectoryMergeInfo* p); bool init( FileAccess& dirA, FileAccess& dirB, FileAccess& dirC, FileAccess& dirDest, bool bDirectoryMerge, bool bReload = false ); bool isFileSelected(); bool isDirectoryMergeInProgress(); int totalColumnWidth(); bool isSyncMode(); bool isScanning(); void initDirectoryMergeActions( QObject* pKDiff3App, KActionCollection* ac ); void updateAvailabilities( bool bDirCompare, bool bDiffWindowVisible, KToggleAction* chooseA, KToggleAction* chooseB, KToggleAction* chooseC ); void updateFileVisibilities(); - virtual void mousePressEvent( QMouseEvent* e ); - virtual void keyPressEvent( QKeyEvent* e ); - virtual void focusInEvent( QFocusEvent* e ); - virtual void focusOutEvent( QFocusEvent* e ); - virtual void contextMenuEvent( QContextMenuEvent* e ); + void mousePressEvent( QMouseEvent* e ) override; + void keyPressEvent( QKeyEvent* e ) override; + void focusInEvent( QFocusEvent* e ) override; + void focusOutEvent( QFocusEvent* e ) override; + void contextMenuEvent( QContextMenuEvent* e ) override; QString getDirNameA(); QString getDirNameB(); QString getDirNameC(); QString getDirNameDest(); public Q_SLOTS: void reload(); void mergeCurrentFile(); void compareCurrentFile(); void slotRunOperationForAllItems(); void slotRunOperationForCurrentItem(); void mergeResultSaved(const QString& fileName); void slotChooseAEverywhere(); void slotChooseBEverywhere(); void slotChooseCEverywhere(); void slotAutoChooseEverywhere(); void slotNoOpEverywhere(); void slotFoldAllSubdirs(); void slotUnfoldAllSubdirs(); void slotShowIdenticalFiles(); void slotShowDifferentFiles(); void slotShowFilesOnlyInA(); void slotShowFilesOnlyInB(); void slotShowFilesOnlyInC(); void slotSynchronizeDirectories(); void slotChooseNewerFiles(); void slotCompareExplicitlySelectedFiles(); void slotMergeExplicitlySelectedFiles(); // Merge current item (merge mode) void slotCurrentDoNothing(); void slotCurrentChooseA(); void slotCurrentChooseB(); void slotCurrentChooseC(); void slotCurrentMerge(); void slotCurrentDelete(); // Sync current item void slotCurrentCopyAToB(); void slotCurrentCopyBToA(); void slotCurrentDeleteA(); void slotCurrentDeleteB(); void slotCurrentDeleteAAndB(); void slotCurrentMergeToA(); void slotCurrentMergeToB(); void slotCurrentMergeToAAndB(); void slotSaveMergeState(); void slotLoadMergeState(); Q_SIGNALS: void startDiffMerge(QString fn1,QString fn2, QString fn3, QString ofn, QString,QString,QString,TotalDiffStatus*); void checkIfCanContinue( bool* pbContinue ); void updateAvailabilities(); void statusBarMessage( const QString& msg ); protected Q_SLOTS: void onDoubleClick( const QModelIndex& ); void onExpanded(); - void currentChanged( const QModelIndex & current, const QModelIndex & previous ); // override + void currentChanged( const QModelIndex & current, const QModelIndex & previous ) override; // override private: class Data; friend class Data; Data* d; class DirMergeItemDelegate; friend class DirMergeItemDelegate; }; class DirectoryMergeInfo : public QFrame { Q_OBJECT public: explicit DirectoryMergeInfo( QWidget* pParent ); void setInfo( const FileAccess& APath, const FileAccess& BPath, const FileAccess& CPath, const FileAccess& DestPath, MergeFileInfos& mfi ); QTreeWidget* getInfoList() {return m_pInfoList;} - virtual bool eventFilter( QObject* o, QEvent* e ); + bool eventFilter( QObject* o, QEvent* e ) override; Q_SIGNALS: void gotFocus(); private: QLabel* m_pInfoA; QLabel* m_pInfoB; QLabel* m_pInfoC; QLabel* m_pInfoDest; QLabel* m_pA; QLabel* m_pB; QLabel* m_pC; QLabel* m_pDest; QTreeWidget* m_pInfoList; }; #endif diff --git a/src/kdiff3.h b/src/kdiff3.h index 5afb4bc..fd2853c 100644 --- a/src/kdiff3.h +++ b/src/kdiff3.h @@ -1,432 +1,432 @@ /*************************************************************************** kdiff3.h - 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. * * * ***************************************************************************/ #ifndef KDIFF3_H #define KDIFF3_H #include "diff.h" // include files for Qt #include #include #include #include // include files for KDE #include #include #include #include #include #include #include #include #include // forward declaration of the KDiff3 classes class OptionDialog; class FindDialog; class ManualDiffHelpDialog; class DiffTextWindow; class DiffTextWindowFrame; class MergeResultWindow; class WindowTitleWidget; class Overview; class QScrollBar; class QSplitter; class QStatusBar; class QMenu; class KToggleAction; class KToolBar; class KActionCollection; namespace KParts { class MainWindow; } class KDiff3Part; class DirectoryMergeWindow; class DirectoryMergeInfo; class ReversibleScrollBar : public QScrollBar { Q_OBJECT bool* m_pbRightToLeftLanguage; int m_realVal; public: ReversibleScrollBar( Qt::Orientation o, bool* pbRightToLeftLanguage ) : QScrollBar( o ) { m_pbRightToLeftLanguage=pbRightToLeftLanguage; m_realVal=0; connect( this, SIGNAL(valueChanged(int)), this, SLOT(slotValueChanged(int))); } void setAgain(){ setValue(m_realVal); } public Q_SLOTS: void slotValueChanged(int i) { m_realVal = i; if(m_pbRightToLeftLanguage && *m_pbRightToLeftLanguage) m_realVal = maximum()-(i-minimum()); emit valueChanged2(m_realVal); } void setValue(int i) { if(m_pbRightToLeftLanguage && *m_pbRightToLeftLanguage) QScrollBar::setValue( maximum()-(i-minimum()) ); else QScrollBar::setValue( i ); } int value() const { return m_realVal; } Q_SIGNALS: void valueChanged2(int); }; class KDiff3App : public QSplitter { Q_OBJECT public: /** constructor of KDiff3App, calls all init functions to create the application. */ KDiff3App( QWidget* parent, const char* name, KDiff3Part* pKDiff3Part ); - ~KDiff3App(); + ~KDiff3App() override; bool isPart(); /** initializes the KActions of the application */ void initActions( KActionCollection* ); /** save general Options like all bar positions and status as well as the geometry and the recent file list to the configuration file */ void saveOptions( KSharedConfigPtr ); /** read general Options again and initialize all variables like the recent file list */ void readOptions( KSharedConfigPtr ); // Finish initialisation (virtual, so that it can be called from the shell too.) virtual void completeInit(const QString& fn1="", const QString& fn2="", const QString& fn3=""); /** queryClose is called by KMainWindow on each closeEvent of a window. Against the * default implementation (only returns true), this calles saveModified() on the document object to ask if the document shall * be saved if Modified; on cancel the closeEvent is rejected. * @see KMainWindow#queryClose * @see KMainWindow#closeEvent */ virtual bool queryClose(); virtual bool isFileSaved(); virtual bool isDirComparison(); Q_SIGNALS: void createNewInstance( const QString& fn1, const QString& fn2, const QString& fn3 ); protected: void initDirectoryMergeActions(); /** sets up the statusbar for the main window by initialzing a statuslabel. */ void initStatusBar(); /** creates the centerwidget of the KMainWindow instance and sets it as the view */ void initView(); public Q_SLOTS: /** open a file and load it into the document*/ void slotFileOpen(); void slotFileOpen2( QString fn1, QString fn2, QString fn3, QString ofn, QString an1, QString an2, QString an3, TotalDiffStatus* pTotalDiffStatus ); void slotFileNameChanged(const QString& fileName, int winIdx); /** save a document */ void slotFileSave(); /** save a document by a new filename*/ void slotFileSaveAs(); void slotFilePrint(); /** closes all open windows by calling close() on each memberList item until the list is empty, then quits the application. * If queryClose() returns false because the user canceled the saveModified() dialog, the closing breaks. */ void slotFileQuit(); /** put the marked text/object into the clipboard and remove * it from the document */ void slotEditCut(); /** put the marked text/object into the clipboard */ void slotEditCopy(); /** paste the clipboard into the document */ void slotEditPaste(); /** toggles the toolbar */ void slotViewToolBar(); /** toggles the statusbar */ void slotViewStatusBar(); /** changes the statusbar contents for the standard label permanently, used to indicate current actions. * @param text the text that is displayed in the statusbar */ void slotStatusMsg(const QString &text); private: /** the configuration object of the application */ //KConfig *config; // QAction pointers to enable/disable actions QAction *fileOpen; QAction *fileSave; QAction *fileSaveAs; QAction *filePrint; QAction *fileQuit; QAction *fileReload; QAction *editCut; QAction *editCopy; QAction *editPaste; QAction *editSelectAll; KToggleAction* viewToolBar; KToggleAction* viewStatusBar; //////////////////////////////////////////////////////////////////////// // Special KDiff3 specific stuff starts here QAction *editFind; QAction *editFindNext; QAction *goCurrent; QAction *goTop; QAction *goBottom; QAction *goPrevUnsolvedConflict; QAction *goNextUnsolvedConflict; QAction *goPrevConflict; QAction *goNextConflict; QAction *goPrevDelta; QAction *goNextDelta; KToggleAction *chooseA; KToggleAction *chooseB; KToggleAction *chooseC; KToggleAction *autoAdvance; KToggleAction *wordWrap; QAction * splitDiff; QAction * joinDiffs; QAction * addManualDiffHelp; QAction * clearManualDiffHelpList; KToggleAction *showWhiteSpaceCharacters; KToggleAction *showWhiteSpace; KToggleAction *showLineNumbers; QAction * chooseAEverywhere; QAction * chooseBEverywhere; QAction * chooseCEverywhere; QAction * chooseAForUnsolvedConflicts; QAction * chooseBForUnsolvedConflicts; QAction * chooseCForUnsolvedConflicts; QAction * chooseAForUnsolvedWhiteSpaceConflicts; QAction * chooseBForUnsolvedWhiteSpaceConflicts; QAction * chooseCForUnsolvedWhiteSpaceConflicts; QAction * autoSolve; QAction * unsolve; QAction * mergeHistory; QAction * mergeRegExp; KToggleAction *showWindowA; KToggleAction *showWindowB; KToggleAction *showWindowC; QAction *winFocusNext; QAction *winFocusPrev; QAction *winToggleSplitOrientation; KToggleAction *dirShowBoth; QAction *dirViewToggle; KToggleAction *overviewModeNormal; KToggleAction *overviewModeAB; KToggleAction *overviewModeAC; KToggleAction *overviewModeBC; QMenu* m_pMergeEditorPopupMenu; QSplitter* m_pMainSplitter; QWidget* m_pMainWidget; QWidget* m_pMergeWindowFrame; ReversibleScrollBar* m_pHScrollBar; QScrollBar* m_pDiffVScrollBar; QScrollBar* m_pMergeVScrollBar; DiffTextWindow* m_pDiffTextWindow1; DiffTextWindow* m_pDiffTextWindow2; DiffTextWindow* m_pDiffTextWindow3; DiffTextWindowFrame* m_pDiffTextWindowFrame1; DiffTextWindowFrame* m_pDiffTextWindowFrame2; DiffTextWindowFrame* m_pDiffTextWindowFrame3; QSplitter* m_pDiffWindowSplitter; MergeResultWindow* m_pMergeResultWindow; WindowTitleWidget* m_pMergeResultWindowTitle; bool m_bTripleDiff; QSplitter* m_pDirectoryMergeSplitter; DirectoryMergeWindow* m_pDirectoryMergeWindow; DirectoryMergeInfo* m_pDirectoryMergeInfo; bool m_bDirCompare; Overview* m_pOverview; QWidget* m_pCornerWidget; TotalDiffStatus m_totalDiffStatus; SourceData m_sd1; SourceData m_sd2; SourceData m_sd3; QString m_outputFilename; bool m_bDefaultFilename; DiffList m_diffList12; DiffList m_diffList23; DiffList m_diffList13; DiffBufferInfo m_diffBufferInfo; Diff3LineList m_diff3LineList; Diff3LineVector m_diff3LineVector; //ManualDiffHelpDialog* m_pManualDiffHelpDialog; ManualDiffHelpList m_manualDiffHelpList; int m_neededLines; int m_DTWHeight; bool m_bOutputModified; bool m_bFileSaved; bool m_bTimerBlock; // Synchronisation OptionDialog* m_pOptionDialog; Options* m_pOptions; FindDialog* m_pFindDialog; void mainInit( TotalDiffStatus* pTotalDiffStatus=nullptr, bool bLoadFiles=true, bool bUseCurrentEncoding = false); bool m_bFinishMainInit; bool m_bLoadFiles; - virtual bool eventFilter( QObject* o, QEvent* e ); - virtual void resizeEvent(QResizeEvent*); - virtual void childEvent(QChildEvent *c); + bool eventFilter( QObject* o, QEvent* e ) override; + void resizeEvent(QResizeEvent*) override; + void childEvent(QChildEvent *c) override; bool improveFilenames(bool bCreateNewInstance); bool canContinue(); void choose(int choice); KActionCollection* actionCollection(); QStatusBar* statusBar(); KToolBar* toolBar(const char*); KDiff3Part* m_pKDiff3Part; KParts::MainWindow* m_pKDiff3Shell; bool m_bAutoFlag; bool m_bAutoMode; void recalcWordWrap(int nofVisibleColumns=-1); bool m_bRecalcWordWrapPosted; void setHScrollBarRange(); int m_iCumulativeWheelDelta; int m_visibleTextWidthForPrinting; // only needed during recalcWordWrap int m_firstD3LIdx; // only needed during recalcWordWrap QPointer m_pEventLoopForPrinting; public Q_SLOTS: void resizeDiffTextWindowHeight(int newHeight); void resizeMergeResultWindow(); void slotRecalcWordWrap(); void postRecalcWordWrap(); void slotFinishRecalcWordWrap(); void showPopupMenu( const QPoint& point ); void scrollDiffTextWindow( int deltaX, int deltaY ); void scrollMergeResultWindow( int deltaX, int deltaY ); void setDiff3Line( int line ); void sourceMask( int srcMask, int enabledMask ); void slotDirShowBoth(); void slotDirViewToggle(); void slotUpdateAvailabilities(); void slotEditSelectAll(); void slotEditFind(); void slotEditFindNext(); void slotGoCurrent(); void slotGoTop(); void slotGoBottom(); void slotGoPrevUnsolvedConflict(); void slotGoNextUnsolvedConflict(); void slotGoPrevConflict(); void slotGoNextConflict(); void slotGoPrevDelta(); void slotGoNextDelta(); void slotChooseA(); void slotChooseB(); void slotChooseC(); void slotAutoSolve(); void slotUnsolve(); void slotMergeHistory(); void slotRegExpAutoMerge(); void slotChooseAEverywhere(); void slotChooseBEverywhere(); void slotChooseCEverywhere(); void slotChooseAForUnsolvedConflicts(); void slotChooseBForUnsolvedConflicts(); void slotChooseCForUnsolvedConflicts(); void slotChooseAForUnsolvedWhiteSpaceConflicts(); void slotChooseBForUnsolvedWhiteSpaceConflicts(); void slotChooseCForUnsolvedWhiteSpaceConflicts(); void slotConfigure(); void slotConfigureKeys(); void slotRefresh(); void slotSelectionEnd(); void slotSelectionStart(); void slotClipboardChanged(); void slotOutputModified(bool); void slotFinishMainInit(); void slotMergeCurrentFile(); void slotReload(); void slotCheckIfCanContinue( bool* pbContinue ); void slotShowWhiteSpaceToggled(); void slotShowLineNumbersToggled(); void slotAutoAdvanceToggled(); void slotWordWrapToggled(); void slotShowWindowAToggled(); void slotShowWindowBToggled(); void slotShowWindowCToggled(); void slotWinFocusNext(); void slotWinFocusPrev(); void slotWinToggleSplitterOrientation(); void slotOverviewNormal(); void slotOverviewAB(); void slotOverviewAC(); void slotOverviewBC(); void slotSplitDiff(); void slotJoinDiffs(); void slotAddManualDiffHelp(); void slotClearManualDiffHelpList(); void slotNoRelevantChangesDetected(); void slotEncodingChangedA(QTextCodec*); void slotEncodingChangedB(QTextCodec*); void slotEncodingChangedC(QTextCodec*); }; #endif // KDIFF3_H diff --git a/src/kdiff3_part.h b/src/kdiff3_part.h index 2e9ea94..cc061fa 100644 --- a/src/kdiff3_part.h +++ b/src/kdiff3_part.h @@ -1,79 +1,79 @@ /*************************************************************************** * Copyright (C) 2003-2007 Joachim Eibl * * * * 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. * ***************************************************************************/ #ifndef _KDIFF3PART_H_ #define _KDIFF3PART_H_ #include #include #include class QWidget; class KDiff3App; /** * This is a "Part". It that does all the real work in a KPart * application. * * @short Main Part * @author Joachim Eibl */ class KDiff3Part : public KParts::ReadWritePart { Q_OBJECT public: /** * Default constructor */ KDiff3Part(QWidget *parentWidget, QObject *parent, const QVariantList &args ); /** * Destructor */ - virtual ~KDiff3Part(); + ~KDiff3Part() override; /** * This is a virtual function inherited from KParts::ReadWritePart. * A shell will use this to inform this Part if it should act * read-only */ - virtual void setReadWrite(bool rw); + void setReadWrite(bool rw) override; /** * Reimplemented to disable and enable Save action */ - virtual void setModified(bool modified); + void setModified(bool modified) override; protected: /** * This must be implemented by each part */ - virtual bool openFile(); + bool openFile() override; /** * This must be implemented by each read-write part */ - virtual bool saveFile(); + bool saveFile() override; private: KDiff3App* m_widget; bool m_bIsShell; }; #endif // _KDIFF3PART_H_ diff --git a/src/kdiff3_shell.h b/src/kdiff3_shell.h index f44815a..a7720b8 100644 --- a/src/kdiff3_shell.h +++ b/src/kdiff3_shell.h @@ -1,81 +1,81 @@ /*************************************************************************** * Copyright (C) 2003-2007 Joachim Eibl * * * * 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. * ***************************************************************************/ #ifndef _KDIFF3SHELL_H_ #define _KDIFF3SHELL_H_ #include #include class KToggleAction; namespace KParts { class ReadWritePart; } class KDiff3App; /** * This is the application "Shell". It has a menubar, toolbar, and * statusbar but relies on the "Part" to do all the real work. * * @short Application Shell * @author Joachim Eibl */ class KDiff3Shell : public KParts::MainWindow { Q_OBJECT public: /** * Default Constructor */ KDiff3Shell(bool bCompleteInit=true); /** * Default Destructor */ - virtual ~KDiff3Shell(); + ~KDiff3Shell() override; - bool queryClose(); + bool queryClose() override; bool queryExit(); - virtual void closeEvent(QCloseEvent*e); + void closeEvent(QCloseEvent*e) override; static inline QCommandLineParser* getParser(){ static QCommandLineParser *parser = new QCommandLineParser(); return parser; }; private Q_SLOTS: void optionsShowToolbar(); void optionsShowStatusbar(); void optionsConfigureKeys(); void optionsConfigureToolbars(); void applyNewToolbarConfig(); void slotNewInstance( const QString& fn1, const QString& fn2, const QString& fn3 ); private: KParts::ReadWritePart *m_part; KDiff3App *m_widget; KToggleAction *m_toolbarAction; KToggleAction *m_statusbarAction; bool m_bUnderConstruction; }; #endif // _KDIFF3_H_ diff --git a/src/mergeresultwindow.h b/src/mergeresultwindow.h index 50876d0..5c7dc55 100644 --- a/src/mergeresultwindow.h +++ b/src/mergeresultwindow.h @@ -1,465 +1,465 @@ /*************************************************************************** mergeresultwindow.h - 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. * * * ***************************************************************************/ #ifndef MERGERESULTWINDOW_H #define MERGERESULTWINDOW_H #include "diff.h" #include #include #include #include #include class QPainter; class Overview : public QWidget { Q_OBJECT public: explicit Overview( Options* pOptions ); void init( Diff3LineList* pDiff3LineList, bool bTripleDiff ); void reset(); void setRange( int firstLine, int pageHeight ); void setPaintingAllowed( bool bAllowPainting ); enum e_OverviewMode { eOMNormal, eOMAvsB, eOMAvsC, eOMBvsC }; void setOverviewMode( e_OverviewMode eOverviewMode ); e_OverviewMode getOverviewMode(); public Q_SLOTS: void setFirstLine(int firstLine); void slotRedraw(); Q_SIGNALS: void setLine(int); private: const Diff3LineList* m_pDiff3LineList; Options* m_pOptions; bool m_bTripleDiff; int m_firstLine; int m_pageHeight; QPixmap m_pixmap; bool m_bPaintingAllowed; e_OverviewMode m_eOverviewMode; int m_nofLines; - virtual void paintEvent( QPaintEvent* e ); - virtual void mousePressEvent( QMouseEvent* e ); - virtual void mouseMoveEvent( QMouseEvent* e ); + void paintEvent( QPaintEvent* e ) override; + void mousePressEvent( QMouseEvent* e ) override; + void mouseMoveEvent( QMouseEvent* e ) override; void drawColumn( QPainter& p, e_OverviewMode eOverviewMode, int x, int w, int h, int nofLines ); }; enum e_MergeDetails { eDefault, eNoChange, eBChanged, eCChanged, eBCChanged, // conflict eBCChangedAndEqual, // possible conflict eBDeleted, eCDeleted, eBCDeleted, // possible conflict eBChanged_CDeleted, // conflict eCChanged_BDeleted, // conflict eBAdded, eCAdded, eBCAdded, // conflict eBCAddedAndEqual // possible conflict }; void mergeOneLine( const Diff3Line& d, e_MergeDetails& mergeDetails, bool& bConflict, bool& bLineRemoved, int& src, bool bTwoInputs ); enum e_MergeSrcSelector { A=1, B=2, C=3 }; class MergeResultWindow : public QWidget { Q_OBJECT public: MergeResultWindow( QWidget* pParent, Options* pOptions, QStatusBar* pStatusBar ); void init( const LineData* pLineDataA, int sizeA, const LineData* pLineDataB, int sizeB, const LineData* pLineDataC, int sizeC, const Diff3LineList* pDiff3LineList, TotalDiffStatus* pTotalDiffStatus ); void reset(); bool saveDocument( const QString& fileName, QTextCodec* pEncoding, e_LineEndStyle eLineEndStyle ); int getNrOfUnsolvedConflicts(int* pNrOfWhiteSpaceConflicts=nullptr); void choose(int selector); void chooseGlobal(int selector, bool bConflictsOnly, bool bWhiteSpaceOnly ); int getMaxTextWidth(); // width of longest text line int getNofLines(); int getVisibleTextAreaWidth(); // text area width without the border int getNofVisibleLines(); QString getSelection(); void resetSelection(); void showNrOfConflicts(); bool isDeltaAboveCurrent(); bool isDeltaBelowCurrent(); bool isConflictAboveCurrent(); bool isConflictBelowCurrent(); bool isUnsolvedConflictAtCurrent(); bool isUnsolvedConflictAboveCurrent(); bool isUnsolvedConflictBelowCurrent(); bool findString( const QString& s, int& d3vLine, int& posInLine, bool bDirDown, bool bCaseSensitive ); void setSelection( int firstLine, int startPos, int lastLine, int endPos ); void setOverviewMode( Overview::e_OverviewMode eOverviewMode ); Overview::e_OverviewMode getOverviewMode(); public Q_SLOTS: void setFirstLine(int firstLine); void setHorizScrollOffset(int horizScrollOffset); void slotGoCurrent(); void slotGoTop(); void slotGoBottom(); void slotGoPrevDelta(); void slotGoNextDelta(); void slotGoPrevUnsolvedConflict(); void slotGoNextUnsolvedConflict(); void slotGoPrevConflict(); void slotGoNextConflict(); void slotAutoSolve(); void slotUnsolve(); void slotMergeHistory(); void slotRegExpAutoMerge(); void slotSplitDiff( int firstD3lLineIdx, int lastD3lLineIdx ); void slotJoinDiffs( int firstD3lLineIdx, int lastD3lLineIdx ); void slotSetFastSelectorLine(int); void setPaintingAllowed(bool); void updateSourceMask(); void slotStatusMessageChanged( const QString& ); Q_SIGNALS: void scroll( int deltaX, int deltaY ); void modifiedChanged(bool bModified); void setFastSelectorRange( int line1, int nofLines ); void sourceMask( int srcMask, int enabledMask ); void resizeSignal(); void selectionEnd(); void newSelection(); void updateAvailabilities(); void showPopupMenu( const QPoint& point ); void noRelevantChangesDetected(); private: void merge(bool bAutoSolve, int defaultSelector, bool bConflictsOnly=false, bool bWhiteSpaceOnly=false ); QString getString( int lineIdx ); Options* m_pOptions; const LineData* m_pldA; const LineData* m_pldB; const LineData* m_pldC; int m_sizeA; int m_sizeB; int m_sizeC; const Diff3LineList* m_pDiff3LineList; TotalDiffStatus* m_pTotalDiffStatus; bool m_bPaintingAllowed; int m_delayedDrawTimer; Overview::e_OverviewMode m_eOverviewMode; QString m_persistentStatusMessage; void showUnsolvedConflictsStatusMessage(); private: class MergeEditLine { public: MergeEditLine(Diff3LineList::const_iterator i, int src=0){m_id3l=i; m_src=src; m_bLineRemoved=false; } void setConflict() { m_src=0; m_bLineRemoved=false; m_str=QString(); } bool isConflict() { return m_src==0 && !m_bLineRemoved && m_str.isEmpty(); } void setRemoved(int src=0) { m_src=src; m_bLineRemoved=true; m_str=QString(); } bool isRemoved() { return m_bLineRemoved; } bool isEditableText() { return !isConflict() && !isRemoved(); } void setString( const QString& s ){ m_str=s; m_bLineRemoved=false; m_src=0; } QString getString( const MergeResultWindow* ); bool isModified() { return ! m_str.isEmpty() || (m_bLineRemoved && m_src==0); } void setSource( int src, bool bLineRemoved ) { m_src=src; m_bLineRemoved =bLineRemoved; } int src() { return m_src; } Diff3LineList::const_iterator id3l(){return m_id3l;} // getString() is implemented as MergeResultWindow::getString() private: Diff3LineList::const_iterator m_id3l; int m_src; // 1, 2 or 3 for A, B or C respectively, or 0 when line is from neither source. QString m_str; // String when modified by user or null-string when orig data is used. bool m_bLineRemoved; }; class MergeEditLineList : private std::list { // I want to know the size immediately! private: typedef std::list BASE; int m_size; int* m_pTotalSize; public: typedef std::list::iterator iterator; typedef std::list::reverse_iterator reverse_iterator; typedef std::list::const_iterator const_iterator; MergeEditLineList(){m_size=0; m_pTotalSize=nullptr; } void clear() { ds(-m_size); BASE::clear(); } void push_back( const MergeEditLine& m) { ds(+1); BASE::push_back(m); } void push_front( const MergeEditLine& m) { ds(+1); BASE::push_front(m); } void pop_back() { ds(-1); BASE::pop_back(); } iterator erase( iterator i ) { ds(-1); return BASE::erase(i); } iterator insert( iterator i, const MergeEditLine& m ) { ds(+1); return BASE::insert(i,m); } int size(){ if (!m_pTotalSize) m_size = (int) BASE::size(); return m_size; } iterator begin(){return BASE::begin();} iterator end(){return BASE::end();} reverse_iterator rbegin(){return BASE::rbegin();} reverse_iterator rend(){return BASE::rend();} MergeEditLine& front(){return BASE::front();} MergeEditLine& back(){return BASE::back();} bool empty() { return m_size==0; } void splice(iterator destPos, MergeEditLineList& srcList, iterator srcFirst, iterator srcLast) { int* pTotalSize = getTotalSizePtr() ? getTotalSizePtr() : srcList.getTotalSizePtr(); srcList.setTotalSizePtr(nullptr); // Force size-recalc after splice, because splice doesn't handle size-tracking setTotalSizePtr(nullptr); BASE::splice( destPos, srcList, srcFirst, srcLast ); srcList.setTotalSizePtr( pTotalSize ); setTotalSizePtr( pTotalSize ); } void setTotalSizePtr(int* pTotalSize) { if ( pTotalSize==nullptr && m_pTotalSize!=nullptr ) { *m_pTotalSize -= size(); } else if ( pTotalSize!=nullptr && m_pTotalSize==nullptr ) { *pTotalSize += size(); } m_pTotalSize = pTotalSize; } int* getTotalSizePtr() { return m_pTotalSize; } private: void ds(int deltaSize) { m_size+=deltaSize; if (m_pTotalSize!=nullptr) *m_pTotalSize+=deltaSize; } }; friend class MergeEditLine; struct MergeLine { MergeLine() { srcSelect=0; mergeDetails=eDefault; d3lLineIdx = -1; srcRangeLength=0; bConflict=false; bDelta=false; bWhiteSpaceConflict=false; } Diff3LineList::const_iterator id3l; int d3lLineIdx; // Needed to show the correct window pos. int srcRangeLength; // how many src-lines have this properties e_MergeDetails mergeDetails; bool bConflict; bool bWhiteSpaceConflict; bool bDelta; int srcSelect; MergeEditLineList mergeEditLineList; void split( MergeLine& ml2, int d3lLineIdx2 ) // The caller must insert the ml2 after this ml in the m_mergeLineList { if ( d3lLineIdx2= d3lLineIdx + srcRangeLength ) return; //Error ml2.mergeDetails = mergeDetails; ml2.bConflict = bConflict; ml2.bWhiteSpaceConflict = bWhiteSpaceConflict; ml2.bDelta = bDelta; ml2.srcSelect = srcSelect; ml2.d3lLineIdx = d3lLineIdx2; ml2.srcRangeLength = srcRangeLength - (d3lLineIdx2-d3lLineIdx); srcRangeLength = d3lLineIdx2-d3lLineIdx; // current MergeLine controls fewer lines ml2.id3l = id3l; for(int i=0; iid3l()==ml2.id3l) { ml2.mergeEditLineList.splice( ml2.mergeEditLineList.begin(), mergeEditLineList, i, mergeEditLineList.end() ); return; } } ml2.mergeEditLineList.setTotalSizePtr( mergeEditLineList.getTotalSizePtr() ); ml2.mergeEditLineList.push_back(MergeEditLine(ml2.id3l)); } void join( MergeLine& ml2 ) // The caller must remove the ml2 from the m_mergeLineList after this call { srcRangeLength += ml2.srcRangeLength; ml2.mergeEditLineList.clear(); mergeEditLineList.clear(); mergeEditLineList.push_back(MergeEditLine(id3l)); // Create a simple conflict if ( ml2.bConflict ) bConflict = true; if ( !ml2.bWhiteSpaceConflict ) bWhiteSpaceConflict = false; if ( ml2.bDelta ) bDelta = true; } }; private: static bool sameKindCheck( const MergeLine& ml1, const MergeLine& ml2 ); struct HistoryMapEntry { MergeEditLineList mellA; MergeEditLineList mellB; MergeEditLineList mellC; MergeEditLineList& choice( bool bThreeInputs ); bool staysInPlace( bool bThreeInputs, Diff3LineList::const_iterator& iHistoryEnd ); }; typedef std::map HistoryMap; void collectHistoryInformation( int src, Diff3LineList::const_iterator iHistoryBegin, Diff3LineList::const_iterator iHistoryEnd, HistoryMap& historyMap, std::list< HistoryMap::iterator >& hitList ); typedef std::list MergeLineList; MergeLineList m_mergeLineList; MergeLineList::iterator m_currentMergeLineIt; bool isItAtEnd( bool bIncrement, MergeLineList::iterator i ) { if ( bIncrement ) return i!=m_mergeLineList.end(); else return i!=m_mergeLineList.begin(); } int m_currentPos; bool checkOverviewIgnore(MergeLineList::iterator &i); enum e_Direction { eUp, eDown }; enum e_EndPoint { eDelta, eConflict, eUnsolvedConflict, eLine, eEnd }; void go( e_Direction eDir, e_EndPoint eEndPoint ); void calcIteratorFromLineNr( int line, MergeLineList::iterator& mlIt, MergeEditLineList::iterator& melIt ); MergeLineList::iterator splitAtDiff3LineIdx( int d3lLineIdx ); - virtual void paintEvent( QPaintEvent* e ); + void paintEvent( QPaintEvent* e ) override; int getTextXOffset(); QVector getTextLayoutForLine(int line, const QString& s, QTextLayout& textLayout ); void myUpdate(int afterMilliSecs); - virtual void timerEvent(QTimerEvent*); + void timerEvent(QTimerEvent*) override; void writeLine( MyPainter& p, int line, const QString& str, int srcSelect, e_MergeDetails mergeDetails, int rangeMark, bool bUserModified, bool bLineRemoved, bool bWhiteSpaceConflict ); void setFastSelector(MergeLineList::iterator i); int convertToLine( int y ); - bool event(QEvent*); - virtual void mousePressEvent ( QMouseEvent* e ); - virtual void mouseDoubleClickEvent ( QMouseEvent* e ); - virtual void mouseReleaseEvent ( QMouseEvent * ); - virtual void mouseMoveEvent ( QMouseEvent * ); - virtual void resizeEvent( QResizeEvent* e ); - virtual void keyPressEvent( QKeyEvent* e ); - virtual void wheelEvent( QWheelEvent* e ); - virtual void focusInEvent( QFocusEvent* e ); + bool event(QEvent*) override; + void mousePressEvent ( QMouseEvent* e ) override; + void mouseDoubleClickEvent ( QMouseEvent* e ) override; + void mouseReleaseEvent ( QMouseEvent * ) override; + void mouseMoveEvent ( QMouseEvent * ) override; + void resizeEvent( QResizeEvent* e ) override; + void keyPressEvent( QKeyEvent* e ) override; + void wheelEvent( QWheelEvent* e ) override; + void focusInEvent( QFocusEvent* e ) override; QPixmap m_pixmap; int m_firstLine; int m_horizScrollOffset; int m_nofLines; int m_totalSize; //Same as m_nofLines, but calculated differently int m_maxTextWidth; bool m_bMyUpdate; bool m_bInsertMode; bool m_bModified; void setModified(bool bModified=true); int m_scrollDeltaX; int m_scrollDeltaY; int m_cursorXPos; int m_cursorXPixelPos; int m_cursorYPos; int m_cursorOldXPixelPos; bool m_bCursorOn; // blinking on and off each second QTimer m_cursorTimer; bool m_bCursorUpdate; QStatusBar* m_pStatusBar; Selection m_selection; bool deleteSelection2( QString& str, int& x, int& y, MergeLineList::iterator& mlIt, MergeEditLineList::iterator& melIt ); bool doRelevantChangesExist(); public Q_SLOTS: void deleteSelection(); void pasteClipboard(bool bFromSelection); private Q_SLOTS: void slotCursorUpdate(); }; class QLineEdit; class QTextCodec; class QComboBox; class QLabel; class WindowTitleWidget : public QWidget { Q_OBJECT private: QLabel* m_pLabel; QLineEdit* m_pFileNameLineEdit; //QPushButton* m_pBrowseButton; QLabel* m_pModifiedLabel; QLabel* m_pLineEndStyleLabel; QComboBox* m_pLineEndStyleSelector; QLabel* m_pEncodingLabel; QComboBox* m_pEncodingSelector; Options* m_pOptions; public: explicit WindowTitleWidget(Options* pOptions); QTextCodec* getEncoding(); void setFileName(const QString& fileName ); QString getFileName(); void setEncodings( QTextCodec* pCodecForA, QTextCodec* pCodecForB, QTextCodec* pCodecForC ); void setEncoding( QTextCodec* pCodec ); void setLineEndStyles( e_LineEndStyle eLineEndStyleA, e_LineEndStyle eLineEndStyleB, e_LineEndStyle eLineEndStyleC); e_LineEndStyle getLineEndStyle(); - bool eventFilter( QObject* o, QEvent* e ); + bool eventFilter( QObject* o, QEvent* e ) override; public Q_SLOTS: void slotSetModified( bool bModified ); //private Q_SLOTS: // void slotBrowseButtonClicked(); }; #endif diff --git a/src/optiondialog.cpp b/src/optiondialog.cpp index f5ff42b..2ceeded 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) { Q_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; } + void preserve() override { m_preservedVal = *m_pVar; } + void unpreserve() override { *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); } + void setToDefault() override { setChecked(m_defaultVal); } + void setToCurrent() override { setChecked(*m_pVar); } + void apply() override { *m_pVar = isChecked(); } + void write(ValueMap* config) override { config->writeEntry(m_saveName, *m_pVar); } + void read(ValueMap* config) override { *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); } + void setToDefault() override { setChecked(m_defaultVal); } + void setToCurrent() override { setChecked(*m_pVar); } + void apply() override { *m_pVar = isChecked(); } + void write(ValueMap* config) override { config->writeEntry(m_saveName, *m_pVar); } + void read(ValueMap* config) override { *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); } + void setToDefault() override {} + void setToCurrent() override {} + void apply() override {} + void write(ValueMap* vm) override { writeEntry(vm, this->m_saveName, *this->m_pVar); } + void read(ValueMap* vm) override { *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); } + void setToDefault() override { setFont(m_defaultVal, false); } + void setToCurrent() override { setFont(*m_pVar, false); } + void apply() override { *m_pVar = font(); } + void write(ValueMap* config) override { config->writeEntry(m_saveName, *m_pVar); } + void read(ValueMap* config) override { *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); } + void setToDefault() override { setColor(m_defaultVal); } + void setToCurrent() override { setColor(*m_pVar); } + void apply() override { *m_pVar = color(); } + void write(ValueMap* config) override { config->writeEntry(m_saveName, *m_pVar); } + void read(ValueMap* config) override { *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() + void setToDefault() override { setEditText(m_defaultVal); } - void setToCurrent() + void setToCurrent() override { setEditText(*m_pVar); } - void apply() + void apply() override { *m_pVar = currentText(); insertText(); } - void write(ValueMap* config) + void write(ValueMap* config) override { config->writeEntry(m_saveName, m_list); } - void read(ValueMap* config) + void read(ValueMap* config) override { 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() + void setToDefault() override { QString s; s.setNum(m_defaultVal); setText(s); } - void setToCurrent() + void setToCurrent() override { QString s; s.setNum(*m_pVar); setText(s); } - void apply() + void apply() override { 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); } + void write(ValueMap* config) override { config->writeEntry(m_saveName, *m_pVar); } + void read(ValueMap* config) override { *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() + void setToDefault() override { setCurrentIndex(m_defaultVal); if(m_pVarStr != nullptr) { *m_pVarStr = currentText(); } } - void setToCurrent() + void setToCurrent() override { if(m_pVarNum != nullptr) setCurrentIndex(*m_pVarNum); else setText(*m_pVarStr); } - void apply() + void apply() override { if(m_pVarNum != nullptr) { *m_pVarNum = currentIndex(); } else { *m_pVarStr = currentText(); } } - void write(ValueMap* config) + void write(ValueMap* config) override { if(m_pVarStr != nullptr) config->writeEntry(m_saveName, *m_pVarStr); else config->writeEntry(m_saveName, *m_pVarNum); } - void read(ValueMap* config) + void read(ValueMap* config) override { if(m_pVarStr != nullptr) setText(config->readEntry(m_saveName, currentText())); else *m_pVarNum = config->readNumEntry(m_saveName, *m_pVarNum); } - void preserve() + void preserve() override { if(m_pVarStr != nullptr) { m_preservedStrVal = *m_pVarStr; } else { m_preservedNumVal = *m_pVarNum; } } - void unpreserve() + void unpreserve() override { 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() + void setToDefault() override { 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() + void setToCurrent() override { if(m_ppVarCodec != nullptr) { for(int i = 0; i < m_codecVec.size(); ++i) { if(*m_ppVarCodec == m_codecVec[i]) { setCurrentIndex(i); break; } } } } - void apply() + void apply() override { if(m_ppVarCodec != nullptr) { *m_ppVarCodec = m_codecVec[currentIndex()]; } } - void write(ValueMap* config) + void write(ValueMap* config) override { if(m_ppVarCodec != nullptr) config->writeEntry(m_saveName, QString((*m_ppVarCodec)->name())); } - void read(ValueMap* config) + void read(ValueMap* config) override { 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); } + void preserve() override { m_preservedVal = currentIndex(); } + void unpreserve() override { 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(Q_OS_WIN) 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(Q_OS_WIN) 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(Q_OS_WIN) && !defined(Q_OS_MAC) // 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 Q_OS_WIN #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 Q_OS_WIN 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 Q_OS_WIN char kdiff3CommandPath[1000]; GetModuleFileNameA(0, kdiff3CommandPath, sizeof(kdiff3CommandPath) - 1); integrateWithClearCase("install", kdiff3CommandPath); #endif } void OptionDialog::slotRemoveClearCaseIntegration() { #ifdef Q_OS_WIN 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 Q_OS_WIN 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) + void writeEntry(const QString& s, const QFont& v) override { m_config.writeEntry(s, v); } - void writeEntry(const QString& s, const QColor& v) + void writeEntry(const QString& s, const QColor& v) override { m_config.writeEntry(s, v); } - void writeEntry(const QString& s, const QSize& v) + void writeEntry(const QString& s, const QSize& v) override { m_config.writeEntry(s, v); } - void writeEntry(const QString& s, const QPoint& v) + void writeEntry(const QString& s, const QPoint& v) override { m_config.writeEntry(s, v); } - void writeEntry(const QString& s, int v) + void writeEntry(const QString& s, int v) override { m_config.writeEntry(s, v); } - void writeEntry(const QString& s, bool v) + void writeEntry(const QString& s, bool v) override { m_config.writeEntry(s, v); } - void writeEntry(const QString& s, const QString& v) + void writeEntry(const QString& s, const QString& v) override { m_config.writeEntry(s, v); } - void writeEntry(const QString& s, const char* v) + void writeEntry(const QString& s, const char* v) override { m_config.writeEntry(s, v); } - QFont readFontEntry(const QString& s, const QFont* defaultVal) + QFont readFontEntry(const QString& s, const QFont* defaultVal) override { return m_config.readEntry(s, *defaultVal); } - QColor readColorEntry(const QString& s, const QColor* defaultVal) + QColor readColorEntry(const QString& s, const QColor* defaultVal) override { return m_config.readEntry(s, *defaultVal); } - QSize readSizeEntry(const QString& s, const QSize* defaultVal) + QSize readSizeEntry(const QString& s, const QSize* defaultVal) override { return m_config.readEntry(s, *defaultVal); } - QPoint readPointEntry(const QString& s, const QPoint* defaultVal) + QPoint readPointEntry(const QString& s, const QPoint* defaultVal) override { return m_config.readEntry(s, *defaultVal); } - bool readBoolEntry(const QString& s, bool defaultVal) + bool readBoolEntry(const QString& s, bool defaultVal) override { return m_config.readEntry(s, defaultVal); } - int readNumEntry(const QString& s, int defaultVal) + int readNumEntry(const QString& s, int defaultVal) override { return m_config.readEntry(s, defaultVal); } - QString readStringEntry(const QString& s, const QString& defaultVal) + QString readStringEntry(const QString& s, const QString& defaultVal) override { return m_config.readEntry(s, defaultVal); } - void writeEntry(const QString& s, const QStringList& v) + void writeEntry(const QString& s, const QStringList& v) override { m_config.writeEntry(s, v); } - QStringList readListEntry(const QString& s, const QStringList& def) + QStringList readListEntry(const QString& s, const QStringList& def) override { 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" diff --git a/src/optiondialog.h b/src/optiondialog.h index 2bcb16b..e972950 100644 --- a/src/optiondialog.h +++ b/src/optiondialog.h @@ -1,134 +1,134 @@ /* * kdiff3 - Text Diff And Merge Tool * Copyright (C) 2002-2007 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. * */ #ifndef OPTION_DIALOG_H #define OPTION_DIALOG_H #include #include #include #include #include "options.h" class QLabel; class QPlainTextEdit; class OptionItem; class OptionCheckBox; class OptionEncodingComboBox; class OptionLineEdit; class KKeyDialog; class OptionDialog : public KPageDialog { Q_OBJECT public: OptionDialog( bool bShowDirMergeSettings, QWidget *parent = nullptr, char *name = nullptr ); - ~OptionDialog( void ); + ~OptionDialog( void ) override; QString parseOptions( const QStringList& optionList ); QString calcOptionHelp(); Options m_options; void saveOptions(KSharedConfigPtr config); void readOptions(KSharedConfigPtr config); void setState(); // Must be called before calling exec(); void addOptionItem(OptionItem*); KKeyDialog* m_pKeyDialog; protected Q_SLOTS: virtual void slotDefault( void ); virtual void slotOk( void ); virtual void slotApply( void ); //virtual void buttonClicked( QAbstractButton* ); virtual void helpRequested(); void slotEncodingChanged(); void slotHistoryMergeRegExpTester(); void slotIntegrateWithClearCase(); void slotRemoveClearCaseIntegration(); Q_SIGNALS: void applyDone(); private: void resetToDefaults(); std::list m_optionItemList; //QDialogButtonBox *mButtonBox; OptionCheckBox* m_pSameEncoding; OptionEncodingComboBox* m_pEncodingAComboBox; OptionCheckBox* m_pAutoDetectUnicodeA; OptionEncodingComboBox* m_pEncodingBComboBox; OptionCheckBox* m_pAutoDetectUnicodeB; OptionEncodingComboBox* m_pEncodingCComboBox; OptionCheckBox* m_pAutoDetectUnicodeC; OptionEncodingComboBox* m_pEncodingOutComboBox; OptionCheckBox* m_pAutoSelectOutEncoding; OptionEncodingComboBox* m_pEncodingPPComboBox; OptionCheckBox* m_pHistoryAutoMerge; OptionLineEdit* m_pAutoMergeRegExpLineEdit; OptionLineEdit* m_pHistoryStartRegExpLineEdit; OptionLineEdit* m_pHistoryEntryStartRegExpLineEdit; OptionCheckBox* m_pHistoryMergeSorting; OptionLineEdit* m_pHistorySortKeyOrderLineEdit; private: void setupFontPage(); void setupColorPage(); void setupEditPage(); void setupDiffPage(); void setupMergePage(); void setupDirectoryMergePage(); void setupKeysPage(); void setupRegionalPage(); void setupIntegrationPage(); void setupOtherOptions(); }; class FontChooser : public QGroupBox { Q_OBJECT QFont m_font; QPushButton* m_pSelectFont; QPlainTextEdit* m_pExampleTextEdit; QLabel* m_pLabel; public: explicit FontChooser( QWidget* pParent ); QFont font(); void setFont( const QFont&, bool ); private slots: void slotSelectFont(); }; #endif diff --git a/src/progress.h b/src/progress.h index 2e6762f..a995fc5 100644 --- a/src/progress.h +++ b/src/progress.h @@ -1,139 +1,139 @@ /*************************************************************************** * Copyright (C) 2003-2007 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. * ***************************************************************************/ #ifndef PROGRESS_H #define PROGRESS_H #include #include #include class KJob; class QEventLoop; class QLabel; class QProgressBar; class QStatusBar; class ProgressDialog : public QDialog { Q_OBJECT public: ProgressDialog( QWidget* pParent,QStatusBar* ); void setStayHidden( bool bStayHidden ); void setInformation( const QString& info, bool bRedrawUpdate=true ); void setInformation( const QString& info, int current, bool bRedrawUpdate=true ); void setCurrent( int current, bool bRedrawUpdate=true ); void step( bool bRedrawUpdate=true ); void setMaxNofSteps( int dMaxNofSteps ); void addNofSteps( int nofSteps ); void push(); void pop(bool bRedrawUpdate=true); // The progressbar goes from 0 to 1 usually. // By supplying a subrange transformation the subCurrent-values // 0 to 1 will be transformed to dMin to dMax instead. // Requirement: 0 < dMin < dMax < 1 void setRangeTransformation( double dMin, double dMax ); void setSubRangeTransformation( double dMin, double dMax ); void exitEventLoop(); void enterEventLoop( KJob* pJob, const QString& jobInfo ); bool wasCancelled(); enum e_CancelReason{eUserAbort,eResize}; void cancel(e_CancelReason); e_CancelReason cancelReason(); void clearCancelState(); void show(); void hide(); void hideStatusBarWidget(); void delayedHideStatusBarWidget(); - virtual void timerEvent(QTimerEvent*); + void timerEvent(QTimerEvent*) override; public slots: void recalc(bool bRedrawUpdate); private: struct ProgressLevelData { ProgressLevelData() { m_current=0; m_maxNofSteps=1; m_dRangeMin=0; m_dRangeMax=1; m_dSubRangeMin = 0; m_dSubRangeMax = 1; } QAtomicInt m_current; QAtomicInt m_maxNofSteps; // when step() is used. double m_dRangeMax; double m_dRangeMin; double m_dSubRangeMax; double m_dSubRangeMin; }; QList m_progressStack; int m_progressDelayTimer; int m_delayedHideTimer; int m_delayedHideStatusBarWidgetTimer; QList m_eventLoopStack; QProgressBar* m_pProgressBar; QProgressBar* m_pSubProgressBar; QLabel* m_pInformation; QLabel* m_pSubInformation; QLabel* m_pSlowJobInfo; QPushButton* m_pAbortButton; QTime m_t1; QTime m_t2; bool m_bWasCancelled; e_CancelReason m_eCancelReason; KJob* m_pJob; QString m_currentJobInfo; // Needed if the job doesn't stop after a reasonable time. bool m_bStayHidden; QThread* m_pGuiThread; QStatusBar* m_pStatusBar; // status bar of main window (if exists) QWidget* m_pStatusBarWidget; QProgressBar* m_pStatusProgressBar; QPushButton* m_pStatusAbortButton; protected: - virtual void reject(); + void reject() override; private slots: void delayedHide(); void slotAbort(); }; // When using the ProgressProxy you need not take care of the push and pop, except when explicit. class ProgressProxy: public QObject { Q_OBJECT public: ProgressProxy(); - ~ProgressProxy(); + ~ProgressProxy() override; void setInformation( const QString& info, bool bRedrawUpdate=true ); void setInformation( const QString& info, int current, bool bRedrawUpdate=true ); void setCurrent( int current, bool bRedrawUpdate=true ); void step( bool bRedrawUpdate=true ); void setMaxNofSteps( int maxNofSteps ); void addNofSteps( int nofSteps ); bool wasCancelled(); void setRangeTransformation( double dMin, double dMax ); void setSubRangeTransformation( double dMin, double dMax ); static void exitEventLoop(); static void enterEventLoop( KJob* pJob, const QString& jobInfo ); static QDialog *getDialog(); static void recalc(); private: }; extern ProgressDialog* g_pProgressDialog; #endif diff --git a/src/smalldialogs.h b/src/smalldialogs.h index 5833905..f032045 100644 --- a/src/smalldialogs.h +++ b/src/smalldialogs.h @@ -1,118 +1,118 @@ /*************************************************************************** * Copyright (C) 2005 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. * * * * 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. * ***************************************************************************/ #ifndef SMALLDIALOGS_H #define SMALLDIALOGS_H #include class Options; class QComboBox; class QCheckBox; class QLineEdit; class OpenDialog : public QDialog { Q_OBJECT public: OpenDialog( QWidget* pParent, const QString& n1, const QString& n2, const QString& n3, bool bMerge, const QString& outputName, const char* slotConfigure, Options* pOptions ); QComboBox* m_pLineA; QComboBox* m_pLineB; QComboBox* m_pLineC; QComboBox* m_pLineOut; QCheckBox* m_pMerge; - virtual void accept(); - virtual bool eventFilter(QObject* o, QEvent* e); + void accept() override; + bool eventFilter(QObject* o, QEvent* e) override; private: Options* m_pOptions; void selectURL( QComboBox* pLine, bool bDir, int i, bool bSave ); bool m_bInputFileNameChanged; private Q_SLOTS: void selectFileA(); void selectFileB(); void selectFileC(); void selectDirA(); void selectDirB(); void selectDirC(); void selectOutputName(); void selectOutputDir(); void internalSlot(int); void inputFilenameChanged(); void slotSwapCopyNames(QAction*); Q_SIGNALS: void internalSignal(bool); }; class FindDialog : public QDialog { Q_OBJECT public: explicit FindDialog(QWidget* pParent); - void setVisible(bool); //override QDialog::setVisible() + void setVisible(bool) override; Q_SIGNALS: void findNext(); public: QLineEdit* m_pSearchString; QCheckBox* m_pSearchInA; QCheckBox* m_pSearchInB; QCheckBox* m_pSearchInC; QCheckBox* m_pSearchInOutput; QCheckBox* m_pCaseSensitive; int currentLine; int currentPos; int currentWindow; }; class RegExpTester : public QDialog { Q_OBJECT private: QLineEdit* m_pAutoMergeRegExpEdit; QLineEdit* m_pAutoMergeMatchResult; QLineEdit* m_pAutoMergeExampleEdit; QLineEdit* m_pHistoryStartRegExpEdit; QLineEdit* m_pHistoryStartMatchResult; QLineEdit* m_pHistoryStartExampleEdit; QLineEdit* m_pHistoryEntryStartRegExpEdit; QLineEdit* m_pHistorySortKeyOrderEdit; QLineEdit* m_pHistoryEntryStartExampleEdit; QLineEdit* m_pHistoryEntryStartMatchResult; QLineEdit* m_pHistorySortKeyResult; public: RegExpTester( QWidget* pParent, const QString& autoMergeRegExpToolTip, const QString& historyStartRegExpToolTip, const QString& historyEntryStartRegExpToolTip, const QString& historySortKeyOrderToolTip ); void init( const QString& autoMergeRegExp, const QString& historyStartRegExp, const QString& historyEntryStartRegExp, const QString sortKeyOrder ); QString autoMergeRegExp(); QString historyStartRegExp(); QString historyEntryStartRegExp(); QString historySortKeyOrder(); public Q_SLOTS: void slotRecalc(); }; #endif