diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f7b5cc0..973eadd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,65 +1,66 @@ ########### kdiff3 KPart ############### find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Parts WidgetsAddons) set(kdiff3part_PART_SRCS kdiff3_part.cpp kdiff3.cpp directorymergewindow.cpp merger.cpp pdiff.cpp difftextwindow.cpp diff.cpp optiondialog.cpp mergeresultwindow.cpp fileaccess.cpp gnudiff_analyze.cpp gnudiff_io.cpp gnudiff_xmalloc.cpp common.cpp smalldialogs.cpp progress.cpp ProgressProxyExtender.cpp PixMapUtils.cpp MergeFileInfos.cpp Utils.cpp selection.cpp cvsignorelist.cpp - SourceData.cpp ) + SourceData.cpp + Overview.cpp ) add_library(kdiff3part MODULE ${kdiff3part_PART_SRCS}) set_target_properties(kdiff3part PROPERTIES DEFINE_SYMBOL KDIFF3_PART) target_compile_features(kdiff3part PRIVATE ${needed_features}) target_link_libraries(kdiff3part ${KDiff3_LIBRARIES} KF5::Parts) install(TARGETS kdiff3part DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/parts ) ########### kdiff3 executable ############### find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Parts WidgetsAddons Config) set(kdiff3_SRCS main.cpp kdiff3_shell.cpp ${kdiff3part_PART_SRCS} ) add_executable(kdiff3 ${kdiff3_SRCS}) target_link_libraries(kdiff3 KF5::ConfigCore KF5::ConfigGui KF5::Parts ${KDiff3_LIBRARIES} ) target_compile_features(kdiff3 PRIVATE ${needed_features}) install(TARGETS kdiff3 ${INSTALL_TARGETS_DEFAULT_ARGS}) ########### install files ############### install( FILES kdiff3part.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} ) install( FILES kdiff3_part.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/kdiff3part ) install( FILES kdiff3_shell.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/kdiff3 ) #install( PROGRAMS kdiff3.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) install( PROGRAMS org.kde.kdiff3.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) install( FILES org.kde.kdiff3.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR} ) add_subdirectory(icons) diff --git a/src/Overview.cpp b/src/Overview.cpp new file mode 100644 index 0000000..7c8dca8 --- /dev/null +++ b/src/Overview.cpp @@ -0,0 +1,314 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Joachim Eibl * + * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#include "Overview.h" +#include "mergeresultwindow.h" +#include "options.h" +#include "diff.h" + +#include +#include +#include + +Overview::Overview(Options* pOptions) +//: QWidget( pParent, 0, Qt::WNoAutoErase ) +{ + m_pDiff3LineList = nullptr; + m_pOptions = pOptions; + m_bTripleDiff = false; + m_eOverviewMode = eOMNormal; + m_nofLines = 1; + setUpdatesEnabled(false); + m_firstLine = 0; + m_pageHeight = 0; + + setFixedWidth(20); +} + +void Overview::init(Diff3LineList* pDiff3LineList, bool bTripleDiff) +{ + m_pDiff3LineList = pDiff3LineList; + m_bTripleDiff = bTripleDiff; + m_pixmap = QPixmap(QSize(0, 0)); // make sure that a redraw happens + update(); +} + +void Overview::reset() +{ + m_pDiff3LineList = nullptr; +} + +void Overview::slotRedraw() +{ + m_pixmap = QPixmap(QSize(0, 0)); // make sure that a redraw happens + update(); +} + +void Overview::setRange(int firstLine, int pageHeight) +{ + m_firstLine = firstLine; + m_pageHeight = pageHeight; + update(); +} +void Overview::setFirstLine(int firstLine) +{ + m_firstLine = firstLine; + update(); +} + +void Overview::setOverviewMode(e_OverviewMode eOverviewMode) +{ + m_eOverviewMode = eOverviewMode; + slotRedraw(); +} + +Overview::e_OverviewMode Overview::getOverviewMode() +{ + return m_eOverviewMode; +} + +void Overview::mousePressEvent(QMouseEvent* e) +{ + int h = height() - 1; + int h1 = h * m_pageHeight / std::max(1, m_nofLines) + 3; + if(h > 0) + emit setLine((e->y() - h1 / 2) * m_nofLines / h); +} + +void Overview::mouseMoveEvent(QMouseEvent* e) +{ + mousePressEvent(e); +} + +void Overview::setPaintingAllowed(bool bAllowPainting) +{ + if(updatesEnabled() != bAllowPainting) + { + + setUpdatesEnabled(bAllowPainting); + if(bAllowPainting) + update(); + else + reset(); + } +} + +void Overview::drawColumn(QPainter& p, e_OverviewMode eOverviewMode, int x, int w, int h, int nofLines) +{ + p.setPen(Qt::black); + p.drawLine(x, 0, x, h); + + if(nofLines == 0) return; + + int line = 0; + int oldY = 0; + int oldConflictY = -1; + int wrapLineIdx = 0; + Diff3LineList::const_iterator i; + for(i = m_pDiff3LineList->begin(); i != m_pDiff3LineList->end();) + { + const Diff3Line& d3l = *i; + int y = h * (line + 1) / nofLines; + e_MergeDetails md; + bool bConflict; + bool bLineRemoved; + int src; + mergeOneLine(d3l, md, bConflict, bLineRemoved, src, !m_bTripleDiff); + + QColor c = m_pOptions->m_bgColor; + bool bWhiteSpaceChange = false; + //if( bConflict ) c=m_pOptions->m_colorForConflict; + //else + if(eOverviewMode == eOMNormal) + { + switch(md) + { + case eDefault: + case eNoChange: + c = m_pOptions->m_bgColor; + break; + + case eBAdded: + case eBDeleted: + case eBChanged: + c = bConflict ? m_pOptions->m_colorForConflict : m_pOptions->m_colorB; + bWhiteSpaceChange = d3l.bAEqB || (d3l.bWhiteLineA && d3l.bWhiteLineB); + break; + + case eCAdded: + case eCDeleted: + case eCChanged: + bWhiteSpaceChange = d3l.bAEqC || (d3l.bWhiteLineA && d3l.bWhiteLineC); + c = bConflict ? m_pOptions->m_colorForConflict : m_pOptions->m_colorC; + break; + + case eBCChanged: // conflict + case eBCChangedAndEqual: // possible conflict + case eBCDeleted: // possible conflict + case eBChanged_CDeleted: // conflict + case eCChanged_BDeleted: // conflict + case eBCAdded: // conflict + case eBCAddedAndEqual: // possible conflict + c = m_pOptions->m_colorForConflict; + break; + default: + Q_ASSERT(true); + break; + } + } + else if(eOverviewMode == eOMAvsB) + { + switch(md) + { + case eDefault: + case eNoChange: + case eCAdded: + case eCDeleted: + case eCChanged: + break; + default: + c = m_pOptions->m_colorForConflict; + bWhiteSpaceChange = d3l.bAEqB || (d3l.bWhiteLineA && d3l.bWhiteLineB); + break; + } + } + else if(eOverviewMode == eOMAvsC) + { + switch(md) + { + case eDefault: + case eNoChange: + case eBAdded: + case eBDeleted: + case eBChanged: + break; + default: + c = m_pOptions->m_colorForConflict; + bWhiteSpaceChange = d3l.bAEqC || (d3l.bWhiteLineA && d3l.bWhiteLineC); + break; + } + } + else if(eOverviewMode == eOMBvsC) + { + switch(md) + { + case eDefault: + case eNoChange: + case eBCChangedAndEqual: + case eBCDeleted: + case eBCAddedAndEqual: + break; + default: + c = m_pOptions->m_colorForConflict; + bWhiteSpaceChange = d3l.bBEqC || (d3l.bWhiteLineB && d3l.bWhiteLineC); + break; + } + } + + int x2 = x; + int w2 = w; + + if(!m_bTripleDiff) + { + if(d3l.getLineA() == -1 && d3l.getLineB() >= 0) + { + c = m_pOptions->m_colorA; + x2 = w / 2; + w2 = x2; + } + if(d3l.getLineA() >= 0 && d3l.getLineB() == -1) + { + c = m_pOptions->m_colorB; + w2 = w / 2; + } + } + + if(!bWhiteSpaceChange || m_pOptions->m_bShowWhiteSpace) + { + // Make sure that lines with conflict are not overwritten. + if(c == m_pOptions->m_colorForConflict) + { + p.fillRect(x2 + 1, oldY, w2, std::max(1, y - oldY), bWhiteSpaceChange ? QBrush(c, Qt::Dense4Pattern) : QBrush(c)); + oldConflictY = oldY; + } + else if(c != m_pOptions->m_bgColor && oldY > oldConflictY) + { + p.fillRect(x2 + 1, oldY, w2, std::max(1, y - oldY), bWhiteSpaceChange ? QBrush(c, Qt::Dense4Pattern) : QBrush(c)); + } + } + + oldY = y; + + ++line; + if(m_pOptions->m_bWordWrap) + { + ++wrapLineIdx; + if(wrapLineIdx >= d3l.linesNeededForDisplay) + { + wrapLineIdx = 0; + ++i; + } + } + else + { + ++i; + } + } +} + +void Overview::paintEvent(QPaintEvent*) +{ + if(m_pDiff3LineList == nullptr) return; + int h = height() - 1; + int w = width(); + + if(m_pixmap.size() != size()) + { + if(m_pOptions->m_bWordWrap) + { + m_nofLines = 0; + Diff3LineList::const_iterator i; + for(i = m_pDiff3LineList->begin(); i != m_pDiff3LineList->end(); ++i) + { + m_nofLines += i->linesNeededForDisplay; + } + } + else + { + m_nofLines = m_pDiff3LineList->size(); + } + + m_pixmap = QPixmap(size()); + + QPainter p(&m_pixmap); + p.fillRect(rect(), m_pOptions->m_bgColor); + + if(!m_bTripleDiff || m_eOverviewMode == eOMNormal) + { + drawColumn(p, eOMNormal, 0, w, h, m_nofLines); + } + else + { + drawColumn(p, eOMNormal, 0, w / 2, h, m_nofLines); + drawColumn(p, m_eOverviewMode, w / 2, w / 2, h, m_nofLines); + } + } + + QPainter painter(this); + painter.drawPixmap(0, 0, m_pixmap); + int y1=0, h1=0; + if(m_nofLines > 0) + { + y1 = h * m_firstLine / m_nofLines - 1; + h1 = h * m_pageHeight / m_nofLines + 3; + } + painter.setPen(Qt::black); + painter.drawRect(1, y1, w - 1, h1); +} diff --git a/src/Overview.h b/src/Overview.h new file mode 100644 index 0000000..b01ca39 --- /dev/null +++ b/src/Overview.h @@ -0,0 +1,56 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Joachim Eibl * + * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifndef OVERVIEW_H +#define OVERVIEW_H + +#include "options.h" +#include "diff.h" + +#include +#include + +class Overview : public QWidget +{ + Q_OBJECT +public: + explicit Overview( Options* pOptions ); + + void init( Diff3LineList* pDiff3LineList, bool bTripleDiff ); + void reset(); + void setRange( int firstLine, int pageHeight ); + void setPaintingAllowed( bool bAllowPainting ); + + enum e_OverviewMode { eOMNormal, eOMAvsB, eOMAvsC, eOMBvsC }; + void setOverviewMode( e_OverviewMode eOverviewMode ); + e_OverviewMode getOverviewMode(); + +public Q_SLOTS: + void setFirstLine(int firstLine); + void slotRedraw(); +Q_SIGNALS: + void setLine(int); +private: + const Diff3LineList* m_pDiff3LineList; + Options* m_pOptions; + bool m_bTripleDiff; + int m_firstLine; + int m_pageHeight; + QPixmap m_pixmap; + e_OverviewMode m_eOverviewMode; + int m_nofLines; + + void paintEvent( QPaintEvent* e ) override; + void mousePressEvent( QMouseEvent* e ) override; + void mouseMoveEvent( QMouseEvent* e ) override; + void drawColumn( QPainter& p, e_OverviewMode eOverviewMode, int x, int w, int h, int nofLines ); +}; + +#endif // !OVERVIEW_H diff --git a/src/mergeresultwindow.cpp b/src/mergeresultwindow.cpp index c9af2a5..0b11cdd 100644 --- a/src/mergeresultwindow.cpp +++ b/src/mergeresultwindow.cpp @@ -1,3615 +1,3313 @@ /*************************************************************************** - mergeresultwindow.cpp - description - ------------------- - begin : Sun Apr 14 2002 - copyright : (C) 2002-2007 by Joachim Eibl - email : joachim.eibl at gmx.de - ***************************************************************************/ - -/*************************************************************************** + * Copyright (C) 2002-2007 by Joachim Eibl * + * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mergeresultwindow.h" #include "options.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include int g_bAutoSolve = true; #undef leftInfoWidth MergeResultWindow::MergeResultWindow( QWidget* pParent, Options* pOptions, QStatusBar* pStatusBar) : QWidget(pParent) { setObjectName("MergeResultWindow"); setFocusPolicy(Qt::ClickFocus); m_firstLine = 0; m_horizScrollOffset = 0; m_nofLines = 0; m_totalSize = 0; m_bMyUpdate = false; m_bInsertMode = true; m_scrollDeltaX = 0; m_scrollDeltaY = 0; m_bModified = false; m_eOverviewMode = Overview::eOMNormal; m_pldA = nullptr; m_pldB = nullptr; m_pldC = nullptr; m_sizeA = 0; m_sizeB = 0; m_sizeC = 0; m_pDiff3LineList = nullptr; m_pTotalDiffStatus = nullptr; m_pStatusBar = pStatusBar; if(m_pStatusBar) connect(m_pStatusBar, &QStatusBar::messageChanged, this, &MergeResultWindow::slotStatusMessageChanged); m_pOptions = pOptions; setUpdatesEnabled(false); m_delayedDrawTimer = 0; m_cursorXPos = 0; m_cursorOldXPixelPos = 0; m_cursorYPos = 0; m_bCursorOn = true; m_bCursorUpdate = false; m_maxTextWidth = -1; 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 LineData* pLineDataA, LineRef sizeA, const LineData* pLineDataB, LineRef sizeB, const LineData* 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, -1); g_bAutoSolve = true; update(); updateSourceMask(); showUnsolvedConflictsStatusMessage(); } void MergeResultWindow::showUnsolvedConflictsStatusMessage() { if(m_pStatusBar) { int wsc; int nofUnsolved = getNrOfUnsolvedConflicts(&wsc); m_persistentStatusMessage = i18n("Number of remaining unsolved conflicts: %1 (of which %2 are whitespace)", nofUnsolved, wsc); m_pStatusBar->showMessage(m_persistentStatusMessage); } } void MergeResultWindow::slotStatusMessageChanged(const QString& s) { if(s.isEmpty() && !m_persistentStatusMessage.isEmpty()) { m_pStatusBar->showMessage(m_persistentStatusMessage, 0); } } 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 mergeOneLine( const Diff3Line& d, e_MergeDetails& mergeDetails, bool& bConflict, bool& bLineRemoved, int& src, bool bTwoInputs) { mergeDetails = eDefault; bConflict = false; bLineRemoved = false; src = 0; if(bTwoInputs) // Only two input files { if(d.getLineA() != -1 && d.getLineB() != -1) { if(d.pFineAB == nullptr) { mergeDetails = eNoChange; src = A; } else { mergeDetails = eBChanged; bConflict = true; } } else { if(d.getLineA() != -1 && d.getLineB() == -1) { mergeDetails = eBDeleted; bConflict = true; } else if(d.getLineA() == -1 && d.getLineB() != -1) { mergeDetails = eBDeleted; bConflict = true; } } return; } // A is base. if(d.getLineA() != -1 && d.getLineB() != -1 && d.getLineC() != -1) { if(d.pFineAB == nullptr && d.pFineBC == nullptr && d.pFineCA == nullptr) { mergeDetails = eNoChange; src = A; } else if(d.pFineAB == nullptr && d.pFineBC != nullptr && d.pFineCA != nullptr) { mergeDetails = eCChanged; src = C; } else if(d.pFineAB != nullptr && d.pFineBC != nullptr && d.pFineCA == nullptr) { mergeDetails = eBChanged; src = B; } else if(d.pFineAB != nullptr && d.pFineBC == nullptr && d.pFineCA != nullptr) { mergeDetails = eBCChangedAndEqual; src = C; } else if(d.pFineAB != nullptr && d.pFineBC != nullptr && d.pFineCA != nullptr) { mergeDetails = eBCChanged; bConflict = true; } else Q_ASSERT(true); } else if(d.getLineA() != -1 && d.getLineB() != -1 && d.getLineC() == -1) { if(d.pFineAB != nullptr) { mergeDetails = eBChanged_CDeleted; bConflict = true; } else { mergeDetails = eCDeleted; bLineRemoved = true; src = C; } } else if(d.getLineA() != -1 && d.getLineB() == -1 && d.getLineC() != -1) { if(d.pFineCA != nullptr) { mergeDetails = eCChanged_BDeleted; bConflict = true; } else { mergeDetails = eBDeleted; bLineRemoved = true; src = B; } } else if(d.getLineA() == -1 && d.getLineB() != -1 && d.getLineC() != -1) { if(d.pFineBC != nullptr) { mergeDetails = eBCAdded; bConflict = true; } else // B==C { mergeDetails = eBCAddedAndEqual; src = C; } } else if(d.getLineA() == -1 && d.getLineB() == -1 && d.getLineC() != -1) { mergeDetails = eCAdded; src = C; } else if(d.getLineA() == -1 && d.getLineB() != -1 && d.getLineC() == -1) { mergeDetails = eBAdded; src = B; } else if(d.getLineA() != -1 && d.getLineB() == -1 && d.getLineC() == -1) { mergeDetails = eBCDeleted; bLineRemoved = true; src = 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->bAEqC == ml2.id3l->bAEqC && ml1.id3l->bAEqB == ml2.id3l->bAEqB; } else return ( (!ml1.bConflict && !ml2.bConflict && ml1.bDelta && ml2.bDelta && ml1.srcSelect == ml2.srcSelect && (ml1.mergeDetails == ml2.mergeDetails || (ml1.mergeDetails != eBCAddedAndEqual && ml2.mergeDetails != eBCAddedAndEqual))) || (!ml1.bDelta && !ml2.bDelta)); } void MergeResultWindow::merge(bool bAutoSolve, int 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(); m_totalSize = 0; 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; mergeOneLine(d, ml.mergeDetails, ml.bConflict, bLineRemoved, ml.srcSelect, m_pldC == nullptr); // Automatic solving for only whitespace changes. if(ml.bConflict && ((m_pldC == nullptr && (d.bAEqB || (d.bWhiteLineA && d.bWhiteLineB))) || (m_pldC != nullptr && ((d.bAEqB && d.bAEqC) || (d.bWhiteLineA && d.bWhiteLineB && d.bWhiteLineC))))) { ml.bWhiteSpaceConflict = true; } ml.d3lLineIdx = lineIdx; ml.bDelta = ml.srcSelect != 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 { ml.mergeEditLineList.setTotalSizePtr(&m_totalSize); 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 != 0) // Only two inputs { defaultSelector = m_pOptions->m_whiteSpace2FileMergeDefault; bWhiteSpaceOnly = true; bSolveWhiteSpaceConflicts = true; } else if(m_pldC != nullptr && m_pOptions->m_whiteSpace3FileMergeDefault != 0) { defaultSelector = 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 == -1 && 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 == 1 ? d3llit->getLineA() : defaultSelector == 2 ? d3llit->getLineB() : defaultSelector == 3 ? d3llit->getLineC() : -1; if(srcLine != -1) { 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 = -1; int oldSrc = -1; MergeEditLineList::iterator melIt; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end();) { MergeEditLine& mel = *melIt; int melsrc = mel.src(); LineRef srcLine = mel.isRemoved() ? -1 : melsrc == 1 ? mel.id3l()->getLineA() : melsrc == 2 ? mel.id3l()->getLineB() : melsrc == 3 ? mel.id3l()->getLineC() : -1; // At least one line remains because oldSrc != melsrc for first line in list // Other empty lines will be removed if(srcLine == -1 && oldSrcLine == -1 && 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()) 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(); emit updateAvailabilities(); update(); } void MergeResultWindow::setFirstLine(int 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(this); 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() { return m_totalSize; } int MergeResultWindow::getVisibleTextAreaWidth() { // QFontMetrics fm = fontMetrics(); // FIXME used? return width() - getTextXOffset(); } int MergeResultWindow::getNofVisibleLines() { QFontMetrics fm = fontMetrics(); return (height() - 3) / fm.lineSpacing() - 2; } int MergeResultWindow::getTextXOffset() { QFontMetrics fm = fontMetrics(); return 3 * fm.width('0'); } void MergeResultWindow::resizeEvent(QResizeEvent* e) { QWidget::resizeEvent(e); emit resizeSignal(); } Overview::e_OverviewMode MergeResultWindow::getOverviewMode() { return m_eOverviewMode; } void MergeResultWindow::setOverviewMode(Overview::e_OverviewMode eOverviewMode) { m_eOverviewMode = eOverviewMode; } // Check whether we should ignore current delta when moving to next/previous delta bool MergeResultWindow::checkOverviewIgnore(MergeLineList::iterator& i) { if(m_eOverviewMode == Overview::eOMNormal) return false; if(m_eOverviewMode == Overview::eOMAvsB) return i->mergeDetails == eCAdded || i->mergeDetails == eCDeleted || i->mergeDetails == eCChanged; if(m_eOverviewMode == Overview::eOMAvsC) return i->mergeDetails == eBAdded || i->mergeDetails == eBDeleted || i->mergeDetails == eBChanged; if(m_eOverviewMode == Overview::eOMBvsC) return i->mergeDetails == eBCAddedAndEqual || i->mergeDetails == eBCDeleted || i->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(int 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->bBinaryAEqB && m_pTotalDiffStatus->bBinaryAEqC) totalInfo += i18n("All input files are binary equal."); else if(m_pTotalDiffStatus->bTextAEqB && m_pTotalDiffStatus->bTextAEqC) totalInfo += i18n("All input files contain the same text."); else { if(m_pTotalDiffStatus->bBinaryAEqB) totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("A"), i18n("B")); else if(m_pTotalDiffStatus->bTextAEqB) totalInfo += i18n("Files %1 and %2 have equal text.\n", i18n("A"), i18n("B")); if(m_pTotalDiffStatus->bBinaryAEqC) totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("A"), i18n("C")); else if(m_pTotalDiffStatus->bTextAEqC) totalInfo += i18n("Files %1 and %2 have equal text.\n", i18n("A"), i18n("C")); if(m_pTotalDiffStatus->bBinaryBEqC) totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("B"), i18n("C")); else if(m_pTotalDiffStatus->bTextBEqC) 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; 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) { emit scrollMergeResultWindow(0, newFirstLine - m_firstLine); } if(m_selection.isEmpty()) { m_cursorXPos = 0; m_cursorOldXPixelPos = 0; m_cursorYPos = line1; } update(); updateSourceMask(); emit updateAvailabilities(); } void MergeResultWindow::choose(int 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() == 1 ? mel.id3l()->getLineA() : mel.src() == 2 ? mel.id3l()->getLineB() : mel.src() == 3 ? mel.id3l()->getLineC() : -1; if(srcLine == -1) 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_totalSize) { m_cursorYPos = m_totalSize - 1; m_cursorXPos = 0; } m_maxTextWidth = -1; update(); updateSourceMask(); emit updateAvailabilities(); showUnsolvedConflictsStatusMessage(); } // bConflictsOnly: automatically choose for conflicts only (true) or for everywhere (false) void MergeResultWindow::chooseGlobal(int selector, bool bConflictsOnly, bool bWhiteSpaceOnly) { resetSelection(); merge(false, selector, bConflictsOnly, bWhiteSpaceOnly); setModified(true); update(); showUnsolvedConflictsStatusMessage(); } void MergeResultWindow::slotAutoSolve() { resetSelection(); merge(true, -1); setModified(true); update(); showUnsolvedConflictsStatusMessage(); } void MergeResultWindow::slotUnsolve() { resetSelection(); merge(false, -1); setModified(true); update(); showUnsolvedConflictsStatusMessage(); } static QString calcHistoryLead(const QString& s) { // Return the start of the line until the first white char after the first non white char. int i; for(i = 0; i < s.length(); ++i) { if(s[i] != ' ' && s[i] != '\t') { for(; i < s.length(); ++i) { if(s[i] == ' ' || s[i] == '\t') { return s.left(i); } } return s; // Very unlikely } } return QString(); // Must be an empty string, not a null string. } static void findHistoryRange(const QRegExp& historyStart, bool bThreeFiles, const Diff3LineList* pD3LList, Diff3LineList::const_iterator& iBegin, Diff3LineList::const_iterator& iEnd, int& idxBegin, int& idxEnd) { QString historyLead; // Search for start of history for(iBegin = pD3LList->begin(), idxBegin = 0; iBegin != pD3LList->end(); ++iBegin, ++idxBegin) { if(historyStart.exactMatch(iBegin->getString(A)) && historyStart.exactMatch(iBegin->getString(B)) && (!bThreeFiles || historyStart.exactMatch(iBegin->getString(C)))) { historyLead = calcHistoryLead(iBegin->getString(A)); break; } } // Search for end of history for(iEnd = iBegin, idxEnd = idxBegin; iEnd != pD3LList->end(); ++iEnd, ++idxEnd) { QString sA = iEnd->getString(A); QString sB = iEnd->getString(B); QString sC = iEnd->getString(C); if(!((sA.isEmpty() || historyLead == calcHistoryLead(sA)) && (sB.isEmpty() || historyLead == calcHistoryLead(sB)) && (!bThreeFiles || sC.isEmpty() || historyLead == calcHistoryLead(sC)))) { break; // End of the history } } } 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) { QStringList keyOrderList = keyOrder.split(','); QString key; for(QStringList::iterator keyIt = keyOrderList.begin(); keyIt != keyOrderList.end(); ++keyIt) { if((*keyIt).isEmpty()) continue; bool bOk = false; int groupIdx = (*keyIt).toInt(&bOk); if(!bOk || groupIdx < 0 || groupIdx > (int)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.sprintf("%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.sprintf("%02d", idx + 1); // Up to 99 words in the groupRegExp (more than 12 aren't expected) key += sIdx + ' '; } } } return key; } void MergeResultWindow::collectHistoryInformation( int 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); QString s(pld->getLine(), pld->size()); historyLead = calcHistoryLead(s); } 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; QString s(pld->getLine(), pld->size()); if(historyLead.isEmpty()) historyLead = calcHistoryLead(s); QString sLine = s.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 == A) hme.mellA = melList; if(src == B) hme.mellB = melList; if(src == 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(s)) { 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 == A) hme.mellA = melList; if(src == B) hme.mellB = melList; if(src == C) hme.mellC = melList; if(p.second) // Not in list yet? { hitList.insert(itHitListFront, p.first); } } // End of the history } MergeResultWindow::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 findHistoryRange(QRegExp(m_pOptions->m_historyStartRegExp), m_pldC != nullptr, m_pDiff3LineList, iD3LHistoryBegin, iD3LHistoryEnd, d3lHistoryBeginLineIdx, d3lHistoryEndLineIdx); if(iD3LHistoryBegin != m_pDiff3LineList->end()) { // Now collect the historyMap information HistoryMap historyMap; std::list hitList; if(m_pldC == nullptr) { collectHistoryInformation(A, iD3LHistoryBegin, iD3LHistoryEnd, historyMap, hitList); collectHistoryInformation(B, iD3LHistoryBegin, iD3LHistoryEnd, historyMap, hitList); } else { collectHistoryInformation(A, iD3LHistoryBegin, iD3LHistoryEnd, historyMap, hitList); collectHistoryInformation(B, iD3LHistoryBegin, iD3LHistoryEnd, historyMap, hitList); collectHistoryInformation(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 ? B : C)); QString lead = calcHistoryLead(iD3LHistoryBegin->getString(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(this); QString firstLineOfEnd = iMLLEnd->mergeEditLineList.front().getString(this); 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(A)) && vcsKeywords.exactMatch(id3l->getString(B)) && (m_pldC == nullptr || vcsKeywords.exactMatch(id3l->getString(C)))) { MergeEditLine& mel = *i->mergeEditLineList.begin(); mel.setSource(m_pldC == nullptr ? B : 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() != C) || i->srcSelect == B) { return true; } } return false; } // Returns the iterator to the MergeLine after the split MergeResultWindow::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); emit scrollMergeResultWindow(m_scrollDeltaX, m_scrollDeltaY); killTimer(m_delayedDrawTimer); m_delayedDrawTimer = startTimer(50); } } QString MergeResultWindow::MergeEditLine::getString(const MergeResultWindow* mrw) { if(isRemoved()) { return QString(); } if(!isModified()) { int src = m_src; if(src == 0) { return QString(); } const Diff3Line& d3l = *m_id3l; const LineData* pld = nullptr; Q_ASSERT(src == A || src == B || src == C); if(src == A && d3l.getLineA() != -1) pld = &mrw->m_pldA[d3l.getLineA()]; else if(src == B && d3l.getLineB() != -1) pld = &mrw->m_pldB[d3l.getLineB()]; else if(src == C && d3l.getLineC() != -1) pld = &mrw->m_pldC[d3l.getLineC()]; //Not an error. if(pld == nullptr) { return QString(); } return QString(pld->getLine(), pld->size()); } else { return m_str; } return QString(); } /// Converts the cursor-posOnScreen into a text index, considering tabulators. int convertToPosInText(const QString& /*s*/, int posOnScreen, int /*tabSize*/) { return posOnScreen; } // int localPosOnScreen = 0; // int size=s.length(); // for ( int i=0; i=posOnScreen ) // return i; // // All letters except tabulator have width one. // int letterWidth = s[i]!='\t' ? 1 : tabber( localPosOnScreen, tabSize ); // localPosOnScreen += letterWidth; // if ( localPosOnScreen>posOnScreen ) // return i; // } // return size; //} /// Converts the index into the text to a cursor-posOnScreen considering tabulators. int convertToPosOnScreen(const QString& /*p*/, int posInText, int /*tabSize*/) { return posInText; } // int posOnScreen = 0; // for ( int i=0; i 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 = convertToPosInText(str, m_selection.firstPosInLine(line), m_pOptions->m_tabSize); int lastPosInText = convertToPosInText(str, m_selection.lastPosInLine(line), m_pOptions->m_tabSize); 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( MyPainter& p, int line, const QString& str, int 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 == A && mergeDetails != eNoChange) srcName = i18n("A"); else if(srcSelect == B) srcName = i18n("B"); else if(srcSelect == C) srcName = i18n("C"); if(rangeMark & 4) { p.fillRect(xOffset, yOffset, width(), fontHeight, m_pOptions->m_currentRangeBgColor); } if((srcSelect > 0 || 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 == 0) { 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 -= fm.width('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 = fm.width('0'); if(!m_bCursorUpdate) // Don't redraw everything for blinking cursor? { m_selection.bSelectionContainsData = false; if(size() != m_pixmap.size()) m_pixmap = QPixmap(size()); MyPainter 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; int 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(this); writeLine(p, line, s, mel.src(), ml.mergeDetails, rangeMark, mel.isModified(), mel.isRemoved(), ml.bWhiteSpaceConflict); } ++line; } } } if(line != m_nofLines) { m_nofLines = line; Q_ASSERT(m_nofLines == m_totalSize); 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()) 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() == 1) srcMask |= 1; if(mel.src() == 2) srcMask |= 2; if(mel.src() == 3) srcMask |= 4; if(mel.isModified() || !mel.isEditableText()) bModified = true; } if(ml.mergeDetails == eNoChange) { srcMask = 0; enabledMask = bModified ? 1 : 0; } } emit sourceMask(srcMask, enabledMask); } void MergeResultWindow::focusInEvent(QFocusEvent* e) { updateSourceMask(); QWidget::focusInEvent(e); } int MergeResultWindow::convertToLine(int y) { const QFontMetrics& fm = fontMetrics(); int fontHeight = fm.lineSpacing(); int topLineYOffset = 0; int yOffset = topLineYOffset - m_firstLine * fontHeight; int line = std::min((y - yOffset) / fontHeight, m_totalSize - 1); return line; } void MergeResultWindow::mousePressEvent(QMouseEvent* e) { m_bCursorOn = true; int xOffset = getTextXOffset(); int 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()); 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(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) { emit showPopupMenu(QCursor::pos()); } } else if(bLMB) // Normal cursor placement { pos = std::max(pos, 0); line = std::max(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(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) { int 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; calcTokenPos(s, pos, pos1, pos2, m_pOptions->m_tabSize); resetSelection(); m_selection.start(line, convertToPosOnScreen(s, pos1, m_pOptions->m_tabSize)); m_selection.end(line, convertToPosOnScreen(s, pos2, m_pOptions->m_tabSize)); update(); // 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()) { emit selectionEnd(); } } } void MergeResultWindow::mouseMoveEvent(QMouseEvent* e) { int 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 = fm.width('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) { 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* e) { int d = -e->delta() * QApplication::wheelScrollLines() / 120; e->accept(); emit scrollMergeResultWindow(0, std::min(d, getNofVisibleLines())); } 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(this); int x = convertToPosInText(str, m_cursorXPos, m_pOptions->m_tabSize); 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: break; //case Key_Tab: break; case Qt::Key_Backtab: break; case Qt::Key_Delete: { if(deleteSelection2(str, x, y, mlIt, melIt)) break; if(!melIt->isEditableText()) break; if(x >= (int)str.length()) { if(y < m_totalSize - 1) { setModified(); MergeLineList::iterator mlIt1; MergeEditLineList::iterator melIt1; calcIteratorFromLineNr(y + 1, mlIt1, melIt1); if(melIt1->isEditableText()) { QString s2 = melIt1->getString(this); 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(this); 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(this); 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 < (int)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: break; case Qt::Key_Print: break; 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_totalSize - 1) { ++y; x = 0; } else { x = newX; } } else { while(x < (int)str.length() && (str[x] == ' ' || str[x] == '\t')) { int newX = textLayoutOrig.nextCursorPosition(x); if(newX == x) break; x = newX; } while(x < (int)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_totalSize - 1); calcIteratorFromLineNr(y, mlIt, melIt); str = melIt->getString(this); 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()); emit resizeSignal(); } if(!bYMoveKey) m_cursorOldXPixelPos = m_cursorXPixelPos; m_bCursorOn = true; m_cursorTimer.start(500); update(); if(newFirstLine != m_firstLine || newHorizScrollOffset != m_horizScrollOffset) { emit scrollMergeResultWindow(newHorizScrollOffset - m_horizScrollOffset, newFirstLine - m_firstLine); return; } } void MergeResultWindow::calcIteratorFromLineNr( int line, MergeResultWindow::MergeLineList::iterator& mlIt, MergeResultWindow::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(this); // 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(this); x = convertToPosInText(s, m_cursorXPos, m_pOptions->m_tabSize); return true; } return false; } void MergeResultWindow::deleteSelection() { if(!m_selection.selectionContainsData()) { return; } Q_ASSERT(m_selection.isValidFirstLine()); setModified(); int line = 0; MergeLineList::iterator mlItFirst; MergeEditLineList::iterator melItFirst; QString firstLineString; int firstLine = -1; int lastLine = -1; 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 == -1) firstLine = line; lastLine = line; } ++line; } } if(firstLine == -1) { 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(this); int firstPosInLine = m_selection.firstPosInLine(line); int lastPosInLine = m_selection.lastPosInLine(line); if(line == firstLine) { mlItFirst = mlIt; melItFirst = melIt; int pos = convertToPosInText(lineString, firstPosInLine, m_pOptions->m_tabSize); firstLineString = lineString.left(pos); } if(line == lastLine) { // This is the last line in the selection int pos = convertToPosInText(lineString, lastPosInLine, m_pOptions->m_tabSize); 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(this); int x = convertToPosInText(str, m_cursorXPos, m_pOptions->m_tabSize); 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 = convertToPosOnScreen(currentLine, x, m_pOptions->m_tabSize); m_cursorOldXPixelPos = m_cursorXPixelPos; update(); } void MergeResultWindow::resetSelection() { m_selection.reset(); update(); } void MergeResultWindow::setModified(bool bModified) { if(bModified != m_bModified) { m_bModified = bModified; 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(this); 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) { MergeResultWindow::MergeLineList::iterator mlIt; MergeResultWindow::MergeEditLineList::iterator melIt; calcIteratorFromLineNr(lineIdx, mlIt, melIt); QString s = melIt->getString(this); return s; } bool MergeResultWindow::findString(const QString& s, int& 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, convertToPosOnScreen(getString(firstLine), startPos, m_pOptions->m_tabSize)); m_selection.end(lastLine, convertToPosOnScreen(getString(lastLine), endPos, m_pOptions->m_tabSize)); update(); } -Overview::Overview(Options* pOptions) -//: QWidget( pParent, 0, Qt::WNoAutoErase ) -{ - m_pDiff3LineList = nullptr; - m_pOptions = pOptions; - m_bTripleDiff = false; - m_eOverviewMode = eOMNormal; - m_nofLines = 1; - setUpdatesEnabled(false); - m_firstLine = 0; - m_pageHeight = 0; - - setFixedWidth(20); -} - -void Overview::init(Diff3LineList* pDiff3LineList, bool bTripleDiff) -{ - m_pDiff3LineList = pDiff3LineList; - m_bTripleDiff = bTripleDiff; - m_pixmap = QPixmap(QSize(0, 0)); // make sure that a redraw happens - update(); -} - -void Overview::reset() -{ - m_pDiff3LineList = nullptr; -} - -void Overview::slotRedraw() -{ - m_pixmap = QPixmap(QSize(0, 0)); // make sure that a redraw happens - update(); -} - -void Overview::setRange(int firstLine, int pageHeight) -{ - m_firstLine = firstLine; - m_pageHeight = pageHeight; - update(); -} -void Overview::setFirstLine(int firstLine) -{ - m_firstLine = firstLine; - update(); -} - -void Overview::setOverviewMode(e_OverviewMode eOverviewMode) -{ - m_eOverviewMode = eOverviewMode; - slotRedraw(); -} - -Overview::e_OverviewMode Overview::getOverviewMode() -{ - return m_eOverviewMode; -} - -void Overview::mousePressEvent(QMouseEvent* e) -{ - int h = height() - 1; - int h1 = h * m_pageHeight / std::max(1, m_nofLines) + 3; - if(h > 0) - emit setLine((e->y() - h1 / 2) * m_nofLines / h); -} - -void Overview::mouseMoveEvent(QMouseEvent* e) -{ - mousePressEvent(e); -} - -void Overview::setPaintingAllowed(bool bAllowPainting) -{ - if(updatesEnabled() != bAllowPainting) - { - - setUpdatesEnabled(bAllowPainting); - if(bAllowPainting) - update(); - else - reset(); - } -} - -void Overview::drawColumn(QPainter& p, e_OverviewMode eOverviewMode, int x, int w, int h, int nofLines) -{ - p.setPen(Qt::black); - p.drawLine(x, 0, x, h); - - if(nofLines == 0) return; - - int line = 0; - int oldY = 0; - int oldConflictY = -1; - int wrapLineIdx = 0; - Diff3LineList::const_iterator i; - for(i = m_pDiff3LineList->begin(); i != m_pDiff3LineList->end();) - { - const Diff3Line& d3l = *i; - int y = h * (line + 1) / nofLines; - e_MergeDetails md; - bool bConflict; - bool bLineRemoved; - int src; - mergeOneLine(d3l, md, bConflict, bLineRemoved, src, !m_bTripleDiff); - - QColor c = m_pOptions->m_bgColor; - bool bWhiteSpaceChange = false; - //if( bConflict ) c=m_pOptions->m_colorForConflict; - //else - if(eOverviewMode == eOMNormal) - { - switch(md) - { - case eDefault: - case eNoChange: - c = m_pOptions->m_bgColor; - break; - - case eBAdded: - case eBDeleted: - case eBChanged: - c = bConflict ? m_pOptions->m_colorForConflict : m_pOptions->m_colorB; - bWhiteSpaceChange = d3l.bAEqB || (d3l.bWhiteLineA && d3l.bWhiteLineB); - break; - - case eCAdded: - case eCDeleted: - case eCChanged: - bWhiteSpaceChange = d3l.bAEqC || (d3l.bWhiteLineA && d3l.bWhiteLineC); - c = bConflict ? m_pOptions->m_colorForConflict : m_pOptions->m_colorC; - break; - - case eBCChanged: // conflict - case eBCChangedAndEqual: // possible conflict - case eBCDeleted: // possible conflict - case eBChanged_CDeleted: // conflict - case eCChanged_BDeleted: // conflict - case eBCAdded: // conflict - case eBCAddedAndEqual: // possible conflict - c = m_pOptions->m_colorForConflict; - break; - default: - Q_ASSERT(true); - break; - } - } - else if(eOverviewMode == eOMAvsB) - { - switch(md) - { - case eDefault: - case eNoChange: - case eCAdded: - case eCDeleted: - case eCChanged: - break; - default: - c = m_pOptions->m_colorForConflict; - bWhiteSpaceChange = d3l.bAEqB || (d3l.bWhiteLineA && d3l.bWhiteLineB); - break; - } - } - else if(eOverviewMode == eOMAvsC) - { - switch(md) - { - case eDefault: - case eNoChange: - case eBAdded: - case eBDeleted: - case eBChanged: - break; - default: - c = m_pOptions->m_colorForConflict; - bWhiteSpaceChange = d3l.bAEqC || (d3l.bWhiteLineA && d3l.bWhiteLineC); - break; - } - } - else if(eOverviewMode == eOMBvsC) - { - switch(md) - { - case eDefault: - case eNoChange: - case eBCChangedAndEqual: - case eBCDeleted: - case eBCAddedAndEqual: - break; - default: - c = m_pOptions->m_colorForConflict; - bWhiteSpaceChange = d3l.bBEqC || (d3l.bWhiteLineB && d3l.bWhiteLineC); - break; - } - } - - int x2 = x; - int w2 = w; - - if(!m_bTripleDiff) - { - if(d3l.getLineA() == -1 && d3l.getLineB() >= 0) - { - c = m_pOptions->m_colorA; - x2 = w / 2; - w2 = x2; - } - if(d3l.getLineA() >= 0 && d3l.getLineB() == -1) - { - c = m_pOptions->m_colorB; - w2 = w / 2; - } - } - - if(!bWhiteSpaceChange || m_pOptions->m_bShowWhiteSpace) - { - // Make sure that lines with conflict are not overwritten. - if(c == m_pOptions->m_colorForConflict) - { - p.fillRect(x2 + 1, oldY, w2, std::max(1, y - oldY), bWhiteSpaceChange ? QBrush(c, Qt::Dense4Pattern) : QBrush(c)); - oldConflictY = oldY; - } - else if(c != m_pOptions->m_bgColor && oldY > oldConflictY) - { - p.fillRect(x2 + 1, oldY, w2, std::max(1, y - oldY), bWhiteSpaceChange ? QBrush(c, Qt::Dense4Pattern) : QBrush(c)); - } - } - - oldY = y; - - ++line; - if(m_pOptions->m_bWordWrap) - { - ++wrapLineIdx; - if(wrapLineIdx >= d3l.linesNeededForDisplay) - { - wrapLineIdx = 0; - ++i; - } - } - else - { - ++i; - } - } -} - -void Overview::paintEvent(QPaintEvent*) -{ - if(m_pDiff3LineList == nullptr) return; - int h = height() - 1; - int w = width(); - - if(m_pixmap.size() != size()) - { - if(m_pOptions->m_bWordWrap) - { - m_nofLines = 0; - Diff3LineList::const_iterator i; - for(i = m_pDiff3LineList->begin(); i != m_pDiff3LineList->end(); ++i) - { - m_nofLines += i->linesNeededForDisplay; - } - } - else - { - m_nofLines = m_pDiff3LineList->size(); - } - - m_pixmap = QPixmap(size()); - - QPainter p(&m_pixmap); - p.fillRect(rect(), m_pOptions->m_bgColor); - - if(!m_bTripleDiff || m_eOverviewMode == eOMNormal) - { - drawColumn(p, eOMNormal, 0, w, h, m_nofLines); - } - else - { - drawColumn(p, eOMNormal, 0, w / 2, h, m_nofLines); - drawColumn(p, m_eOverviewMode, w / 2, w / 2, h, m_nofLines); - } - } - - QPainter painter(this); - painter.drawPixmap(0, 0, m_pixmap); - int y1=0, h1=0; - if(m_nofLines > 0) - { - y1 = h * m_firstLine / m_nofLines - 1; - h1 = h * m_pageHeight / m_nofLines + 3; - } - painter.setPen(Qt::black); - painter.drawRect(1, y1, w - 1, h1); -} - WindowTitleWidget::WindowTitleWidget(Options* 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 QLineEdit(); pHLayout->addWidget(m_pFileNameLineEdit, 6); m_pFileNameLineEdit->installEventFilter(this); 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(); foreach(int i, mibs) { QTextCodec* c = QTextCodec::codecForMib(i); if(c != nullptr) names[QLatin1String(c->name())] = c; } if(pCodecForA) m_pEncodingSelector->addItem(i18n("Codec from A: %1", QLatin1String(pCodecForA->name())), QVariant::fromValue((void*)pCodecForA)); if(pCodecForB) m_pEncodingSelector->addItem(i18n("Codec from B: %1", QLatin1String(pCodecForB->name())), QVariant::fromValue((void*)pCodecForB)); if(pCodecForC) 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 && pCodecForB && pCodecForA) { if(pCodecForA == pCodecForB) m_pEncodingSelector->setCurrentIndex(2); // C else if(pCodecForA == pCodecForC) m_pEncodingSelector->setCurrentIndex(1); // B else m_pEncodingSelector->setCurrentIndex(2); // C } else if(pCodecForA && pCodecForB) 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) { 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); } if(o == m_pFileNameLineEdit && e->type() == QEvent::Drop) { QDropEvent* d = static_cast(e); if(d->mimeData()->hasUrls()) { QList lst = d->mimeData()->urls(); if(lst.count() > 0) { static_cast(o)->setText(lst[0].toString()); static_cast(o)->setFocus(); return true; } } } return false; } //#include "mergeresultwindow.moc" diff --git a/src/mergeresultwindow.h b/src/mergeresultwindow.h index 15d50c0..055e94d 100644 --- a/src/mergeresultwindow.h +++ b/src/mergeresultwindow.h @@ -1,460 +1,417 @@ /*************************************************************************** - mergeresultwindow.h - description - ------------------- - begin : Mon Mar 18 2002 - copyright : (C) 2002-2007 by Joachim Eibl - email : joachim.eibl at gmx.de - ***************************************************************************/ - -/*************************************************************************** + * Copyright (C) 2002-2007 by Joachim Eibl * + * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef MERGERESULTWINDOW_H #define MERGERESULTWINDOW_H #include "diff.h" #include "selection.h" +#include "Overview.h" #include -#include #include #include #include class QPainter; -class Overview : public QWidget -{ - Q_OBJECT -public: - explicit Overview( Options* pOptions ); - - void init( Diff3LineList* pDiff3LineList, bool bTripleDiff ); - void reset(); - void setRange( int firstLine, int pageHeight ); - void setPaintingAllowed( bool bAllowPainting ); - - enum e_OverviewMode { eOMNormal, eOMAvsB, eOMAvsC, eOMBvsC }; - void setOverviewMode( e_OverviewMode eOverviewMode ); - e_OverviewMode getOverviewMode(); - -public Q_SLOTS: - void setFirstLine(int firstLine); - void slotRedraw(); -Q_SIGNALS: - void setLine(int); -private: - const Diff3LineList* m_pDiff3LineList; - Options* m_pOptions; - bool m_bTripleDiff; - int m_firstLine; - int m_pageHeight; - QPixmap m_pixmap; - e_OverviewMode m_eOverviewMode; - int m_nofLines; - - void paintEvent( QPaintEvent* e ) override; - void mousePressEvent( QMouseEvent* e ) override; - void mouseMoveEvent( QMouseEvent* e ) override; - void drawColumn( QPainter& p, e_OverviewMode eOverviewMode, int x, int w, int h, int nofLines ); -}; - - enum e_MergeDetails { eDefault, eNoChange, eBChanged, eCChanged, eBCChanged, // conflict eBCChangedAndEqual, // possible conflict eBDeleted, eCDeleted, eBCDeleted, // possible conflict eBChanged_CDeleted, // conflict eCChanged_BDeleted, // conflict eBAdded, eCAdded, eBCAdded, // conflict eBCAddedAndEqual // possible conflict }; void mergeOneLine( const Diff3Line& d, e_MergeDetails& mergeDetails, bool& bConflict, bool& bLineRemoved, int& src, bool bTwoInputs ); enum e_MergeSrcSelector { A=1, B=2, C=3 }; class MergeResultWindow : public QWidget { Q_OBJECT public: MergeResultWindow(QWidget* pParent, Options* pOptions, QStatusBar* pStatusBar); void init( const LineData* pLineDataA, LineRef sizeA, const LineData* pLineDataB, LineRef sizeB, const LineData* pLineDataC, LineRef sizeC, const Diff3LineList* pDiff3LineList, TotalDiffStatus* pTotalDiffStatus ); void reset(); bool saveDocument( const QString& fileName, QTextCodec* pEncoding, e_LineEndStyle eLineEndStyle ); int getNrOfUnsolvedConflicts(int* pNrOfWhiteSpaceConflicts=nullptr); void choose(int selector); void chooseGlobal(int selector, bool bConflictsOnly, bool bWhiteSpaceOnly ); int getMaxTextWidth(); // width of longest text line int getNofLines(); int getVisibleTextAreaWidth(); // text area width without the border int getNofVisibleLines(); QString getSelection(); void resetSelection(); void showNrOfConflicts(); bool isDeltaAboveCurrent(); bool isDeltaBelowCurrent(); bool isConflictAboveCurrent(); bool isConflictBelowCurrent(); bool isUnsolvedConflictAtCurrent(); bool isUnsolvedConflictAboveCurrent(); bool isUnsolvedConflictBelowCurrent(); bool findString( const QString& s, int& d3vLine, int& posInLine, bool bDirDown, bool bCaseSensitive ); void setSelection( int firstLine, int startPos, int lastLine, int endPos ); void setOverviewMode( Overview::e_OverviewMode eOverviewMode ); Overview::e_OverviewMode getOverviewMode(); public Q_SLOTS: void setFirstLine(int firstLine); void setHorizScrollOffset(int horizScrollOffset); void slotGoCurrent(); void slotGoTop(); void slotGoBottom(); void slotGoPrevDelta(); void slotGoNextDelta(); void slotGoPrevUnsolvedConflict(); void slotGoNextUnsolvedConflict(); void slotGoPrevConflict(); void slotGoNextConflict(); void slotAutoSolve(); void slotUnsolve(); void slotMergeHistory(); void slotRegExpAutoMerge(); void slotSplitDiff( int firstD3lLineIdx, int lastD3lLineIdx ); void slotJoinDiffs( int firstD3lLineIdx, int lastD3lLineIdx ); void slotSetFastSelectorLine(int); void setPaintingAllowed(bool); void updateSourceMask(); void slotStatusMessageChanged( const QString& ); Q_SIGNALS: void scrollMergeResultWindow( int deltaX, int deltaY ); void modifiedChanged(bool bModified); void setFastSelectorRange( int line1, int nofLines ); void sourceMask( int srcMask, int enabledMask ); void resizeSignal(); void selectionEnd(); void newSelection(); void updateAvailabilities(); void showPopupMenu( const QPoint& point ); void noRelevantChangesDetected(); private: void merge(bool bAutoSolve, int defaultSelector, bool bConflictsOnly=false, bool bWhiteSpaceOnly=false ); QString getString( int lineIdx ); Options* m_pOptions; const LineData* m_pldA; const LineData* m_pldB; const LineData* m_pldC; LineRef m_sizeA; LineRef m_sizeB; LineRef m_sizeC; const Diff3LineList* m_pDiff3LineList; TotalDiffStatus* m_pTotalDiffStatus; int m_delayedDrawTimer; Overview::e_OverviewMode m_eOverviewMode; QString m_persistentStatusMessage; void showUnsolvedConflictsStatusMessage(); private: class MergeEditLine { public: explicit MergeEditLine(const Diff3LineList::const_iterator &i, int src=0){m_id3l=i; m_src=src; m_bLineRemoved=false; } void setConflict() { m_src=0; m_bLineRemoved=false; m_str=QString(); } bool isConflict() { return m_src==0 && !m_bLineRemoved && m_str.isEmpty(); } void setRemoved(int src=0) { m_src=src; m_bLineRemoved=true; m_str=QString(); } bool isRemoved() { return m_bLineRemoved; } bool isEditableText() { return !isConflict() && !isRemoved(); } void setString( const QString& s ){ m_str=s; m_bLineRemoved=false; m_src=0; } QString getString( const MergeResultWindow* ); bool isModified() { return ! m_str.isEmpty() || (m_bLineRemoved && m_src==0); } void setSource( int src, bool bLineRemoved ) { m_src=src; m_bLineRemoved =bLineRemoved; } int src() { return m_src; } Diff3LineList::const_iterator id3l(){return m_id3l;} // getString() is implemented as MergeResultWindow::getString() private: Diff3LineList::const_iterator m_id3l; int m_src; // 1, 2 or 3 for A, B or C respectively, or 0 when line is from neither source. QString m_str; // String when modified by user or null-string when orig data is used. bool m_bLineRemoved; }; class MergeEditLineList : private std::list { // I want to know the size immediately! private: typedef std::list BASE; int m_size; int* m_pTotalSize; public: typedef std::list::iterator iterator; typedef std::list::reverse_iterator reverse_iterator; typedef std::list::const_iterator const_iterator; MergeEditLineList(){m_size=0; m_pTotalSize=nullptr; } void clear() { ds(-m_size); BASE::clear(); } void push_back( const MergeEditLine& m) { ds(+1); BASE::push_back(m); } void push_front( const MergeEditLine& m) { ds(+1); BASE::push_front(m); } void pop_back() { ds(-1); BASE::pop_back(); } iterator erase( iterator i ) { ds(-1); return BASE::erase(i); } iterator insert( iterator i, const MergeEditLine& m ) { ds(+1); return BASE::insert(i,m); } int size(){ if (!m_pTotalSize) m_size = (int) BASE::size(); return m_size; } iterator begin(){return BASE::begin();} iterator end(){return BASE::end();} reverse_iterator rbegin(){return BASE::rbegin();} reverse_iterator rend(){return BASE::rend();} MergeEditLine& front(){return BASE::front();} MergeEditLine& back(){return BASE::back();} bool empty() { return m_size==0; } void splice(iterator destPos, MergeEditLineList& srcList, iterator srcFirst, iterator srcLast) { int* pTotalSize = getTotalSizePtr() ? getTotalSizePtr() : srcList.getTotalSizePtr(); srcList.setTotalSizePtr(nullptr); // Force size-recalc after splice, because splice doesn't handle size-tracking setTotalSizePtr(nullptr); BASE::splice( destPos, srcList, srcFirst, srcLast ); srcList.setTotalSizePtr( pTotalSize ); setTotalSizePtr( pTotalSize ); } void setTotalSizePtr(int* pTotalSize) { if ( pTotalSize==nullptr && m_pTotalSize!=nullptr ) { *m_pTotalSize -= size(); } else if ( pTotalSize!=nullptr && m_pTotalSize==nullptr ) { *pTotalSize += size(); } m_pTotalSize = pTotalSize; } int* getTotalSizePtr() { return m_pTotalSize; } private: void ds(int deltaSize) { m_size+=deltaSize; if (m_pTotalSize!=nullptr) *m_pTotalSize+=deltaSize; } }; friend class MergeEditLine; struct MergeLine { MergeLine() { srcSelect=0; mergeDetails=eDefault; d3lLineIdx = -1; srcRangeLength=0; bConflict=false; bDelta=false; bWhiteSpaceConflict=false; } Diff3LineList::const_iterator id3l; int d3lLineIdx; // Needed to show the correct window pos. int srcRangeLength; // how many src-lines have this properties e_MergeDetails mergeDetails; bool bConflict; bool bWhiteSpaceConflict; bool bDelta; int srcSelect; MergeEditLineList mergeEditLineList; void split( MergeLine& ml2, int d3lLineIdx2 ) // The caller must insert the ml2 after this ml in the m_mergeLineList { if ( d3lLineIdx2= d3lLineIdx + srcRangeLength ) return; //Error ml2.mergeDetails = mergeDetails; ml2.bConflict = bConflict; ml2.bWhiteSpaceConflict = bWhiteSpaceConflict; ml2.bDelta = bDelta; ml2.srcSelect = srcSelect; ml2.d3lLineIdx = d3lLineIdx2; ml2.srcRangeLength = srcRangeLength - (d3lLineIdx2-d3lLineIdx); srcRangeLength = d3lLineIdx2-d3lLineIdx; // current MergeLine controls fewer lines ml2.id3l = id3l; for(int i=0; iid3l()==ml2.id3l) { ml2.mergeEditLineList.splice( ml2.mergeEditLineList.begin(), mergeEditLineList, i, mergeEditLineList.end() ); return; } } ml2.mergeEditLineList.setTotalSizePtr( mergeEditLineList.getTotalSizePtr() ); ml2.mergeEditLineList.push_back(MergeEditLine(ml2.id3l)); } void join( MergeLine& ml2 ) // The caller must remove the ml2 from the m_mergeLineList after this call { srcRangeLength += ml2.srcRangeLength; ml2.mergeEditLineList.clear(); mergeEditLineList.clear(); mergeEditLineList.push_back(MergeEditLine(id3l)); // Create a simple conflict if ( ml2.bConflict ) bConflict = true; if ( !ml2.bWhiteSpaceConflict ) bWhiteSpaceConflict = false; if ( ml2.bDelta ) bDelta = true; } }; private: static bool sameKindCheck( const MergeLine& ml1, const MergeLine& ml2 ); struct HistoryMapEntry { MergeEditLineList mellA; MergeEditLineList mellB; MergeEditLineList mellC; MergeEditLineList& choice( bool bThreeInputs ); bool staysInPlace( bool bThreeInputs, Diff3LineList::const_iterator& iHistoryEnd ); }; typedef std::map HistoryMap; void collectHistoryInformation( int src, Diff3LineList::const_iterator &iHistoryBegin, Diff3LineList::const_iterator &iHistoryEnd, HistoryMap& historyMap, std::list< HistoryMap::iterator >& hitList ); typedef std::list MergeLineList; MergeLineList m_mergeLineList; MergeLineList::iterator m_currentMergeLineIt; bool isItAtEnd( bool bIncrement, MergeLineList::iterator i ) { if ( bIncrement ) return i!=m_mergeLineList.end(); else return i!=m_mergeLineList.begin(); } int m_currentPos; bool checkOverviewIgnore(MergeLineList::iterator &i); enum e_Direction { eUp, eDown }; enum e_EndPoint { eDelta, eConflict, eUnsolvedConflict, eLine, eEnd }; void go( e_Direction eDir, e_EndPoint eEndPoint ); void calcIteratorFromLineNr( int line, MergeLineList::iterator& mlIt, MergeEditLineList::iterator& melIt ); MergeLineList::iterator splitAtDiff3LineIdx( int d3lLineIdx ); 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( MyPainter& p, int line, const QString& str, int srcSelect, e_MergeDetails mergeDetails, int rangeMark, bool bUserModified, bool bLineRemoved, bool bWhiteSpaceConflict ); void setFastSelector(MergeLineList::iterator i); int convertToLine( int y ); bool event(QEvent*) override; void mousePressEvent ( QMouseEvent* e ) override; void mouseDoubleClickEvent ( QMouseEvent* e ) override; void mouseReleaseEvent ( QMouseEvent * ) override; void mouseMoveEvent ( QMouseEvent * ) override; void resizeEvent( QResizeEvent* e ) override; void keyPressEvent( QKeyEvent* e ) override; void wheelEvent( QWheelEvent* e ) override; void focusInEvent( QFocusEvent* e ) override; QPixmap m_pixmap; int m_firstLine; int m_horizScrollOffset; int m_nofLines; int m_totalSize; //Same as m_nofLines, but calculated differently int m_maxTextWidth; bool m_bMyUpdate; bool m_bInsertMode; bool m_bModified; void setModified(bool bModified=true); int m_scrollDeltaX; int m_scrollDeltaY; int m_cursorXPos; int m_cursorXPixelPos; int m_cursorYPos; int m_cursorOldXPixelPos; bool m_bCursorOn; // blinking on and off each second QTimer m_cursorTimer; bool m_bCursorUpdate; QStatusBar* m_pStatusBar; Selection m_selection; bool deleteSelection2( QString& str, int& x, int& y, MergeLineList::iterator& mlIt, MergeEditLineList::iterator& melIt ); bool doRelevantChangesExist(); public Q_SLOTS: void deleteSelection(); void pasteClipboard(bool bFromSelection); private Q_SLOTS: void slotCursorUpdate(); }; class QLineEdit; class QTextCodec; class QComboBox; class QLabel; class WindowTitleWidget : public QWidget { Q_OBJECT private: QLabel* m_pLabel; QLineEdit* m_pFileNameLineEdit; //QPushButton* m_pBrowseButton; QLabel* m_pModifiedLabel; QLabel* m_pLineEndStyleLabel; QComboBox* m_pLineEndStyleSelector; QLabel* m_pEncodingLabel; QComboBox* m_pEncodingSelector; Options* m_pOptions; public: explicit WindowTitleWidget(Options* pOptions); QTextCodec* getEncoding(); void setFileName(const QString& fileName ); QString getFileName(); void setEncodings( QTextCodec* pCodecForA, QTextCodec* pCodecForB, QTextCodec* pCodecForC ); void setEncoding( QTextCodec* 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