diff --git a/src/difftextwindow.cpp b/src/difftextwindow.cpp index 5dd7ca3..a8b336a 100644 --- a/src/difftextwindow.cpp +++ b/src/difftextwindow.cpp @@ -1,2103 +1,2115 @@ /* * KDiff3 - Text Diff And Merge Tool * * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com * SPDX-License-Identifier: GPL-2.0-or-later */ #include "difftextwindow.h" #include "FileNameLineEdit.h" #include "RLPainter.h" #include "SourceData.h" // for SourceData #include "Utils.h" // for Utils #include "common.h" // for getAtomic, max3, min3 #include "kdiff3.h" #include "merger.h" #include "options.h" #include "progress.h" #include "selection.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include QScrollBar* DiffTextWindow::mVScrollBar = nullptr; QList DiffTextWindow::s_runnables; //Used in startRunables and recalWordWrap class RecalcWordWrapRunnable : public QRunnable { private: static QAtomicInt s_runnableCount; DiffTextWindow* m_pDTW; int m_visibleTextWidth; int m_cacheIdx; public: static QAtomicInt s_maxNofRunnables; RecalcWordWrapRunnable(DiffTextWindow* p, int visibleTextWidth, int cacheIdx) : m_pDTW(p), m_visibleTextWidth(visibleTextWidth), m_cacheIdx(cacheIdx) { setAutoDelete(true); s_runnableCount.fetchAndAddOrdered(1); } void run() override { m_pDTW->recalcWordWrapHelper(0, m_visibleTextWidth, m_cacheIdx); int newValue = s_runnableCount.fetchAndAddOrdered(-1) - 1; g_pProgressDialog->setCurrent(s_maxNofRunnables - getAtomic(s_runnableCount)); if(newValue == 0) { Q_EMIT m_pDTW->finishRecalcWordWrap(m_visibleTextWidth); } } }; QAtomicInt RecalcWordWrapRunnable::s_runnableCount = 0; QAtomicInt RecalcWordWrapRunnable::s_maxNofRunnables = 0; class WrapLineCacheData { public: WrapLineCacheData() {} WrapLineCacheData(int d3LineIdx, int textStart, int textLength) : m_d3LineIdx(d3LineIdx), m_textStart(textStart), m_textLength(textLength) {} qint32 d3LineIdx() const { return m_d3LineIdx; } qint32 textStart() const { return m_textStart; } qint32 textLength() const { return m_textLength; } private: qint32 m_d3LineIdx = 0; qint32 m_textStart = 0; qint32 m_textLength = 0; }; class DiffTextWindowData { public: explicit DiffTextWindowData(DiffTextWindow* p) { m_pDiffTextWindow = p; #if defined(Q_OS_WIN) m_eLineEndStyle = eLineEndStyleDos; #else m_eLineEndStyle = eLineEndStyleUnix; #endif } QString getString(int d3lIdx); QString getLineString(int line); void writeLine( RLPainter& p, const LineData* pld, const DiffList* pLineDiff1, const DiffList* pLineDiff2, const LineRef& line, const ChangeFlags whatChanged, const ChangeFlags whatChanged2, const LineRef& srcLineIdx, int wrapLineOffset, int wrapLineLength, bool bWrapLine, const QRect& invalidRect); void draw(RLPainter& p, const QRect& invalidRect, int beginLine, int endLine); void myUpdate(int afterMilliSecs); int leftInfoWidth() const { return 4 + m_lineNumberWidth; } // Nr of information columns on left side int convertLineOnScreenToLineInSource(int lineOnScreen, e_CoordType coordType, bool bFirstLine); void prepareTextLayout(QTextLayout& textLayout, int visibleTextWidth = -1); bool isThreeWay() const { return m_bTriple; }; const QString& getFileName() { return m_filename; } const Diff3LineVector* getDiff3LineVector() { return m_pDiff3LineVector; } const QSharedPointer& getOptions() { return m_pOptions; } private: //TODO: Remove friend classes after creating accessors. Please don't add new classes here friend DiffTextWindow; DiffTextWindow* m_pDiffTextWindow; QTextCodec* m_pTextCodec = nullptr; e_LineEndStyle m_eLineEndStyle; const QVector* m_pLineData = nullptr; int m_size = 0; QString m_filename; bool m_bWordWrap = false; int m_delayedDrawTimer = 0; const Diff3LineVector* m_pDiff3LineVector = nullptr; Diff3WrapLineVector m_diff3WrapLineVector; const ManualDiffHelpList* m_pManualDiffHelpList = nullptr; QList> m_wrapLineCacheList; QSharedPointer m_pOptions; QColor m_cThis; QColor m_cDiff1; QColor m_cDiff2; QColor m_cDiffBoth; int m_fastSelectorLine1 = 0; int m_fastSelectorNofLines = 0; bool m_bTriple = false; e_SrcSelector m_winIdx = e_SrcSelector::None; int m_firstLine = 0; int m_oldFirstLine = 0; int m_horizScrollOffset = 0; int m_lineNumberWidth = 0; QAtomicInt m_maxTextWidth = -1; Selection m_selection; int m_scrollDeltaX = 0; int m_scrollDeltaY = 0; bool m_bMyUpdate = false; bool m_bSelectionInProgress = false; QPoint m_lastKnownMousePos; QSharedPointer sourceData; }; void DiffTextWindow::setSourceData(const QSharedPointer& inData) { d->sourceData = inData; } bool DiffTextWindow::isThreeWay() const { return d->isThreeWay(); }; const QString& DiffTextWindow::getFileName() const { return d->getFileName(); } e_SrcSelector DiffTextWindow::getWindowIndex() const { return d->m_winIdx; }; const QString DiffTextWindow::getEncodingDisplayString() const { return d->m_pTextCodec != nullptr ? QLatin1String(d->m_pTextCodec->name()) : QString(); } e_LineEndStyle DiffTextWindow::getLineEndStyle() const { return d->m_eLineEndStyle; } const Diff3LineVector* DiffTextWindow::getDiff3LineVector() const { return d->getDiff3LineVector(); } qint32 DiffTextWindow::getLineNumberWidth() const { return (int)log10((double)std::max(d->m_size, 1)) + 1; } DiffTextWindow::DiffTextWindow( DiffTextWindowFrame* pParent, const QSharedPointer& pOptions, e_SrcSelector winIdx) : QWidget(pParent) { setObjectName(QString("DiffTextWindow%1").arg((int)winIdx)); setAttribute(Qt::WA_OpaquePaintEvent); //setAttribute( Qt::WA_PaintOnScreen ); setUpdatesEnabled(false); d = new DiffTextWindowData(this); setFocusPolicy(Qt::ClickFocus); setAcceptDrops(true); d->m_pOptions = pOptions; init(QString(""), nullptr, d->m_eLineEndStyle, nullptr, 0, nullptr, nullptr, false); setMinimumSize(QSize(20, 20)); setUpdatesEnabled(true); d->m_bWordWrap = false; d->m_winIdx = winIdx; setFont(d->getOptions()->m_font); } DiffTextWindow::~DiffTextWindow() { delete d; } void DiffTextWindow::init( const QString& filename, QTextCodec* pTextCodec, e_LineEndStyle eLineEndStyle, const QVector* pLineData, int size, const Diff3LineVector* pDiff3LineVector, const ManualDiffHelpList* pManualDiffHelpList, bool bTriple) { d->m_filename = filename; d->m_pLineData = pLineData; d->m_size = size; d->m_pDiff3LineVector = pDiff3LineVector; d->m_diff3WrapLineVector.clear(); d->m_pManualDiffHelpList = pManualDiffHelpList; d->m_firstLine = 0; d->m_oldFirstLine = -1; d->m_horizScrollOffset = 0; d->m_bTriple = bTriple; d->m_scrollDeltaX = 0; d->m_scrollDeltaY = 0; d->m_bMyUpdate = false; d->m_fastSelectorLine1 = 0; d->m_fastSelectorNofLines = 0; d->m_lineNumberWidth = 0; d->m_maxTextWidth = -1; d->m_pTextCodec = pTextCodec; d->m_eLineEndStyle = eLineEndStyle; update(); } void DiffTextWindow::setupConnections(const KDiff3App *app) const { Q_ASSERT(qobject_cast(parent()) != nullptr); connect(this, &DiffTextWindow::scrollVertically, mVScrollBar, &QScrollBar::setValue); connect(this, &DiffTextWindow::firstLineChanged, dynamic_cast(parent()), &DiffTextWindowFrame::setFirstLine); connect(this, &DiffTextWindow::newSelection, app, &KDiff3App::slotSelectionStart); connect(this, &DiffTextWindow::selectionEnd, app, &KDiff3App::slotSelectionEnd); connect(this, &DiffTextWindow::scrollDiffTextWindow, app, &KDiff3App::scrollDiffTextWindow); connect(this, &DiffTextWindow::finishRecalcWordWrap, app, &KDiff3App::slotFinishRecalcWordWrap, Qt::QueuedConnection); connect(this, &DiffTextWindow::checkIfCanContinue, app, &KDiff3App::slotCheckIfCanContinue); connect(this, &DiffTextWindow::finishDrop, app, &KDiff3App::slotFinishDrop); connect(this, &DiffTextWindow::statusBarMessage, app, &KDiff3App::slotStatusMsg); connect(app, &KDiff3App::showWhiteSpaceToggled, this, static_cast(&DiffTextWindow::update)); connect(app, &KDiff3App::showLineNumbersToggled, this, static_cast(&DiffTextWindow::update)); connect(app, &KDiff3App::doRefresh, this, &DiffTextWindow::slotRefresh); + connect(app, &KDiff3App::selectAll, this, &DiffTextWindow::slotSelectAll); } void DiffTextWindow::reset() { d->m_pLineData = nullptr; d->m_size = 0; d->m_pDiff3LineVector = nullptr; d->m_filename = ""; d->m_diff3WrapLineVector.clear(); } void DiffTextWindow::slotRefresh() { setFont(d->getOptions()->m_font); update(); } +void DiffTextWindow::slotSelectAll() +{ + LineRef l = 0; + int p = 0; // needed as dummy return values + + if(hasFocus()) + { + setSelection(0, 0, getNofLines(), 0, l, p); + } +} + void DiffTextWindow::setPaintingAllowed(bool bAllowPainting) { if(updatesEnabled() != bAllowPainting) { setUpdatesEnabled(bAllowPainting); if(bAllowPainting) update(); else reset(); } } void DiffTextWindow::dragEnterEvent(QDragEnterEvent* e) { e->setAccepted(e->mimeData()->hasUrls() || e->mimeData()->hasText()); } void DiffTextWindow::dropEvent(QDropEvent* dropEvent) { dropEvent->accept(); if(dropEvent->mimeData()->hasUrls()) { QList urlList = dropEvent->mimeData()->urls(); bool bShouldConintue = false; Q_EMIT checkIfCanContinue(bShouldConintue); if(bShouldConintue && !urlList.isEmpty()) { QString filename = urlList.first().toLocalFile(); d->sourceData->setFilename(filename); Q_EMIT finishDrop(); } } else if(dropEvent->mimeData()->hasText()) { QString text = dropEvent->mimeData()->text(); bool bShouldConintue = false; Q_EMIT checkIfCanContinue(bShouldConintue); if(bShouldConintue) { QString error; error = d->sourceData->setData(text); if(!error.isEmpty()) { KMessageBox::error(this, error); } Q_EMIT finishDrop(); } } } void DiffTextWindow::printWindow(RLPainter& painter, const QRect& view, const QString& headerText, int line, int linesPerPage, const QColor& fgColor) { QRect clipRect = view; clipRect.setTop(0); painter.setClipRect(clipRect); painter.translate(view.left(), 0); QFontMetrics fm = painter.fontMetrics(); //if ( fm.width(headerText) > view.width() ) { // A simple wrapline algorithm int l = 0; for(int p = 0; p < headerText.length();) { QString s = headerText.mid(p); int i; for(i = 2; i < s.length(); ++i) if(Utils::getHorizontalAdvance(fm, s, i) > view.width()) { --i; break; } //QString s2 = s.left(i); painter.drawText(0, l * fm.height() + fm.ascent(), s.left(i)); p += i; ++l; } painter.setPen(fgColor); painter.drawLine(0, view.top() - 2, view.width(), view.top() - 2); } painter.translate(0, view.top()); print(painter, view, line, linesPerPage); painter.resetTransform(); } void DiffTextWindow::setFirstLine(QtNumberType firstLine) { int fontHeight = fontMetrics().lineSpacing(); LineRef newFirstLine = std::max(0, firstLine); int deltaY = fontHeight * (d->m_firstLine - newFirstLine); d->m_firstLine = newFirstLine; if(d->m_bSelectionInProgress && d->m_selection.isValidFirstLine()) { LineRef line; int pos; convertToLinePos(d->m_lastKnownMousePos.x(), d->m_lastKnownMousePos.y(), line, pos); d->m_selection.end(line, pos); update(); } else { scroll(0, deltaY); } Q_EMIT firstLineChanged(d->m_firstLine); } int DiffTextWindow::getFirstLine() { return d->m_firstLine; } void DiffTextWindow::setHorizScrollOffset(int horizScrollOffset) { int fontWidth = Utils::getHorizontalAdvance(fontMetrics(), '0'); int xOffset = d->leftInfoWidth() * fontWidth; int deltaX = d->m_horizScrollOffset - std::max(0, horizScrollOffset); d->m_horizScrollOffset = std::max(0, horizScrollOffset); if(d->m_bSelectionInProgress && d->m_selection.isValidFirstLine()) { LineRef line; int pos; convertToLinePos(d->m_lastKnownMousePos.x(), d->m_lastKnownMousePos.y(), line, pos); d->m_selection.end(line, pos); } else { QRect r(xOffset, 0, width(), height()); if(d->getOptions()->m_bRightToLeftLanguage) { deltaX = -deltaX; r = QRect(width() - xOffset - 2, 0, -(width()), height()).normalized(); } scroll(deltaX, 0, r); } update(); } 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); if(textLayout.maximumWidth() > getAtomic(d->m_maxTextWidth)) d->m_maxTextWidth = qCeil(textLayout.maximumWidth()); } } return getAtomic(d->m_maxTextWidth); } LineCount DiffTextWindow::getNofLines() { return d->m_bWordWrap ? d->m_diff3WrapLineVector.size() : d->m_pDiff3LineVector->size(); } int DiffTextWindow::convertLineToDiff3LineIdx(LineRef line) { if(line.isValid() && d->m_bWordWrap && d->m_diff3WrapLineVector.size() > 0) return d->m_diff3WrapLineVector[std::min((LineRef::LineType)line, d->m_diff3WrapLineVector.size() - 1)].diff3LineIndex; else return line; } LineRef DiffTextWindow::convertDiff3LineIdxToLine(int d3lIdx) { if(d->m_bWordWrap && d->m_pDiff3LineVector != nullptr && d->m_pDiff3LineVector->size() > 0) return (*d->m_pDiff3LineVector)[std::min(d3lIdx, (int)d->m_pDiff3LineVector->size() - 1)]->sumLinesNeededForDisplay(); else return d3lIdx; } /** Returns a line number where the linerange [line, line+nofLines] can be displayed best. If it fits into the currently visible range then the returned value is the current firstLine. */ int getBestFirstLine(int line, int nofLines, int firstLine, int visibleLines) { int newFirstLine = firstLine; if(line < firstLine || line + nofLines + 2 > firstLine + visibleLines) { if(nofLines > visibleLines || nofLines <= (2 * visibleLines / 3 - 1)) newFirstLine = line - visibleLines / 3; else newFirstLine = line - (visibleLines - nofLines); } return newFirstLine; } void DiffTextWindow::setFastSelectorRange(int line1, int nofLines) { d->m_fastSelectorLine1 = line1; d->m_fastSelectorNofLines = nofLines; if(isVisible()) { int newFirstLine = getBestFirstLine( convertDiff3LineIdxToLine(d->m_fastSelectorLine1), convertDiff3LineIdxToLine(d->m_fastSelectorLine1 + d->m_fastSelectorNofLines) - convertDiff3LineIdxToLine(d->m_fastSelectorLine1), d->m_firstLine, getNofVisibleLines()); if(newFirstLine != d->m_firstLine) { Q_EMIT scrollVertically(newFirstLine - d->m_firstLine); } update(); } } /* Takes the line number estimated from mouse position and converts it to the actual line in the file. Then sets the status message accordingly. emits lineClicked signal. */ void DiffTextWindow::showStatusLine(const LineRef aproxLine) { int d3lIdx = convertLineToDiff3LineIdx(aproxLine); if(d->m_pDiff3LineVector != nullptr && d3lIdx >= 0 && d3lIdx < (int)d->m_pDiff3LineVector->size()) { const Diff3Line* pD3l = (*d->m_pDiff3LineVector)[d3lIdx]; if(pD3l != nullptr) { LineRef actualLine = pD3l->getLineInFile(d->m_winIdx); QString message; if(actualLine.isValid()) message = i18n("File %1: Line %2", d->m_filename, actualLine + 1); else message = i18n("File %1: Line not available", d->m_filename); Q_EMIT statusBarMessage(message); Q_EMIT lineClicked(d->m_winIdx, actualLine); } } } void DiffTextWindow::focusInEvent(QFocusEvent* e) { Q_EMIT gotFocus(); QWidget::focusInEvent(e); } void DiffTextWindow::mousePressEvent(QMouseEvent* e) { if(e->button() == Qt::LeftButton) { LineRef line; int pos; convertToLinePos(e->x(), e->y(), line, pos); int fontWidth = Utils::getHorizontalAdvance(fontMetrics(), '0'); int xOffset = d->leftInfoWidth() * fontWidth; if((!d->getOptions()->m_bRightToLeftLanguage && e->x() < xOffset) || (d->getOptions()->m_bRightToLeftLanguage && e->x() > width() - xOffset)) { Q_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); } } } void DiffTextWindow::mouseDoubleClickEvent(QMouseEvent* e) { d->m_bSelectionInProgress = false; d->m_lastKnownMousePos = e->pos(); if(e->button() == Qt::LeftButton) { LineRef line; int pos; convertToLinePos(e->x(), e->y(), line, pos); // Get the string data of the current line QString s; if(d->m_bWordWrap) { if(!line.isValid() || 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.isValid() || line >= (int)d->m_pDiff3LineVector->size()) return; s = d->getString(line); } if(!s.isEmpty()) { int pos1, pos2; Utils::calcTokenPos(s, pos, pos1, pos2); resetSelection(); d->m_selection.start(line, pos1); d->m_selection.end(line, pos2); update(); // Q_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()) { Q_EMIT selectionEnd(); } } d->m_scrollDeltaX = 0; d->m_scrollDeltaY = 0; } void DiffTextWindow::mouseMoveEvent(QMouseEvent* e) {//Handles selection highlighting. LineRef line; int pos; convertToLinePos(e->x(), e->y(), line, pos); d->m_lastKnownMousePos = e->pos(); if(d->m_selection.isValidFirstLine()) { d->m_selection.end(line, pos); showStatusLine(line); // Scroll because mouse moved out of the window const QFontMetrics& fm = fontMetrics(); int fontWidth = Utils::getHorizontalAdvance(fm, '0'); int deltaX = 0; int deltaY = 0; if(!d->getOptions()->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 - (int)std::pow(e->y(), 2) / (int)std::pow(fm.lineSpacing(), 2); if(e->y() > height()) deltaY = 1 + (int)std::pow(e->y() - height(), 2) / (int)std::pow(fm.lineSpacing(), 2); if((deltaX != 0 && d->m_scrollDeltaX != deltaX) || (deltaY != 0 && d->m_scrollDeltaY != deltaY)) { d->m_scrollDeltaX = deltaX; d->m_scrollDeltaY = deltaY; Q_EMIT scrollDiffTextWindow(deltaX, deltaY); if(d->m_delayedDrawTimer) killTimer(d->m_delayedDrawTimer); d->m_delayedDrawTimer = startTimer(50); } else { d->m_scrollDeltaX = deltaX; d->m_scrollDeltaY = deltaY; d->myUpdate(0); } } } void DiffTextWindow::wheelEvent(QWheelEvent* pWheelEvent) { QPoint delta = pWheelEvent->angleDelta(); //Block diagonal scrolling easily generated unintentionally with track pads. if(delta.y() != 0 && abs(delta.y()) > abs(delta.x()) && mVScrollBar != nullptr) { pWheelEvent->accept(); QCoreApplication::postEvent(mVScrollBar, new QWheelEvent(*pWheelEvent)); } } void DiffTextWindowData::myUpdate(int afterMilliSecs) { if(m_delayedDrawTimer) m_pDiffTextWindow->killTimer(m_delayedDrawTimer); m_bMyUpdate = true; m_delayedDrawTimer = m_pDiffTextWindow->startTimer(afterMilliSecs); } void DiffTextWindow::timerEvent(QTimerEvent*) { killTimer(d->m_delayedDrawTimer); d->m_delayedDrawTimer = 0; if(d->m_bMyUpdate) { int fontHeight = fontMetrics().lineSpacing(); if(d->m_selection.getOldLastLine().isValid()) { int lastLine; int firstLine; if(d->m_selection.getOldFirstLine().isValid()) { firstLine = min3(d->m_selection.getOldFirstLine(), d->m_selection.getLastLine(), d->m_selection.getOldLastLine()); lastLine = max3(d->m_selection.getOldFirstLine(), d->m_selection.getLastLine(), d->m_selection.getOldLastLine()); } else { firstLine = std::min(d->m_selection.getLastLine(), d->m_selection.getOldLastLine()); lastLine = std::max(d->m_selection.getLastLine(), d->m_selection.getOldLastLine()); } int y1 = (firstLine - d->m_firstLine) * fontHeight; int y2 = std::min(height(), (lastLine - d->m_firstLine + 1) * fontHeight); if(y1 < height() && y2 > 0) { QRect invalidRect = QRect(0, y1 - 1, width(), y2 - y1 + fontHeight); // Some characters in exotic exceed the regular bottom. update(invalidRect); } } d->m_bMyUpdate = false; } if(d->m_scrollDeltaX != 0 || d->m_scrollDeltaY != 0) { d->m_selection.end(d->m_selection.getLastLine() + d->m_scrollDeltaY, d->m_selection.getLastPos() + d->m_scrollDeltaX); Q_EMIT scrollDiffTextWindow(d->m_scrollDeltaX, d->m_scrollDeltaY); killTimer(d->m_delayedDrawTimer); d->m_delayedDrawTimer = startTimer(50); } } void DiffTextWindow::resetSelection() { d->m_selection.reset(); update(); } void DiffTextWindow::convertToLinePos(int x, int y, LineRef& line, int& pos) { const QFontMetrics& fm = fontMetrics(); int fontHeight = fm.lineSpacing(); int yOffset = -d->m_firstLine * fontHeight; line = (y - yOffset) / fontHeight; if(line.isValid() && (!d->getOptions()->wordWrapOn()|| line < d->m_diff3WrapLineVector.count())) { QString s = d->getLineString(line); QTextLayout textLayout(s, font(), this); d->prepareTextLayout(textLayout); pos = textLayout.lineAt(0).xToCursor(x - textLayout.position().x()); } else pos = -1; } class FormatRangeHelper { private: QFont m_font; QPen m_pen; QColor m_background; int m_currentPos; QVector m_formatRanges; public: inline operator QVector() { return m_formatRanges; } FormatRangeHelper() { m_pen = QColor(Qt::black); m_background = QColor(Qt::white); m_currentPos = 0; } void setFont(const QFont& f) { m_font = f; } void setPen(const QPen& pen) { m_pen = pen; } void setBackground(const QColor& background) { m_background = background; } void next() { if(m_formatRanges.isEmpty() || m_formatRanges.back().format.foreground().color() != m_pen.color() || m_formatRanges.back().format.background().color() != m_background) { QTextLayout::FormatRange fr; fr.length = 1; fr.start = m_currentPos; fr.format.setForeground(m_pen.color()); fr.format.setBackground(m_background); m_formatRanges.append(fr); } else { ++m_formatRanges.back().length; } ++m_currentPos; } }; void DiffTextWindowData::prepareTextLayout(QTextLayout& textLayout, int visibleTextWidth) { QTextOption textOption; #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) textOption.setTabStop(QFontMetricsF(m_pDiffTextWindow->font()).width(' ') * m_pOptions->m_tabSize); #else textOption.setTabStopDistance(QFontMetricsF(m_pDiffTextWindow->font()).width(' ') * m_pOptions->m_tabSize); #endif if(m_pOptions->m_bShowWhiteSpaceCharacters) textOption.setFlags(QTextOption::ShowTabsAndSpaces); if(m_pOptions->m_bRightToLeftLanguage) textOption.setAlignment(Qt::AlignRight); // only relevant for multi line text layout if(visibleTextWidth >= 0) textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); textLayout.setTextOption(textOption); if(m_pOptions->m_bShowWhiteSpaceCharacters) { // This additional format is only necessary for the tab arrow QVector formats; QTextLayout::FormatRange formatRange; formatRange.start = 0; formatRange.length = textLayout.text().length(); formatRange.format.setFont(m_pDiffTextWindow->font()); formats.append(formatRange); textLayout.setFormats(formats); } textLayout.beginLayout(); int leading = m_pDiffTextWindow->fontMetrics().leading(); int height = 0; int fontWidth = Utils::getHorizontalAdvance(m_pDiffTextWindow->fontMetrics(), '0'); int xOffset = leftInfoWidth() * fontWidth - m_horizScrollOffset; int textWidth = visibleTextWidth; if(textWidth < 0) textWidth = m_pDiffTextWindow->width() - xOffset; int indentation = 0; while(true) { QTextLine line = textLayout.createLine(); if(!line.isValid()) break; height += leading; //if ( !bFirstLine ) // indentation = m_pDiffTextWindow->fontMetrics().width(' ') * m_pOptions->m_tabSize; if(visibleTextWidth >= 0) { line.setLineWidth(visibleTextWidth - indentation); line.setPosition(QPointF(indentation, height)); height += qCeil(line.height()); //bFirstLine = false; } else // only one line { line.setPosition(QPointF(indentation, height)); break; } } textLayout.endLayout(); if(m_pOptions->m_bRightToLeftLanguage) textLayout.setPosition(QPointF(textWidth - textLayout.maximumWidth(), 0)); else textLayout.setPosition(QPointF(xOffset, 0)); } /* Don't try to use invalid rect to block drawing of lines based on there apparent horizontal dementions. This does not always work for very long lines being scrolled horzontally. (Causes blanking of diff text area) */ void DiffTextWindowData::writeLine( RLPainter& p, const LineData* pld, const DiffList* pLineDiff1, const DiffList* pLineDiff2, const LineRef& line, const ChangeFlags whatChanged, const ChangeFlags whatChanged2, const LineRef& srcLineIdx, int wrapLineOffset, int wrapLineLength, bool bWrapLine, const QRect& invalidRect) { QFont normalFont = p.font(); const QFontMetrics& fm = p.fontMetrics(); int fontHeight = fm.lineSpacing(); int fontAscent = fm.ascent(); int fontWidth = Utils::getHorizontalAdvance(fm, '0'); int xOffset = leftInfoWidth() * fontWidth - m_horizScrollOffset; int yOffset = (line - m_firstLine) * fontHeight; 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; ChangeFlags changed = whatChanged; if(pLineDiff1 != nullptr) changed |= AChanged; if(pLineDiff2 != nullptr) changed |= BChanged; QColor penColor = m_pOptions->m_fgColor; p.setPen(penColor); if(changed == BChanged) { penColor = m_cDiff2; } else if(changed == AChanged) { penColor = m_cDiff1; } else if(changed == Both) { penColor = m_cDiffBoth; } if(pld != nullptr) { // First calculate the "changed" information for each character. int i = 0; QString lineString = pld->getLine(); if(!lineString.isEmpty()) { switch(lineString[lineString.length() - 1].unicode()) { case '\n': lineString[lineString.length() - 1] = 0x00B6; break; // "Pilcrow", "paragraph mark" case '\r': lineString[lineString.length() - 1] = 0x00A4; break; // Currency sign ;0x2761 "curved stem paragraph sign ornament" //case '\0b' : lineString[lineString.length()-1] = 0x2756; break; // some other nice looking character } } QVector charChanged(pld->size()); 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) { penColor = m_pOptions->m_fgColor; ChangeFlags cchanged = charChanged[i] | whatChanged; if(cchanged == BChanged) { penColor = m_cDiff2; } else if(cchanged == AChanged) { penColor = m_cDiff1; } else if(cchanged == Both) { penColor = m_cDiffBoth; } if(penColor != m_pOptions->m_fgColor && whatChanged2 == NoChange && !m_pOptions->m_bShowWhiteSpace) { // The user doesn't want to see highlighted white space. penColor = m_pOptions->m_fgColor; } frh.setBackground(bgColor); if(!m_selection.within(line, outPos)) { if(penColor != 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(penColor); 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); textLayout.draw(&p, QPoint(0, yOffset), frh /*, const QRectF & clip = QRectF() */); } p.fillRect(0, yOffset, leftInfoWidth() * fontWidth, fontHeight, m_pOptions->m_bgColor); xOffset = (m_lineNumberWidth + 2) * fontWidth; int xLeft = m_lineNumberWidth * fontWidth; p.setPen(m_pOptions->m_fgColor); if(pld != nullptr) { if(m_pOptions->m_bShowLineNumbers && !bWrapLine) { QString num = QString::number(srcLineIdx + 1); Q_ASSERT(!num.isEmpty()); p.drawText(0, yOffset + fontAscent, num); } 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(penColor != m_pOptions->m_fgColor && whatChanged2 == NoChange) { if(m_pOptions->m_bShowWhiteSpace) { p.setBrushOrigin(0, 0); p.fillRect(xLeft, yOffset, fontWidth * 2 - 1, fontHeight, QBrush(penColor, Qt::Dense5Pattern)); } } else { p.fillRect(xLeft, yOffset, fontWidth * 2 - 1, fontHeight, penColor == m_pOptions->m_fgColor ? bgColor : penColor); } 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; LineRef rangeLine1; LineRef rangeLine2; mdhe.getRangeForUI(m_winIdx, &rangeLine1, &rangeLine2); if(rangeLine1.isValid() && rangeLine2.isValid() && srcLineIdx >= rangeLine1 && srcLineIdx <= rangeLine2) { p.fillRect(xOffset - fontWidth, yOffset, fontWidth - 1, fontHeight, m_pOptions->m_manualHelpRangeColor); break; } } } void DiffTextWindow::paintEvent(QPaintEvent* e) { QRect invalidRect = e->rect(); if(invalidRect.isEmpty()) return; if(d->m_pDiff3LineVector == nullptr || (d->m_diff3WrapLineVector.empty() && d->m_bWordWrap)) { QPainter p(this); p.fillRect(invalidRect, d->getOptions()->m_bgColor); return; } bool bOldSelectionContainsData = d->m_selection.bSelectionContainsData; d->m_selection.bSelectionContainsData = false; int endLine = std::min(d->m_firstLine + getNofVisibleLines() + 2, getNofLines()); RLPainter p(this, d->getOptions()->m_bRightToLeftLanguage, width(), Utils::getHorizontalAdvance(fontMetrics(), '0')); p.setFont(font()); p.QPainter::fillRect(invalidRect, d->getOptions()->m_bgColor); d->draw(p, invalidRect, d->m_firstLine, endLine); p.end(); d->m_oldFirstLine = d->m_firstLine; d->m_selection.clearOldSelection(); if(!bOldSelectionContainsData && d->m_selection.selectionContainsData()) Q_EMIT newSelection(); } void DiffTextWindow::print(RLPainter& p, const QRect&, int firstLine, int nofLinesPerPage) { if(d->m_pDiff3LineVector == nullptr || !updatesEnabled() || (d->m_diff3WrapLineVector.empty() && d->m_bWordWrap)) return; resetSelection(); int oldFirstLine = d->m_firstLine; d->m_firstLine = firstLine; QRect invalidRect = QRect(0, 0, 1000000000, 1000000000); QColor bgColor = d->getOptions()->m_bgColor; d->getOptions()->m_bgColor = Qt::white; d->draw(p, invalidRect, firstLine, std::min(firstLine + nofLinesPerPage, getNofLines())); d->getOptions()->m_bgColor = bgColor; d->m_firstLine = oldFirstLine; } void DiffTextWindowData::draw(RLPainter& p, const QRect& invalidRect, int beginLine, int endLine) { m_lineNumberWidth = m_pOptions->m_bShowLineNumbers ? (int)log10((double)std::max(m_size, 1)) + 1 : 0; if(m_winIdx == e_SrcSelector::A) { m_cThis = m_pOptions->m_colorA; m_cDiff1 = m_pOptions->m_colorB; m_cDiff2 = m_pOptions->m_colorC; } if(m_winIdx == e_SrcSelector::B) { m_cThis = m_pOptions->m_colorB; m_cDiff1 = m_pOptions->m_colorC; m_cDiff2 = m_pOptions->m_colorA; } if(m_winIdx == e_SrcSelector::C) { m_cThis = m_pOptions->m_colorC; m_cDiff1 = m_pOptions->m_colorA; m_cDiff2 = m_pOptions->m_colorB; } m_cDiffBoth = m_pOptions->m_colorForConflict; // Conflict color p.setPen(m_cThis); for(int line = beginLine; line < endLine; ++line) { int wrapLineOffset = 0; int wrapLineLength = 0; const Diff3Line* d3l = nullptr; bool bWrapLine = false; if(m_bWordWrap) { Diff3WrapLine& d3wl = m_diff3WrapLineVector[line]; wrapLineOffset = d3wl.wrapLineOffset; wrapLineLength = d3wl.wrapLineLength; d3l = d3wl.pD3L; bWrapLine = line > 0 && m_diff3WrapLineVector[line - 1].pD3L == d3l; } else { d3l = (*m_pDiff3LineVector)[line]; } DiffList* pFineDiff1; DiffList* pFineDiff2; ChangeFlags changed = NoChange; ChangeFlags changed2 = NoChange; LineRef srcLineIdx; d3l->getLineInfo(m_winIdx, m_bTriple, srcLineIdx, pFineDiff1, pFineDiff2, changed, changed2); writeLine( p, // QPainter !srcLineIdx.isValid() ? nullptr : &(*m_pLineData)[srcLineIdx], // Text in this line pFineDiff1, pFineDiff2, line, // Line on the screen changed, changed2, srcLineIdx, wrapLineOffset, wrapLineLength, bWrapLine, invalidRect); } } QString DiffTextWindowData::getString(int d3lIdx) { if(d3lIdx < 0 || d3lIdx >= m_pDiff3LineVector->size()) return QString(); const Diff3Line* d3l = (*m_pDiff3LineVector)[d3lIdx]; DiffList* pFineDiff1; DiffList* pFineDiff2; ChangeFlags changed = NoChange; ChangeFlags changed2 = NoChange; LineRef lineIdx; d3l->getLineInfo(m_winIdx, m_bTriple, lineIdx, pFineDiff1, pFineDiff2, changed, changed2); if(!lineIdx.isValid()) return QString(); return (*m_pLineData)[lineIdx].getLine(); } 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 DiffTextWindow::resizeEvent(QResizeEvent* e) { QSize s = e->size(); QFontMetrics fm = fontMetrics(); int visibleLines = s.height() / fm.lineSpacing() - 2; int visibleColumns = s.width() / Utils::getHorizontalAdvance(fm, '0') - d->leftInfoWidth(); if(e->size().height() != e->oldSize().height()) Q_EMIT resizeHeightChangedSignal(visibleLines); if(e->size().width() != e->oldSize().width()) Q_EMIT resizeWidthChangedSignal(visibleColumns); QWidget::resizeEvent(e); } int DiffTextWindow::getNofVisibleLines() { QFontMetrics fm = fontMetrics(); int fmh = fm.lineSpacing(); int h = height(); return h / fmh - 1; } int DiffTextWindow::getVisibleTextAreaWidth() { QFontMetrics fm = fontMetrics(); return width() - d->leftInfoWidth() * Utils::getHorizontalAdvance(fm, '0'); } QString DiffTextWindow::getSelection() { if(d->m_pLineData == nullptr) return QString(); QString selectionString; int line = 0; int lineIdx = 0; int it; int vectorSize = d->m_bWordWrap ? d->m_diff3WrapLineVector.size() : d->m_pDiff3LineVector->size(); for(it = 0; it < vectorSize; ++it) { const Diff3Line* d3l = d->m_bWordWrap ? d->m_diff3WrapLineVector[it].pD3L : (*d->m_pDiff3LineVector)[it]; Q_ASSERT(d->m_winIdx >= e_SrcSelector::A && d->m_winIdx <= e_SrcSelector::C); if(d->m_winIdx == e_SrcSelector::A) { lineIdx = d3l->getLineA(); } else if(d->m_winIdx == e_SrcSelector::B) { lineIdx = d3l->getLineB(); } else if(d->m_winIdx == e_SrcSelector::C) { lineIdx = d3l->getLineC(); } if(lineIdx != -1) { int size = (*d->m_pLineData)[lineIdx].size(); QString lineString = (*d->m_pLineData)[lineIdx].getLine(); if(d->m_bWordWrap) { size = d->m_diff3WrapLineVector[it].wrapLineLength; lineString = lineString.mid(d->m_diff3WrapLineVector[it].wrapLineOffset, size); } for(int i = 0; i < size; ++i) { if(d->m_selection.within(line, i)) { selectionString += lineString[i]; } } if(d->m_selection.within(line, size) && !(d->m_bWordWrap && it + 1 < vectorSize && d3l == d->m_diff3WrapLineVector[it + 1].pD3L)) { #if defined(Q_OS_WIN) selectionString += '\r'; #endif selectionString += '\n'; } } ++line; } return selectionString; } bool DiffTextWindow::findString(const QString& s, LineRef& d3vLine, int& posInLine, bool bDirDown, bool bCaseSensitive) { int it = d3vLine; int endIt = bDirDown ? d->m_pDiff3LineVector->size() : -1; int step = bDirDown ? 1 : -1; int startPos = posInLine; for(; it != endIt; it += step) { QString line = d->getString(it); if(!line.isEmpty()) { int pos = line.indexOf(s, startPos, bCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive); if(pos != -1) { d3vLine = it; posInLine = pos; return true; } startPos = 0; } } return false; } void DiffTextWindow::convertD3LCoordsToLineCoords(int d3LIdx, int d3LPos, int& line, int& pos) { if(d->m_bWordWrap) { int wrapPos = d3LPos; int wrapLine = convertDiff3LineIdxToLine(d3LIdx); while(wrapPos > d->m_diff3WrapLineVector[wrapLine].wrapLineLength) { wrapPos -= d->m_diff3WrapLineVector[wrapLine].wrapLineLength; ++wrapLine; } pos = wrapPos; line = wrapLine; } else { pos = d3LPos; line = d3LIdx; } } void DiffTextWindow::convertLineCoordsToD3LCoords(int line, int pos, int& d3LIdx, int& d3LPos) { if(d->m_bWordWrap) { d3LPos = pos; d3LIdx = convertLineToDiff3LineIdx(line); int wrapLine = convertDiff3LineIdxToLine(d3LIdx); // First wrap line belonging to this d3LIdx while(wrapLine < line) { d3LPos += d->m_diff3WrapLineVector[wrapLine].wrapLineLength; ++wrapLine; } } else { d3LPos = pos; d3LIdx = line; } } void DiffTextWindow::setSelection(LineRef firstLine, int startPos, LineRef lastLine, int endPos, LineRef& l, int& p) { d->m_selection.reset(); if(lastLine >= getNofLines()) { lastLine = getNofLines() - 1; const Diff3Line* d3l = (*d->m_pDiff3LineVector)[convertLineToDiff3LineIdx(lastLine)]; LineRef line; if(d->m_winIdx == e_SrcSelector::A) line = d3l->getLineA(); if(d->m_winIdx == e_SrcSelector::B) line = d3l->getLineB(); if(d->m_winIdx == e_SrcSelector::C) line = d3l->getLineC(); if(line.isValid()) endPos = (*d->m_pLineData)[line].width(d->getOptions()->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, wrapStartPos); d->m_selection.end(lastWrapLine, wrapEndPos); l = firstWrapLine; p = wrapStartPos; } else { if(d->m_pDiff3LineVector != nullptr) { d->m_selection.start(firstLine, startPos); d->m_selection.end(lastLine, endPos); l = firstLine; p = startPos; } } update(); } int DiffTextWindowData::convertLineOnScreenToLineInSource(int lineOnScreen, e_CoordType coordType, bool bFirstLine) { LineRef line; if(lineOnScreen >= 0) { if(coordType == eWrapCoords) return lineOnScreen; int d3lIdx = m_pDiffTextWindow->convertLineToDiff3LineIdx(lineOnScreen); if(!bFirstLine && d3lIdx >= m_pDiff3LineVector->size()) d3lIdx = m_pDiff3LineVector->size() - 1; if(coordType == eD3LLineCoords) return d3lIdx; while(!line.isValid() && d3lIdx < m_pDiff3LineVector->size() && d3lIdx >= 0) { const Diff3Line* d3l = (*m_pDiff3LineVector)[d3lIdx]; if(m_winIdx == e_SrcSelector::A) line = d3l->getLineA(); if(m_winIdx == e_SrcSelector::B) line = d3l->getLineB(); if(m_winIdx == e_SrcSelector::C) line = d3l->getLineC(); if(bFirstLine) ++d3lIdx; else --d3lIdx; } if(coordType == eFileCoords) return line; } return line; } void DiffTextWindow::getSelectionRange(LineRef* pFirstLine, LineRef* pLastLine, e_CoordType coordType) { if(pFirstLine) *pFirstLine = d->convertLineOnScreenToLineInSource(d->m_selection.beginLine(), coordType, true); if(pLastLine) *pLastLine = d->convertLineOnScreenToLineInSource(d->m_selection.endLine(), coordType, false); } void DiffTextWindow::convertSelectionToD3LCoords() { if(d->m_pDiff3LineVector == nullptr || !updatesEnabled() || !isVisible() || d->m_selection.isEmpty()) { return; } // convert the d->m_selection to unwrapped coordinates: Later restore to new coords int firstD3LIdx, firstD3LPos; QString s = d->getLineString(d->m_selection.beginLine()); int firstPosInText = d->m_selection.beginPos(); convertLineCoordsToD3LCoords(d->m_selection.beginLine(), firstPosInText, firstD3LIdx, firstD3LPos); int lastD3LIdx, lastD3LPos; s = d->getLineString(d->m_selection.endLine()); int lastPosInText = d->m_selection.endPos(); convertLineCoordsToD3LCoords(d->m_selection.endLine(), lastPosInText, lastD3LIdx, lastD3LPos); d->m_selection.start(firstD3LIdx, firstD3LPos); d->m_selection.end(lastD3LIdx, lastD3LPos); } bool DiffTextWindow::startRunnables() { if(s_runnables.count() == 0) { return false; } else { g_pProgressDialog->setStayHidden(true); g_pProgressDialog->push(); g_pProgressDialog->setMaxNofSteps(s_runnables.count()); RecalcWordWrapRunnable::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; } } 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->getOptions()->m_bShowLineNumbers ? (int)log10((double)std::max(d->m_size, 1)) + 1 : 0; d->m_diff3WrapLineVector.resize(wrapLineVectorSize); if(wrapLineVectorSize == 0) { d->m_wrapLineCacheList.clear(); setUpdatesEnabled(false); for(int i = 0, j = 0; i < d->m_pDiff3LineVector->size(); i += s_linesPerRunnable, ++j) { d->m_wrapLineCacheList.append(QVector()); s_runnables.push_back(new RecalcWordWrapRunnable(this, visibleTextWidth, j)); } } else { recalcWordWrapHelper(wrapLineVectorSize, visibleTextWidth, 0); setUpdatesEnabled(true); } } else { if(wrapLineVectorSize == 0 && getAtomic(d->m_maxTextWidth) < 0) { d->m_diff3WrapLineVector.resize(0); d->m_wrapLineCacheList.clear(); setUpdatesEnabled(false); for(int i = 0, j = 0; i < d->m_pDiff3LineVector->size(); i += s_linesPerRunnable, ++j) { s_runnables.push_back(new RecalcWordWrapRunnable(this, visibleTextWidth, j)); } } else { setUpdatesEnabled(true); } } } void DiffTextWindow::recalcWordWrapHelper(int wrapLineVectorSize, int visibleTextWidth, int cacheListIdx) { if(d->m_bWordWrap) { if(g_pProgressDialog->wasCancelled()) return; if(visibleTextWidth < 0) visibleTextWidth = getVisibleTextAreaWidth(); else visibleTextWidth -= d->leftInfoWidth() * Utils::getHorizontalAdvance(fontMetrics(), '0'); int i; int wrapLineIdx = 0; int size = d->m_pDiff3LineVector->size(); int firstD3LineIdx = wrapLineVectorSize > 0 ? 0 : cacheListIdx * s_linesPerRunnable; int endIdx = wrapLineVectorSize > 0 ? size : std::min(firstD3LineIdx + s_linesPerRunnable, size); QVector& wrapLineCache = d->m_wrapLineCacheList[cacheListIdx]; int cacheListIdx2 = 0; QTextLayout textLayout(QString(), font(), this); for(i = firstD3LineIdx; i < endIdx; ++i) { if(g_pProgressDialog->wasCancelled()) return; int linesNeeded = 0; if(wrapLineVectorSize == 0) { QString s = d->getString(i); textLayout.clearLayout(); textLayout.setText(s); d->prepareTextLayout(textLayout, visibleTextWidth); linesNeeded = textLayout.lineCount(); for(int l = 0; l < linesNeeded; ++l) { QTextLine line = textLayout.lineAt(l); wrapLineCache.push_back(WrapLineCacheData(i, line.textStart(), line.textLength())); } } else if(wrapLineVectorSize > 0 && cacheListIdx2 < d->m_wrapLineCacheList.count()) { 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->d3LineIdx() <= i) { if(pWrapLineCache->d3LineIdx() == i) { Diff3WrapLine* pDiff3WrapLine = &d->m_diff3WrapLineVector[wrapLineIdx + l]; pDiff3WrapLine->wrapLineOffset = pWrapLineCache->textStart(); pDiff3WrapLine->wrapLineLength = pWrapLineCache->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.setLinesNeeded(linesNeeded); } if(wrapLineVectorSize > 0) { int j; for(j = 0; j < d3l.linesNeededForDisplay(); ++j, ++wrapLineIdx) { Diff3WrapLine& d3wl = d->m_diff3WrapLineVector[wrapLineIdx]; d3wl.diff3LineIndex = i; d3wl.pD3L = (*d->m_pDiff3LineVector)[i]; if(j >= linesNeeded) { d3wl.wrapLineOffset = 0; d3wl.wrapLineLength = 0; } } } } if(wrapLineVectorSize > 0) { d->m_firstLine = std::min(d->m_firstLine, wrapLineVectorSize - 1); d->m_horizScrollOffset = 0; Q_EMIT firstLineChanged(d->m_firstLine); } } else // no word wrap, just calc the maximum text width { if(g_pProgressDialog->wasCancelled()) return; int size = d->m_pDiff3LineVector->size(); int firstD3LineIdx = cacheListIdx * s_linesPerRunnable; int endIdx = std::min(firstD3LineIdx + s_linesPerRunnable, size); int maxTextWidth = getAtomic(d->m_maxTextWidth); // current value QTextLayout textLayout(QString(), font(), this); for(int i = firstD3LineIdx; i < endIdx; ++i) { if(g_pProgressDialog->wasCancelled()) return; textLayout.clearLayout(); textLayout.setText(d->getString(i)); d->prepareTextLayout(textLayout); if(textLayout.maximumWidth() > maxTextWidth) maxTextWidth = qCeil(textLayout.maximumWidth()); } for(;;) { int prevMaxTextWidth = d->m_maxTextWidth.fetchAndStoreOrdered(maxTextWidth); if(prevMaxTextWidth <= maxTextWidth) break; maxTextWidth = prevMaxTextWidth; } } if(!d->m_selection.isEmpty() && (!d->m_bWordWrap || wrapLineVectorSize > 0)) { // Assume unwrapped coordinates //( Why? ->Conversion to unwrapped coords happened a few lines above in this method. // Also see KDiff3App::recalcWordWrap() on the role of wrapLineVectorSize) // Wrap them now. // convert the d->m_selection to unwrapped coordinates. int firstLine, firstPos; convertD3LCoordsToLineCoords(d->m_selection.beginLine(), d->m_selection.beginPos(), firstLine, firstPos); int lastLine, lastPos; convertD3LCoordsToLineCoords(d->m_selection.endLine(), d->m_selection.endPos(), lastLine, lastPos); d->m_selection.start(firstLine, firstPos); d->m_selection.end(lastLine, lastPos); } } class DiffTextWindowFrameData { public: DiffTextWindowFrameData(DiffTextWindowFrame* frame, const QSharedPointer& pOptions, const e_SrcSelector winIdx) { m_winIdx = winIdx; m_pOptions = pOptions; m_pTopLineWidget = new QWidget(frame); m_pFileSelection = new FileNameLineEdit(m_pTopLineWidget); m_pBrowseButton = new QPushButton("...", m_pTopLineWidget); m_pBrowseButton->setFixedWidth(30); m_pFileSelection->setAcceptDrops(true); m_pLabel = new QLabel("A:", m_pTopLineWidget); m_pTopLine = new QLabel(m_pTopLineWidget); } const QPushButton* getBrowseButton() const { return m_pBrowseButton; } const FileNameLineEdit* getFileSelectionField() const { return m_pFileSelection; } const QWidget* getTopLineWidget() const { return m_pTopLineWidget; } const QLabel* getLabel() const { return m_pLabel; } const QSharedPointer getOptions() { return m_pOptions; } private: friend DiffTextWindowFrame; DiffTextWindow* m_pDiffTextWindow; FileNameLineEdit* m_pFileSelection; QPushButton* m_pBrowseButton; QSharedPointer m_pOptions; QLabel* m_pLabel; QLabel* m_pTopLine; QLabel* m_pEncoding; QLabel* m_pLineEndStyle; QWidget* m_pTopLineWidget; e_SrcSelector m_winIdx; QSharedPointer mSourceData; }; DiffTextWindowFrame::DiffTextWindowFrame(QWidget* pParent, const QSharedPointer& pOptions, e_SrcSelector winIdx, QSharedPointer psd) : QWidget(pParent) { d = new DiffTextWindowFrameData(this, pOptions, winIdx); d->mSourceData = psd; setAutoFillBackground(true); connect(d->getBrowseButton(), &QPushButton::clicked, this, &DiffTextWindowFrame::slotBrowseButtonClicked); connect(d->getFileSelectionField(), &QLineEdit::returnPressed, this, &DiffTextWindowFrame::slotReturnPressed); d->m_pDiffTextWindow = new DiffTextWindow(this, pOptions, winIdx); d->m_pDiffTextWindow->setSourceData(psd); QVBoxLayout* pVTopLayout = new QVBoxLayout(const_cast(d->getTopLineWidget())); 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(const_cast(d->getLabel()), 0); pHL->addWidget(const_cast(d->getFileSelectionField()), 1); pHL->addWidget(const_cast(d->getBrowseButton()), 0); pHL->addWidget(d->m_pTopLine, 0); // Lower line pHL2->setMargin(0); pHL2->setSpacing(2); pHL2->addWidget(d->m_pTopLine, 0); d->m_pEncoding = new EncodingLabel(i18n("Encoding:"), psd, pOptions); //EncodeLabel::EncodingChanged should be handled asyncroniously. connect((EncodingLabel*)d->m_pEncoding, &EncodingLabel::encodingChanged, this, &DiffTextWindowFrame::slotEncodingChanged, Qt::QueuedConnection); 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(const_cast(d->getTopLineWidget()), 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->getFileName()); d->m_pFileSelection->setText(s); QString winId = pDTW->getWindowIndex() == e_SrcSelector::A ? (pDTW->isThreeWay() ? i18n("A (Base)") : i18n("A")) : (pDTW->getWindowIndex() == e_SrcSelector::B ? i18n("B") : i18n("C")); d->m_pLabel->setText(winId + ':'); d->m_pEncoding->setText(i18n("Encoding: %1", pDTW->getEncodingDisplayString())); d->m_pLineEndStyle->setText(i18n("Line end style: %1", pDTW->getLineEndStyle() == eLineEndStyleDos ? i18n("DOS") : i18n("Unix"))); } } void DiffTextWindowFrame::setupConnections(const KDiff3App *app) { connect(this, &DiffTextWindowFrame::fileNameChanged, app, &KDiff3App::slotFileNameChanged); connect(this, &DiffTextWindowFrame::encodingChanged, app, &KDiff3App::slotEncodingChanged); connect(this, &DiffTextWindowFrame::encodingChanged, d->mSourceData.data(), &SourceData::setEncoding); } // Search for the first visible line (search loop needed when no line exists for this file.) LineRef DiffTextWindow::calcTopLineInFile(const LineRef firstLine) { LineRef currentLine; for(int i = convertLineToDiff3LineIdx(firstLine); i < (int)d->m_pDiff3LineVector->size(); ++i) { const Diff3Line* d3l = (*d->m_pDiff3LineVector)[i]; currentLine = d3l->getLineInFile(d->m_winIdx); if(currentLine.isValid()) break; } return currentLine; } void DiffTextWindowFrame::setFirstLine(QtNumberType firstLine) { DiffTextWindow* pDTW = d->m_pDiffTextWindow; if(pDTW && pDTW->getDiff3LineVector()) { QString s = i18n("Top line"); int lineNumberWidth = pDTW->getLineNumberWidth(); LineRef topVisiableLine = pDTW->calcTopLineInFile(firstLine); int w = Utils::getHorizontalAdvance(d->m_pTopLine->fontMetrics(), s + ' ' + QString().fill('0', lineNumberWidth)); d->m_pTopLine->setMinimumWidth(w); if(!topVisiableLine.isValid()) s = i18n("End"); else s += ' ' + QString::number(topVisiableLine + 1); d->m_pTopLine->setText(s); d->m_pTopLine->repaint(); } } DiffTextWindow* DiffTextWindowFrame::getDiffTextWindow() { return d->m_pDiffTextWindow; } bool DiffTextWindowFrame::eventFilter(QObject* o, QEvent* e) { Q_UNUSED(o); if(e->type() == QEvent::FocusIn || e->type() == QEvent::FocusOut) { QColor c1 = d->getOptions()->m_bgColor; QColor c2; if(d->m_winIdx == e_SrcSelector::A) c2 = d->getOptions()->m_colorA; else if(d->m_winIdx == e_SrcSelector::B) c2 = d->getOptions()->m_colorB; else if(d->m_winIdx == e_SrcSelector::C) c2 = d->getOptions()->m_colorC; QPalette p = d->m_pTopLineWidget->palette(); if(e->type() == QEvent::FocusOut) std::swap(c1, c2); p.setColor(QPalette::Window, c2); setPalette(p); p.setColor(QPalette::WindowText, c1); d->m_pLabel->setPalette(p); d->m_pTopLine->setPalette(p); d->m_pEncoding->setPalette(p); d->m_pLineEndStyle->setPalette(p); } return false; } void DiffTextWindowFrame::slotReturnPressed() { DiffTextWindow* pDTW = d->m_pDiffTextWindow; if(pDTW->getFileName() != d->m_pFileSelection->text()) { Q_EMIT fileNameChanged(d->m_pFileSelection->text(), pDTW->getWindowIndex()); } } 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; Q_EMIT fileNameChanged(newURL.url(), pDTW->getWindowIndex()); } } EncodingLabel::EncodingLabel(const QString& text, QSharedPointer pSD, const QSharedPointer& pOptions) : QLabel(text) { m_pOptions = pOptions; m_pSourceData = pSD; m_pContextEncodingMenu = nullptr; setMouseTracking(true); } void EncodingLabel::mouseMoveEvent(QMouseEvent*) { // When there is no data to display or it came from clipboard, // we will be use UTF-8 only, // in that case there is no possibility to change the encoding in the SourceData // so, we should hide the HandCursor and display usual ArrowCursor if(m_pSourceData->isFromBuffer() || m_pSourceData->isEmpty()) setCursor(QCursor(Qt::ArrowCursor)); else setCursor(QCursor(Qt::PointingHandCursor)); } void EncodingLabel::mousePressEvent(QMouseEvent*) { if(!(m_pSourceData->isFromBuffer() || m_pSourceData->isEmpty())) { delete m_pContextEncodingMenu; m_pContextEncodingMenu = new QMenu(this); QMenu* pContextEncodingSubMenu = new QMenu(m_pContextEncodingMenu); int currentTextCodecEnum = m_pSourceData->getEncoding()->mibEnum(); // the codec that will be checked in the context menu QList mibs = QTextCodec::availableMibs(); QList codecEnumList; // Adding "main" encodings insertCodec(i18n("Unicode, 8 bit"), QTextCodec::codecForName("UTF-8"), codecEnumList, m_pContextEncodingMenu, currentTextCodecEnum); if(QTextCodec::codecForName("System")) { insertCodec(QString(), QTextCodec::codecForName("System"), codecEnumList, m_pContextEncodingMenu, currentTextCodecEnum); } // Adding recent encodings if(m_pOptions != nullptr) { QStringList& recentEncodings = m_pOptions->m_recentEncodings; for(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")); for(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) const { int CodecMIBEnum = pCodec->mibEnum(); if(pCodec != nullptr && !codecEnumList.contains(CodecMIBEnum)) { QAction* pAction = new QAction(pMenu); // menu takes ownership, so deleting the menu deletes the action too. QByteArray nameArray = pCodec->name(); QLatin1String codecName = QLatin1String(nameArray); pAction->setText(visibleCodecName.isEmpty() ? codecName : visibleCodecName + QLatin1String(" (") + codecName + QLatin1String(")")); pAction->setData(CodecMIBEnum); pAction->setCheckable(true); if(currentTextCodecEnum == CodecMIBEnum) pAction->setChecked(true); pMenu->addAction(pAction); connect(pAction, &QAction::triggered, this, &EncodingLabel::slotSelectEncoding); codecEnumList.append(CodecMIBEnum); } } void EncodingLabel::slotSelectEncoding() { QAction* pAction = qobject_cast(sender()); if(pAction) { QTextCodec* pCodec = QTextCodec::codecForMib(pAction->data().toInt()); if(pCodec != nullptr) { QString s(QLatin1String(pCodec->name())); QStringList& recentEncodings = m_pOptions->m_recentEncodings; if(!recentEncodings.contains(s) && s != "UTF-8" && s != "System") { int itemsToRemove = recentEncodings.size() - m_maxRecentEncodings + 1; for(int i = 0; i < itemsToRemove; ++i) { recentEncodings.removeFirst(); } recentEncodings.append(s); } } Q_EMIT encodingChanged(pCodec); } } diff --git a/src/difftextwindow.h b/src/difftextwindow.h index 62ff904..d4f6b48 100644 --- a/src/difftextwindow.h +++ b/src/difftextwindow.h @@ -1,207 +1,209 @@ /* * KDiff3 - Text Diff And Merge Tool * * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef DIFFTEXTWINDOW_H #define DIFFTEXTWINDOW_H #include "diff.h" #include #include // for QSharedPointer #include // for QString class QMenu; class RecalcWordWrapRunnable; class QScrollBar; class QStatusBar; class Options; class DiffTextWindowData; class DiffTextWindowFrame; class EncodingLabel; class RLPainter; class KDiff3App; class DiffTextWindow : public QWidget { Q_OBJECT public: //Using this as a scoped global static QScrollBar* mVScrollBar; DiffTextWindow(DiffTextWindowFrame* pParent, const QSharedPointer &pOptions, e_SrcSelector winIdx); ~DiffTextWindow() override; void init( const QString& fileName, QTextCodec* pTextCodec, e_LineEndStyle eLineEndStyle, const QVector* pLineData, int size, const Diff3LineVector* pDiff3LineVector, const ManualDiffHelpList* pManualDiffHelpList, bool bTriple ); void setupConnections(const KDiff3App *app) const; void reset(); void convertToLinePos(int x, int y, LineRef& line, int& pos); QString getSelection(); int getFirstLine(); LineRef calcTopLineInFile(const LineRef firstLine); int getMaxTextWidth(); LineCount getNofLines(); int getNofVisibleLines(); int getVisibleTextAreaWidth(); int convertLineToDiff3LineIdx(LineRef line); LineRef 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, LineRef& d3vLine, int& posInLine, bool bDirDown, bool bCaseSensitive); void setSelection(LineRef firstLine, int startPos, LineRef lastLine, int endPos, LineRef& l, int& p); void getSelectionRange(LineRef* firstLine, LineRef* lastLine, e_CoordType coordType); void setPaintingAllowed(bool bAllowPainting); void recalcWordWrap(bool bWordWrap, int wrapLineVectorSize, int visibleTextWidth); void recalcWordWrapHelper(int wrapLineVectorSize, int visibleTextWidth, int cacheListIdx); void printWindow(RLPainter& painter, const QRect& view, const QString& headerText, int line, int linesPerPage, const QColor& fgColor); void print(RLPainter& painter, const QRect& r, int firstLine, int nofLinesPerPage); static bool startRunnables(); bool isThreeWay() const; const QString& getFileName() const; e_SrcSelector getWindowIndex() const; const QString getEncodingDisplayString() const; e_LineEndStyle getLineEndStyle() const; const Diff3LineVector* getDiff3LineVector() const; qint32 getLineNumberWidth() const; void setSourceData(const QSharedPointer& inData); Q_SIGNALS: void statusBarMessage(const QString& message); void scrollVertically(QtNumberType deltaY); void resizeHeightChangedSignal(int nofVisibleLines); void resizeWidthChangedSignal(int nofVisibleColumns); void scrollDiffTextWindow(int deltaX, int deltaY); void newSelection(); void selectionEnd(); void setFastSelectorLine(LineIndex line); void gotFocus(); void lineClicked(e_SrcSelector winIdx, LineRef line); void finishRecalcWordWrap(int visibleTextWidthForPrinting); void checkIfCanContinue(bool& pbContinue); void finishDrop(); void firstLineChanged(QtNumberType firstLine); public Q_SLOTS: void setFirstLine(QtNumberType line); void setHorizScrollOffset(int horizScrollOffset); void resetSelection(); void setFastSelectorRange(int line1, int nofLines); void slotRefresh(); + void slotSelectAll(); + protected: void mousePressEvent(QMouseEvent*) override; void mouseReleaseEvent(QMouseEvent*) override; void mouseMoveEvent(QMouseEvent*) override; void mouseDoubleClickEvent(QMouseEvent* e) override; void wheelEvent(QWheelEvent*) override; void paintEvent(QPaintEvent*) override; void dragEnterEvent(QDragEnterEvent* e) override; void dropEvent(QDropEvent* dropEvent) override; void focusInEvent(QFocusEvent* e) override; void resizeEvent(QResizeEvent*) override; void timerEvent(QTimerEvent*) override; private: static QList s_runnables; static constexpr int s_linesPerRunnable = 2000; DiffTextWindowData* d; void showStatusLine(const LineRef lineFromPos); }; class DiffTextWindowFrameData; class DiffTextWindowFrame : public QWidget { Q_OBJECT public: DiffTextWindowFrame(QWidget* pParent, const QSharedPointer &pOptions, e_SrcSelector winIdx, QSharedPointer psd); ~DiffTextWindowFrame() override; DiffTextWindow* getDiffTextWindow(); void init(); void setupConnections(const KDiff3App *app); Q_SIGNALS: void fileNameChanged(const QString&, e_SrcSelector); void encodingChanged(QTextCodec*); public Q_SLOTS: void setFirstLine(QtNumberType firstLine); protected: bool eventFilter(QObject*, QEvent*) override; //void paintEvent(QPaintEvent*); private Q_SLOTS: void slotReturnPressed(); void slotBrowseButtonClicked(); void slotEncodingChanged(QTextCodec* c) { Q_EMIT encodingChanged(c); };//relay signal from encoding label private: DiffTextWindowFrameData* d; }; class EncodingLabel : public QLabel { Q_OBJECT public: EncodingLabel(const QString& text, QSharedPointer psd, const QSharedPointer &pOptions); protected: void mouseMoveEvent(QMouseEvent* ev) override; void mousePressEvent(QMouseEvent* ev) override; Q_SIGNALS: void encodingChanged(QTextCodec*); private Q_SLOTS: void slotSelectEncoding(); private: QMenu* m_pContextEncodingMenu; QSharedPointer m_pSourceData; //SourceData to get access to "isEmpty()" and "isFromBuffer()" functions static const int m_maxRecentEncodings = 5; QSharedPointer m_pOptions; void insertCodec(const QString& visibleCodecName, QTextCodec* pCodec, QList& CodecEnumList, QMenu* pMenu, int currentTextCodecEnum) const; }; #endif diff --git a/src/kdiff3.h b/src/kdiff3.h index 249dfcd..0eb2299 100644 --- a/src/kdiff3.h +++ b/src/kdiff3.h @@ -1,434 +1,436 @@ /* * KDiff3 - Text Diff And Merge Tool * * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef KDIFF3_H #define KDIFF3_H #include "diff.h" // include files for Qt #include #include #include #include #include #include // include files for KDE #include #include #include #include #include // forward declaration of the KDiff3 classes class OptionDialog; class Overview; enum class e_OverviewMode; class FindDialog; //class ManualDiffHelpDialog; class DiffTextWindow; class DiffTextWindowFrame; class MergeResultWindow; class WindowTitleWidget; 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, &ReversibleScrollBar::valueChanged, this, &ReversibleScrollBar::slotValueChanged); } void setAgain() { setValue(m_realVal); } void setValue(int i) { if(m_pbRightToLeftLanguage != nullptr && *m_pbRightToLeftLanguage) QScrollBar::setValue(maximum() - (i - minimum())); else QScrollBar::setValue(i); } int value() const { return m_realVal; } public Q_SLOTS: void slotValueChanged(int i) { m_realVal = i; if(m_pbRightToLeftLanguage != nullptr && *m_pbRightToLeftLanguage) m_realVal = maximum() - (i - minimum()); Q_EMIT valueChanged2(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 QString& name, KDiff3Part* pKDiff3Part); ~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 = QString(), const QString& fn2 = QString(), const QString& fn3 = QString()); /** 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); void sigRecalcWordWrap(); void checkIfCanContinue(bool& pbContinue); void finishDrop(); void showWhiteSpaceToggled(); void showLineNumbersToggled(); void doRefresh(); void autoSolve(); void unsolve(); void mergeHistory(); void regExpAutoMerge(); void goCurrent(); void goTop(); void goBottom(); void goPrevUnsolvedConflict(); void goNextUnsolvedConflict(); void goPrevConflict(); void goNextConflict(); void goPrevDelta(); void goNextDelta(); void cut(); + void selectAll(); + void changeOverViewMode(e_OverviewMode); protected: void setLockPainting(bool bLock); void createCaption(); 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(const QString& fn1, const QString& fn2, const QString& fn3, const QString& ofn, const QString& an1, const QString& an2, const QString& an3, TotalDiffStatus* pTotalDiffStatus); void slotFileNameChanged(const QString& fileName, e_SrcSelector 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); void resizeDiffTextWindowHeight(int newHeight); void slotRecalcWordWrap(); void postRecalcWordWrap(); void slotFinishRecalcWordWrap(int visibleTextWidth); void showPopupMenu(const QPoint& point); void scrollDiffTextWindow(int deltaX, int deltaY); void scrollMergeResultWindow(int deltaX, int deltaY); 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 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 slotEncodingChanged(QTextCodec*); void slotFinishDrop(); void setHScrollBarRange(); private: /** the configuration object of the application */ //KConfig *config; // QAction pointers to enable/disable actions QPointer fileOpen; QPointer fileSave; QPointer fileSaveAs; QPointer filePrint; QPointer fileQuit; QPointer fileReload; QPointer editCut; QPointer editCopy; QPointer editPaste; QPointer editSelectAll; KToggleAction* viewToolBar = nullptr; KToggleAction* viewStatusBar; //////////////////////////////////////////////////////////////////////// // Special KDiff3 specific stuff starts here QPointer editFind; QPointer editFindNext; QPointer mGoCurrent; QPointer mGoTop; QPointer mGoBottom; QPointer mGoPrevUnsolvedConflict; QPointer mGoNextUnsolvedConflict; QPointer mGoPrevConflict; QPointer mGoNextConflict; QPointer mGoPrevDelta; QPointer mGoNextDelta; KToggleAction* chooseA; KToggleAction* chooseB; KToggleAction* chooseC; KToggleAction* autoAdvance; KToggleAction* wordWrap; QPointer splitDiff; QPointer joinDiffs; QPointer addManualDiffHelp; QPointer clearManualDiffHelpList; KToggleAction* showWhiteSpaceCharacters; KToggleAction* showWhiteSpace; KToggleAction* showLineNumbers; QPointer mAutoSolve; QPointer mUnsolve; QPointer mMergeHistory; QPointer mergeRegExp; KToggleAction* showWindowA; KToggleAction* showWindowB; KToggleAction* showWindowC; QPointer winFocusNext; QPointer winFocusPrev; QPointer winToggleSplitOrientation; KToggleAction* dirShowBoth; QPointer dirViewToggle; KToggleAction* overviewModeNormal; KToggleAction* overviewModeAB; KToggleAction* overviewModeAC; KToggleAction* overviewModeBC; QMenu* m_pMergeEditorPopupMenu; QSplitter* m_pMainSplitter = nullptr; QWidget* m_pMainWidget = nullptr; QWidget* m_pMergeWindowFrame = nullptr; ReversibleScrollBar* m_pHScrollBar = nullptr; DiffTextWindow* m_pDiffTextWindow1 = nullptr; DiffTextWindow* m_pDiffTextWindow2 = nullptr; DiffTextWindow* m_pDiffTextWindow3 = nullptr; DiffTextWindowFrame* m_pDiffTextWindowFrame1 = nullptr; DiffTextWindowFrame* m_pDiffTextWindowFrame2 = nullptr; DiffTextWindowFrame* m_pDiffTextWindowFrame3 = nullptr; QSplitter* m_pDiffWindowSplitter = nullptr; MergeResultWindow* m_pMergeResultWindow = nullptr; WindowTitleWidget* m_pMergeResultWindowTitle; bool m_bTripleDiff = false; QSplitter* m_pDirectoryMergeSplitter = nullptr; DirectoryMergeWindow* m_pDirectoryMergeWindow = nullptr; DirectoryMergeInfo* m_pDirectoryMergeInfo; bool m_bDirCompare = false; Overview* m_pOverview = nullptr; QWidget* m_pCornerWidget = nullptr; TotalDiffStatus m_totalDiffStatus; QSharedPointer m_sd1 = QSharedPointer::create(); QSharedPointer m_sd2 = QSharedPointer::create(); QSharedPointer m_sd3 = QSharedPointer::create(); QSharedPointer m_dirinfo; QString m_outputFilename; bool m_bDefaultFilename; DiffList m_diffList12; DiffList m_diffList23; DiffList m_diffList13; QSharedPointer m_diffBufferInfo = QSharedPointer::create(); Diff3LineList m_diff3LineList; Diff3LineVector m_diff3LineVector; //ManualDiffHelpDialog* m_pManualDiffHelpDialog; ManualDiffHelpList m_manualDiffHelpList; int m_neededLines; int m_DTWHeight; bool m_bOutputModified = false; bool m_bFileSaved = false; bool m_bTimerBlock = false; // Synchronization OptionDialog* m_pOptionDialog = nullptr; QSharedPointer m_pOptions = nullptr; FindDialog* m_pFindDialog = nullptr; void mainInit(TotalDiffStatus* pTotalDiffStatus = nullptr, bool bLoadFiles = true, bool bUseCurrentEncoding = false); bool m_bFinishMainInit = false; bool m_bLoadFiles = false; void mainWindowEnable(bool bEnable); virtual void wheelEvent(QWheelEvent* pWheelEvent) override; virtual void keyPressEvent(QKeyEvent* event) override; void resizeEvent(QResizeEvent*) override; bool improveFilenames(bool bCreateNewInstance); bool canContinue(); void choose(e_SrcSelector choice); KActionCollection* actionCollection(); QStatusBar* statusBar(); KToolBar* toolBar(QLatin1String); KDiff3Part* m_pKDiff3Part = nullptr; KParts::MainWindow* m_pKDiff3Shell = nullptr; bool m_bAutoFlag = false; bool m_bAutoMode = false; void recalcWordWrap(int visibleTextWidthForPrinting = -1); bool m_bRecalcWordWrapPosted = false; int m_firstD3LIdx; // only needed during recalcWordWrap QPointer m_pEventLoopForPrinting; }; #endif // KDIFF3_H diff --git a/src/mergeresultwindow.cpp b/src/mergeresultwindow.cpp index 82ea49a..5fca744 100644 --- a/src/mergeresultwindow.cpp +++ b/src/mergeresultwindow.cpp @@ -1,3267 +1,3276 @@ /* * KDiff3 - Text Diff And Merge Tool * * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com * SPDX-License-Identifier: GPL-2.0-or-later */ #include "mergeresultwindow.h" #include "kdiff3.h" #include "options.h" #include "RLPainter.h" #include "guiutils.h" #include "Utils.h" // for Utils #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include bool g_bAutoSolve = true; QScrollBar* MergeResultWindow::mVScrollBar = nullptr; QPointer MergeResultWindow::chooseAEverywhere; QPointer MergeResultWindow::chooseBEverywhere; QPointer MergeResultWindow::chooseCEverywhere; QPointer MergeResultWindow::chooseAForUnsolvedConflicts; QPointer MergeResultWindow::chooseBForUnsolvedConflicts; QPointer MergeResultWindow::chooseCForUnsolvedConflicts; QPointer MergeResultWindow::chooseAForUnsolvedWhiteSpaceConflicts; QPointer MergeResultWindow::chooseBForUnsolvedWhiteSpaceConflicts; QPointer MergeResultWindow::chooseCForUnsolvedWhiteSpaceConflicts; MergeResultWindow::MergeResultWindow( QWidget* pParent, const QSharedPointer &pOptions, QStatusBar* pStatusBar) : QWidget(pParent) { setObjectName("MergeResultWindow"); setFocusPolicy(Qt::ClickFocus); mOverviewMode = e_OverviewMode::eOMNormal; m_pStatusBar = pStatusBar; if(m_pStatusBar != nullptr) connect(m_pStatusBar, &QStatusBar::messageChanged, this, &MergeResultWindow::slotStatusMessageChanged); m_pOptions = pOptions; setUpdatesEnabled(false); connect(&m_cursorTimer, &QTimer::timeout, this, &MergeResultWindow::slotCursorUpdate); m_cursorTimer.setSingleShot(true); m_cursorTimer.start(500 /*ms*/); m_selection.reset(); setMinimumSize(QSize(20, 20)); setFont(m_pOptions->m_font); } void MergeResultWindow::init( const QVector* pLineDataA, LineRef sizeA, const QVector* pLineDataB, LineRef sizeB, const QVector* pLineDataC, LineRef sizeC, const Diff3LineList* pDiff3LineList, TotalDiffStatus* pTotalDiffStatus) { m_firstLine = 0; m_horizScrollOffset = 0; m_nofLines = 0; m_bMyUpdate = false; m_bInsertMode = true; m_scrollDeltaX = 0; m_scrollDeltaY = 0; setModified(false); m_pldA = pLineDataA; m_pldB = pLineDataB; m_pldC = pLineDataC; m_sizeA = sizeA; m_sizeB = sizeB; m_sizeC = sizeC; m_pDiff3LineList = pDiff3LineList; m_pTotalDiffStatus = pTotalDiffStatus; m_selection.reset(); m_cursorXPos = 0; m_cursorOldXPixelPos = 0; m_cursorYPos = 0; m_maxTextWidth = -1; merge(g_bAutoSolve, e_SrcSelector::Invalid); g_bAutoSolve = true; update(); updateSourceMask(); showUnsolvedConflictsStatusMessage(); } //This must be called before KXMLGui::SetXMLFile and friends or the actions will not be shown in the menu. //At that point in startup we don't have a MergeResultWindow object so we cannot connect the signals yet. void MergeResultWindow::initActions(KActionCollection* ac) { if(ac == nullptr){ KMessageBox::error(nullptr, "actionCollection==0"); exit(-1);//we cannot recover from this. } chooseAEverywhere = GuiUtils::createAction(i18n("Choose A Everywhere"), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_1), ac, "merge_choose_a_everywhere"); chooseBEverywhere = GuiUtils::createAction(i18n("Choose B Everywhere"), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_2), ac, "merge_choose_b_everywhere"); chooseCEverywhere = GuiUtils::createAction(i18n("Choose C Everywhere"), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_3), ac, "merge_choose_c_everywhere"); chooseAForUnsolvedConflicts = GuiUtils::createAction(i18n("Choose A for All Unsolved Conflicts"), ac, "merge_choose_a_for_unsolved_conflicts"); chooseBForUnsolvedConflicts = GuiUtils::createAction(i18n("Choose B for All Unsolved Conflicts"), ac, "merge_choose_b_for_unsolved_conflicts"); chooseCForUnsolvedConflicts = GuiUtils::createAction(i18n("Choose C for All Unsolved Conflicts"), ac, "merge_choose_c_for_unsolved_conflicts"); chooseAForUnsolvedWhiteSpaceConflicts = GuiUtils::createAction(i18n("Choose A for All Unsolved Whitespace Conflicts"), ac, "merge_choose_a_for_unsolved_whitespace_conflicts"); chooseBForUnsolvedWhiteSpaceConflicts = GuiUtils::createAction(i18n("Choose B for All Unsolved Whitespace Conflicts"), ac, "merge_choose_b_for_unsolved_whitespace_conflicts"); chooseCForUnsolvedWhiteSpaceConflicts = GuiUtils::createAction(i18n("Choose C for All Unsolved Whitespace Conflicts"), ac, "merge_choose_c_for_unsolved_whitespace_conflicts"); } void MergeResultWindow::connectActions() const { QObject::connect(chooseAEverywhere, &QAction::triggered, this, &MergeResultWindow::slotChooseAEverywhere); QObject::connect(chooseBEverywhere, &QAction::triggered, this, &MergeResultWindow::slotChooseBEverywhere); QObject::connect(chooseCEverywhere, &QAction::triggered, this, &MergeResultWindow::slotChooseCEverywhere); QObject::connect(chooseAForUnsolvedConflicts, &QAction::triggered, this, &MergeResultWindow::slotChooseAForUnsolvedConflicts); QObject::connect(chooseBForUnsolvedConflicts, &QAction::triggered, this, &MergeResultWindow::slotChooseBForUnsolvedConflicts); QObject::connect(chooseCForUnsolvedConflicts, &QAction::triggered, this, &MergeResultWindow::slotChooseCForUnsolvedConflicts); QObject::connect(chooseAForUnsolvedWhiteSpaceConflicts, &QAction::triggered, this, &MergeResultWindow::slotChooseAForUnsolvedWhiteSpaceConflicts); QObject::connect(chooseBForUnsolvedWhiteSpaceConflicts, &QAction::triggered, this, &MergeResultWindow::slotChooseBForUnsolvedWhiteSpaceConflicts); QObject::connect(chooseCForUnsolvedWhiteSpaceConflicts, &QAction::triggered, this, &MergeResultWindow::slotChooseCForUnsolvedWhiteSpaceConflicts); } void MergeResultWindow::setupConnections(const KDiff3App *app) const { connect(app, &KDiff3App::cut, this, &MergeResultWindow::slotCut); + connect(app, &KDiff3App::selectAll, this, &MergeResultWindow::slotSelectAll); connect(this, &MergeResultWindow::scrollVertically, mVScrollBar, &QScrollBar::setValue); connect(this, &MergeResultWindow::scrollMergeResultWindow, app, &KDiff3App::scrollMergeResultWindow); connect(this, &MergeResultWindow::sourceMask, app, &KDiff3App::sourceMask); connect(this, &MergeResultWindow::resizeSignal, app, &KDiff3App::setHScrollBarRange); connect(this, &MergeResultWindow::resizeSignal, this, &MergeResultWindow::slotResize); connect(this, &MergeResultWindow::selectionEnd, app, &KDiff3App::slotSelectionEnd); connect(this, &MergeResultWindow::newSelection, app, &KDiff3App::slotSelectionStart); connect(this, &MergeResultWindow::modifiedChanged, app, &KDiff3App::slotOutputModified); connect(this, &MergeResultWindow::updateAvailabilities, app, &KDiff3App::slotUpdateAvailabilities); connect(this, &MergeResultWindow::showPopupMenu, app, &KDiff3App::showPopupMenu); connect(this, &MergeResultWindow::noRelevantChangesDetected, app, &KDiff3App::slotNoRelevantChangesDetected); connect(this, &MergeResultWindow::statusBarMessage, app, &KDiff3App::slotStatusMsg); //connect menu actions connect(app, &KDiff3App::showWhiteSpaceToggled, this, static_cast(&MergeResultWindow::update)); connect(app, &KDiff3App::doRefresh, this, &MergeResultWindow::slotRefresh); connect(app, &KDiff3App::autoSolve, this, &MergeResultWindow::slotAutoSolve); connect(app, &KDiff3App::unsolve, this, &MergeResultWindow::slotUnsolve); connect(app, &KDiff3App::mergeHistory, this, &MergeResultWindow::slotMergeHistory); connect(app, &KDiff3App::regExpAutoMerge, this, &MergeResultWindow::slotRegExpAutoMerge); connect(app, &KDiff3App::goCurrent, this, &MergeResultWindow::slotGoCurrent); connect(app, &KDiff3App::goTop, this, &MergeResultWindow::slotGoTop); connect(app, &KDiff3App::goBottom, this, &MergeResultWindow::slotGoBottom); connect(app, &KDiff3App::goPrevUnsolvedConflict, this, &MergeResultWindow::slotGoPrevUnsolvedConflict); connect(app, &KDiff3App::goNextUnsolvedConflict, this, &MergeResultWindow::slotGoNextUnsolvedConflict); connect(app, &KDiff3App::goPrevConflict, this, &MergeResultWindow::slotGoPrevConflict); connect(app, &KDiff3App::goNextConflict, this, &MergeResultWindow::slotGoNextConflict); connect(app, &KDiff3App::goPrevDelta, this, &MergeResultWindow::slotGoPrevDelta); connect(app, &KDiff3App::goNextDelta, this, &MergeResultWindow::slotGoNextDelta); connect(app, &KDiff3App::changeOverViewMode, this, &MergeResultWindow::setOverviewMode); } void MergeResultWindow::slotResize() { mVScrollBar->setRange(0, std::max(0, getNofLines() - getNofVisibleLines())); mVScrollBar->setPageStep(getNofVisibleLines()); } void MergeResultWindow::slotCut() { QString s; s = getSelection(); deleteSelection(); update(); if(!s.isEmpty()) { QApplication::clipboard()->setText(s, QClipboard::Clipboard); } } +void MergeResultWindow::slotSelectAll() +{ + if(hasFocus()) + { + setSelection(0, 0, getNofLines(), 0); + } +} + void MergeResultWindow::showUnsolvedConflictsStatusMessage() { if(m_pStatusBar != nullptr) { int wsc; int nofUnsolved = getNrOfUnsolvedConflicts(&wsc); m_persistentStatusMessage = i18n("Number of remaining unsolved conflicts: %1 (of which %2 are whitespace)", nofUnsolved, wsc); Q_EMIT statusBarMessage(m_persistentStatusMessage); } } void MergeResultWindow::slotRefresh() { setFont(m_pOptions->m_font); update(); } void MergeResultWindow::slotUpdateAvailabilities(bool bMergeEditorVisible,const bool bTripleDiff) { chooseAEverywhere->setEnabled(bMergeEditorVisible); chooseBEverywhere->setEnabled(bMergeEditorVisible); chooseCEverywhere->setEnabled(bMergeEditorVisible && bTripleDiff); chooseAForUnsolvedConflicts->setEnabled(bMergeEditorVisible); chooseBForUnsolvedConflicts->setEnabled(bMergeEditorVisible); chooseCForUnsolvedConflicts->setEnabled(bMergeEditorVisible && bTripleDiff); chooseAForUnsolvedWhiteSpaceConflicts->setEnabled(bMergeEditorVisible); chooseBForUnsolvedWhiteSpaceConflicts->setEnabled(bMergeEditorVisible); chooseCForUnsolvedWhiteSpaceConflicts->setEnabled(bMergeEditorVisible && bTripleDiff); } void MergeResultWindow::slotStatusMessageChanged(const QString& s) { if(s.isEmpty() && !m_persistentStatusMessage.isEmpty()) { Q_EMIT statusBarMessage(m_persistentStatusMessage); } } void MergeResultWindow::reset() { m_pDiff3LineList = nullptr; m_pTotalDiffStatus = nullptr; m_pldA = nullptr; m_pldB = nullptr; m_pldC = nullptr; if(!m_persistentStatusMessage.isEmpty()) { m_persistentStatusMessage = QString(); } } // Calculate the merge information for the given Diff3Line. // Results will be stored in mergeDetails, bConflict, bLineRemoved and src. void Diff3Line::mergeOneLine( e_MergeDetails& mergeDetails, bool& bConflict, bool& bLineRemoved, e_SrcSelector& src, bool bTwoInputs) const { mergeDetails = e_MergeDetails::eDefault; bConflict = false; bLineRemoved = false; src = e_SrcSelector::None; if(bTwoInputs) // Only two input files { if(getLineA().isValid() && getLineB().isValid()) { if(pFineAB == nullptr) { mergeDetails = e_MergeDetails::eNoChange; src = e_SrcSelector::A; } else { mergeDetails = e_MergeDetails::eBChanged; bConflict = true; } } else { mergeDetails = e_MergeDetails::eBDeleted; bConflict = true; } return; } // A is base. if(getLineA().isValid() && getLineB().isValid() && getLineC().isValid()) { if(pFineAB == nullptr && pFineBC == nullptr && pFineCA == nullptr) { mergeDetails = e_MergeDetails::eNoChange; src = e_SrcSelector::A; } else if(pFineAB == nullptr && pFineBC != nullptr && pFineCA != nullptr) { mergeDetails = e_MergeDetails::eCChanged; src = e_SrcSelector::C; } else if(pFineAB != nullptr && pFineBC != nullptr && pFineCA == nullptr) { mergeDetails = e_MergeDetails::eBChanged; src = e_SrcSelector::B; } else if(pFineAB != nullptr && pFineBC == nullptr && pFineCA != nullptr) { mergeDetails = e_MergeDetails::eBCChangedAndEqual; src = e_SrcSelector::C; } else if(pFineAB != nullptr && pFineBC != nullptr && pFineCA != nullptr) { mergeDetails = e_MergeDetails::eBCChanged; bConflict = true; } else Q_ASSERT(true); } else if(getLineA().isValid() && getLineB().isValid() && !getLineC().isValid()) { if(pFineAB != nullptr) { mergeDetails = e_MergeDetails::eBChanged_CDeleted; bConflict = true; } else { mergeDetails = e_MergeDetails::eCDeleted; bLineRemoved = true; src = e_SrcSelector::C; } } else if(getLineA().isValid() && !getLineB().isValid() && getLineC().isValid()) { if(pFineCA != nullptr) { mergeDetails = e_MergeDetails::eCChanged_BDeleted; bConflict = true; } else { mergeDetails = e_MergeDetails::eBDeleted; bLineRemoved = true; src = e_SrcSelector::B; } } else if(!getLineA().isValid() && getLineB().isValid() && getLineC().isValid()) { if(pFineBC != nullptr) { mergeDetails = e_MergeDetails::eBCAdded; bConflict = true; } else // B==C { mergeDetails = e_MergeDetails::eBCAddedAndEqual; src = e_SrcSelector::C; } } else if(!getLineA().isValid() && !getLineB().isValid() && getLineC().isValid()) { mergeDetails = e_MergeDetails::eCAdded; src = e_SrcSelector::C; } else if(!getLineA().isValid() && getLineB().isValid() && !getLineC().isValid()) { mergeDetails = e_MergeDetails::eBAdded; src = e_SrcSelector::B; } else if(getLineA().isValid() && !getLineB().isValid() && !getLineC().isValid()) { mergeDetails = e_MergeDetails::eBCDeleted; bLineRemoved = true; src = e_SrcSelector::C; } else Q_ASSERT(true); } bool MergeResultWindow::sameKindCheck(const MergeLine& ml1, const MergeLine& ml2) { if(ml1.bConflict && ml2.bConflict) { // Both lines have conflicts: If one is only a white space conflict and // the other one is a real conflict, then this line returns false. return ml1.id3l->isEqualAC() == ml2.id3l->isEqualAC() && ml1.id3l->isEqualAB() == ml2.id3l->isEqualAB(); } else return ( (!ml1.bConflict && !ml2.bConflict && ml1.bDelta && ml2.bDelta && ml1.srcSelect == ml2.srcSelect && (ml1.mergeDetails == ml2.mergeDetails || (ml1.mergeDetails != e_MergeDetails::eBCAddedAndEqual && ml2.mergeDetails != e_MergeDetails::eBCAddedAndEqual))) || (!ml1.bDelta && !ml2.bDelta)); } void MergeResultWindow::merge(bool bAutoSolve, e_SrcSelector defaultSelector, bool bConflictsOnly, bool bWhiteSpaceOnly) { if(!bConflictsOnly) { if(m_bModified) { int result = KMessageBox::warningYesNo(this, i18n("The output has been modified.\n" "If you continue your changes will be lost."), i18n("Warning"), KStandardGuiItem::cont(), KStandardGuiItem::cancel()); if(result == KMessageBox::No) return; } m_mergeLineList.clear(); int lineIdx = 0; Diff3LineList::const_iterator it; for(it = m_pDiff3LineList->begin(); it != m_pDiff3LineList->end(); ++it, ++lineIdx) { const Diff3Line& d = *it; MergeLine ml; bool bLineRemoved; d.mergeOneLine( ml.mergeDetails, ml.bConflict, bLineRemoved, ml.srcSelect, m_pldC == nullptr); // Automatic solving for only whitespace changes. if(ml.bConflict && ((m_pldC == nullptr && (d.isEqualAB() || (d.isWhiteLine(e_SrcSelector::A) && d.isWhiteLine(e_SrcSelector::B)))) || (m_pldC != nullptr && ((d.isEqualAB() && d.isEqualAC()) || (d.isWhiteLine(e_SrcSelector::A) && d.isWhiteLine(e_SrcSelector::B) && d.isWhiteLine(e_SrcSelector::C)))))) { ml.bWhiteSpaceConflict = true; } ml.d3lLineIdx = lineIdx; ml.bDelta = ml.srcSelect != e_SrcSelector::A; ml.id3l = it; ml.srcRangeLength = 1; MergeLine* back = m_mergeLineList.empty() ? nullptr : &m_mergeLineList.back(); bool bSame = back != nullptr && sameKindCheck(ml, *back); if(bSame) { ++back->srcRangeLength; if(back->bWhiteSpaceConflict && !ml.bWhiteSpaceConflict) back->bWhiteSpaceConflict = false; } else { m_mergeLineList.push_back(ml); } if(!ml.bConflict) { MergeLine& tmpBack = m_mergeLineList.back(); MergeEditLine mel(ml.id3l); mel.setSource(ml.srcSelect, bLineRemoved); tmpBack.mergeEditLineList.push_back(mel); } else if(back == nullptr || !back->bConflict || !bSame) { MergeLine& tmpBack = m_mergeLineList.back(); MergeEditLine mel(ml.id3l); mel.setConflict(); tmpBack.mergeEditLineList.push_back(mel); } } } bool bSolveWhiteSpaceConflicts = false; if(bAutoSolve) // when true, then the other params are not used and we can change them here. (see all invocations of merge()) { if(m_pldC == nullptr && m_pOptions->m_whiteSpace2FileMergeDefault != (int)e_SrcSelector::None) // Only two inputs { Q_ASSERT(m_pOptions->m_whiteSpace2FileMergeDefault <= (int)e_SrcSelector::Max && m_pOptions->m_whiteSpace2FileMergeDefault >= (int)e_SrcSelector::Min); defaultSelector = (e_SrcSelector)m_pOptions->m_whiteSpace2FileMergeDefault; bWhiteSpaceOnly = true; bSolveWhiteSpaceConflicts = true; } else if(m_pldC != nullptr && m_pOptions->m_whiteSpace3FileMergeDefault != (int)e_SrcSelector::None) { Q_ASSERT(m_pOptions->m_whiteSpace3FileMergeDefault <= (int)e_SrcSelector::Max && m_pOptions->m_whiteSpace2FileMergeDefault >= (int)e_SrcSelector::Min); defaultSelector = (e_SrcSelector)m_pOptions->m_whiteSpace3FileMergeDefault; bWhiteSpaceOnly = true; bSolveWhiteSpaceConflicts = true; } } if(!bAutoSolve || bSolveWhiteSpaceConflicts) { // Change all auto selections MergeLineList::iterator mlIt; for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; bool bConflict = ml.mergeEditLineList.empty() || ml.mergeEditLineList.begin()->isConflict(); if(ml.bDelta && (!bConflictsOnly || bConflict) && (!bWhiteSpaceOnly || ml.bWhiteSpaceConflict)) { ml.mergeEditLineList.clear(); if(defaultSelector == e_SrcSelector::Invalid && ml.bDelta) { MergeEditLine mel(ml.id3l); mel.setConflict(); ml.bConflict = true; ml.mergeEditLineList.push_back(mel); } else { Diff3LineList::const_iterator d3llit = ml.id3l; int j; for(j = 0; j < ml.srcRangeLength; ++j) { MergeEditLine mel(d3llit); mel.setSource(defaultSelector, false); LineRef srcLine = defaultSelector == e_SrcSelector::A ? d3llit->getLineA() : defaultSelector == e_SrcSelector::B ? d3llit->getLineB() : defaultSelector == e_SrcSelector::C ? d3llit->getLineC() : LineRef(); if(srcLine.isValid()) { ml.mergeEditLineList.push_back(mel); } ++d3llit; } if(ml.mergeEditLineList.empty()) // Make a line nevertheless { MergeEditLine mel(ml.id3l); mel.setRemoved(defaultSelector); ml.mergeEditLineList.push_back(mel); } } } } } MergeLineList::iterator mlIt; for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; // Remove all lines that are empty, because no src lines are there. LineRef oldSrcLine; e_SrcSelector oldSrc = e_SrcSelector::Invalid; MergeEditLineList::iterator melIt; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end();) { MergeEditLine& mel = *melIt; e_SrcSelector melsrc = mel.src(); LineRef srcLine = mel.isRemoved() ? LineRef() : melsrc == e_SrcSelector::A ? mel.id3l()->getLineA() : melsrc == e_SrcSelector::B ? mel.id3l()->getLineB() : melsrc == e_SrcSelector::C ? mel.id3l()->getLineC() : LineRef(); // At least one line remains because oldSrc != melsrc for first line in list // Other empty lines will be removed if(!srcLine.isValid() && !oldSrcLine.isValid() && oldSrc == melsrc) melIt = ml.mergeEditLineList.erase(melIt); else ++melIt; oldSrcLine = srcLine; oldSrc = melsrc; } } if(bAutoSolve && !bConflictsOnly) { if(m_pOptions->m_bRunHistoryAutoMergeOnMergeStart) slotMergeHistory(); if(m_pOptions->m_bRunRegExpAutoMergeOnMergeStart) slotRegExpAutoMerge(); if(m_pldC != nullptr && !doRelevantChangesExist()) Q_EMIT noRelevantChangesDetected(); } int nrOfSolvedConflicts = 0; int nrOfUnsolvedConflicts = 0; int nrOfWhiteSpaceConflicts = 0; MergeLineList::iterator i; for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i) { if(i->bConflict) ++nrOfUnsolvedConflicts; else if(i->bDelta) ++nrOfSolvedConflicts; if(i->bWhiteSpaceConflict) ++nrOfWhiteSpaceConflicts; } m_pTotalDiffStatus->setUnsolvedConflicts(nrOfUnsolvedConflicts); m_pTotalDiffStatus->setSolvedConflicts(nrOfSolvedConflicts); m_pTotalDiffStatus->setWhitespaceConflicts(nrOfWhiteSpaceConflicts); m_cursorXPos = 0; m_cursorOldXPixelPos = 0; m_cursorYPos = 0; m_maxTextWidth = -1; //m_firstLine = 0; // Must not set line/column without scrolling there //m_horizScrollOffset = 0; setModified(false); m_currentMergeLineIt = m_mergeLineList.begin(); slotGoTop(); Q_EMIT updateAvailabilities(); update(); } void MergeResultWindow::setFirstLine(QtNumberType firstLine) { m_firstLine = std::max(0, firstLine); update(); } void MergeResultWindow::setHorizScrollOffset(int horizScrollOffset) { m_horizScrollOffset = std::max(0, horizScrollOffset); update(); } int MergeResultWindow::getMaxTextWidth() { if(m_maxTextWidth < 0) { m_maxTextWidth = 0; MergeLineList::iterator mlIt = m_mergeLineList.begin(); for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; MergeEditLineList::iterator melIt; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt) { MergeEditLine& mel = *melIt; QString s = mel.getString(m_pldA, m_pldB, m_pldC); QTextLayout textLayout(s, font(), this); textLayout.beginLayout(); textLayout.createLine(); textLayout.endLayout(); if(m_maxTextWidth < textLayout.maximumWidth()) { m_maxTextWidth = qCeil(textLayout.maximumWidth()); } } } m_maxTextWidth += 5; // cursorwidth } return m_maxTextWidth; } int MergeResultWindow::getNofLines() const { return m_nofLines; } int MergeResultWindow::getVisibleTextAreaWidth() { return width() - getTextXOffset(); } int MergeResultWindow::getNofVisibleLines() { QFontMetrics fm = fontMetrics(); return (height() - 3) / fm.lineSpacing() - 2; } int MergeResultWindow::getTextXOffset() { QFontMetrics fm = fontMetrics(); return 3 * Utils::getHorizontalAdvance(fm, '0'); } void MergeResultWindow::resizeEvent(QResizeEvent* e) { QWidget::resizeEvent(e); Q_EMIT resizeSignal(); } e_OverviewMode MergeResultWindow::getOverviewMode() { return mOverviewMode; } void MergeResultWindow::setOverviewMode(e_OverviewMode eOverviewMode) { mOverviewMode = eOverviewMode; } // Check whether we should ignore current delta when moving to next/previous delta bool MergeResultWindow::checkOverviewIgnore(MergeLineList::iterator& i) { if(mOverviewMode == e_OverviewMode::eOMNormal) return false; if(mOverviewMode == e_OverviewMode::eOMAvsB) return i->mergeDetails == e_MergeDetails::eCAdded || i->mergeDetails == e_MergeDetails::eCDeleted || i->mergeDetails == e_MergeDetails::eCChanged; if(mOverviewMode == e_OverviewMode::eOMAvsC) return i->mergeDetails == e_MergeDetails::eBAdded || i->mergeDetails == e_MergeDetails::eBDeleted || i->mergeDetails == e_MergeDetails::eBChanged; if(mOverviewMode == e_OverviewMode::eOMBvsC) return i->mergeDetails == e_MergeDetails::eBCAddedAndEqual || i->mergeDetails == e_MergeDetails::eBCDeleted || i->mergeDetails == e_MergeDetails::eBCChangedAndEqual; return false; } // Go to prev/next delta/conflict or first/last delta. void MergeResultWindow::go(e_Direction eDir, e_EndPoint eEndPoint) { Q_ASSERT(eDir == eUp || eDir == eDown); MergeLineList::iterator i = m_currentMergeLineIt; bool bSkipWhiteConflicts = !m_pOptions->m_bShowWhiteSpace; if(eEndPoint == eEnd) { if(eDir == eUp) i = m_mergeLineList.begin(); // first mergeline else i = --m_mergeLineList.end(); // last mergeline while(isItAtEnd(eDir == eUp, i) && !i->bDelta) { if(eDir == eUp) ++i; // search downwards else --i; // search upwards } } else if(eEndPoint == eDelta && isItAtEnd(eDir != eUp, i)) { do { if(eDir == eUp) --i; else ++i; } while(isItAtEnd(eDir != eUp, i) && (!i->bDelta || checkOverviewIgnore(i) || (bSkipWhiteConflicts && i->bWhiteSpaceConflict))); } else if(eEndPoint == eConflict && isItAtEnd(eDir != eUp, i)) { do { if(eDir == eUp) --i; else ++i; } while(isItAtEnd(eDir != eUp, i) && (!i->bConflict || (bSkipWhiteConflicts && i->bWhiteSpaceConflict))); } else if(isItAtEnd(eDir != eUp, i) && eEndPoint == eUnsolvedConflict) { do { if(eDir == eUp) --i; else ++i; } while(isItAtEnd(eDir != eUp, i) && !i->mergeEditLineList.begin()->isConflict()); } if(isVisible()) setFocus(); setFastSelector(i); } bool MergeResultWindow::isDeltaAboveCurrent() { bool bSkipWhiteConflicts = !m_pOptions->m_bShowWhiteSpace; if(m_mergeLineList.empty()) return false; MergeLineList::iterator i = m_currentMergeLineIt; if(i == m_mergeLineList.begin()) return false; do { --i; if(i->bDelta && !checkOverviewIgnore(i) && !(bSkipWhiteConflicts && i->bWhiteSpaceConflict)) return true; } while(i != m_mergeLineList.begin()); return false; } bool MergeResultWindow::isDeltaBelowCurrent() { bool bSkipWhiteConflicts = !m_pOptions->m_bShowWhiteSpace; if(m_mergeLineList.empty()) return false; MergeLineList::iterator i = m_currentMergeLineIt; if(i != m_mergeLineList.end()) { ++i; for(; i != m_mergeLineList.end(); ++i) { if(i->bDelta && !checkOverviewIgnore(i) && !(bSkipWhiteConflicts && i->bWhiteSpaceConflict)) return true; } } return false; } bool MergeResultWindow::isConflictAboveCurrent() { if(m_mergeLineList.empty()) return false; MergeLineList::iterator i = m_currentMergeLineIt; if(i == m_mergeLineList.begin()) return false; bool bSkipWhiteConflicts = !m_pOptions->m_bShowWhiteSpace; do { --i; if(i->bConflict && !(bSkipWhiteConflicts && i->bWhiteSpaceConflict)) return true; } while(i != m_mergeLineList.begin()); return false; } bool MergeResultWindow::isConflictBelowCurrent() { MergeLineList::iterator i = m_currentMergeLineIt; if(m_mergeLineList.empty()) return false; bool bSkipWhiteConflicts = !m_pOptions->m_bShowWhiteSpace; if(i != m_mergeLineList.end()) { ++i; for(; i != m_mergeLineList.end(); ++i) { if(i->bConflict && !(bSkipWhiteConflicts && i->bWhiteSpaceConflict)) return true; } } return false; } bool MergeResultWindow::isUnsolvedConflictAtCurrent() { if(m_mergeLineList.empty()) return false; MergeLineList::iterator i = m_currentMergeLineIt; return i->mergeEditLineList.begin()->isConflict(); } bool MergeResultWindow::isUnsolvedConflictAboveCurrent() { if(m_mergeLineList.empty()) return false; MergeLineList::iterator i = m_currentMergeLineIt; if(i == m_mergeLineList.begin()) return false; do { --i; if(i->mergeEditLineList.begin()->isConflict()) return true; } while(i != m_mergeLineList.begin()); return false; } bool MergeResultWindow::isUnsolvedConflictBelowCurrent() { MergeLineList::iterator i = m_currentMergeLineIt; if(m_mergeLineList.empty()) return false; if(i != m_mergeLineList.end()) { ++i; for(; i != m_mergeLineList.end(); ++i) { if(i->mergeEditLineList.begin()->isConflict()) return true; } } return false; } void MergeResultWindow::slotGoTop() { go(eUp, eEnd); } void MergeResultWindow::slotGoCurrent() { setFastSelector(m_currentMergeLineIt); } void MergeResultWindow::slotGoBottom() { go(eDown, eEnd); } void MergeResultWindow::slotGoPrevDelta() { go(eUp, eDelta); } void MergeResultWindow::slotGoNextDelta() { go(eDown, eDelta); } void MergeResultWindow::slotGoPrevConflict() { go(eUp, eConflict); } void MergeResultWindow::slotGoNextConflict() { go(eDown, eConflict); } void MergeResultWindow::slotGoPrevUnsolvedConflict() { go(eUp, eUnsolvedConflict); } void MergeResultWindow::slotGoNextUnsolvedConflict() { go(eDown, eUnsolvedConflict); } /** The line is given as a index in the Diff3LineList. The function calculates the corresponding iterator. */ void MergeResultWindow::slotSetFastSelectorLine(LineIndex line) { MergeLineList::iterator i; for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i) { if(line >= i->d3lLineIdx && line < i->d3lLineIdx + i->srcRangeLength) { //if ( i->bDelta ) { setFastSelector(i); } break; } } } int MergeResultWindow::getNrOfUnsolvedConflicts(int* pNrOfWhiteSpaceConflicts) { int nrOfUnsolvedConflicts = 0; if(pNrOfWhiteSpaceConflicts != nullptr) *pNrOfWhiteSpaceConflicts = 0; MergeLineList::iterator mlIt = m_mergeLineList.begin(); for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; MergeEditLineList::iterator melIt = ml.mergeEditLineList.begin(); if(melIt->isConflict()) { ++nrOfUnsolvedConflicts; if(ml.bWhiteSpaceConflict && pNrOfWhiteSpaceConflicts != nullptr) ++*pNrOfWhiteSpaceConflicts; } } return nrOfUnsolvedConflicts; } void MergeResultWindow::showNrOfConflicts() { if(!m_pOptions->m_bShowInfoDialogs) return; int nrOfConflicts = 0; MergeLineList::iterator i; for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i) { if(i->bConflict || i->bDelta) ++nrOfConflicts; } QString totalInfo; if(m_pTotalDiffStatus->isBinaryEqualAB() && m_pTotalDiffStatus->isBinaryEqualAC()) totalInfo += i18n("All input files are binary equal."); else if(m_pTotalDiffStatus->isTextEqualAB() && m_pTotalDiffStatus->isTextEqualAC()) totalInfo += i18n("All input files contain the same text."); else { if(m_pTotalDiffStatus->isBinaryEqualAB()) totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("A"), i18n("B")); else if(m_pTotalDiffStatus->isTextEqualAB()) totalInfo += i18n("Files %1 and %2 have equal text.\n", i18n("A"), i18n("B")); if(m_pTotalDiffStatus->isBinaryEqualAC()) totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("A"), i18n("C")); else if(m_pTotalDiffStatus->isTextEqualAC()) totalInfo += i18n("Files %1 and %2 have equal text.\n", i18n("A"), i18n("C")); if(m_pTotalDiffStatus->isBinaryEqualBC()) totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("B"), i18n("C")); else if(m_pTotalDiffStatus->isTextEqualBC()) totalInfo += i18n("Files %1 and %2 have equal text.\n", i18n("B"), i18n("C")); } int nrOfUnsolvedConflicts = getNrOfUnsolvedConflicts(); KMessageBox::information(this, i18n("Total number of conflicts: %1\n" "Nr of automatically solved conflicts: %2\n" "Nr of unsolved conflicts: %3\n" "%4", nrOfConflicts, nrOfConflicts - nrOfUnsolvedConflicts, nrOfUnsolvedConflicts, totalInfo), i18n("Conflicts")); } void MergeResultWindow::setFastSelector(MergeLineList::iterator i) { if(i == m_mergeLineList.end()) return; m_currentMergeLineIt = i; Q_EMIT setFastSelectorRange(i->d3lLineIdx, i->srcRangeLength); int line1 = 0; MergeLineList::iterator mlIt = m_mergeLineList.begin(); for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { if(mlIt == m_currentMergeLineIt) break; line1 += mlIt->mergeEditLineList.size(); } int nofLines = m_currentMergeLineIt->mergeEditLineList.size(); int newFirstLine = getBestFirstLine(line1, nofLines, m_firstLine, getNofVisibleLines()); if(newFirstLine != m_firstLine) { Q_EMIT scrollVertically(newFirstLine - m_firstLine); } if(m_selection.isEmpty()) { m_cursorXPos = 0; m_cursorOldXPixelPos = 0; m_cursorYPos = line1; } update(); updateSourceMask(); Q_EMIT updateAvailabilities(); } void MergeResultWindow::choose(e_SrcSelector selector) { if(m_currentMergeLineIt == m_mergeLineList.end()) return; setModified(); // First find range for which this change works. MergeLine& ml = *m_currentMergeLineIt; MergeEditLineList::iterator melIt; // Now check if selector is active for this range already. bool bActive = false; // Remove unneeded lines in the range. for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end();) { MergeEditLine& mel = *melIt; if(mel.src() == selector) bActive = true; if(mel.src() == selector || !mel.isEditableText() || mel.isModified()) melIt = ml.mergeEditLineList.erase(melIt); else ++melIt; } if(!bActive) // Selected source wasn't active. { // Append the lines from selected source here at rangeEnd. Diff3LineList::const_iterator d3llit = ml.id3l; int j; for(j = 0; j < ml.srcRangeLength; ++j) { MergeEditLine mel(d3llit); mel.setSource(selector, false); ml.mergeEditLineList.push_back(mel); ++d3llit; } } if(!ml.mergeEditLineList.empty()) { // Remove all lines that are empty, because no src lines are there. for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end();) { MergeEditLine& mel = *melIt; LineRef srcLine = mel.src() == e_SrcSelector::A ? mel.id3l()->getLineA() : mel.src() == e_SrcSelector::B ? mel.id3l()->getLineB() : mel.src() == e_SrcSelector::C ? mel.id3l()->getLineC() : LineRef(); if(!srcLine.isValid()) melIt = ml.mergeEditLineList.erase(melIt); else ++melIt; } } if(ml.mergeEditLineList.empty()) { // Insert a dummy line: MergeEditLine mel(ml.id3l); if(bActive) mel.setConflict(); // All src entries deleted => conflict else mel.setRemoved(selector); // No lines in corresponding src found. ml.mergeEditLineList.push_back(mel); } if(m_cursorYPos >= m_nofLines) { m_cursorYPos = m_nofLines - 1; m_cursorXPos = 0; } m_maxTextWidth = -1; update(); updateSourceMask(); Q_EMIT updateAvailabilities(); showUnsolvedConflictsStatusMessage(); } // bConflictsOnly: automatically choose for conflicts only (true) or for everywhere (false) void MergeResultWindow::chooseGlobal(e_SrcSelector selector, bool bConflictsOnly, bool bWhiteSpaceOnly) { resetSelection(); merge(false, selector, bConflictsOnly, bWhiteSpaceOnly); setModified(true); update(); showUnsolvedConflictsStatusMessage(); } void MergeResultWindow::slotAutoSolve() { resetSelection(); merge(true, e_SrcSelector::Invalid); setModified(true); update(); showUnsolvedConflictsStatusMessage(); showNrOfConflicts(); } void MergeResultWindow::slotUnsolve() { resetSelection(); merge(false, e_SrcSelector::Invalid); setModified(true); update(); showUnsolvedConflictsStatusMessage(); } bool findParenthesesGroups(const QString& s, QStringList& sl) { sl.clear(); int i = 0; std::list startPosStack; int length = s.length(); for(i = 0; i < length; ++i) { if(s[i] == '\\' && i + 1 < length && (s[i + 1] == '\\' || s[i + 1] == '(' || s[i + 1] == ')')) { ++i; continue; } if(s[i] == '(') { startPosStack.push_back(i); } else if(s[i] == ')') { if(startPosStack.empty()) return false; // Parentheses don't match int startPos = startPosStack.back(); startPosStack.pop_back(); sl.push_back(s.mid(startPos + 1, i - startPos - 1)); } } return startPosStack.empty(); // false if parentheses don't match } QString calcHistorySortKey(const QString& keyOrder, QRegExp& matchedRegExpr, const QStringList& parenthesesGroupList) { const QStringList keyOrderList = keyOrder.split(','); QString key; for(const QString& keyIt : keyOrderList) { if(keyIt.isEmpty()) continue; bool bOk = false; int groupIdx = keyIt.toInt(&bOk); if(!bOk || groupIdx < 0 || groupIdx > parenthesesGroupList.size()) continue; QString s = matchedRegExpr.cap(groupIdx); if(groupIdx == 0) { key += s + ' '; continue; } QString groupRegExp = parenthesesGroupList[groupIdx - 1]; if(groupRegExp.indexOf('|') < 0 || groupRegExp.indexOf('(') >= 0) { bOk = false; int i = s.toInt(&bOk); if(bOk && i >= 0 && i < 10000) s.asprintf("%04d", i); // This should help for correct sorting of numbers. key += s + ' '; } else { // Assume that the groupRegExp consists of something like "Jan|Feb|Mar|Apr" // s is the string that managed to match. // Now we want to know at which position it occurred. e.g. Jan=0, Feb=1, Mar=2, etc. QStringList sl = groupRegExp.split('|'); int idx = sl.indexOf(s); if(idx < 0) { // Didn't match } else { QString sIdx; sIdx.asprintf("%02d", idx + 1); // Up to 99 words in the groupRegExp (more than 12 aren't expected) key += sIdx + ' '; } } } return key; } void MergeResultWindow::collectHistoryInformation( e_SrcSelector src, Diff3LineList::const_iterator &iHistoryBegin, Diff3LineList::const_iterator &iHistoryEnd, HistoryMap& historyMap, std::list& hitList // list of iterators ) { std::list::iterator itHitListFront = hitList.begin(); Diff3LineList::const_iterator id3l = iHistoryBegin; QString historyLead; { const LineData* pld = id3l->getLineData(src); historyLead = Utils::calcHistoryLead(pld->getLine()); } QRegExp historyStart(m_pOptions->m_historyStartRegExp); if(id3l == iHistoryEnd) return; ++id3l; // Skip line with "$Log ... $" QRegExp newHistoryEntry(m_pOptions->m_historyEntryStartRegExp); QStringList parenthesesGroups; findParenthesesGroups(m_pOptions->m_historyEntryStartRegExp, parenthesesGroups); QString key; MergeEditLineList melList; bool bPrevLineIsEmpty = true; bool bUseRegExp = !m_pOptions->m_historyEntryStartRegExp.isEmpty(); for(; id3l != iHistoryEnd; ++id3l) { const LineData* pld = id3l->getLineData(src); if(!pld) continue; const QString& oriLine = pld->getLine(); if(historyLead.isEmpty()) historyLead = Utils::calcHistoryLead(oriLine); QString sLine = oriLine.mid(historyLead.length()); if((!bUseRegExp && !sLine.trimmed().isEmpty() && bPrevLineIsEmpty) || (bUseRegExp && newHistoryEntry.exactMatch(sLine))) { if(!key.isEmpty() && !melList.empty()) { // Only insert new HistoryMapEntry if key not found; in either case p.first is a valid iterator to element key. std::pair p = historyMap.insert(HistoryMap::value_type(key, HistoryMapEntry())); HistoryMapEntry& hme = p.first->second; if(src == e_SrcSelector::A) hme.mellA = melList; if(src == e_SrcSelector::B) hme.mellB = melList; if(src == e_SrcSelector::C) hme.mellC = melList; if(p.second) // Not in list yet? { hitList.insert(itHitListFront, p.first); } } if(!bUseRegExp) key = sLine; else key = calcHistorySortKey(m_pOptions->m_historyEntryStartSortKeyOrder, newHistoryEntry, parenthesesGroups); melList.clear(); melList.push_back(MergeEditLine(id3l, src)); } else if(!historyStart.exactMatch(oriLine)) { melList.push_back(MergeEditLine(id3l, src)); } bPrevLineIsEmpty = sLine.trimmed().isEmpty(); } if(!key.isEmpty()) { // Only insert new HistoryMapEntry if key not found; in either case p.first is a valid iterator to element key. std::pair p = historyMap.insert(HistoryMap::value_type(key, HistoryMapEntry())); HistoryMapEntry& hme = p.first->second; if(src == e_SrcSelector::A) hme.mellA = melList; if(src == e_SrcSelector::B) hme.mellB = melList; if(src == e_SrcSelector::C) hme.mellC = melList; if(p.second) // Not in list yet? { hitList.insert(itHitListFront, p.first); } } // End of the history } MergeEditLineList& MergeResultWindow::HistoryMapEntry::choice(bool bThreeInputs) { if(!bThreeInputs) return mellA.empty() ? mellB : mellA; else { if(mellA.empty()) return mellC.empty() ? mellB : mellC; // A doesn't exist, return one that exists else if(!mellB.empty() && !mellC.empty()) { // A, B and C exist return mellA; } else return mellB.empty() ? mellB : mellC; // A exists, return the one that doesn't exist } } bool MergeResultWindow::HistoryMapEntry::staysInPlace(bool bThreeInputs, Diff3LineList::const_iterator& iHistoryEnd) { // The entry should stay in place if the decision made by the automerger is correct. Diff3LineList::const_iterator& iHistoryLast = iHistoryEnd; --iHistoryLast; if(!bThreeInputs) { if(!mellA.empty() && !mellB.empty() && mellA.begin()->id3l() == mellB.begin()->id3l() && mellA.back().id3l() == iHistoryLast && mellB.back().id3l() == iHistoryLast) { iHistoryEnd = mellA.begin()->id3l(); return true; } else { return false; } } else { if(!mellA.empty() && !mellB.empty() && !mellC.empty() && mellA.begin()->id3l() == mellB.begin()->id3l() && mellA.begin()->id3l() == mellC.begin()->id3l() && mellA.back().id3l() == iHistoryLast && mellB.back().id3l() == iHistoryLast && mellC.back().id3l() == iHistoryLast) { iHistoryEnd = mellA.begin()->id3l(); return true; } else { return false; } } } void MergeResultWindow::slotMergeHistory() { Diff3LineList::const_iterator iD3LHistoryBegin; Diff3LineList::const_iterator iD3LHistoryEnd; int d3lHistoryBeginLineIdx = -1; int d3lHistoryEndLineIdx = -1; // Search for history start, history end in the diff3LineList m_pDiff3LineList->findHistoryRange(QRegExp(m_pOptions->m_historyStartRegExp), m_pldC != nullptr, iD3LHistoryBegin, iD3LHistoryEnd, d3lHistoryBeginLineIdx, d3lHistoryEndLineIdx); if(iD3LHistoryBegin != m_pDiff3LineList->end()) { // Now collect the historyMap information HistoryMap historyMap; std::list hitList; if(m_pldC == nullptr) { collectHistoryInformation(e_SrcSelector::A, iD3LHistoryBegin, iD3LHistoryEnd, historyMap, hitList); collectHistoryInformation(e_SrcSelector::B, iD3LHistoryBegin, iD3LHistoryEnd, historyMap, hitList); } else { collectHistoryInformation(e_SrcSelector::A, iD3LHistoryBegin, iD3LHistoryEnd, historyMap, hitList); collectHistoryInformation(e_SrcSelector::B, iD3LHistoryBegin, iD3LHistoryEnd, historyMap, hitList); collectHistoryInformation(e_SrcSelector::C, iD3LHistoryBegin, iD3LHistoryEnd, historyMap, hitList); } Diff3LineList::const_iterator iD3LHistoryOrigEnd = iD3LHistoryEnd; bool bHistoryMergeSorting = m_pOptions->m_bHistoryMergeSorting && !m_pOptions->m_historyEntryStartSortKeyOrder.isEmpty() && !m_pOptions->m_historyEntryStartRegExp.isEmpty(); if(m_pOptions->m_maxNofHistoryEntries == -1) { // Remove parts from the historyMap and hitList that stay in place if(bHistoryMergeSorting) { while(!historyMap.empty()) { HistoryMap::iterator hMapIt = historyMap.begin(); if(hMapIt->second.staysInPlace(m_pldC != nullptr, iD3LHistoryEnd)) historyMap.erase(hMapIt); else break; } } else { while(!hitList.empty()) { HistoryMap::iterator hMapIt = hitList.back(); if(hMapIt->second.staysInPlace(m_pldC != nullptr, iD3LHistoryEnd)) hitList.pop_back(); else break; } } while(iD3LHistoryOrigEnd != iD3LHistoryEnd) { --iD3LHistoryOrigEnd; --d3lHistoryEndLineIdx; } } MergeLineList::iterator iMLLStart = splitAtDiff3LineIdx(d3lHistoryBeginLineIdx); MergeLineList::iterator iMLLEnd = splitAtDiff3LineIdx(d3lHistoryEndLineIdx); // Now join all MergeLines in the history MergeLineList::iterator i = iMLLStart; if(i != iMLLEnd) { ++i; while(i != iMLLEnd) { iMLLStart->join(*i); i = m_mergeLineList.erase(i); } } iMLLStart->mergeEditLineList.clear(); // Now insert the complete history into the first MergeLine of the history iMLLStart->mergeEditLineList.push_back(MergeEditLine(iD3LHistoryBegin, m_pldC == nullptr ? e_SrcSelector::B : e_SrcSelector::C)); QString lead = Utils::calcHistoryLead(iD3LHistoryBegin->getString(e_SrcSelector::A)); MergeEditLine mel(m_pDiff3LineList->end()); mel.setString(lead); iMLLStart->mergeEditLineList.push_back(mel); int historyCount = 0; if(bHistoryMergeSorting) { // Create a sorted history HistoryMap::reverse_iterator hmit; for(hmit = historyMap.rbegin(); hmit != historyMap.rend(); ++hmit) { if(historyCount == m_pOptions->m_maxNofHistoryEntries) break; ++historyCount; HistoryMapEntry& hme = hmit->second; MergeEditLineList& mell = hme.choice(m_pldC != nullptr); if(!mell.empty()) iMLLStart->mergeEditLineList.splice(iMLLStart->mergeEditLineList.end(), mell, mell.begin(), mell.end()); } } else { // Create history in order of appearance std::list::iterator hlit; for(hlit = hitList.begin(); hlit != hitList.end(); ++hlit) { if(historyCount == m_pOptions->m_maxNofHistoryEntries) break; ++historyCount; HistoryMapEntry& hme = (*hlit)->second; MergeEditLineList& mell = hme.choice(m_pldC != nullptr); if(!mell.empty()) iMLLStart->mergeEditLineList.splice(iMLLStart->mergeEditLineList.end(), mell, mell.begin(), mell.end()); } // If the end of start is empty and the first line at the end is empty remove the last line of start if(!iMLLStart->mergeEditLineList.empty() && !iMLLEnd->mergeEditLineList.empty()) { QString lastLineOfStart = iMLLStart->mergeEditLineList.back().getString(m_pldA, m_pldB, m_pldC); QString firstLineOfEnd = iMLLEnd->mergeEditLineList.front().getString(m_pldA, m_pldB, m_pldC); if(lastLineOfStart.mid(lead.length()).trimmed().isEmpty() && firstLineOfEnd.mid(lead.length()).trimmed().isEmpty()) iMLLStart->mergeEditLineList.pop_back(); } } setFastSelector(iMLLStart); update(); } } void MergeResultWindow::slotRegExpAutoMerge() { if(m_pOptions->m_autoMergeRegExp.isEmpty()) return; QRegExp vcsKeywords(m_pOptions->m_autoMergeRegExp); MergeLineList::iterator i; for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i) { if(i->bConflict) { Diff3LineList::const_iterator id3l = i->id3l; if(vcsKeywords.exactMatch(id3l->getString(e_SrcSelector::A)) && vcsKeywords.exactMatch(id3l->getString(e_SrcSelector::B)) && (m_pldC == nullptr || vcsKeywords.exactMatch(id3l->getString(e_SrcSelector::C)))) { MergeEditLine& mel = *i->mergeEditLineList.begin(); mel.setSource(m_pldC == nullptr ? e_SrcSelector::B : e_SrcSelector::C, false); splitAtDiff3LineIdx(i->d3lLineIdx + 1); } } } update(); } // This doesn't detect user modifications and should only be called after automatic merge // This will only do something for three file merge. // Irrelevant changes are those where all contributions from B are already contained in C. // Also irrelevant are conflicts automatically solved (automerge regexp and history automerge) // Precondition: The VCS-keyword would also be C. bool MergeResultWindow::doRelevantChangesExist() { if(m_pldC == nullptr || m_mergeLineList.size() <= 1) return true; MergeLineList::iterator i; for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i) { if((i->bConflict && i->mergeEditLineList.begin()->src() != e_SrcSelector::C) || i->srcSelect == e_SrcSelector::B) { return true; } } return false; } // Returns the iterator to the MergeLine after the split MergeLineList::iterator MergeResultWindow::splitAtDiff3LineIdx(int d3lLineIdx) { MergeLineList::iterator i; for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i) { if(i->d3lLineIdx == d3lLineIdx) { // No split needed, this is the beginning of a MergeLine return i; } else if(i->d3lLineIdx > d3lLineIdx) { // The split must be in the previous MergeLine --i; MergeLine& ml = *i; MergeLine newML; ml.split(newML, d3lLineIdx); ++i; return m_mergeLineList.insert(i, newML); } } // The split must be in the previous MergeLine --i; MergeLine& ml = *i; MergeLine newML; ml.split(newML, d3lLineIdx); ++i; return m_mergeLineList.insert(i, newML); } void MergeResultWindow::slotSplitDiff(int firstD3lLineIdx, int lastD3lLineIdx) { if(lastD3lLineIdx >= 0) splitAtDiff3LineIdx(lastD3lLineIdx + 1); setFastSelector(splitAtDiff3LineIdx(firstD3lLineIdx)); } void MergeResultWindow::slotJoinDiffs(int firstD3lLineIdx, int lastD3lLineIdx) { MergeLineList::iterator i; MergeLineList::iterator iMLLStart = m_mergeLineList.end(); MergeLineList::iterator iMLLEnd = m_mergeLineList.end(); for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i) { MergeLine& ml = *i; if(firstD3lLineIdx >= ml.d3lLineIdx && firstD3lLineIdx < ml.d3lLineIdx + ml.srcRangeLength) { iMLLStart = i; } if(lastD3lLineIdx >= ml.d3lLineIdx && lastD3lLineIdx < ml.d3lLineIdx + ml.srcRangeLength) { iMLLEnd = i; ++iMLLEnd; break; } } bool bJoined = false; for(i = iMLLStart; i != iMLLEnd && i != m_mergeLineList.end();) { if(i == iMLLStart) { ++i; } else { iMLLStart->join(*i); i = m_mergeLineList.erase(i); bJoined = true; } } if(bJoined) { iMLLStart->mergeEditLineList.clear(); // Insert a conflict line as placeholder iMLLStart->mergeEditLineList.push_back(MergeEditLine(iMLLStart->id3l)); } setFastSelector(iMLLStart); } void MergeResultWindow::myUpdate(int afterMilliSecs) { if(m_delayedDrawTimer) killTimer(m_delayedDrawTimer); m_bMyUpdate = true; m_delayedDrawTimer = startTimer(afterMilliSecs); } void MergeResultWindow::timerEvent(QTimerEvent*) { killTimer(m_delayedDrawTimer); m_delayedDrawTimer = 0; if(m_bMyUpdate) { update(); m_bMyUpdate = false; } if(m_scrollDeltaX != 0 || m_scrollDeltaY != 0) { m_selection.end(m_selection.getLastLine() + m_scrollDeltaY, m_selection.getLastPos() + m_scrollDeltaX); Q_EMIT scrollMergeResultWindow(m_scrollDeltaX, m_scrollDeltaY); killTimer(m_delayedDrawTimer); m_delayedDrawTimer = startTimer(50); } } QVector MergeResultWindow::getTextLayoutForLine(int line, const QString& str, QTextLayout& textLayout) { // tabs QTextOption textOption; #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) textOption.setTabStop(QFontMetricsF(font()).width(' ') * m_pOptions->m_tabSize); #else textOption.setTabStopDistance(QFontMetricsF(font()).width(' ') * m_pOptions->m_tabSize); #endif if(m_pOptions->m_bShowWhiteSpaceCharacters) { textOption.setFlags(QTextOption::ShowTabsAndSpaces); } 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 = str.length(); formatRange.format.setFont(font()); formats.append(formatRange); textLayout.setFormats(formats); } QVector selectionFormat; textLayout.beginLayout(); if(m_selection.lineWithin(line)) { int firstPosInText = m_selection.firstPosInLine(line); int lastPosInText = m_selection.lastPosInLine(line); int lengthInText = std::max(0, lastPosInText - firstPosInText); if(lengthInText > 0) m_selection.bSelectionContainsData = true; QTextLayout::FormatRange selection; selection.start = firstPosInText; selection.length = lengthInText; selection.format.setBackground(palette().highlight()); selection.format.setForeground(palette().highlightedText().color()); selectionFormat.push_back(selection); } QTextLine textLine = textLayout.createLine(); textLine.setPosition(QPointF(0, fontMetrics().leading())); textLayout.endLayout(); int cursorWidth = 5; if(m_pOptions->m_bRightToLeftLanguage) textLayout.setPosition(QPointF(width() - textLayout.maximumWidth() - getTextXOffset() + m_horizScrollOffset - cursorWidth, 0)); else textLayout.setPosition(QPointF(getTextXOffset() - m_horizScrollOffset, 0)); return selectionFormat; } void MergeResultWindow::writeLine( RLPainter& p, int line, const QString& str, e_SrcSelector srcSelect, e_MergeDetails mergeDetails, int rangeMark, bool bUserModified, bool bLineRemoved, bool bWhiteSpaceConflict) { const QFontMetrics& fm = fontMetrics(); int fontHeight = fm.lineSpacing(); int fontAscent = fm.ascent(); int topLineYOffset = 0; int xOffset = getTextXOffset(); int yOffset = (line - m_firstLine) * fontHeight; if(yOffset < 0 || yOffset > height()) return; yOffset += topLineYOffset; QString srcName = QChar(' '); if(bUserModified) srcName = QChar('m'); else if(srcSelect == e_SrcSelector::A && mergeDetails != e_MergeDetails::eNoChange) srcName = i18n("A"); else if(srcSelect == e_SrcSelector::B) srcName = i18n("B"); else if(srcSelect == e_SrcSelector::C) srcName = i18n("C"); if(rangeMark & 4) { p.fillRect(xOffset, yOffset, width(), fontHeight, m_pOptions->m_currentRangeBgColor); } if((srcSelect > e_SrcSelector::None || bUserModified) && !bLineRemoved) { if(!m_pOptions->m_bRightToLeftLanguage) p.setClipRect(QRectF(xOffset, 0, width() - xOffset, height())); else p.setClipRect(QRectF(0, 0, width() - xOffset, height())); int outPos = 0; QString s; int size = str.length(); for(int i = 0; i < size; ++i) { int spaces = 1; if(str[i] == '\t') { spaces = tabber(outPos, m_pOptions->m_tabSize); for(int j = 0; j < spaces; ++j) s += ' '; } else { s += str[i]; } outPos += spaces; } p.setPen(m_pOptions->m_fgColor); QTextLayout textLayout(str, font(), this); QVector selectionFormat = getTextLayoutForLine(line, str, textLayout); textLayout.draw(&p, QPointF(0, yOffset), selectionFormat); if(line == m_cursorYPos) { m_cursorXPixelPos = qCeil(textLayout.lineAt(0).cursorToX(m_cursorXPos)); if(m_pOptions->m_bRightToLeftLanguage) m_cursorXPixelPos += qCeil(textLayout.position().x() - m_horizScrollOffset); } p.setClipping(false); p.setPen(m_pOptions->m_fgColor); p.drawText(1, yOffset + fontAscent, srcName, true); } else if(bLineRemoved) { p.setPen(m_pOptions->m_colorForConflict); p.drawText(xOffset, yOffset + fontAscent, i18n("")); p.drawText(1, yOffset + fontAscent, srcName); if(m_cursorYPos == line) m_cursorXPos = 0; } else if(srcSelect == e_SrcSelector::None) { p.setPen(m_pOptions->m_colorForConflict); if(bWhiteSpaceConflict) p.drawText(xOffset, yOffset + fontAscent, i18n("")); else p.drawText(xOffset, yOffset + fontAscent, i18n("")); p.drawText(1, yOffset + fontAscent, "?"); if(m_cursorYPos == line) m_cursorXPos = 0; } else Q_ASSERT(true); xOffset -= Utils::getHorizontalAdvance(fm, '0'); p.setPen(m_pOptions->m_fgColor); if(rangeMark & 1) // begin mark { p.drawLine(xOffset, yOffset + 1, xOffset, yOffset + fontHeight / 2); p.drawLine(xOffset, yOffset + 1, xOffset - 2, yOffset + 1); } else { p.drawLine(xOffset, yOffset, xOffset, yOffset + fontHeight / 2); } if(rangeMark & 2) // end mark { p.drawLine(xOffset, yOffset + fontHeight / 2, xOffset, yOffset + fontHeight - 1); p.drawLine(xOffset, yOffset + fontHeight - 1, xOffset - 2, yOffset + fontHeight - 1); } else { p.drawLine(xOffset, yOffset + fontHeight / 2, xOffset, yOffset + fontHeight); } if(rangeMark & 4) { p.fillRect(xOffset + 3, yOffset, 3, fontHeight, m_pOptions->m_fgColor); /* p.setPen( blue ); p.drawLine( xOffset+2, yOffset, xOffset+2, yOffset+fontHeight-1 ); p.drawLine( xOffset+3, yOffset, xOffset+3, yOffset+fontHeight-1 );*/ } } void MergeResultWindow::setPaintingAllowed(bool bPaintingAllowed) { setUpdatesEnabled(bPaintingAllowed); if(!bPaintingAllowed) { m_currentMergeLineIt = m_mergeLineList.end(); reset(); } else update(); } void MergeResultWindow::paintEvent(QPaintEvent*) { if(m_pDiff3LineList == nullptr) return; bool bOldSelectionContainsData = m_selection.selectionContainsData(); const QFontMetrics& fm = fontMetrics(); int fontWidth = Utils::getHorizontalAdvance(fm, '0'); if(!m_bCursorUpdate) // Don't redraw everything for blinking cursor? { m_selection.bSelectionContainsData = false; if(size() != m_pixmap.size()) m_pixmap = QPixmap(size()); RLPainter p(&m_pixmap, m_pOptions->m_bRightToLeftLanguage, width(), fontWidth); p.setFont(font()); p.QPainter::fillRect(rect(), m_pOptions->m_bgColor); //int visibleLines = height() / fontHeight; int lastVisibleLine = m_firstLine + getNofVisibleLines() + 5; LineRef line = 0; MergeLineList::iterator mlIt = m_mergeLineList.begin(); for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; if(line > lastVisibleLine || line + ml.mergeEditLineList.size() < m_firstLine) { line += ml.mergeEditLineList.size(); } else { MergeEditLineList::iterator melIt; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt) { if(line >= m_firstLine && line <= lastVisibleLine) { MergeEditLine& mel = *melIt; MergeEditLineList::iterator melIt1 = melIt; ++melIt1; int rangeMark = 0; if(melIt == ml.mergeEditLineList.begin()) rangeMark |= 1; // Begin range mark if(melIt1 == ml.mergeEditLineList.end()) rangeMark |= 2; // End range mark if(mlIt == m_currentMergeLineIt) rangeMark |= 4; // Mark of the current line QString s; s = mel.getString(m_pldA, m_pldB, m_pldC); writeLine(p, line, s, mel.src(), ml.mergeDetails, rangeMark, mel.isModified(), mel.isRemoved(), ml.bWhiteSpaceConflict); } ++line; } } } if(line != m_nofLines) { m_nofLines = line; Q_EMIT resizeSignal(); } p.end(); } QPainter painter(this); if(!m_bCursorUpdate) painter.drawPixmap(0, 0, m_pixmap); else { painter.drawPixmap(0, 0, m_pixmap); // Draw everything. (Internally cursor rect is clipped anyway.) m_bCursorUpdate = false; } if(m_bCursorOn && hasFocus() && m_cursorYPos >= m_firstLine) { painter.setPen(m_pOptions->m_fgColor); QString str = getString(m_cursorYPos); QTextLayout textLayout(str, font(), this); getTextLayoutForLine(m_cursorYPos, str, textLayout); textLayout.drawCursor(&painter, QPointF(0, (m_cursorYPos - m_firstLine) * fontMetrics().lineSpacing()), m_cursorXPos); } painter.end(); if(!bOldSelectionContainsData && m_selection.selectionContainsData()) Q_EMIT newSelection(); } void MergeResultWindow::updateSourceMask() { int srcMask = 0; int enabledMask = 0; if(!hasFocus() || m_pDiff3LineList == nullptr || !updatesEnabled() || m_currentMergeLineIt == m_mergeLineList.end()) { srcMask = 0; enabledMask = 0; } else { enabledMask = m_pldC == nullptr ? 3 : 7; MergeLine& ml = *m_currentMergeLineIt; srcMask = 0; bool bModified = false; MergeEditLineList::iterator melIt; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt) { MergeEditLine& mel = *melIt; if(mel.src() == e_SrcSelector::A) srcMask |= 1; if(mel.src() == e_SrcSelector::B) srcMask |= 2; if(mel.src() == e_SrcSelector::C) srcMask |= 4; if(mel.isModified() || !mel.isEditableText()) bModified = true; } if(ml.mergeDetails == e_MergeDetails::eNoChange) { srcMask = 0; enabledMask = bModified ? 1 : 0; } } Q_EMIT sourceMask(srcMask, enabledMask); } void MergeResultWindow::focusInEvent(QFocusEvent* e) { updateSourceMask(); QWidget::focusInEvent(e); } LineRef MergeResultWindow::convertToLine(int y) { const QFontMetrics& fm = fontMetrics(); int fontHeight = fm.lineSpacing(); int topLineYOffset = 0; int yOffset = topLineYOffset - m_firstLine * fontHeight; LineRef line = std::min((y - yOffset) / fontHeight, m_nofLines - 1); return line; } void MergeResultWindow::mousePressEvent(QMouseEvent* e) { m_bCursorOn = true; int xOffset = getTextXOffset(); LineRef line = convertToLine(e->y()); QString s = getString(line); QTextLayout textLayout(s, font(), this); getTextLayoutForLine(line, s, textLayout); QtNumberType pos = textLayout.lineAt(0).xToCursor(e->x() - textLayout.position().x()); bool bLMB = e->button() == Qt::LeftButton; bool bMMB = e->button() == Qt::MidButton; bool bRMB = e->button() == Qt::RightButton; if((bLMB && (e->x() < xOffset)) || bRMB) // Fast range selection { m_cursorXPos = 0; m_cursorOldXPixelPos = 0; m_cursorYPos = std::max((LineRef::LineType)line, 0); int l = 0; MergeLineList::iterator i = m_mergeLineList.begin(); for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i) { if(l == line) break; l += i->mergeEditLineList.size(); if(l > line) break; } m_selection.reset(); // Disable current selection m_bCursorOn = true; setFastSelector(i); if(bRMB) { Q_EMIT showPopupMenu(QCursor::pos()); } } else if(bLMB) // Normal cursor placement { pos = std::max(pos, 0); line = std::max((LineRef::LineType)line, 0); if(e->QInputEvent::modifiers() & Qt::ShiftModifier) { if(!m_selection.isValidFirstLine()) m_selection.start(line, pos); m_selection.end(line, pos); } else { // Selection m_selection.reset(); m_selection.start(line, pos); m_selection.end(line, pos); } m_cursorXPos = pos; m_cursorXPixelPos = qCeil(textLayout.lineAt(0).cursorToX(pos)); if(m_pOptions->m_bRightToLeftLanguage) m_cursorXPixelPos += qCeil(textLayout.position().x() - m_horizScrollOffset); m_cursorOldXPixelPos = m_cursorXPixelPos; m_cursorYPos = line; update(); //showStatusLine( line, m_winIdx, m_pFilename, m_pDiff3LineList, m_pStatusBar ); } else if(bMMB) // Paste clipboard { pos = std::max(pos, 0); line = std::max((LineRef::LineType)line, 0); m_selection.reset(); m_cursorXPos = pos; m_cursorOldXPixelPos = m_cursorXPixelPos; m_cursorYPos = line; pasteClipboard(true); } } void MergeResultWindow::mouseDoubleClickEvent(QMouseEvent* e) { if(e->button() == Qt::LeftButton) { LineRef line = convertToLine(e->y()); QString s = getString(line); QTextLayout textLayout(s, font(), this); getTextLayoutForLine(line, s, textLayout); int pos = textLayout.lineAt(0).xToCursor(e->x() - textLayout.position().x()); m_cursorXPos = pos; m_cursorOldXPixelPos = m_cursorXPixelPos; m_cursorYPos = line; if(!s.isEmpty()) { int pos1, pos2; Utils::calcTokenPos(s, pos, pos1, pos2); resetSelection(); m_selection.start(line, pos1); m_selection.end(line, pos2); update(); // Q_EMIT selectionEnd() happens in the mouseReleaseEvent. } } } void MergeResultWindow::mouseReleaseEvent(QMouseEvent* e) { if(e->button() == Qt::LeftButton) { if(m_delayedDrawTimer) { killTimer(m_delayedDrawTimer); m_delayedDrawTimer = 0; } if(m_selection.isValidFirstLine()) { Q_EMIT selectionEnd(); } } } void MergeResultWindow::mouseMoveEvent(QMouseEvent* e) { LineRef line = convertToLine(e->y()); QString s = getString(line); QTextLayout textLayout(s, font(), this); getTextLayoutForLine(line, s, textLayout); int pos = textLayout.lineAt(0).xToCursor(e->x() - textLayout.position().x()); m_cursorXPos = pos; m_cursorOldXPixelPos = m_cursorXPixelPos; m_cursorYPos = line; if(m_selection.isValidFirstLine()) { m_selection.end(line, pos); myUpdate(0); //showStatusLine( line, m_winIdx, m_pFilename, m_pDiff3LineList, m_pStatusBar ); // Scroll because mouse moved out of the window const QFontMetrics& fm = fontMetrics(); int fontWidth = Utils::getHorizontalAdvance(fm, '0'); int topLineYOffset = 0; int deltaX = 0; int deltaY = 0; if(!m_pOptions->m_bRightToLeftLanguage) { if(e->x() < getTextXOffset()) deltaX = -1; if(e->x() > width()) deltaX = +1; } else { if(e->x() > width() - 1 - getTextXOffset()) deltaX = -1; if(e->x() < fontWidth) deltaX = +1; } if(e->y() < topLineYOffset) deltaY = -1; if(e->y() > height()) deltaY = +1; m_scrollDeltaX = deltaX; m_scrollDeltaY = deltaY; if(deltaX != 0 || deltaY != 0) { Q_EMIT scrollMergeResultWindow(deltaX, deltaY); } } } void MergeResultWindow::slotCursorUpdate() { m_cursorTimer.stop(); m_bCursorOn = !m_bCursorOn; if(isVisible()) { m_bCursorUpdate = true; const QFontMetrics& fm = fontMetrics(); int topLineYOffset = 0; int yOffset = (m_cursorYPos - m_firstLine) * fm.lineSpacing() + topLineYOffset; repaint(0, yOffset, width(), fm.lineSpacing() + 2); m_bCursorUpdate = false; } m_cursorTimer.start(500); } void MergeResultWindow::wheelEvent(QWheelEvent* pWheelEvent) { QPoint delta = pWheelEvent->angleDelta(); //Block diagonal scrolling easily generated unintentionally with track pads. if(delta.y() != 0 && abs(delta.y()) > abs(delta.x()) && mVScrollBar != nullptr) { pWheelEvent->accept(); QCoreApplication::postEvent(mVScrollBar, new QWheelEvent(*pWheelEvent)); } } bool MergeResultWindow::event(QEvent* e) { if(e->type() == QEvent::KeyPress) { QKeyEvent* ke = static_cast(e); if(ke->key() == Qt::Key_Tab) { // special tab handling here to avoid moving focus keyPressEvent(ke); return true; } } return QWidget::event(e); } void MergeResultWindow::keyPressEvent(QKeyEvent* e) { int y = m_cursorYPos; MergeLineList::iterator mlIt; MergeEditLineList::iterator melIt; calcIteratorFromLineNr(y, mlIt, melIt); QString str = melIt->getString(m_pldA, m_pldB, m_pldC); int x = m_cursorXPos; QTextLayout textLayoutOrig(str, font(), this); getTextLayoutForLine(y, str, textLayoutOrig); bool bCtrl = (e->QInputEvent::modifiers() & Qt::ControlModifier) != 0; bool bShift = (e->QInputEvent::modifiers() & Qt::ShiftModifier) != 0; #ifdef Q_OS_WIN bool bAlt = (e->QInputEvent::modifiers() & Qt::AltModifier) != 0; if(bCtrl && bAlt) { bCtrl = false; bAlt = false; } // AltGr-Key pressed. #endif bool bYMoveKey = false; // Special keys switch(e->key()) { case Qt::Key_Escape: //case Qt::Key_Tab: case Qt::Key_Backtab: break; case Qt::Key_Delete: { if(deleteSelection2(str, x, y, mlIt, melIt) || !melIt->isEditableText()) break; if(x >= str.length()) { if(y < m_nofLines - 1) { setModified(); MergeLineList::iterator mlIt1; MergeEditLineList::iterator melIt1; calcIteratorFromLineNr(y + 1, mlIt1, melIt1); if(melIt1->isEditableText()) { QString s2 = melIt1->getString(m_pldA, m_pldB, m_pldC); melIt->setString(str + s2); // Remove the line if(mlIt1->mergeEditLineList.size() > 1) mlIt1->mergeEditLineList.erase(melIt1); else melIt1->setRemoved(); } } } else { QString s = str.left(x); s += str.midRef(x + 1); melIt->setString(s); setModified(); } break; } case Qt::Key_Backspace: { if(deleteSelection2(str, x, y, mlIt, melIt)) break; if(!melIt->isEditableText()) break; if(x == 0) { if(y > 0) { setModified(); MergeLineList::iterator mlIt1; MergeEditLineList::iterator melIt1; calcIteratorFromLineNr(y - 1, mlIt1, melIt1); if(melIt1->isEditableText()) { QString s1 = melIt1->getString(m_pldA, m_pldB, m_pldC); melIt1->setString(s1 + str); // Remove the previous line if(mlIt->mergeEditLineList.size() > 1) mlIt->mergeEditLineList.erase(melIt); else melIt->setRemoved(); --y; x = str.length(); } } } else { QString s = str.left(x - 1); s += str.midRef(x); --x; melIt->setString(s); setModified(); } break; } case Qt::Key_Return: case Qt::Key_Enter: { if(!melIt->isEditableText()) break; deleteSelection2(str, x, y, mlIt, melIt); setModified(); QString indentation; if(m_pOptions->m_bAutoIndentation) { // calc last indentation MergeLineList::iterator mlIt1 = mlIt; MergeEditLineList::iterator melIt1 = melIt; for(;;) { const QString s = melIt1->getString(m_pldA, m_pldB, m_pldC); if(!s.isEmpty()) { int i; for(i = 0; i < s.length(); ++i) { if(s[i] != ' ' && s[i] != '\t') break; } if(i < s.length()) { indentation = s.left(i); break; } } // Go back one line if(melIt1 != mlIt1->mergeEditLineList.begin()) --melIt1; else { if(mlIt1 == m_mergeLineList.begin()) break; --mlIt1; melIt1 = mlIt1->mergeEditLineList.end(); --melIt1; } } } MergeEditLine mel(mlIt->id3l); // Associate every mel with an id3l, even if not really valid. mel.setString(indentation + str.mid(x)); if(x < str.length()) // Cut off the old line. { // Since ps possibly points into melIt->str, first copy it into a temporary. QString temp = str.left(x); melIt->setString(temp); } ++melIt; mlIt->mergeEditLineList.insert(melIt, mel); x = indentation.length(); ++y; break; } case Qt::Key_Insert: m_bInsertMode = !m_bInsertMode; break; case Qt::Key_Pause: case Qt::Key_Print: case Qt::Key_SysReq: break; case Qt::Key_Home: x = 0; if(bCtrl) { y = 0; } break; // cursor movement case Qt::Key_End: x = INT_MAX; if(bCtrl) { y = INT_MAX; } break; case Qt::Key_Left: case Qt::Key_Right: if((e->key() == Qt::Key_Left) != m_pOptions->m_bRightToLeftLanguage) { if(!bCtrl) { int newX = textLayoutOrig.previousCursorPosition(x); if(newX == x && y > 0) { --y; x = INT_MAX; } else { x = newX; } } else { while(x > 0 && (str[x - 1] == ' ' || str[x - 1] == '\t')) { int newX = textLayoutOrig.previousCursorPosition(x); if(newX == x) break; x = newX; } while(x > 0 && (str[x - 1] != ' ' && str[x - 1] != '\t')) { int newX = textLayoutOrig.previousCursorPosition(x); if(newX == x) break; x = newX; } } } else { if(!bCtrl) { int newX = textLayoutOrig.nextCursorPosition(x); if(newX == x && y < m_nofLines - 1) { ++y; x = 0; } else { x = newX; } } else { while(x < str.length() && (str[x] == ' ' || str[x] == '\t')) { int newX = textLayoutOrig.nextCursorPosition(x); if(newX == x) break; x = newX; } while(x < str.length() && (str[x] != ' ' && str[x] != '\t')) { int newX = textLayoutOrig.nextCursorPosition(x); if(newX == x) break; x = newX; } } } break; case Qt::Key_Up: if(!bCtrl) { --y; bYMoveKey = true; } break; case Qt::Key_Down: if(!bCtrl) { ++y; bYMoveKey = true; } break; case Qt::Key_PageUp: if(!bCtrl) { y -= getNofVisibleLines(); bYMoveKey = true; } break; case Qt::Key_PageDown: if(!bCtrl) { y += getNofVisibleLines(); bYMoveKey = true; } break; default: { QString t = e->text(); if(t.isEmpty() || bCtrl) { e->ignore(); return; } else { if(bCtrl) { e->ignore(); return; } else { if(!melIt->isEditableText()) break; deleteSelection2(str, x, y, mlIt, melIt); setModified(); // Characters to insert QString s = str; if(t[0] == '\t' && m_pOptions->m_bReplaceTabs) { int spaces = (m_cursorXPos / m_pOptions->m_tabSize + 1) * m_pOptions->m_tabSize - m_cursorXPos; t.fill(' ', spaces); } if(m_bInsertMode) s.insert(x, t); else s.replace(x, t.length(), t); melIt->setString(s); x += t.length(); bShift = false; } } } } y = qBound(0, y, m_nofLines - 1); calcIteratorFromLineNr(y, mlIt, melIt); str = melIt->getString(m_pldA, m_pldB, m_pldC); x = qBound(0, x, (int)str.length()); int newFirstLine = m_firstLine; int newHorizScrollOffset = m_horizScrollOffset; if(y < m_firstLine) newFirstLine = y; else if(y > m_firstLine + getNofVisibleLines()) newFirstLine = y - getNofVisibleLines(); QTextLayout textLayout(str, font(), this); getTextLayoutForLine(m_cursorYPos, str, textLayout); // try to preserve cursor x pixel position when moving to another line if(bYMoveKey) { if(m_pOptions->m_bRightToLeftLanguage) x = textLayout.lineAt(0).xToCursor(m_cursorOldXPixelPos - (textLayout.position().x() - m_horizScrollOffset)); else x = textLayout.lineAt(0).xToCursor(m_cursorOldXPixelPos); } m_cursorXPixelPos = qCeil(textLayout.lineAt(0).cursorToX(x)); int hF = 1; // horizontal factor if(m_pOptions->m_bRightToLeftLanguage) { m_cursorXPixelPos += qCeil(textLayout.position().x() - m_horizScrollOffset); hF = -1; } int cursorWidth = 5; if(m_cursorXPixelPos < hF * m_horizScrollOffset) newHorizScrollOffset = hF * m_cursorXPixelPos; else if(m_cursorXPixelPos > hF * m_horizScrollOffset + getVisibleTextAreaWidth() - cursorWidth) newHorizScrollOffset = hF * (m_cursorXPixelPos - (getVisibleTextAreaWidth() - cursorWidth)); int newCursorX = x; if(bShift) { if(!m_selection.isValidFirstLine()) m_selection.start(m_cursorYPos, m_cursorXPos); m_selection.end(y, newCursorX); } else m_selection.reset(); m_cursorYPos = y; m_cursorXPos = newCursorX; // TODO if width of current line exceeds the current maximum width then force recalculating the scrollbars if(textLayout.maximumWidth() > getMaxTextWidth()) { m_maxTextWidth = qCeil(textLayout.maximumWidth()); Q_EMIT resizeSignal(); } if(!bYMoveKey) m_cursorOldXPixelPos = m_cursorXPixelPos; m_bCursorOn = true; m_cursorTimer.start(500); update(); if(newFirstLine != m_firstLine || newHorizScrollOffset != m_horizScrollOffset) { Q_EMIT scrollMergeResultWindow(newHorizScrollOffset - m_horizScrollOffset, newFirstLine - m_firstLine); return; } } void MergeResultWindow::calcIteratorFromLineNr( int line, MergeLineList::iterator& mlIt, MergeEditLineList::iterator& melIt) { for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; if(line > ml.mergeEditLineList.size()) { line -= ml.mergeEditLineList.size(); } else { for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt) { --line; if(line < 0) return; } } } } QString MergeResultWindow::getSelection() { QString selectionString; int line = 0; MergeLineList::iterator mlIt = m_mergeLineList.begin(); for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; MergeEditLineList::iterator melIt; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt) { MergeEditLine& mel = *melIt; if(m_selection.lineWithin(line)) { int outPos = 0; if(mel.isEditableText()) { const QString str = mel.getString(m_pldA, m_pldB, m_pldC); // Consider tabs for(int i = 0; i < str.length(); ++i) { int spaces = 1; if(str[i] == '\t') { spaces = tabber(outPos, m_pOptions->m_tabSize); } if(m_selection.within(line, outPos)) { selectionString += str[i]; } outPos += spaces; } } else if(mel.isConflict()) { selectionString += i18n(""); } if(m_selection.within(line, outPos)) { #ifdef Q_OS_WIN selectionString += '\r'; #endif selectionString += '\n'; } } ++line; } } return selectionString; } bool MergeResultWindow::deleteSelection2(QString& s, int& x, int& y, MergeLineList::iterator& mlIt, MergeEditLineList::iterator& melIt) { if(m_selection.selectionContainsData()) { Q_ASSERT(m_selection.isValidFirstLine()); deleteSelection(); y = m_cursorYPos; calcIteratorFromLineNr(y, mlIt, melIt); s = melIt->getString(m_pldA, m_pldB, m_pldC); x = m_cursorXPos; return true; } return false; } void MergeResultWindow::deleteSelection() { if(!m_selection.selectionContainsData()) { return; } Q_ASSERT(m_selection.isValidFirstLine()); setModified(); LineRef line = 0; MergeLineList::iterator mlItFirst; MergeEditLineList::iterator melItFirst; QString firstLineString; LineRef firstLine; LineRef lastLine; MergeLineList::iterator mlIt; for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; MergeEditLineList::iterator melIt; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt) { MergeEditLine& mel = *melIt; if(mel.isEditableText() && m_selection.lineWithin(line)) { if(!firstLine.isValid()) firstLine = line; lastLine = line; } ++line; } } if(!firstLine.isValid()) { return; // Nothing to delete. } line = 0; for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; MergeEditLineList::iterator melIt, melIt1; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end();) { MergeEditLine& mel = *melIt; melIt1 = melIt; ++melIt1; if(mel.isEditableText() && m_selection.lineWithin(line)) { QString lineString = mel.getString(m_pldA, m_pldB, m_pldC); int firstPosInLine = m_selection.firstPosInLine(line); int lastPosInLine = m_selection.lastPosInLine(line); if(line == firstLine) { mlItFirst = mlIt; melItFirst = melIt; int pos = firstPosInLine; firstLineString = lineString.left(pos); } if(line == lastLine) { // This is the last line in the selection int pos = lastPosInLine; firstLineString += lineString.midRef(pos); // rest of line melItFirst->setString(firstLineString); } if(line != firstLine || (m_selection.endPos() - m_selection.beginPos()) == lineString.length()) { // Remove the line if(mlIt->mergeEditLineList.size() > 1) mlIt->mergeEditLineList.erase(melIt); else melIt->setRemoved(); } } ++line; melIt = melIt1; } } m_cursorYPos = m_selection.beginLine(); m_cursorXPos = m_selection.beginPos(); m_cursorOldXPixelPos = m_cursorXPixelPos; m_selection.reset(); } void MergeResultWindow::pasteClipboard(bool bFromSelection) { //checking of m_selection if needed is done by deleteSelection no need for check here. deleteSelection(); setModified(); int y = m_cursorYPos; MergeLineList::iterator mlIt; MergeEditLineList::iterator melIt, melItAfter; calcIteratorFromLineNr(y, mlIt, melIt); melItAfter = melIt; ++melItAfter; QString str = melIt->getString(m_pldA, m_pldB, m_pldC); int x = m_cursorXPos; if(!QApplication::clipboard()->supportsSelection()) bFromSelection = false; QString clipBoard = QApplication::clipboard()->text(bFromSelection ? QClipboard::Selection : QClipboard::Clipboard); QString currentLine = str.left(x); QString endOfLine = str.mid(x); int i; int len = clipBoard.length(); for(i = 0; i < len; ++i) { QChar c = clipBoard[i]; if(c == '\r') continue; if(c == '\n') { melIt->setString(currentLine); MergeEditLine mel(mlIt->id3l); // Associate every mel with an id3l, even if not really valid. melIt = mlIt->mergeEditLineList.insert(melItAfter, mel); currentLine = ""; x = 0; ++y; } else { currentLine += c; ++x; } } currentLine += endOfLine; melIt->setString(currentLine); m_cursorYPos = y; m_cursorXPos = x; m_cursorOldXPixelPos = m_cursorXPixelPos; update(); } void MergeResultWindow::resetSelection() { m_selection.reset(); update(); } void MergeResultWindow::setModified(bool bModified) { if(bModified != m_bModified) { m_bModified = bModified; Q_EMIT modifiedChanged(m_bModified); } } /// Saves and returns true when successful. bool MergeResultWindow::saveDocument(const QString& fileName, QTextCodec* pEncoding, e_LineEndStyle eLineEndStyle) { // Are still conflicts somewhere? if(getNrOfUnsolvedConflicts() > 0) { KMessageBox::error(this, i18n("Not all conflicts are solved yet.\n" "File not saved."), i18n("Conflicts Left")); return false; } if(eLineEndStyle == eLineEndStyleConflict || eLineEndStyle == eLineEndStyleUndefined) { KMessageBox::error(this, i18n("There is a line end style conflict. Please choose the line end style manually.\n" "File not saved."), i18n("Conflicts Left")); return false; } update(); FileAccess file(fileName, true /*bWantToWrite*/); if(m_pOptions->m_bDmCreateBakFiles && file.exists()) { bool bSuccess = file.createBackup(".orig"); if(!bSuccess) { KMessageBox::error(this, file.getStatusText() + i18n("\n\nCreating backup failed. File not saved."), i18n("File Save Error")); return false; } } QByteArray dataArray; QTextStream textOutStream(&dataArray, QIODevice::WriteOnly); if(pEncoding->name() == "UTF-8") textOutStream.setGenerateByteOrderMark(false); // Shouldn't be necessary. Bug in Qt or docs else textOutStream.setGenerateByteOrderMark(true); // Only for UTF-16 textOutStream.setCodec(pEncoding); int line = 0; MergeLineList::iterator mlIt = m_mergeLineList.begin(); for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; MergeEditLineList::iterator melIt; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt) { MergeEditLine& mel = *melIt; if(mel.isEditableText()) { QString str = mel.getString(m_pldA, m_pldB, m_pldC); if(line > 0) // Prepend line feed, but not for first line { if(eLineEndStyle == eLineEndStyleDos) { str.prepend("\r\n"); } else { str.prepend("\n"); } } textOutStream << str; ++line; } } } textOutStream.flush(); bool bSuccess = file.writeFile(dataArray.data(), dataArray.size()); if(!bSuccess) { KMessageBox::error(this, i18n("Error while writing."), i18n("File Save Error")); return false; } setModified(false); update(); return true; } QString MergeResultWindow::getString(int lineIdx) { MergeLineList::iterator mlIt; MergeEditLineList::iterator melIt; if(m_mergeLineList.empty()) { return QString(); } calcIteratorFromLineNr(lineIdx, mlIt, melIt); QString s = melIt->getString(m_pldA, m_pldB, m_pldC); return s; } bool MergeResultWindow::findString(const QString& s, LineRef& d3vLine, int& posInLine, bool bDirDown, bool bCaseSensitive) { int it = d3vLine; int endIt = bDirDown ? getNofLines() : -1; int step = bDirDown ? 1 : -1; int startPos = posInLine; for(; it != endIt; it += step) { QString line = 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 MergeResultWindow::setSelection(int firstLine, int startPos, int lastLine, int endPos) { if(lastLine >= getNofLines()) { lastLine = getNofLines() - 1; QString s = getString(lastLine); endPos = s.length(); } m_selection.reset(); m_selection.start(firstLine, startPos); m_selection.end(lastLine, endPos); update(); } WindowTitleWidget::WindowTitleWidget(const QSharedPointer &pOptions) { m_pOptions = pOptions; setAutoFillBackground(true); QHBoxLayout* pHLayout = new QHBoxLayout(this); pHLayout->setMargin(2); pHLayout->setSpacing(2); m_pLabel = new QLabel(i18n("Output:")); pHLayout->addWidget(m_pLabel); m_pFileNameLineEdit = new FileNameLineEdit(); pHLayout->addWidget(m_pFileNameLineEdit, 6); m_pFileNameLineEdit->installEventFilter(this);//for focus tracking m_pFileNameLineEdit->setAcceptDrops(true); m_pFileNameLineEdit->setReadOnly(true); //m_pBrowseButton = new QPushButton("..."); //pHLayout->addWidget( m_pBrowseButton, 0 ); //connect( m_pBrowseButton, &QPushButton::clicked), this, &MergeResultWindow::slotBrowseButtonClicked); m_pModifiedLabel = new QLabel(i18n("[Modified]")); pHLayout->addWidget(m_pModifiedLabel); m_pModifiedLabel->setMinimumSize(m_pModifiedLabel->sizeHint()); m_pModifiedLabel->setText(""); pHLayout->addStretch(1); m_pEncodingLabel = new QLabel(i18n("Encoding for saving:")); pHLayout->addWidget(m_pEncodingLabel); m_pEncodingSelector = new QComboBox(); m_pEncodingSelector->setSizeAdjustPolicy(QComboBox::AdjustToContents); pHLayout->addWidget(m_pEncodingSelector, 2); setEncodings(nullptr, nullptr, nullptr); m_pLineEndStyleLabel = new QLabel(i18n("Line end style:")); pHLayout->addWidget(m_pLineEndStyleLabel); m_pLineEndStyleSelector = new QComboBox(); m_pLineEndStyleSelector->setSizeAdjustPolicy(QComboBox::AdjustToContents); pHLayout->addWidget(m_pLineEndStyleSelector); setLineEndStyles(eLineEndStyleUndefined, eLineEndStyleUndefined, eLineEndStyleUndefined); } void WindowTitleWidget::setFileName(const QString& fileName) { m_pFileNameLineEdit->setText(QDir::toNativeSeparators(fileName)); } QString WindowTitleWidget::getFileName() { return m_pFileNameLineEdit->text(); } //static QString getLineEndStyleName( e_LineEndStyle eLineEndStyle ) //{ // if ( eLineEndStyle == eLineEndStyleDos ) // return "DOS"; // else if ( eLineEndStyle == eLineEndStyleUnix ) // return "Unix"; // return QString(); //} void WindowTitleWidget::setLineEndStyles(e_LineEndStyle eLineEndStyleA, e_LineEndStyle eLineEndStyleB, e_LineEndStyle eLineEndStyleC) { m_pLineEndStyleSelector->clear(); QString dosUsers; if(eLineEndStyleA == eLineEndStyleDos) dosUsers += i18n("A"); if(eLineEndStyleB == eLineEndStyleDos) dosUsers += QLatin1String(dosUsers.isEmpty() ? "" : ", ") + i18n("B"); if(eLineEndStyleC == eLineEndStyleDos) dosUsers += QLatin1String(dosUsers.isEmpty() ? "" : ", ") + i18n("C"); QString unxUsers; if(eLineEndStyleA == eLineEndStyleUnix) unxUsers += i18n("A"); if(eLineEndStyleB == eLineEndStyleUnix) unxUsers += QLatin1String(unxUsers.isEmpty() ? "" : ", ") + i18n("B"); if(eLineEndStyleC == eLineEndStyleUnix) unxUsers += QLatin1String(unxUsers.isEmpty() ? "" : ", ") + i18n("C"); m_pLineEndStyleSelector->addItem(i18n("Unix") + (unxUsers.isEmpty() ? QString("") : QLatin1String(" (") + unxUsers + QLatin1String(")"))); m_pLineEndStyleSelector->addItem(i18n("DOS") + (dosUsers.isEmpty() ? QString("") : QLatin1String(" (") + dosUsers + QLatin1String(")"))); e_LineEndStyle autoChoice = (e_LineEndStyle)m_pOptions->m_lineEndStyle; if(m_pOptions->m_lineEndStyle == eLineEndStyleAutoDetect) { if(eLineEndStyleA != eLineEndStyleUndefined && eLineEndStyleB != eLineEndStyleUndefined && eLineEndStyleC != eLineEndStyleUndefined) { if(eLineEndStyleA == eLineEndStyleB) autoChoice = eLineEndStyleC; else if(eLineEndStyleA == eLineEndStyleC) autoChoice = eLineEndStyleB; else autoChoice = eLineEndStyleConflict; //conflict (not likely while only two values exist) } else { e_LineEndStyle c1, c2; if(eLineEndStyleA == eLineEndStyleUndefined) { c1 = eLineEndStyleB; c2 = eLineEndStyleC; } else if(eLineEndStyleB == eLineEndStyleUndefined) { c1 = eLineEndStyleA; c2 = eLineEndStyleC; } else /*if( eLineEndStyleC == eLineEndStyleUndefined )*/ { c1 = eLineEndStyleA; c2 = eLineEndStyleB; } if(c1 == c2 && c1 != eLineEndStyleUndefined) autoChoice = c1; else autoChoice = eLineEndStyleConflict; } } if(autoChoice == eLineEndStyleUnix) m_pLineEndStyleSelector->setCurrentIndex(0); else if(autoChoice == eLineEndStyleDos) m_pLineEndStyleSelector->setCurrentIndex(1); else if(autoChoice == eLineEndStyleConflict) { m_pLineEndStyleSelector->addItem(i18n("Conflict")); m_pLineEndStyleSelector->setCurrentIndex(2); } } e_LineEndStyle WindowTitleWidget::getLineEndStyle() { int current = m_pLineEndStyleSelector->currentIndex(); if(current == 0) return eLineEndStyleUnix; else if(current == 1) return eLineEndStyleDos; else return eLineEndStyleConflict; } void WindowTitleWidget::setEncodings(QTextCodec* pCodecForA, QTextCodec* pCodecForB, QTextCodec* pCodecForC) { m_pEncodingSelector->clear(); // First sort codec names: std::map names; QList mibs = QTextCodec::availableMibs(); for(int i: mibs) { QTextCodec* c = QTextCodec::codecForMib(i); if(c != nullptr) names[QLatin1String(c->name())] = c; } if(pCodecForA != nullptr) m_pEncodingSelector->addItem(i18n("Codec from A: %1", QLatin1String(pCodecForA->name())), QVariant::fromValue((void*)pCodecForA)); if(pCodecForB != nullptr) m_pEncodingSelector->addItem(i18n("Codec from B: %1", QLatin1String(pCodecForB->name())), QVariant::fromValue((void*)pCodecForB)); if(pCodecForC != nullptr) m_pEncodingSelector->addItem(i18n("Codec from C: %1", QLatin1String(pCodecForC->name())), QVariant::fromValue((void*)pCodecForC)); std::map::iterator it; for(it = names.begin(); it != names.end(); ++it) { m_pEncodingSelector->addItem(it->first, QVariant::fromValue((void*)it->second)); } m_pEncodingSelector->setMinimumSize(m_pEncodingSelector->sizeHint()); if(pCodecForC != nullptr && pCodecForB != nullptr && pCodecForA != nullptr) { if(pCodecForA == pCodecForC) m_pEncodingSelector->setCurrentIndex(1); // B else m_pEncodingSelector->setCurrentIndex(2); // C } else if(pCodecForA != nullptr && pCodecForB != nullptr) m_pEncodingSelector->setCurrentIndex(1); // B else m_pEncodingSelector->setCurrentIndex(0); } QTextCodec* WindowTitleWidget::getEncoding() { return (QTextCodec*)m_pEncodingSelector->itemData(m_pEncodingSelector->currentIndex()).value(); } void WindowTitleWidget::setEncoding(QTextCodec* pEncoding) { int idx = m_pEncodingSelector->findText(QLatin1String(pEncoding->name())); if(idx >= 0) m_pEncodingSelector->setCurrentIndex(idx); } //void WindowTitleWidget::slotBrowseButtonClicked() //{ // QString current = m_pFileNameLineEdit->text(); // // QUrl newURL = KFileDialog::getSaveUrl( current, 0, this, i18n("Select file (not saving yet)")); // if ( !newURL.isEmpty() ) // { // m_pFileNameLineEdit->setText( newURL.url() ); // } //} void WindowTitleWidget::slotSetModified(bool bModified) { m_pModifiedLabel->setText(bModified ? i18n("[Modified]") : ""); } bool WindowTitleWidget::eventFilter(QObject* o, QEvent* e) { Q_UNUSED(o); if(e->type() == QEvent::FocusIn || e->type() == QEvent::FocusOut) { QPalette p = m_pLabel->palette(); QColor c1 = m_pOptions->m_fgColor; QColor c2 = Qt::lightGray; if(e->type() == QEvent::FocusOut) c2 = m_pOptions->m_bgColor; p.setColor(QPalette::Window, c2); setPalette(p); p.setColor(QPalette::WindowText, c1); m_pLabel->setPalette(p); m_pEncodingLabel->setPalette(p); m_pEncodingSelector->setPalette(p); } return false; } //#include "mergeresultwindow.moc" diff --git a/src/mergeresultwindow.h b/src/mergeresultwindow.h index da8cae1..611cef5 100644 --- a/src/mergeresultwindow.h +++ b/src/mergeresultwindow.h @@ -1,308 +1,310 @@ /* * KDiff3 - Text Diff And Merge Tool * * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef MERGERESULTWINDOW_H #define MERGERESULTWINDOW_H #include "diff.h" #include "FileNameLineEdit.h" #include "MergeEditLine.h" #include "Overview.h" #include "selection.h" #include #include #include #include #include #include class QPainter; class RLPainter; class QScrollBar; class KActionCollection; class KToggleAction; class KDiff3App; class MergeResultWindow : public QWidget { Q_OBJECT public: static QScrollBar* mVScrollBar; MergeResultWindow(QWidget* pParent, const QSharedPointer& pOptions, QStatusBar* pStatusBar); void init( const QVector* pLineDataA, LineRef sizeA, const QVector* pLineDataB, LineRef sizeB, const QVector* pLineDataC, LineRef sizeC, const Diff3LineList* pDiff3LineList, TotalDiffStatus* pTotalDiffStatus ); void setupConnections(const KDiff3App* app) const; inline void clearMergeList() { m_mergeLineList.clear(); } static void initActions(KActionCollection* ac); void connectActions() const; void reset(); bool saveDocument(const QString& fileName, QTextCodec* pEncoding, e_LineEndStyle eLineEndStyle); int getNrOfUnsolvedConflicts(int* pNrOfWhiteSpaceConflicts = nullptr); void choose(e_SrcSelector selector); void chooseGlobal(e_SrcSelector selector, bool bConflictsOnly, bool bWhiteSpaceOnly); int getMaxTextWidth(); // width of longest text line int getNofLines() const; 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, LineRef& d3vLine, int& posInLine, bool bDirDown, bool bCaseSensitive); void setSelection(int firstLine, int startPos, int lastLine, int endPos); e_OverviewMode getOverviewMode(); void slotUpdateAvailabilities(const bool bMergeEditorVisible, const bool m_bTripleDiff); public Q_SLOTS: void setOverviewMode(e_OverviewMode eOverviewMode); void setFirstLine(QtNumberType 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(LineIndex firstD3lLineIdx, LineIndex lastD3lLineIdx); void slotJoinDiffs(LineIndex firstD3lLineIdx, LineIndex lastD3lLineIdx); void slotSetFastSelectorLine(LineIndex); void setPaintingAllowed(bool); void updateSourceMask(); void slotStatusMessageChanged(const QString&); void slotChooseAEverywhere() { chooseGlobal(e_SrcSelector::A, false, false); } void slotChooseBEverywhere() { chooseGlobal(e_SrcSelector::B, false, false); } void slotChooseCEverywhere() { chooseGlobal(e_SrcSelector::C, false, false); } void slotChooseAForUnsolvedConflicts() { chooseGlobal(e_SrcSelector::A, true, false); } void slotChooseBForUnsolvedConflicts() { chooseGlobal(e_SrcSelector::B, true, false); } void slotChooseCForUnsolvedConflicts() { chooseGlobal(e_SrcSelector::C, true, false); } void slotChooseAForUnsolvedWhiteSpaceConflicts() { chooseGlobal(e_SrcSelector::A, true, true); } void slotChooseBForUnsolvedWhiteSpaceConflicts() { chooseGlobal(e_SrcSelector::B, true, true); } void slotChooseCForUnsolvedWhiteSpaceConflicts() { chooseGlobal(e_SrcSelector::C, true, true); } void slotRefresh(); void slotResize(); void slotCut(); + void slotSelectAll(); + Q_SIGNALS: void statusBarMessage(const QString& message); void scrollVertically(QtNumberType deltaY); void scrollMergeResultWindow(int deltaX, int deltaY); void modifiedChanged(bool bModified); void setFastSelectorRange(LineRef line1, LineCount 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, e_SrcSelector defaultSelector, bool bConflictsOnly = false, bool bWhiteSpaceOnly = false); QString getString(int lineIdx); void showUnsolvedConflictsStatusMessage(); static QPointer chooseAEverywhere; static QPointer chooseBEverywhere; static QPointer chooseCEverywhere; static QPointer chooseAForUnsolvedConflicts; static QPointer chooseBForUnsolvedConflicts; static QPointer chooseCForUnsolvedConflicts; static QPointer chooseAForUnsolvedWhiteSpaceConflicts; static QPointer chooseBForUnsolvedWhiteSpaceConflicts; static QPointer chooseCForUnsolvedWhiteSpaceConflicts; QSharedPointer m_pOptions = nullptr; const QVector* m_pldA = nullptr; const QVector* m_pldB = nullptr; const QVector* m_pldC = nullptr; LineRef m_sizeA = 0; LineRef m_sizeB = 0; LineRef m_sizeC = 0; const Diff3LineList* m_pDiff3LineList = nullptr; TotalDiffStatus* m_pTotalDiffStatus = nullptr; int m_delayedDrawTimer = 0; e_OverviewMode mOverviewMode; QString m_persistentStatusMessage; 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(e_SrcSelector src, Diff3LineList::const_iterator& iHistoryBegin, Diff3LineList::const_iterator& iHistoryEnd, HistoryMap& historyMap, std::list& hitList); 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); void paintEvent(QPaintEvent* e) override; int getTextXOffset(); QVector getTextLayoutForLine(int line, const QString& s, QTextLayout& textLayout); void myUpdate(int afterMilliSecs); void timerEvent(QTimerEvent*) override; void writeLine( RLPainter& p, int line, const QString& str, enum e_SrcSelector srcSelect, e_MergeDetails mergeDetails, int rangeMark, bool bUserModified, bool bLineRemoved, bool bWhiteSpaceConflict ); void setFastSelector(MergeLineList::iterator i); LineRef convertToLine(QtNumberType y); 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; LineRef m_firstLine = 0; int m_horizScrollOffset = 0; LineCount m_nofLines = 0; int m_maxTextWidth = -1; bool m_bMyUpdate = false; bool m_bInsertMode = true; bool m_bModified = false; void setModified(bool bModified = true); int m_scrollDeltaX = 0; int m_scrollDeltaY = 0; int m_cursorXPos = 0; int m_cursorXPixelPos; int m_cursorYPos = 0; int m_cursorOldXPixelPos = 0; bool m_bCursorOn = true; // blinking on and off each second QTimer m_cursorTimer; bool m_bCursorUpdate = false; 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; FileNameLineEdit* m_pFileNameLineEdit; //QPushButton* m_pBrowseButton; QLabel* m_pModifiedLabel; QLabel* m_pLineEndStyleLabel; QComboBox* m_pLineEndStyleSelector; QLabel* m_pEncodingLabel; QComboBox* m_pEncodingSelector; QSharedPointer m_pOptions; public: explicit WindowTitleWidget(const QSharedPointer& pOptions); QTextCodec* getEncoding(); void setFileName(const QString& fileName); QString getFileName(); void setEncodings(QTextCodec* pCodecForA, QTextCodec* pCodecForB, QTextCodec* pCodecForC); void setEncoding(QTextCodec* pEncoding); void setLineEndStyles(e_LineEndStyle eLineEndStyleA, e_LineEndStyle eLineEndStyleB, e_LineEndStyle eLineEndStyleC); e_LineEndStyle getLineEndStyle(); bool eventFilter(QObject* o, QEvent* e) override; public Q_SLOTS: void slotSetModified(bool bModified); //private Q_SLOTS: // void slotBrowseButtonClicked(); }; #endif diff --git a/src/pdiff.cpp b/src/pdiff.cpp index 42bc140..9a09762 100644 --- a/src/pdiff.cpp +++ b/src/pdiff.cpp @@ -1,2206 +1,2190 @@ /* * This file is part of KDiff3. * * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com * SPDX-License-Identifier: GPL-2.0-or-later */ #include "difftextwindow.h" #include "directorymergewindow.h" #include "fileaccess.h" #include "Logging.h" #include "kdiff3.h" #include "optiondialog.h" #include "progress.h" #include "Utils.h" #include "DirectoryInfo.h" #include "mergeresultwindow.h" #include "smalldialogs.h" #include #include #include #include #include #include #include #include #include // QKeyEvent, QDropEvent, QInputEvent #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Function uses setMinSize( sizeHint ) before adding the widget. // void addWidget(QBoxLayout* layout, QWidget* widget); template void addWidget(L* layout, W* widget) { QSize s = widget->sizeHint(); widget->setMinimumSize(QSize(std::max(s.width(), 0), std::max(s.height(), 0))); layout->addWidget(widget); } void KDiff3App::mainInit(TotalDiffStatus* pTotalDiffStatus, bool bLoadFiles, bool bUseCurrentEncoding) { ProgressProxy pp; QStringList errors; // When doing a full analysis in the directory-comparison, then the statistics-results // will be stored in the given TotalDiffStatus. Otherwise it will be 0. bool bGUI = pTotalDiffStatus == nullptr; if(pTotalDiffStatus == nullptr) pTotalDiffStatus = &m_totalDiffStatus; //bool bPreserveCarriageReturn = m_pOptions->m_bPreserveCarriageReturn; bool bVisibleMergeResultWindow = !m_outputFilename.isEmpty(); if(bVisibleMergeResultWindow && bGUI) { //bPreserveCarriageReturn = false; QString msg; if(!m_pOptions->m_PreProcessorCmd.isEmpty()) { msg += "- " + i18n("PreprocessorCmd: ") + m_pOptions->m_PreProcessorCmd + '\n'; } if(!msg.isEmpty()) { int result = KMessageBox::warningYesNo(this, i18n("The following option(s) you selected might change data:\n") + msg + i18n("\nMost likely this is not wanted during a merge.\n" "Do you want to disable these settings or continue with these settings active?"), i18n("Option Unsafe for Merging"), KGuiItem(i18n("Use These Options During Merge")), KGuiItem(i18n("Disable Unsafe Options"))); if(result == KMessageBox::No) { m_pOptions->m_PreProcessorCmd = ""; } } } // Because of the progressdialog paintevents can occur, but data is invalid, // so painting must be suppressed if(bGUI) setLockPainting(true); //insure merge result window never has stale iterators. if(m_pMergeResultWindow) m_pMergeResultWindow->clearMergeList(); m_diff3LineList.clear(); m_diff3LineVector.clear(); if(bLoadFiles) { m_manualDiffHelpList.clear(); if(m_sd3->isEmpty()) pp.setMaxNofSteps(4); // Read 2 files, 1 comparison, 1 finediff else pp.setMaxNofSteps(9); // Read 3 files, 3 comparisons, 3 finediffs // First get all input data. pp.setInformation(i18n("Loading A")); qCInfo(kdiffMain) << i18n("Loading A: %1", m_sd1->getFilename()); if(bUseCurrentEncoding) errors = m_sd1->readAndPreprocess(m_sd1->getEncoding(), false); else errors = m_sd1->readAndPreprocess(m_pOptions->m_pEncodingA, m_pOptions->m_bAutoDetectUnicodeA); if(!errors.isEmpty()) { KMessageBox::errorList(m_pOptionDialog, i18n("Errors occurred during pre-processing of file A."), errors); } pp.step(); pp.setInformation(i18n("Loading B")); qCInfo(kdiffMain) << i18n("Loading B: %1", m_sd2->getFilename()); if(bUseCurrentEncoding) errors = m_sd2->readAndPreprocess(m_sd2->getEncoding(), false); else errors = m_sd2->readAndPreprocess(m_pOptions->m_pEncodingB, m_pOptions->m_bAutoDetectUnicodeB); if(!errors.isEmpty()) KMessageBox::errorList(m_pOptionDialog, i18n("Errors occurred during pre-processing of file B."), errors); pp.step(); } else { if(m_sd3->isEmpty()) pp.setMaxNofSteps(2); // 1 comparison, 1 finediff else pp.setMaxNofSteps(6); // 3 comparisons, 3 finediffs } if(pTotalDiffStatus) pTotalDiffStatus->reset(); if(errors.isEmpty()) { // Run the diff. if(m_sd3->isEmpty()) { pTotalDiffStatus->setBinaryEqualAB(m_sd1->isBinaryEqualWith(m_sd2)); if(m_sd1->isText() && m_sd2->isText()) { pp.setInformation(i18n("Diff: A <-> B")); qCInfo(kdiffMain) << i18n("Diff: A <-> B") ; m_manualDiffHelpList.runDiff(m_sd1->getLineDataForDiff(), m_sd1->getSizeLines(), m_sd2->getLineDataForDiff(), m_sd2->getSizeLines(), m_diffList12, e_SrcSelector::A, e_SrcSelector::B, m_pOptionDialog->getOptions()); pp.step(); pp.setInformation(i18n("Linediff: A <-> B")); qCInfo(kdiffMain) << i18n("Linediff: A <-> B"); m_diff3LineList.calcDiff3LineListUsingAB(&m_diffList12); pTotalDiffStatus->setTextEqualAB(m_diff3LineList.fineDiff(e_SrcSelector::A, m_sd1->getLineDataForDisplay(), m_sd2->getLineDataForDisplay())); if(m_sd1->getSizeBytes() == 0) pTotalDiffStatus->setTextEqualAB(false); pp.step(); } else { pp.step(); pp.step(); } } else { if(bLoadFiles) { pp.setInformation(i18n("Loading C")); qCInfo(kdiffMain) << i18n("Loading C: %1", m_sd2->getFilename()); if(bUseCurrentEncoding) errors = m_sd3->readAndPreprocess(m_sd3->getEncoding(), false); else errors = m_sd3->readAndPreprocess(m_pOptions->m_pEncodingC, m_pOptions->m_bAutoDetectUnicodeC); if(!errors.isEmpty()) KMessageBox::errorList(m_pOptionDialog, i18n("Errors occurred during pre-processing of file C."), errors); pp.step(); } pTotalDiffStatus->setBinaryEqualAB(m_sd1->isBinaryEqualWith(m_sd2)); pTotalDiffStatus->setBinaryEqualAC(m_sd1->isBinaryEqualWith(m_sd3)); pTotalDiffStatus->setBinaryEqualBC(m_sd3->isBinaryEqualWith(m_sd2)); pp.setInformation(i18n("Diff: A <-> B")); qCInfo(kdiffMain) << i18n("Diff: A <-> B"); if(m_sd1->isText() && m_sd2->isText()) { m_manualDiffHelpList.runDiff(m_sd1->getLineDataForDiff(), m_sd1->getSizeLines(), m_sd2->getLineDataForDiff(), m_sd2->getSizeLines(), m_diffList12, e_SrcSelector::A, e_SrcSelector::B, m_pOptionDialog->getOptions()); m_diff3LineList.calcDiff3LineListUsingAB(&m_diffList12); } pp.step(); pp.setInformation(i18n("Diff: A <-> C")); qCInfo(kdiffMain) << i18n("Diff: A <-> C"); if(m_sd1->isText() && m_sd3->isText()) { m_manualDiffHelpList.runDiff(m_sd1->getLineDataForDiff(), m_sd1->getSizeLines(), m_sd3->getLineDataForDiff(), m_sd3->getSizeLines(), m_diffList13, e_SrcSelector::A, e_SrcSelector::C, m_pOptionDialog->getOptions()); m_diff3LineList.calcDiff3LineListUsingAC(&m_diffList13); m_diff3LineList.correctManualDiffAlignment(&m_manualDiffHelpList); m_diff3LineList.calcDiff3LineListTrim(m_sd1->getLineDataForDiff(), m_sd2->getLineDataForDiff(), m_sd3->getLineDataForDiff(), &m_manualDiffHelpList); } pp.step(); pp.setInformation(i18n("Diff: B <-> C")); qCInfo(kdiffMain) << i18n("Diff: B <-> C"); if(m_sd2->isText() && m_sd3->isText()) { m_manualDiffHelpList.runDiff(m_sd2->getLineDataForDiff(), m_sd2->getSizeLines(), m_sd3->getLineDataForDiff(), m_sd3->getSizeLines(), m_diffList23, e_SrcSelector::B, e_SrcSelector::C, m_pOptionDialog->getOptions()); if(m_pOptions->m_bDiff3AlignBC) { m_diff3LineList.calcDiff3LineListUsingBC(&m_diffList23); m_diff3LineList.correctManualDiffAlignment(&m_manualDiffHelpList); m_diff3LineList.calcDiff3LineListTrim(m_sd1->getLineDataForDiff(), m_sd2->getLineDataForDiff(), m_sd3->getLineDataForDiff(), &m_manualDiffHelpList); } } pp.step(); m_diff3LineList.debugLineCheck(m_sd1->getSizeLines(), e_SrcSelector::A); m_diff3LineList.debugLineCheck(m_sd2->getSizeLines(), e_SrcSelector::B); m_diff3LineList.debugLineCheck(m_sd3->getSizeLines(), e_SrcSelector::C); pp.setInformation(i18n("Linediff: A <-> B")); qCInfo(kdiffMain) << i18n("Linediff: A <-> B"); if(m_sd1->hasData() && m_sd2->hasData() && m_sd1->isText() && m_sd2->isText()) pTotalDiffStatus->setTextEqualAB(m_diff3LineList.fineDiff(e_SrcSelector::A, m_sd1->getLineDataForDisplay(), m_sd2->getLineDataForDisplay())); pp.step(); pp.setInformation(i18n("Linediff: B <-> C")); qCInfo(kdiffMain) << i18n("Linediff: B <-> C"); if(m_sd2->hasData() && m_sd3->hasData() && m_sd2->isText() && m_sd3->isText()) pTotalDiffStatus->setTextEqualBC(m_diff3LineList.fineDiff(e_SrcSelector::B, m_sd2->getLineDataForDisplay(), m_sd3->getLineDataForDisplay())); pp.step(); pp.setInformation(i18n("Linediff: A <-> C")); qCInfo(kdiffMain) << i18n("Linediff: A <-> C"); if(m_sd1->hasData() && m_sd3->hasData() && m_sd1->isText() && m_sd3->isText()) pTotalDiffStatus->setTextEqualAC(m_diff3LineList.fineDiff(e_SrcSelector::C, m_sd3->getLineDataForDisplay(), m_sd1->getLineDataForDisplay())); m_diff3LineList.debugLineCheck(m_sd2->getSizeLines(), e_SrcSelector::B); m_diff3LineList.debugLineCheck(m_sd3->getSizeLines(), e_SrcSelector::C); pp.setInformation(i18n("Linediff: A <-> B")); if(m_sd1->hasData() && m_sd2->hasData() && m_sd1->isText() && m_sd2->isText()) pTotalDiffStatus->setTextEqualAB(m_diff3LineList.fineDiff(e_SrcSelector::A, m_sd1->getLineDataForDisplay(), m_sd2->getLineDataForDisplay())); pp.step(); pp.setInformation(i18n("Linediff: B <-> C")); if(m_sd3->hasData() && m_sd2->hasData() && m_sd3->isText() && m_sd2->isText()) pTotalDiffStatus->setTextEqualBC(m_diff3LineList.fineDiff(e_SrcSelector::B, m_sd2->getLineDataForDisplay(), m_sd3->getLineDataForDisplay())); pp.step(); pp.setInformation(i18n("Linediff: A <-> C")); if(m_sd1->hasData() && m_sd3->hasData() && m_sd1->isText() && m_sd3->isText()) pTotalDiffStatus->setTextEqualAC(m_diff3LineList.fineDiff(e_SrcSelector::C, m_sd3->getLineDataForDisplay(), m_sd1->getLineDataForDisplay())); pp.step(); if(m_sd1->getSizeBytes() == 0) { pTotalDiffStatus->setTextEqualAB(false); pTotalDiffStatus->setTextEqualAC(false); } if(m_sd2->getSizeBytes() == 0) { pTotalDiffStatus->setTextEqualAB(false); pTotalDiffStatus->setTextEqualBC(false); } } } else { pp.clear(); } if(errors.isEmpty() && m_sd1->isText() && m_sd2->isText()) { m_diffBufferInfo->init(&m_diff3LineList, &m_diff3LineVector, m_sd1->getLineDataForDiff(), m_sd1->getSizeLines(), m_sd2->getLineDataForDiff(), m_sd2->getSizeLines(), m_sd3->getLineDataForDiff(), m_sd3->getSizeLines()); Diff3Line::m_pDiffBufferInfo = m_diffBufferInfo; m_diff3LineList.calcWhiteDiff3Lines(m_sd1->getLineDataForDiff(), m_sd2->getLineDataForDiff(), m_sd3->getLineDataForDiff()); m_diff3LineList.calcDiff3LineVector(m_diff3LineVector); } // Calc needed lines for display m_neededLines = m_diff3LineList.size(); QList oldHeights; if(m_pDirectoryMergeSplitter->isVisible()) oldHeights = m_pMainSplitter->sizes(); initView(); m_pMergeResultWindow->connectActions(); if(m_pDirectoryMergeSplitter->isVisible()) { if(oldHeights.count() < 2) oldHeights.append(0); if(oldHeights[1] == 0) // Distribute the available space evenly between the two widgets. { oldHeights[1] = oldHeights[0] / 2; oldHeights[0] -= oldHeights[1]; } if(oldHeights[0] == 0 && oldHeights[1] == 0) { oldHeights[1] = 100; oldHeights[0] = 100; } m_pMainSplitter->setSizes(oldHeights); } m_pMainWidget->setVisible(bGUI); m_bTripleDiff = !m_sd3->isEmpty(); m_pMergeResultWindowTitle->setEncodings(m_sd1->getEncoding(), m_sd2->getEncoding(), m_sd3->getEncoding()); if(!m_pOptions->m_bAutoSelectOutEncoding) m_pMergeResultWindowTitle->setEncoding(m_pOptions->m_pEncodingOut); m_pMergeResultWindowTitle->setLineEndStyles(m_sd1->getLineEndStyle(), m_sd2->getLineEndStyle(), m_sd3->getLineEndStyle()); if(bGUI) { const ManualDiffHelpList* pMDHL = &m_manualDiffHelpList; m_pDiffTextWindow1->init(m_sd1->getAliasName(), m_sd1->getEncoding(), m_sd1->getLineEndStyle(), m_sd1->getLineDataForDisplay(), m_sd1->getSizeLines(), &m_diff3LineVector, pMDHL, m_bTripleDiff); m_pDiffTextWindowFrame1->init(); m_pDiffTextWindow2->init(m_sd2->getAliasName(), m_sd2->getEncoding(), m_sd2->getLineEndStyle(), m_sd2->getLineDataForDisplay(), m_sd2->getSizeLines(), &m_diff3LineVector, pMDHL, m_bTripleDiff); m_pDiffTextWindowFrame2->init(); m_pDiffTextWindow3->init(m_sd3->getAliasName(), m_sd3->getEncoding(), m_sd3->getLineEndStyle(), m_sd3->getLineDataForDisplay(), m_sd3->getSizeLines(), &m_diff3LineVector, pMDHL, m_bTripleDiff); m_pDiffTextWindowFrame3->init(); m_pDiffTextWindowFrame3->setVisible(m_bTripleDiff); } m_bOutputModified = bVisibleMergeResultWindow; m_pMergeResultWindow->init( m_sd1->getLineDataForDisplay(), m_sd1->getSizeLines(), m_sd2->getLineDataForDisplay(), m_sd2->getSizeLines(), m_bTripleDiff ? m_sd3->getLineDataForDisplay() : nullptr, m_sd3->getSizeLines(), &m_diff3LineList, pTotalDiffStatus); m_pMergeResultWindowTitle->setFileName(m_outputFilename.isEmpty() ? QString("unnamed.txt") : m_outputFilename); if(!bGUI) { // We now have all needed information. The rest below is only for GUI-activation. m_sd1->reset(); m_sd2->reset(); m_sd3->reset(); } else { m_pOverview->init(&m_diff3LineList, m_bTripleDiff); DiffTextWindow::mVScrollBar->setValue(0); m_pHScrollBar->setValue(0); MergeResultWindow::mVScrollBar->setValue(0); setLockPainting(false); if(!bVisibleMergeResultWindow) m_pMergeWindowFrame->hide(); else m_pMergeWindowFrame->show(); // Try to create a meaningful but not too long caption if(!isPart() && errors.isEmpty()) { createCaption(); } m_bFinishMainInit = true; // call slotFinishMainInit after finishing the word wrap m_bLoadFiles = bLoadFiles; postRecalcWordWrap(); } } void KDiff3App::setLockPainting(bool bLock) { if(m_pDiffTextWindow1) m_pDiffTextWindow1->setPaintingAllowed(!bLock); if(m_pDiffTextWindow2) m_pDiffTextWindow2->setPaintingAllowed(!bLock); if(m_pDiffTextWindow3) m_pDiffTextWindow3->setPaintingAllowed(!bLock); if(m_pOverview) m_pOverview->setPaintingAllowed(!bLock); if(m_pMergeResultWindow) m_pMergeResultWindow->setPaintingAllowed(!bLock); } void KDiff3App::createCaption() { // Try to create a meaningful but not too long caption // 1. If the filenames are equal then show only one filename QString caption; QString f1 = m_sd1->getAliasName(); QString f2 = m_sd2->getAliasName(); QString f3 = m_sd3->getAliasName(); int p; if((p = f1.lastIndexOf('/')) >= 0 || (p = f1.lastIndexOf('\\')) >= 0) f1 = f1.mid(p + 1); if((p = f2.lastIndexOf('/')) >= 0 || (p = f2.lastIndexOf('\\')) >= 0) f2 = f2.mid(p + 1); if((p = f3.lastIndexOf('/')) >= 0 || (p = f3.lastIndexOf('\\')) >= 0) f3 = f3.mid(p + 1); if(!f1.isEmpty()) { if((f2.isEmpty() && f3.isEmpty()) || (f2.isEmpty() && f1 == f3) || (f3.isEmpty() && f1 == f2) || (f1 == f2 && f1 == f3)) caption = f1; } else if(!f2.isEmpty()) { if(f3.isEmpty() || f2 == f3) caption = f2; } else if(!f3.isEmpty()) caption = f3; // 2. If the files don't have the same name then show all names if(caption.isEmpty() && (!f1.isEmpty() || !f2.isEmpty() || !f3.isEmpty())) { caption = (f1.isEmpty() ? QString("") : f1); caption += QLatin1String(caption.isEmpty() || f2.isEmpty() ? "" : " <-> ") + (f2.isEmpty() ? QString("") : f2); caption += QLatin1String(caption.isEmpty() || f3.isEmpty() ? "" : " <-> ") + (f3.isEmpty() ? QString("") : f3); } m_pKDiff3Shell->setWindowTitle(caption.isEmpty() ? QString("KDiff3") : caption + QString(" - KDiff3")); } void KDiff3App::setHScrollBarRange() { int w1 = m_pDiffTextWindow1 != nullptr && m_pDiffTextWindow1->isVisible() ? m_pDiffTextWindow1->getMaxTextWidth() : 0; int w2 = m_pDiffTextWindow2 != nullptr && m_pDiffTextWindow2->isVisible() ? m_pDiffTextWindow2->getMaxTextWidth() : 0; int w3 = m_pDiffTextWindow3 != nullptr && m_pDiffTextWindow3->isVisible() ? m_pDiffTextWindow3->getMaxTextWidth() : 0; int wm = m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isVisible() ? m_pMergeResultWindow->getMaxTextWidth() : 0; int v1 = m_pDiffTextWindow1 != nullptr && m_pDiffTextWindow1->isVisible() ? m_pDiffTextWindow1->getVisibleTextAreaWidth() : 0; int v2 = m_pDiffTextWindow2 != nullptr && m_pDiffTextWindow2->isVisible() ? m_pDiffTextWindow2->getVisibleTextAreaWidth() : 0; int v3 = m_pDiffTextWindow3 != nullptr && m_pDiffTextWindow3->isVisible() ? m_pDiffTextWindow3->getVisibleTextAreaWidth() : 0; int vm = m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isVisible() ? m_pMergeResultWindow->getVisibleTextAreaWidth() : 0; // Find the minimum, but don't consider 0. int pageStep = 0; if((pageStep == 0 || pageStep > v1) && v1 > 0) pageStep = v1; if((pageStep == 0 || pageStep > v2) && v2 > 0) pageStep = v2; if((pageStep == 0 || pageStep > v3) && v3 > 0) pageStep = v3; if((pageStep == 0 || pageStep > vm) && vm > 0) pageStep = vm; int rangeMax = 0; if(w1 > v1 && w1 - v1 > rangeMax && v1 > 0) rangeMax = w1 - v1; if(w2 > v2 && w2 - v2 > rangeMax && v2 > 0) rangeMax = w2 - v2; if(w3 > v3 && w3 - v3 > rangeMax && v3 > 0) rangeMax = w3 - v3; if(wm > vm && wm - vm > rangeMax && vm > 0) rangeMax = wm - vm; m_pHScrollBar->setRange(0, rangeMax); m_pHScrollBar->setPageStep(pageStep); } void KDiff3App::resizeDiffTextWindowHeight(int newHeight) { m_DTWHeight = newHeight; DiffTextWindow::mVScrollBar->setRange(0, std::max(0, m_neededLines + 1 - newHeight)); DiffTextWindow::mVScrollBar->setPageStep(newHeight); m_pOverview->setRange(DiffTextWindow::mVScrollBar->value(), DiffTextWindow::mVScrollBar->pageStep()); setHScrollBarRange(); } void KDiff3App::scrollDiffTextWindow(int deltaX, int deltaY) { if(deltaY != 0 && DiffTextWindow::mVScrollBar != nullptr) { DiffTextWindow::mVScrollBar->setValue(DiffTextWindow::mVScrollBar->value() + deltaY); } if(deltaX != 0 && m_pHScrollBar != nullptr) m_pHScrollBar->QScrollBar::setValue(m_pHScrollBar->value() + deltaX); } void KDiff3App::scrollMergeResultWindow(int deltaX, int deltaY) { if(deltaY != 0) MergeResultWindow::mVScrollBar->setValue(MergeResultWindow::mVScrollBar->value() + deltaY); if(deltaX != 0) m_pHScrollBar->setValue(m_pHScrollBar->value() + deltaX); } void KDiff3App::sourceMask(int srcMask, int enabledMask) { chooseA->blockSignals(true); chooseB->blockSignals(true); chooseC->blockSignals(true); chooseA->setChecked((srcMask & 1) != 0); chooseB->setChecked((srcMask & 2) != 0); chooseC->setChecked((srcMask & 4) != 0); chooseA->blockSignals(false); chooseB->blockSignals(false); chooseC->blockSignals(false); chooseA->setEnabled((enabledMask & 1) != 0); chooseB->setEnabled((enabledMask & 2) != 0); chooseC->setEnabled((enabledMask & 4) != 0); } void KDiff3App::initView() { // set the main widget here if(m_pMainWidget != nullptr) { return; //delete m_pMainWidget; } m_pMainWidget = new QWidget(); // Contains vertical splitter and horiz scrollbar m_pMainSplitter->addWidget(m_pMainWidget); m_pMainWidget->setObjectName("MainWidget"); QVBoxLayout* pVLayout = new QVBoxLayout(m_pMainWidget); pVLayout->setMargin(0); pVLayout->setSpacing(0); QSplitter* pVSplitter = new QSplitter(); pVSplitter->setObjectName("VSplitter"); pVSplitter->setOpaqueResize(false); pVSplitter->setOrientation(Qt::Vertical); pVLayout->addWidget(pVSplitter); QWidget* pDiffWindowFrame = new QWidget(); // Contains diff windows, overview and vert scrollbar pDiffWindowFrame->setObjectName("DiffWindowFrame"); QHBoxLayout* pDiffHLayout = new QHBoxLayout(pDiffWindowFrame); pDiffHLayout->setMargin(0); pDiffHLayout->setSpacing(0); pVSplitter->addWidget(pDiffWindowFrame); m_pDiffWindowSplitter = new QSplitter(); m_pDiffWindowSplitter->setObjectName("DiffWindowSplitter"); m_pDiffWindowSplitter->setOpaqueResize(false); m_pDiffWindowSplitter->setOrientation(m_pOptions->m_bHorizDiffWindowSplitting ? Qt::Horizontal : Qt::Vertical); pDiffHLayout->addWidget(m_pDiffWindowSplitter); m_pOverview = new Overview(m_pOptionDialog->getOptions()); m_pOverview->setObjectName("Overview"); pDiffHLayout->addWidget(m_pOverview); connect(m_pOverview, &Overview::setLine, DiffTextWindow::mVScrollBar, &QScrollBar::setValue); connect(this, &KDiff3App::showWhiteSpaceToggled, m_pOverview, &Overview::slotRedraw); connect(this, &KDiff3App::changeOverViewMode, m_pOverview, &Overview::setOverviewMode); DiffTextWindow::mVScrollBar = new QScrollBar(Qt::Vertical, pDiffWindowFrame); pDiffHLayout->addWidget(DiffTextWindow::mVScrollBar); m_pDiffTextWindowFrame1 = new DiffTextWindowFrame(m_pDiffWindowSplitter, m_pOptionDialog->getOptions(), e_SrcSelector::A, m_sd1); m_pDiffWindowSplitter->addWidget(m_pDiffTextWindowFrame1); m_pDiffTextWindowFrame2 = new DiffTextWindowFrame(m_pDiffWindowSplitter, m_pOptionDialog->getOptions(), e_SrcSelector::B, m_sd2); m_pDiffWindowSplitter->addWidget(m_pDiffTextWindowFrame2); m_pDiffTextWindowFrame3 = new DiffTextWindowFrame(m_pDiffWindowSplitter, m_pOptionDialog->getOptions(), e_SrcSelector::C, m_sd3); m_pDiffWindowSplitter->addWidget(m_pDiffTextWindowFrame3); m_pDiffTextWindow1 = m_pDiffTextWindowFrame1->getDiffTextWindow(); m_pDiffTextWindow2 = m_pDiffTextWindowFrame2->getDiffTextWindow(); m_pDiffTextWindow3 = m_pDiffTextWindowFrame3->getDiffTextWindow(); m_pDiffTextWindowFrame1->setupConnections(this); m_pDiffTextWindowFrame2->setupConnections(this); m_pDiffTextWindowFrame3->setupConnections(this); // Merge window m_pMergeWindowFrame = new QWidget(pVSplitter); m_pMergeWindowFrame->setObjectName("MergeWindowFrame"); pVSplitter->addWidget(m_pMergeWindowFrame); QHBoxLayout* pMergeHLayout = new QHBoxLayout(m_pMergeWindowFrame); pMergeHLayout->setMargin(0); pMergeHLayout->setSpacing(0); QVBoxLayout* pMergeVLayout = new QVBoxLayout(); pMergeHLayout->addLayout(pMergeVLayout, 1); m_pMergeResultWindowTitle = new WindowTitleWidget(m_pOptionDialog->getOptions()); pMergeVLayout->addWidget(m_pMergeResultWindowTitle); m_pMergeResultWindow = new MergeResultWindow(m_pMergeWindowFrame, m_pOptionDialog->getOptions(), statusBar()); pMergeVLayout->addWidget(m_pMergeResultWindow, 1); MergeResultWindow::mVScrollBar = new QScrollBar(Qt::Vertical, m_pMergeWindowFrame); pMergeHLayout->addWidget(MergeResultWindow::mVScrollBar); m_pMainSplitter->addWidget(m_pMainWidget); autoAdvance->setEnabled(true); QList sizes = pVSplitter->sizes(); int total = sizes[0] + sizes[1]; if(total < 10) total = 100; sizes[0] = total / 2; sizes[1] = total / 2; pVSplitter->setSizes(sizes); QList hSizes; hSizes << 1 << 1 << 1; m_pDiffWindowSplitter->setSizes(hSizes); m_pMergeResultWindow->installEventFilter(m_pMergeResultWindowTitle); // for focus tracking QHBoxLayout* pHScrollBarLayout = new QHBoxLayout(); pVLayout->addLayout(pHScrollBarLayout); m_pHScrollBar = new ReversibleScrollBar(Qt::Horizontal, &m_pOptions->m_bRightToLeftLanguage); pHScrollBarLayout->addWidget(m_pHScrollBar); m_pCornerWidget = new QWidget(m_pMainWidget); pHScrollBarLayout->addWidget(m_pCornerWidget); connect(DiffTextWindow::mVScrollBar, &QScrollBar::valueChanged, m_pOverview, &Overview::setFirstLine); connect(DiffTextWindow::mVScrollBar, &QScrollBar::valueChanged, m_pDiffTextWindow1, &DiffTextWindow::setFirstLine); connect(m_pHScrollBar, &ReversibleScrollBar::valueChanged2, m_pDiffTextWindow1, &DiffTextWindow::setHorizScrollOffset); m_pDiffTextWindow1->setupConnections(this); connect(DiffTextWindow::mVScrollBar, &QScrollBar::valueChanged, m_pDiffTextWindow2, &DiffTextWindow::setFirstLine); connect(m_pHScrollBar, &ReversibleScrollBar::valueChanged2, m_pDiffTextWindow2, &DiffTextWindow::setHorizScrollOffset); m_pDiffTextWindow2->setupConnections(this); connect(DiffTextWindow::mVScrollBar, &QScrollBar::valueChanged, m_pDiffTextWindow3, &DiffTextWindow::setFirstLine); connect(m_pHScrollBar, &ReversibleScrollBar::valueChanged2, m_pDiffTextWindow3, &DiffTextWindow::setHorizScrollOffset); m_pDiffTextWindow3->setupConnections(this); MergeResultWindow* p = m_pMergeResultWindow; connect(MergeResultWindow::mVScrollBar, &QScrollBar::valueChanged, p, &MergeResultWindow::setFirstLine); connect(m_pHScrollBar, &ReversibleScrollBar::valueChanged2, p, &MergeResultWindow::setHorizScrollOffset); connect(p, &MergeResultWindow::modifiedChanged, m_pMergeResultWindowTitle, &WindowTitleWidget::slotSetModified); p->setupConnections(this); sourceMask(0, 0); connect(p, &MergeResultWindow::setFastSelectorRange, m_pDiffTextWindow1, &DiffTextWindow::setFastSelectorRange); connect(p, &MergeResultWindow::setFastSelectorRange, m_pDiffTextWindow2, &DiffTextWindow::setFastSelectorRange); connect(p, &MergeResultWindow::setFastSelectorRange, m_pDiffTextWindow3, &DiffTextWindow::setFastSelectorRange); connect(m_pDiffTextWindow1, &DiffTextWindow::setFastSelectorLine, p, &MergeResultWindow::slotSetFastSelectorLine); connect(m_pDiffTextWindow2, &DiffTextWindow::setFastSelectorLine, p, &MergeResultWindow::slotSetFastSelectorLine); connect(m_pDiffTextWindow3, &DiffTextWindow::setFastSelectorLine, p, &MergeResultWindow::slotSetFastSelectorLine); connect(m_pDiffTextWindow1, &DiffTextWindow::gotFocus, p, &MergeResultWindow::updateSourceMask); connect(m_pDiffTextWindow2, &DiffTextWindow::gotFocus, p, &MergeResultWindow::updateSourceMask); connect(m_pDiffTextWindow3, &DiffTextWindow::gotFocus, p, &MergeResultWindow::updateSourceMask); connect(m_pDirectoryMergeInfo, &DirectoryMergeInfo::gotFocus, p, &MergeResultWindow::updateSourceMask); connect(m_pDiffTextWindow1, &DiffTextWindow::resizeHeightChangedSignal, this, &KDiff3App::resizeDiffTextWindowHeight); // The following two connects cause the wordwrap to be recalced thrice, just to make sure. Better than forgetting one. connect(m_pDiffTextWindow1, &DiffTextWindow::resizeWidthChangedSignal, this, &KDiff3App::postRecalcWordWrap); connect(m_pDiffTextWindow2, &DiffTextWindow::resizeWidthChangedSignal, this, &KDiff3App::postRecalcWordWrap); connect(m_pDiffTextWindow3, &DiffTextWindow::resizeWidthChangedSignal, this, &KDiff3App::postRecalcWordWrap); m_pDiffTextWindow1->setFocus(); m_pMainWidget->setMinimumSize(50, 50); m_pCornerWidget->setFixedSize(DiffTextWindow::mVScrollBar->width(), m_pHScrollBar->height()); showWindowA->setChecked(true); showWindowB->setChecked(true); showWindowC->setChecked(true); } // called after word wrap is complete void KDiff3App::slotFinishMainInit() { Q_ASSERT(m_pDiffTextWindow1 != nullptr && DiffTextWindow::mVScrollBar != nullptr && m_pOverview != nullptr); setHScrollBarRange(); int newHeight = m_pDiffTextWindow1->getNofVisibleLines(); /*int newWidth = m_pDiffTextWindow1->getNofVisibleColumns();*/ m_DTWHeight = newHeight; DiffTextWindow::mVScrollBar->setRange(0, std::max(0, m_neededLines + 1 - newHeight)); DiffTextWindow::mVScrollBar->setPageStep(newHeight); m_pOverview->setRange(DiffTextWindow::mVScrollBar->value(), DiffTextWindow::mVScrollBar->pageStep()); int d3l = -1; if(!m_manualDiffHelpList.empty()) d3l = m_manualDiffHelpList.front().calcManualDiffFirstDiff3LineIdx(m_diff3LineVector); if(d3l >= 0 && m_pDiffTextWindow1) { int line = m_pDiffTextWindow1->convertDiff3LineIdxToLine(d3l); DiffTextWindow::mVScrollBar->setValue(std::max(0, line - 1)); } else { m_pMergeResultWindow->slotGoTop(); if(!m_outputFilename.isEmpty() && !m_pMergeResultWindow->isUnsolvedConflictAtCurrent()) m_pMergeResultWindow->slotGoNextUnsolvedConflict(); } if(m_pCornerWidget) m_pCornerWidget->setFixedSize(DiffTextWindow::mVScrollBar->width(), m_pHScrollBar->height()); slotUpdateAvailabilities(); setUpdatesEnabled(true); bool bVisibleMergeResultWindow = !m_outputFilename.isEmpty(); TotalDiffStatus* pTotalDiffStatus = &m_totalDiffStatus; if(m_bLoadFiles) { if(bVisibleMergeResultWindow) m_pMergeResultWindow->showNrOfConflicts(); else if( // Avoid showing this message during startup without parameters. !(m_sd1->getAliasName().isEmpty() && m_sd2->getAliasName().isEmpty() && m_sd3->getAliasName().isEmpty()) && (m_sd1->isValid() && m_sd2->isValid() && m_sd3->isValid())) { QString totalInfo; if(pTotalDiffStatus->isBinaryEqualAB() && pTotalDiffStatus->isBinaryEqualAC()) totalInfo += i18n("All input files are binary equal."); else if(pTotalDiffStatus->isTextEqualAB() && pTotalDiffStatus->isTextEqualAC()) totalInfo += i18n("All input files contain the same text, but are not binary equal."); else { if(pTotalDiffStatus->isBinaryEqualAB()) totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("A"), i18n("B")); else if(pTotalDiffStatus->isTextEqualAB()) totalInfo += i18n("Files %1 and %2 have equal text, but are not binary equal. \n", i18n("A"), i18n("B")); if(pTotalDiffStatus->isBinaryEqualAC()) totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("A"), i18n("C")); else if(pTotalDiffStatus->isTextEqualAC()) totalInfo += i18n("Files %1 and %2 have equal text, but are not binary equal. \n", i18n("A"), i18n("C")); if(pTotalDiffStatus->isBinaryEqualBC()) totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("B"), i18n("C")); else if(pTotalDiffStatus->isTextEqualBC()) totalInfo += i18n("Files %1 and %2 have equal text, but are not binary equal. \n", i18n("B"), i18n("C")); } if(!totalInfo.isEmpty()) KMessageBox::information(this, totalInfo); } if(bVisibleMergeResultWindow && (!m_sd1->isText() || !m_sd2->isText() || !m_sd3->isText())) { KMessageBox::information(this, i18n( "Some input files do not seem to be pure text files.\n" "Note that the KDiff3 merge was not meant for binary data.\n" "Continue at your own risk.")); } if(m_sd1->isIncompleteConversion() || m_sd2->isIncompleteConversion() || m_sd3->isIncompleteConversion()) { QString files; if(m_sd1->isIncompleteConversion()) files += i18n("A"); if(m_sd2->isIncompleteConversion()) files += files.isEmpty() ? i18n("B") : i18n(", B"); if(m_sd3->isIncompleteConversion()) files += files.isEmpty() ? i18n("C") : i18n(", C"); KMessageBox::information(this, i18n("Some input characters could not be converted to valid unicode.\n" "You might be using the wrong codec. (e.g. UTF-8 for non UTF-8 files).\n" "Do not save the result if unsure. Continue at your own risk.\n" "Affected input files are in %1.", files)); } } if(bVisibleMergeResultWindow && m_pMergeResultWindow) { m_pMergeResultWindow->setFocus(); } else if(m_pDiffTextWindow1) { m_pDiffTextWindow1->setFocus(); } } void KDiff3App::resizeEvent(QResizeEvent* e) { QSplitter::resizeEvent(e); if(m_pCornerWidget) m_pCornerWidget->setFixedSize(DiffTextWindow::mVScrollBar->width(), m_pHScrollBar->height()); } void KDiff3App::wheelEvent(QWheelEvent* pWheelEvent) { pWheelEvent->accept(); QPoint delta = pWheelEvent->angleDelta(); //Block diagonal scrolling easily generated unintentionally with track pads. if(delta.x() != 0 && abs(delta.y()) < abs(delta.x()) && m_pHScrollBar != nullptr) QCoreApplication::postEvent(m_pHScrollBar, new QWheelEvent(*pWheelEvent)); } void KDiff3App::keyPressEvent(QKeyEvent* keyEvent) { if(keyEvent->key() == Qt::Key_Escape && m_pKDiff3Shell && m_pOptions->m_bEscapeKeyQuits) { m_pKDiff3Shell->close(); return; } bool bCtrl = (keyEvent->QInputEvent::modifiers() & Qt::ControlModifier) != 0; switch(keyEvent->key()) { case Qt::Key_Down: case Qt::Key_Up: case Qt::Key_PageDown: case Qt::Key_PageUp: if(DiffTextWindow::mVScrollBar != nullptr) QCoreApplication::postEvent(DiffTextWindow::mVScrollBar, new QKeyEvent(*keyEvent)); return; case Qt::Key_Left: case Qt::Key_Right: if(m_pHScrollBar != nullptr) QCoreApplication::postEvent(m_pHScrollBar, new QKeyEvent(*keyEvent)); break; case Qt::Key_End: case Qt::Key_Home: if(bCtrl) { if(DiffTextWindow::mVScrollBar != nullptr) QCoreApplication::postEvent(DiffTextWindow::mVScrollBar, new QKeyEvent(*keyEvent)); } else { if(m_pHScrollBar != nullptr) QCoreApplication::postEvent(m_pHScrollBar, new QKeyEvent(*keyEvent)); } break; default: break; } } void KDiff3App::slotFinishDrop() { raise(); mainInit(); } void KDiff3App::slotFileOpen() { bool bShouldConintue = false; Q_EMIT checkIfCanContinue(bShouldConintue); if(!bShouldConintue) return; //create dummy DirectoryInfo record for first run so we don't crash. if(m_dirinfo == nullptr) m_dirinfo = QSharedPointer::create(); if(m_pDirectoryMergeWindow->isDirectoryMergeInProgress()) { int result = KMessageBox::warningYesNo(this, i18n("You are currently doing a folder merge. Are you sure, you want to abort?"), i18n("Warning"), KGuiItem(i18n("Abort")), KGuiItem(i18n("Continue Merging"))); if(result != KMessageBox::Yes) return; } slotStatusMsg(i18n("Opening files...")); for(;;) { QPointer d = QPointer(new OpenDialog(this, QDir::toNativeSeparators(m_bDirCompare ? m_dirinfo->dirA().prettyAbsPath() : m_sd1->isFromBuffer() ? QString("") : m_sd1->getAliasName()), QDir::toNativeSeparators(m_bDirCompare ? m_dirinfo->dirB().prettyAbsPath() : m_sd2->isFromBuffer() ? QString("") : m_sd2->getAliasName()), QDir::toNativeSeparators(m_bDirCompare ? m_dirinfo->dirC().prettyAbsPath() : m_sd3->isFromBuffer() ? QString("") : m_sd3->getAliasName()), m_bDirCompare ? !m_dirinfo->destDir().prettyAbsPath().isEmpty() : !m_outputFilename.isEmpty(), QDir::toNativeSeparators(m_bDefaultFilename ? QString("") : m_outputFilename), m_pOptionDialog->getOptions())); int status = d->exec(); if(status == QDialog::Accepted) { m_sd1->setFilename(d->m_pLineA->currentText()); m_sd2->setFilename(d->m_pLineB->currentText()); m_sd3->setFilename(d->m_pLineC->currentText()); if(d->m_pMerge->isChecked()) { if(d->m_pLineOut->currentText().isEmpty()) { m_outputFilename = "unnamed.txt"; m_bDefaultFilename = true; } else { m_outputFilename = d->m_pLineOut->currentText(); m_bDefaultFilename = false; } } else m_outputFilename = ""; m_bDirCompare = m_sd1->isDir(); bool bSuccess = improveFilenames(false); if(!bSuccess) continue; if(m_bDirCompare) { m_pDirectoryMergeSplitter->show(); if(m_pMainWidget != nullptr) { m_pMainWidget->hide(); } break; } else { m_pDirectoryMergeSplitter->hide(); mainInit(); if((!m_sd1->isEmpty() && !m_sd1->hasData()) || (!m_sd2->isEmpty() && !m_sd2->hasData()) || (!m_sd3->isEmpty() && !m_sd3->hasData())) { QString text(i18n("Opening of these files failed:")); text += "\n\n"; if(!m_sd1->isEmpty() && !m_sd1->hasData()) text += " - " + m_sd1->getAliasName() + '\n'; if(!m_sd2->isEmpty() && !m_sd2->hasData()) text += " - " + m_sd2->getAliasName() + '\n'; if(!m_sd3->isEmpty() && !m_sd3->hasData()) text += " - " + m_sd3->getAliasName() + '\n'; KMessageBox::sorry(this, text, i18n("File open error")); continue; } } } break; } slotUpdateAvailabilities(); slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotFileOpen2(const QString& fn1, const QString& fn2, const QString& fn3, const QString& ofn, const QString& an1, const QString& an2, const QString& an3, TotalDiffStatus* pTotalDiffStatus) { bool bShouldConintue = false; Q_EMIT checkIfCanContinue(bShouldConintue); if(!bShouldConintue) return; if(fn1.isEmpty() && fn2.isEmpty() && fn3.isEmpty() && ofn.isEmpty() && m_pMainWidget != nullptr) { m_pMainWidget->hide(); return; } slotStatusMsg(i18n("Opening files...")); m_sd1->setFilename(fn1); m_sd2->setFilename(fn2); m_sd3->setFilename(fn3); m_sd1->setAliasName(an1); m_sd2->setAliasName(an2); m_sd3->setAliasName(an3); if(!ofn.isEmpty()) { m_outputFilename = ofn; m_bDefaultFilename = false; } else { m_outputFilename = ""; m_bDefaultFilename = true; } improveFilenames(true); // Create new window for KDiff3 for directory comparison. if(!m_sd1->isDir()) { mainInit(pTotalDiffStatus); if(pTotalDiffStatus != nullptr) return; if(!((!m_sd1->isEmpty() && !m_sd1->hasData()) || (!m_sd2->isEmpty() && !m_sd2->hasData()) || (!m_sd3->isEmpty() && !m_sd3->hasData()))) { if(m_pDirectoryMergeWindow != nullptr && m_pDirectoryMergeWindow->isVisible() && !dirShowBoth->isChecked()) { slotDirViewToggle(); } } } slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotFileNameChanged(const QString& fileName, e_SrcSelector winIdx) { QString fn1 = m_sd1->getFilename(); QString an1 = m_sd1->getAliasName(); QString fn2 = m_sd2->getFilename(); QString an2 = m_sd2->getAliasName(); QString fn3 = m_sd3->getFilename(); QString an3 = m_sd3->getAliasName(); if(winIdx == e_SrcSelector::A) { fn1 = fileName; an1 = ""; } if(winIdx == e_SrcSelector::B) { fn2 = fileName; an2 = ""; } if(winIdx == e_SrcSelector::C) { fn3 = fileName; an3 = ""; } slotFileOpen2(fn1, fn2, fn3, m_outputFilename, an1, an2, an3, nullptr); } void KDiff3App::slotEditCut() { slotStatusMsg(i18n("Cutting selection...")); Q_EMIT cut(); slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotEditCopy() { slotStatusMsg(i18n("Copying selection to clipboard...")); QString s; if(m_pDiffTextWindow1 != nullptr) s = m_pDiffTextWindow1->getSelection(); if(s.isEmpty() && m_pDiffTextWindow2 != nullptr) s = m_pDiffTextWindow2->getSelection(); if(s.isEmpty() && m_pDiffTextWindow3 != nullptr) s = m_pDiffTextWindow3->getSelection(); if(s.isEmpty() && m_pMergeResultWindow != nullptr) s = m_pMergeResultWindow->getSelection(); if(!s.isEmpty()) { QApplication::clipboard()->setText(s, QClipboard::Clipboard); } slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotEditPaste() { slotStatusMsg(i18n("Inserting clipboard contents...")); if(m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isVisible()) { m_pMergeResultWindow->pasteClipboard(false); } else { bool bShouldConintue = false; Q_EMIT checkIfCanContinue(bShouldConintue); if(bShouldConintue) { QString error; bool do_init = false; if(m_pDiffTextWindow1->hasFocus()) { error = m_sd1->setData(QApplication::clipboard()->text(QClipboard::Clipboard)); do_init = true; } else if(m_pDiffTextWindow2->hasFocus()) { error = m_sd2->setData(QApplication::clipboard()->text(QClipboard::Clipboard)); do_init = true; } else if(m_pDiffTextWindow3->hasFocus()) { error = m_sd3->setData(QApplication::clipboard()->text(QClipboard::Clipboard)); do_init = true; } if(!error.isEmpty()) { KMessageBox::error(m_pOptionDialog, error); } if(do_init) { mainInit(); } } } slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotEditSelectAll() { - LineRef l = 0; - int p = 0; // needed as dummy return values - if(m_pMergeResultWindow && m_pMergeResultWindow->hasFocus()) - { - m_pMergeResultWindow->setSelection(0, 0, m_pMergeResultWindow->getNofLines(), 0); - } - else if(m_pDiffTextWindow1 && m_pDiffTextWindow1->hasFocus()) - { - m_pDiffTextWindow1->setSelection(0, 0, m_pDiffTextWindow1->getNofLines(), 0, l, p); - } - else if(m_pDiffTextWindow2 && m_pDiffTextWindow2->hasFocus()) - { - m_pDiffTextWindow2->setSelection(0, 0, m_pDiffTextWindow2->getNofLines(), 0, l, p); - } - else if(m_pDiffTextWindow3 && m_pDiffTextWindow3->hasFocus()) - { - m_pDiffTextWindow3->setSelection(0, 0, m_pDiffTextWindow3->getNofLines(), 0, l, p); - } + + Q_EMIT selectAll(); slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotGoCurrent() { Q_EMIT goCurrent(); } void KDiff3App::slotGoTop() { Q_EMIT goTop(); } void KDiff3App::slotGoBottom() { Q_EMIT goBottom(); } void KDiff3App::slotGoPrevUnsolvedConflict() { Q_EMIT goPrevUnsolvedConflict(); } void KDiff3App::slotGoNextUnsolvedConflict() { m_bTimerBlock = false; Q_EMIT goNextUnsolvedConflict(); } void KDiff3App::slotGoPrevConflict() { Q_EMIT goPrevConflict(); } void KDiff3App::slotGoNextConflict() { m_bTimerBlock = false; Q_EMIT goNextConflict(); } void KDiff3App::slotGoPrevDelta() { Q_EMIT goPrevDelta(); } void KDiff3App::slotGoNextDelta() { Q_EMIT goNextDelta(); } void KDiff3App::choose(e_SrcSelector choice) { if(!m_bTimerBlock) { if(m_pDirectoryMergeWindow && m_pDirectoryMergeWindow->hasFocus()) { if(choice == e_SrcSelector::A) m_pDirectoryMergeWindow->slotCurrentChooseA(); if(choice == e_SrcSelector::B) m_pDirectoryMergeWindow->slotCurrentChooseB(); if(choice == e_SrcSelector::C) m_pDirectoryMergeWindow->slotCurrentChooseC(); chooseA->setChecked(false); chooseB->setChecked(false); chooseC->setChecked(false); } else if(m_pMergeResultWindow) { m_pMergeResultWindow->choose(choice); if(autoAdvance->isChecked()) { m_bTimerBlock = true; QTimer::singleShot(m_pOptions->m_autoAdvanceDelay, this, &KDiff3App::slotGoNextUnsolvedConflict); } } } } void KDiff3App::slotChooseA() { choose(e_SrcSelector::A); } void KDiff3App::slotChooseB() { choose(e_SrcSelector::B); } void KDiff3App::slotChooseC() { choose(e_SrcSelector::C); } void KDiff3App::slotAutoSolve() { Q_EMIT autoSolve(); slotUpdateAvailabilities(); } void KDiff3App::slotUnsolve() { Q_EMIT unsolve(); } void KDiff3App::slotMergeHistory() { Q_EMIT mergeHistory(); } void KDiff3App::slotRegExpAutoMerge() { Q_EMIT regExpAutoMerge(); } void KDiff3App::slotSplitDiff() { LineRef firstLine; LineRef lastLine; DiffTextWindow* pDTW = nullptr; if(m_pDiffTextWindow1) { pDTW = m_pDiffTextWindow1; pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords); } if(!firstLine.isValid() && m_pDiffTextWindow2) { pDTW = m_pDiffTextWindow2; pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords); } if(!firstLine.isValid() && m_pDiffTextWindow3) { pDTW = m_pDiffTextWindow3; pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords); } if(pDTW && firstLine.isValid() && m_pMergeResultWindow) { pDTW->resetSelection(); m_pMergeResultWindow->slotSplitDiff(firstLine, lastLine); } } void KDiff3App::slotJoinDiffs() { LineRef firstLine; LineRef lastLine; DiffTextWindow* pDTW = nullptr; if(m_pDiffTextWindow1) { pDTW = m_pDiffTextWindow1; pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords); } if(!firstLine.isValid() && m_pDiffTextWindow2) { pDTW = m_pDiffTextWindow2; pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords); } if(!firstLine.isValid() && m_pDiffTextWindow3) { pDTW = m_pDiffTextWindow3; pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords); } if(pDTW && firstLine.isValid() && m_pMergeResultWindow) { pDTW->resetSelection(); m_pMergeResultWindow->slotJoinDiffs(firstLine, lastLine); } } void KDiff3App::slotConfigure() { m_pOptionDialog->setState(); m_pOptionDialog->setMinimumHeight(m_pOptionDialog->minimumHeight() + 40); m_pOptionDialog->exec(); slotRefresh(); } void KDiff3App::slotConfigureKeys() { KShortcutsDialog::configure(actionCollection(), KShortcutsEditor::LetterShortcutsAllowed, this); } void KDiff3App::slotRefresh() { QApplication::setFont(m_pOptions->m_appFont); Q_EMIT doRefresh(); if(m_pHScrollBar != nullptr) { m_pHScrollBar->setAgain(); } if(m_pDiffWindowSplitter != nullptr) { m_pDiffWindowSplitter->setOrientation(m_pOptions->m_bHorizDiffWindowSplitting ? Qt::Horizontal : Qt::Vertical); } } void KDiff3App::slotSelectionStart() { //editCopy->setEnabled( false ); //editCut->setEnabled( false ); const QObject* s = sender(); if(m_pDiffTextWindow1 && s != m_pDiffTextWindow1) m_pDiffTextWindow1->resetSelection(); if(m_pDiffTextWindow2 && s != m_pDiffTextWindow2) m_pDiffTextWindow2->resetSelection(); if(m_pDiffTextWindow3 && s != m_pDiffTextWindow3) m_pDiffTextWindow3->resetSelection(); if(m_pMergeResultWindow && s != m_pMergeResultWindow) m_pMergeResultWindow->resetSelection(); } void KDiff3App::slotSelectionEnd() { //const QObject* s = sender(); //editCopy->setEnabled(true); //editCut->setEnabled( s==m_pMergeResultWindow ); if(m_pOptions->m_bAutoCopySelection) { slotEditCopy(); } else { QClipboard* clipBoard = QApplication::clipboard(); if(clipBoard->supportsSelection()) { QString s; if(m_pDiffTextWindow1 != nullptr) s = m_pDiffTextWindow1->getSelection(); if(s.isEmpty() && m_pDiffTextWindow2 != nullptr) s = m_pDiffTextWindow2->getSelection(); if(s.isEmpty() && m_pDiffTextWindow3 != nullptr) s = m_pDiffTextWindow3->getSelection(); if(s.isEmpty() && m_pMergeResultWindow != nullptr) s = m_pMergeResultWindow->getSelection(); if(!s.isEmpty()) { clipBoard->setText(s, QClipboard::Selection); } } } } void KDiff3App::slotClipboardChanged() { const QClipboard* clipboard = QApplication::clipboard(); const QMimeData* mimeData = clipboard->mimeData(); if(mimeData->hasText()) { QString s = clipboard->text(); editPaste->setEnabled(!s.isEmpty()); } else { editPaste->setEnabled(false); } } void KDiff3App::slotOutputModified(bool bModified) { if(bModified && !m_bOutputModified) { m_bOutputModified = true; slotUpdateAvailabilities(); } } void KDiff3App::slotAutoAdvanceToggled() { m_pOptions->m_bAutoAdvance = autoAdvance->isChecked(); } void KDiff3App::slotWordWrapToggled() { m_pOptions->setWordWrap(wordWrap->isChecked()); postRecalcWordWrap(); } // Enable or disable all widgets except the status bar widget. void KDiff3App::mainWindowEnable(bool bEnable) { if(QMainWindow* pWindow = dynamic_cast(window())) { QWidget* pStatusBarWidget = pWindow->statusBar(); pWindow->setEnabled(bEnable); pStatusBarWidget->setEnabled(true); } } void KDiff3App::postRecalcWordWrap() { if(!m_bRecalcWordWrapPosted) { m_bRecalcWordWrapPosted = true; m_firstD3LIdx = -1; Q_EMIT sigRecalcWordWrap(); } else { g_pProgressDialog->cancel(ProgressDialog::eResize); } } void KDiff3App::slotRecalcWordWrap() { recalcWordWrap(); } // visibleTextWidthForPrinting is >=0 only for printing, otherwise the really visible width is used void KDiff3App::recalcWordWrap(int visibleTextWidthForPrinting) { m_bRecalcWordWrapPosted = true; mainWindowEnable(false); if(m_firstD3LIdx < 0) { m_firstD3LIdx = 0; if(m_pDiffTextWindow1) m_firstD3LIdx = m_pDiffTextWindow1->convertLineToDiff3LineIdx(m_pDiffTextWindow1->getFirstLine()); } // Convert selection to D3L-coords (converting back happens in DiffTextWindow::recalcWordWrap() if(m_pDiffTextWindow1) m_pDiffTextWindow1->convertSelectionToD3LCoords(); if(m_pDiffTextWindow2) m_pDiffTextWindow2->convertSelectionToD3LCoords(); if(m_pDiffTextWindow3) m_pDiffTextWindow3->convertSelectionToD3LCoords(); g_pProgressDialog->clearCancelState(); // clear cancelled state if previously set if(!m_diff3LineList.empty()) { if(m_pOptions->wordWrapOn()) { m_diff3LineList.recalcWordWrap(true); // Let every window calc how many lines will be needed. if(m_pDiffTextWindow1) { m_pDiffTextWindow1->recalcWordWrap(true, 0, visibleTextWidthForPrinting); } if(m_pDiffTextWindow2) { m_pDiffTextWindow2->recalcWordWrap(true, 0, visibleTextWidthForPrinting); } if(m_pDiffTextWindow3) { m_pDiffTextWindow3->recalcWordWrap(true, 0, visibleTextWidthForPrinting); } } else { m_neededLines = m_diff3LineVector.size(); if(m_pDiffTextWindow1) m_pDiffTextWindow1->recalcWordWrap(false, 0, 0); if(m_pDiffTextWindow2) m_pDiffTextWindow2->recalcWordWrap(false, 0, 0); if(m_pDiffTextWindow3) m_pDiffTextWindow3->recalcWordWrap(false, 0, 0); } bool bRunnablesStarted = DiffTextWindow::startRunnables(); if(!bRunnablesStarted) slotFinishRecalcWordWrap(visibleTextWidthForPrinting); else { g_pProgressDialog->setInformation(m_pOptions->wordWrapOn() ? i18n("Word wrap (Cancel disables word wrap)") : i18n("Calculating max width for horizontal scrollbar"), false); } } else { //don't leave proccessing incomplete if m_diff3LineList isEmpty as when an error occures during reading. slotFinishRecalcWordWrap(visibleTextWidthForPrinting); } } void KDiff3App::slotFinishRecalcWordWrap(int visibleTextWidthForPrinting) { g_pProgressDialog->pop(); if(m_pOptions->wordWrapOn() && g_pProgressDialog->wasCancelled()) { if(g_pProgressDialog->cancelReason() == ProgressDialog::eUserAbort) { wordWrap->setChecked(false); m_pOptions->setWordWrap(wordWrap->isChecked()); } Q_EMIT sigRecalcWordWrap(); return; } else { m_bRecalcWordWrapPosted = false; } g_pProgressDialog->setStayHidden(false); bool bPrinting = visibleTextWidthForPrinting >= 0; if(!m_diff3LineList.empty()) { if(m_pOptions->wordWrapOn()) { LineCount sumOfLines = m_diff3LineList.recalcWordWrap(false); // Finish the word wrap if(m_pDiffTextWindow1) m_pDiffTextWindow1->recalcWordWrap(true, sumOfLines, visibleTextWidthForPrinting); if(m_pDiffTextWindow2) m_pDiffTextWindow2->recalcWordWrap(true, sumOfLines, visibleTextWidthForPrinting); if(m_pDiffTextWindow3) m_pDiffTextWindow3->recalcWordWrap(true, sumOfLines, visibleTextWidthForPrinting); m_neededLines = sumOfLines; } else { if(m_pDiffTextWindow1) m_pDiffTextWindow1->recalcWordWrap(false, 1, 0); if(m_pDiffTextWindow2) m_pDiffTextWindow2->recalcWordWrap(false, 1, 0); if(m_pDiffTextWindow3) m_pDiffTextWindow3->recalcWordWrap(false, 1, 0); } slotStatusMsg(QString()); } if(!bPrinting) { if(m_pOverview) m_pOverview->slotRedraw(); if(DiffTextWindow::mVScrollBar) DiffTextWindow::mVScrollBar->setRange(0, std::max(0, m_neededLines + 1 - m_DTWHeight)); if(m_pDiffTextWindow1) { if(DiffTextWindow::mVScrollBar) DiffTextWindow::mVScrollBar->setValue(m_pDiffTextWindow1->convertDiff3LineIdxToLine(m_firstD3LIdx)); setHScrollBarRange(); m_pHScrollBar->setValue(0); } } mainWindowEnable(true); if(m_bFinishMainInit) { m_bFinishMainInit = false; slotFinishMainInit(); } if(m_pEventLoopForPrinting) m_pEventLoopForPrinting->quit(); } void KDiff3App::slotShowWhiteSpaceToggled() { m_pOptions->m_bShowWhiteSpaceCharacters = showWhiteSpaceCharacters->isChecked(); m_pOptions->m_bShowWhiteSpace = showWhiteSpace->isChecked(); Q_EMIT showWhiteSpaceToggled(); } void KDiff3App::slotShowLineNumbersToggled() { m_pOptions->m_bShowLineNumbers = showLineNumbers->isChecked(); if(wordWrap->isChecked()) recalcWordWrap(); Q_EMIT showLineNumbersToggled(); } /// Return true for success, else false bool KDiff3App::improveFilenames(bool bCreateNewInstance) { FileAccess f1(m_sd1->getFilename()); FileAccess f2(m_sd2->getFilename()); FileAccess f3(m_sd3->getFilename()); FileAccess f4(m_outputFilename); if(f1.isFile() && f1.exists()) { if(f2.isDir()) { f2.addPath(f1.fileName()); if(f2.isFile() && f2.exists()) m_sd2->setFileAccess(f2); } if(f3.isDir()) { f3.addPath(f1.fileName()); if(f3.isFile() && f3.exists()) m_sd3->setFileAccess(f3); } if(f4.isDir()) { f4.addPath(f1.fileName()); if(f4.isFile() && f4.exists()) m_outputFilename = f4.absoluteFilePath(); } } else if(f1.isDir()) { if(bCreateNewInstance) { Q_EMIT createNewInstance(f1.absoluteFilePath(), f2.absoluteFilePath(), f3.absoluteFilePath()); } else { bool bDirCompare = m_bDirCompare; FileAccess destDir; if(!m_bDefaultFilename) destDir = f4; m_pDirectoryMergeSplitter->show(); if(m_pMainWidget != nullptr) m_pMainWidget->hide(); setUpdatesEnabled(true); m_dirinfo = QSharedPointer::create(f1, f2, f3, destDir); bool bSuccess = m_pDirectoryMergeWindow->init( m_dirinfo, !m_outputFilename.isEmpty()); //This is a bug if it still happens. Q_ASSERT(m_bDirCompare == bDirCompare); if(bSuccess) { m_sd1->reset(); if(m_pDiffTextWindow1 != nullptr) { m_pDiffTextWindow1->init(QString(""), nullptr, eLineEndStyleDos, nullptr, 0, nullptr, nullptr, false); m_pDiffTextWindowFrame1->init(); } m_sd2->reset(); if(m_pDiffTextWindow2 != nullptr) { m_pDiffTextWindow2->init(QString(""), nullptr, eLineEndStyleDos, nullptr, 0, nullptr, nullptr, false); m_pDiffTextWindowFrame2->init(); } m_sd3->reset(); if(m_pDiffTextWindow3 != nullptr) { m_pDiffTextWindow3->init(QString(""), nullptr, eLineEndStyleDos, nullptr, 0, nullptr, nullptr, false); m_pDiffTextWindowFrame3->init(); } } slotUpdateAvailabilities(); return bSuccess; } } return true; } void KDiff3App::slotReload() { bool bShouldConintue = false; Q_EMIT checkIfCanContinue(bShouldConintue); if(!bShouldConintue) return; mainInit(); } bool KDiff3App::canContinue() { // First test if anything must be saved. if(m_bOutputModified) { int result = KMessageBox::warningYesNoCancel(this, i18n("The merge result has not been saved."), i18n("Warning"), KGuiItem(i18n("Save && Continue")), KGuiItem(i18n("Continue Without Saving"))); if(result == KMessageBox::Cancel) return false; else if(result == KMessageBox::Yes) { slotFileSave(); if(m_bOutputModified) { KMessageBox::sorry(this, i18n("Saving the merge result failed."), i18n("Warning")); return false; } } } m_bOutputModified = false; return true; } /* Translate canContinue return value to out param because Qt does not support return values on slots. */ void KDiff3App::slotCheckIfCanContinue(bool& pbContinue) { pbContinue = canContinue(); } void KDiff3App::slotDirShowBoth() { if(dirShowBoth->isChecked()) { if(m_pDirectoryMergeSplitter) m_pDirectoryMergeSplitter->setVisible(m_bDirCompare); if(m_pMainWidget != nullptr) m_pMainWidget->show(); } else { bool bTextDataAvailable = (m_sd1->hasData() || m_sd2->hasData() || m_sd3->hasData()); if(m_pMainWidget != nullptr && bTextDataAvailable) { m_pMainWidget->show(); m_pDirectoryMergeSplitter->hide(); } else if(m_bDirCompare) { m_pDirectoryMergeSplitter->show(); } } slotUpdateAvailabilities(); } void KDiff3App::slotDirViewToggle() { if(m_bDirCompare) { if(!m_pDirectoryMergeSplitter->isVisible()) { m_pDirectoryMergeSplitter->show(); if(m_pMainWidget != nullptr) m_pMainWidget->hide(); } else { if(m_pMainWidget != nullptr) { m_pDirectoryMergeSplitter->hide(); m_pMainWidget->show(); } } } slotUpdateAvailabilities(); } void KDiff3App::slotShowWindowAToggled() { if(m_pDiffTextWindow1 != nullptr) { m_pDiffTextWindowFrame1->setVisible(showWindowA->isChecked()); slotUpdateAvailabilities(); } } void KDiff3App::slotShowWindowBToggled() { if(m_pDiffTextWindow2 != nullptr) { m_pDiffTextWindowFrame2->setVisible(showWindowB->isChecked()); slotUpdateAvailabilities(); } } void KDiff3App::slotShowWindowCToggled() { if(m_pDiffTextWindow3 != nullptr) { m_pDiffTextWindowFrame3->setVisible(showWindowC->isChecked()); slotUpdateAvailabilities(); } } void KDiff3App::slotEditFind() { m_pFindDialog->currentLine = 0; m_pFindDialog->currentPos = 0; m_pFindDialog->currentWindow = 1; // Use currently selected text: QString s; if(m_pDiffTextWindow1 != nullptr) s = m_pDiffTextWindow1->getSelection(); if(s.isEmpty() && m_pDiffTextWindow2 != nullptr) s = m_pDiffTextWindow2->getSelection(); if(s.isEmpty() && m_pDiffTextWindow3 != nullptr) s = m_pDiffTextWindow3->getSelection(); if(s.isEmpty() && m_pMergeResultWindow != nullptr) s = m_pMergeResultWindow->getSelection(); if(!s.isEmpty() && !s.contains('\n')) { m_pFindDialog->m_pSearchString->setText(s); } if(QDialog::Accepted == m_pFindDialog->exec()) { slotEditFindNext(); } } void KDiff3App::slotEditFindNext() { QString s = m_pFindDialog->m_pSearchString->text(); if(s.isEmpty()) { slotEditFind(); return; } bool bDirDown = true; bool bCaseSensitive = m_pFindDialog->m_pCaseSensitive->isChecked(); LineRef d3vLine = m_pFindDialog->currentLine; int posInLine = m_pFindDialog->currentPos; LineRef l = 0; int p = 0; if(m_pFindDialog->currentWindow == 1) { if(m_pFindDialog->m_pSearchInA->isChecked() && m_pDiffTextWindow1 != nullptr && m_pDiffTextWindow1->findString(s, d3vLine, posInLine, bDirDown, bCaseSensitive)) { m_pDiffTextWindow1->setSelection(d3vLine, posInLine, d3vLine, posInLine + s.length(), l, p); DiffTextWindow::mVScrollBar->setValue(l - DiffTextWindow::mVScrollBar->pageStep() / 2); m_pHScrollBar->setValue(std::max(0, p + s.length() - m_pHScrollBar->pageStep())); m_pFindDialog->currentLine = d3vLine; m_pFindDialog->currentPos = posInLine + 1; return; } m_pFindDialog->currentWindow = 2; m_pFindDialog->currentLine = 0; m_pFindDialog->currentPos = 0; } d3vLine = m_pFindDialog->currentLine; posInLine = m_pFindDialog->currentPos; if(m_pFindDialog->currentWindow == 2) { if(m_pFindDialog->m_pSearchInB->isChecked() && m_pDiffTextWindow2 != nullptr && m_pDiffTextWindow2->findString(s, d3vLine, posInLine, bDirDown, bCaseSensitive)) { m_pDiffTextWindow2->setSelection(d3vLine, posInLine, d3vLine, posInLine + s.length(), l, p); DiffTextWindow::mVScrollBar->setValue(l - DiffTextWindow::mVScrollBar->pageStep() / 2); m_pHScrollBar->setValue(std::max(0, p + s.length() - m_pHScrollBar->pageStep())); m_pFindDialog->currentLine = d3vLine; m_pFindDialog->currentPos = posInLine + 1; return; } m_pFindDialog->currentWindow = 3; m_pFindDialog->currentLine = 0; m_pFindDialog->currentPos = 0; } d3vLine = m_pFindDialog->currentLine; posInLine = m_pFindDialog->currentPos; if(m_pFindDialog->currentWindow == 3) { if(m_pFindDialog->m_pSearchInC->isChecked() && m_pDiffTextWindow3 != nullptr && m_pDiffTextWindow3->findString(s, d3vLine, posInLine, bDirDown, bCaseSensitive)) { m_pDiffTextWindow3->setSelection(d3vLine, posInLine, d3vLine, posInLine + s.length(), l, p); DiffTextWindow::mVScrollBar->setValue(l - DiffTextWindow::mVScrollBar->pageStep() / 2); m_pHScrollBar->setValue(std::max(0, p + s.length() - m_pHScrollBar->pageStep())); m_pFindDialog->currentLine = d3vLine; m_pFindDialog->currentPos = posInLine + 1; return; } m_pFindDialog->currentWindow = 4; m_pFindDialog->currentLine = 0; m_pFindDialog->currentPos = 0; } d3vLine = m_pFindDialog->currentLine; posInLine = m_pFindDialog->currentPos; if(m_pFindDialog->currentWindow == 4) { if(m_pFindDialog->m_pSearchInOutput->isChecked() && m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isVisible() && m_pMergeResultWindow->findString(s, d3vLine, posInLine, bDirDown, bCaseSensitive)) { m_pMergeResultWindow->setSelection(d3vLine, posInLine, d3vLine, posInLine + s.length()); MergeResultWindow::mVScrollBar->setValue(d3vLine - MergeResultWindow::mVScrollBar->pageStep() / 2); m_pHScrollBar->setValue(std::max(0, posInLine + s.length() - m_pHScrollBar->pageStep())); m_pFindDialog->currentLine = d3vLine; m_pFindDialog->currentPos = posInLine + 1; return; } m_pFindDialog->currentWindow = 5; m_pFindDialog->currentLine = 0; m_pFindDialog->currentPos = 0; } KMessageBox::information(this, i18n("Search complete."), i18n("Search Complete")); m_pFindDialog->currentWindow = 1; m_pFindDialog->currentLine = 0; m_pFindDialog->currentPos = 0; } void KDiff3App::slotMergeCurrentFile() { if(m_bDirCompare && m_pDirectoryMergeWindow->isVisible() && m_pDirectoryMergeWindow->isFileSelected()) { m_pDirectoryMergeWindow->mergeCurrentFile(); } else if(m_pMainWidget != nullptr && m_pMainWidget->isVisible()) { bool bShouldConintue = false; Q_EMIT checkIfCanContinue(bShouldConintue); if(!bShouldConintue) return; if(m_outputFilename.isEmpty()) { if(!m_sd3->isEmpty() && !m_sd3->isFromBuffer()) { m_outputFilename = m_sd3->getFilename(); } else if(!m_sd2->isEmpty() && !m_sd2->isFromBuffer()) { m_outputFilename = m_sd2->getFilename(); } else if(!m_sd1->isEmpty() && !m_sd1->isFromBuffer()) { m_outputFilename = m_sd1->getFilename(); } else { m_outputFilename = "unnamed.txt"; m_bDefaultFilename = true; } } mainInit(); } } void KDiff3App::slotWinFocusNext() { QWidget* focus = qApp->focusWidget(); if(focus == m_pDirectoryMergeWindow && m_pDirectoryMergeWindow->isVisible() && !dirShowBoth->isChecked()) { slotDirViewToggle(); } std::list visibleWidgetList; if(m_pDiffTextWindow1 && m_pDiffTextWindow1->isVisible()) visibleWidgetList.push_back(m_pDiffTextWindow1); if(m_pDiffTextWindow2 && m_pDiffTextWindow2->isVisible()) visibleWidgetList.push_back(m_pDiffTextWindow2); if(m_pDiffTextWindow3 && m_pDiffTextWindow3->isVisible()) visibleWidgetList.push_back(m_pDiffTextWindow3); if(m_pMergeResultWindow && m_pMergeResultWindow->isVisible()) visibleWidgetList.push_back(m_pMergeResultWindow); if(m_bDirCompare /*m_pDirectoryMergeWindow->isVisible()*/) visibleWidgetList.push_back(m_pDirectoryMergeWindow); //if ( m_pDirectoryMergeInfo->isVisible() ) visibleWidgetList.push_back(m_pDirectoryMergeInfo->getInfoList()); if(visibleWidgetList.empty()) return; std::list::iterator i = std::find(visibleWidgetList.begin(), visibleWidgetList.end(), focus); ++i; if(i == visibleWidgetList.end()) i = visibleWidgetList.begin(); if(*i == m_pDirectoryMergeWindow && !dirShowBoth->isChecked()) { slotDirViewToggle(); } (*i)->setFocus(); } void KDiff3App::slotWinFocusPrev() { QWidget* focus = qApp->focusWidget(); if(focus == m_pDirectoryMergeWindow && m_pDirectoryMergeWindow->isVisible() && !dirShowBoth->isChecked()) { slotDirViewToggle(); } std::list visibleWidgetList; if(m_pDiffTextWindow1 && m_pDiffTextWindow1->isVisible()) visibleWidgetList.push_back(m_pDiffTextWindow1); if(m_pDiffTextWindow2 && m_pDiffTextWindow2->isVisible()) visibleWidgetList.push_back(m_pDiffTextWindow2); if(m_pDiffTextWindow3 && m_pDiffTextWindow3->isVisible()) visibleWidgetList.push_back(m_pDiffTextWindow3); if(m_pMergeResultWindow && m_pMergeResultWindow->isVisible()) visibleWidgetList.push_back(m_pMergeResultWindow); if(m_bDirCompare /* m_pDirectoryMergeWindow->isVisible() */) visibleWidgetList.push_back(m_pDirectoryMergeWindow); //if ( m_pDirectoryMergeInfo->isVisible() ) visibleWidgetList.push_back(m_pDirectoryMergeInfo->getInfoList()); if(visibleWidgetList.empty()) return; std::list::iterator i = std::find(visibleWidgetList.begin(), visibleWidgetList.end(), focus); if(i == visibleWidgetList.begin()) i = visibleWidgetList.end(); --i; //i will never be if(*i == m_pDirectoryMergeWindow && !dirShowBoth->isChecked()) { slotDirViewToggle(); } (*i)->setFocus(); } void KDiff3App::slotWinToggleSplitterOrientation() { if(m_pDiffWindowSplitter != nullptr) { m_pDiffWindowSplitter->setOrientation( m_pDiffWindowSplitter->orientation() == Qt::Vertical ? Qt::Horizontal : Qt::Vertical); m_pOptions->m_bHorizDiffWindowSplitting = m_pDiffWindowSplitter->orientation() == Qt::Horizontal; } } void KDiff3App::slotOverviewNormal() { Q_EMIT changeOverViewMode(e_OverviewMode::eOMNormal); slotUpdateAvailabilities(); } void KDiff3App::slotOverviewAB() { Q_EMIT changeOverViewMode(e_OverviewMode::eOMAvsB); slotUpdateAvailabilities(); } void KDiff3App::slotOverviewAC() { Q_EMIT changeOverViewMode(e_OverviewMode::eOMAvsC); slotUpdateAvailabilities(); } void KDiff3App::slotOverviewBC() { Q_EMIT changeOverViewMode(e_OverviewMode::eOMBvsC); slotUpdateAvailabilities(); } void KDiff3App::slotNoRelevantChangesDetected() { if(m_bTripleDiff && !m_outputFilename.isEmpty()) { //KMessageBox::information( this, "No relevant changes detected", "KDiff3" ); if(!m_pOptions->m_IrrelevantMergeCmd.isEmpty()) { /* QProcess doesn't check for single quotes and uses non-standard escaping syntax for double quotes. The distinction between single and double quotes is purely a command shell issue. So we split the command string ourselves. */ QStringList args; QString program; Utils::getArguments(m_pOptions->m_IrrelevantMergeCmd, program, args); QProcess process; process.start(program, args); process.waitForFinished(-1); } } } void KDiff3App::slotAddManualDiffHelp() { LineRef firstLine; LineRef lastLine; e_SrcSelector winIdx = e_SrcSelector::Invalid; if(m_pDiffTextWindow1) { m_pDiffTextWindow1->getSelectionRange(&firstLine, &lastLine, eFileCoords); winIdx = e_SrcSelector::A; } if(!firstLine.isValid() && m_pDiffTextWindow2) { m_pDiffTextWindow2->getSelectionRange(&firstLine, &lastLine, eFileCoords); winIdx = e_SrcSelector::B; } if(!firstLine.isValid() && m_pDiffTextWindow3) { m_pDiffTextWindow3->getSelectionRange(&firstLine, &lastLine, eFileCoords); winIdx = e_SrcSelector::C; } if(!firstLine.isValid() || !lastLine.isValid() || lastLine < firstLine) KMessageBox::information(this, i18n("Nothing is selected in either diff input window."), i18n("Error while adding manual diff range")); else { m_manualDiffHelpList.insertEntry(winIdx, firstLine, lastLine); mainInit(nullptr, false); // Init without reload slotRefresh(); } } void KDiff3App::slotClearManualDiffHelpList() { m_manualDiffHelpList.clear(); mainInit(nullptr, false); // Init without reload slotRefresh(); } void KDiff3App::slotEncodingChanged(QTextCodec* c) { Q_UNUSED(c); mainInit(nullptr, true, true); // Init with reload slotRefresh(); } void KDiff3App::slotUpdateAvailabilities() { if(m_pMainSplitter == nullptr || m_pDiffTextWindow2 == nullptr || m_pDiffTextWindow1 == nullptr || m_pDiffTextWindow3 == nullptr) return; bool bTextDataAvailable = (m_sd1->hasData() || m_sd2->hasData() || m_sd3->hasData()); if(dirShowBoth->isChecked()) { if(m_pDirectoryMergeSplitter != nullptr) m_pDirectoryMergeSplitter->setVisible(m_bDirCompare); if(m_pMainWidget != nullptr && !m_pMainWidget->isVisible() && bTextDataAvailable && !m_pDirectoryMergeWindow->isScanning()) m_pMainWidget->show(); } bool bDiffWindowVisible = m_pMainWidget != nullptr && m_pMainWidget->isVisible(); bool bMergeEditorVisible = m_pMergeWindowFrame != nullptr && m_pMergeWindowFrame->isVisible() && m_pMergeResultWindow != nullptr; m_pDirectoryMergeWindow->updateAvailabilities(m_bDirCompare, bDiffWindowVisible, chooseA, chooseB, chooseC); dirShowBoth->setEnabled(m_bDirCompare); dirViewToggle->setEnabled( m_bDirCompare && ((m_pDirectoryMergeSplitter != nullptr && m_pMainWidget != nullptr) && ((!m_pDirectoryMergeSplitter->isVisible() && m_pMainWidget->isVisible()) || (m_pDirectoryMergeSplitter->isVisible() && !m_pMainWidget->isVisible() && bTextDataAvailable)))); bool bDirWindowHasFocus = m_pDirectoryMergeSplitter != nullptr && m_pDirectoryMergeSplitter->isVisible() && m_pDirectoryMergeWindow->hasFocus(); showWhiteSpaceCharacters->setEnabled(bDiffWindowVisible); autoAdvance->setEnabled(bMergeEditorVisible); mAutoSolve->setEnabled(bMergeEditorVisible && m_bTripleDiff); mUnsolve->setEnabled(bMergeEditorVisible); if(!bDirWindowHasFocus) { chooseA->setEnabled(bMergeEditorVisible); chooseB->setEnabled(bMergeEditorVisible); chooseC->setEnabled(bMergeEditorVisible && m_bTripleDiff); } if(m_pMergeResultWindow != nullptr) { m_pMergeResultWindow->slotUpdateAvailabilities(bMergeEditorVisible, m_bTripleDiff); } mMergeHistory->setEnabled(bMergeEditorVisible); mergeRegExp->setEnabled(bMergeEditorVisible); showWindowA->setEnabled(bDiffWindowVisible && (m_pDiffTextWindow2->isVisible() || m_pDiffTextWindow3->isVisible())); showWindowB->setEnabled(bDiffWindowVisible && (m_pDiffTextWindow1->isVisible() || m_pDiffTextWindow3->isVisible())); showWindowC->setEnabled(bDiffWindowVisible && m_bTripleDiff && (m_pDiffTextWindow1->isVisible() || m_pDiffTextWindow2->isVisible())); editFind->setEnabled(bDiffWindowVisible); editFindNext->setEnabled(bDiffWindowVisible); m_pFindDialog->m_pSearchInC->setEnabled(m_bTripleDiff); m_pFindDialog->m_pSearchInOutput->setEnabled(bMergeEditorVisible); bool bSavable = bMergeEditorVisible && m_pMergeResultWindow->getNrOfUnsolvedConflicts() == 0; fileSave->setEnabled(m_bOutputModified && bSavable); fileSaveAs->setEnabled(bSavable); mGoTop->setEnabled(bDiffWindowVisible && m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isDeltaAboveCurrent()); mGoBottom->setEnabled(bDiffWindowVisible && m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isDeltaBelowCurrent()); mGoCurrent->setEnabled(bDiffWindowVisible); mGoPrevUnsolvedConflict->setEnabled(bMergeEditorVisible && m_pMergeResultWindow->isUnsolvedConflictAboveCurrent()); mGoNextUnsolvedConflict->setEnabled(bMergeEditorVisible && m_pMergeResultWindow->isUnsolvedConflictBelowCurrent()); mGoPrevConflict->setEnabled(bDiffWindowVisible && bMergeEditorVisible && m_pMergeResultWindow->isConflictAboveCurrent()); mGoNextConflict->setEnabled(bDiffWindowVisible && bMergeEditorVisible && m_pMergeResultWindow->isConflictBelowCurrent()); mGoPrevDelta->setEnabled(bDiffWindowVisible && m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isDeltaAboveCurrent()); mGoNextDelta->setEnabled(bDiffWindowVisible && m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isDeltaBelowCurrent()); overviewModeNormal->setEnabled(m_bTripleDiff && bDiffWindowVisible); overviewModeAB->setEnabled(m_bTripleDiff && bDiffWindowVisible); overviewModeAC->setEnabled(m_bTripleDiff && bDiffWindowVisible); overviewModeBC->setEnabled(m_bTripleDiff && bDiffWindowVisible); e_OverviewMode overviewMode = m_pOverview == nullptr ? e_OverviewMode::eOMNormal : m_pOverview->getOverviewMode(); overviewModeNormal->setChecked(overviewMode == e_OverviewMode::eOMNormal); overviewModeAB->setChecked(overviewMode == e_OverviewMode::eOMAvsB); overviewModeAC->setChecked(overviewMode == e_OverviewMode::eOMAvsC); overviewModeBC->setChecked(overviewMode == e_OverviewMode::eOMBvsC); winToggleSplitOrientation->setEnabled(bDiffWindowVisible && m_pDiffWindowSplitter != nullptr); }