File indexing completed on 2024-12-01 08:17:28

0001 // clang-format off
0002 /*
0003  * KDiff3 - Text Diff And Merge Tool
0004  *
0005  * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de
0006  * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com
0007  * SPDX-License-Identifier: GPL-2.0-or-later
0008  */
0009 // clang-format on
0010 
0011 #include "mergeresultwindow.h"
0012 
0013 #include "compat.h"
0014 #include "defmac.h"
0015 #include "EncodedDataStream.h"
0016 #include "guiutils.h"
0017 #include "kdiff3.h"
0018 #include "options.h"
0019 #include "RLPainter.h"
0020 #include "TypeUtils.h"
0021 #include "UndoRecord.h"
0022 #include "Utils.h"
0023 
0024 #include <memory>
0025 
0026 #include <QAction>
0027 #include <QApplication>
0028 #include <QClipboard>
0029 #include <QComboBox>
0030 #include <QCursor>
0031 #include <QDir>
0032 #include <QDropEvent>
0033 #include <QEvent>
0034 #include <QFile>
0035 #include <QFocusEvent>
0036 #include <QHBoxLayout>
0037 #include <QInputEvent>
0038 #include <QKeyEvent>
0039 #include <QLabel>
0040 #include <QLineEdit>
0041 #include <QtMath>
0042 #include <QMouseEvent>
0043 #include <QPaintEvent>
0044 #include <QPainter>
0045 #include <QPixmap>
0046 #include <QPointer>
0047 #include <QRegularExpression>
0048 #include <QResizeEvent>
0049 #include <QStatusBar>
0050 #include <QTextCodec>
0051 #include <QTextLayout>
0052 #include <QTextStream>
0053 #include <QTimerEvent>
0054 #include <QUrl>
0055 #include <QWheelEvent>
0056 
0057 #include <KActionCollection>
0058 #include <KLocalizedString>
0059 #include <KMessageBox>
0060 #include <KToggleAction>
0061 
0062 QPointer<QScrollBar> MergeResultWindow::mVScrollBar = nullptr;
0063 QPointer<QAction> MergeResultWindow::chooseAEverywhere;
0064 QPointer<QAction> MergeResultWindow::chooseBEverywhere;
0065 QPointer<QAction> MergeResultWindow::chooseCEverywhere;
0066 QPointer<QAction> MergeResultWindow::chooseAForUnsolvedConflicts;
0067 QPointer<QAction> MergeResultWindow::chooseBForUnsolvedConflicts;
0068 QPointer<QAction> MergeResultWindow::chooseCForUnsolvedConflicts;
0069 QPointer<QAction> MergeResultWindow::chooseAForUnsolvedWhiteSpaceConflicts;
0070 QPointer<QAction> MergeResultWindow::chooseBForUnsolvedWhiteSpaceConflicts;
0071 QPointer<QAction> MergeResultWindow::chooseCForUnsolvedWhiteSpaceConflicts;
0072 
0073 MergeResultWindow::MergeResultWindow(
0074     QWidget* pParent,
0075     QStatusBar* pStatusBar):
0076     QWidget(pParent)
0077 {
0078     setObjectName("MergeResultWindow");
0079     setFocusPolicy(Qt::ClickFocus);
0080 
0081     mOverviewMode = e_OverviewMode::eOMNormal;
0082 
0083     m_pStatusBar = pStatusBar;
0084     if(m_pStatusBar != nullptr)
0085         chk_connect_a(m_pStatusBar, &QStatusBar::messageChanged, this, &MergeResultWindow::slotStatusMessageChanged);
0086 
0087     setUpdatesEnabled(false);
0088 
0089     chk_connect_a(&m_cursorTimer, &QTimer::timeout, this, &MergeResultWindow::slotCursorUpdate);
0090     m_cursorTimer.setSingleShot(true);
0091     m_cursorTimer.start(500 /*ms*/);
0092     m_selection.reset();
0093 
0094     setMinimumSize(QSize(20, 20));
0095     setFont(gOptions->defaultFont());
0096 }
0097 
0098 void MergeResultWindow::init(
0099     const std::shared_ptr<LineDataVector> &pLineDataA, LineRef sizeA,
0100     const std::shared_ptr<LineDataVector> &pLineDataB, LineRef sizeB,
0101     const std::shared_ptr<LineDataVector> &pLineDataC, LineRef sizeC,
0102     const Diff3LineList* pDiff3LineList,
0103     TotalDiffStatus* pTotalDiffStatus,
0104     bool bAutoSolve)
0105 {
0106     m_firstLine = 0;
0107     m_horizScrollOffset = 0;
0108     m_nofLines = 0;
0109     m_bMyUpdate = false;
0110     m_bInsertMode = true;
0111     m_scrollDeltaX = 0;
0112     m_scrollDeltaY = 0;
0113     setModified(false);
0114 
0115     m_pldA = pLineDataA;
0116     m_pldB = pLineDataB;
0117     m_pldC = pLineDataC;
0118     m_sizeA = sizeA;
0119     m_sizeB = sizeB;
0120     m_sizeC = sizeC;
0121 
0122     m_pDiff3LineList = pDiff3LineList;
0123     m_pTotalDiffStatus = pTotalDiffStatus;
0124 
0125     m_selection.reset();
0126     m_cursorXPos = 0;
0127     m_cursorOldXPixelPos = 0;
0128     m_cursorYPos = 0;
0129 
0130     m_maxTextWidth = -1;
0131 
0132     merge(bAutoSolve, e_SrcSelector::Invalid);
0133     update();
0134     updateSourceMask();
0135 
0136     showUnsolvedConflictsStatusMessage();
0137 }
0138 
0139 //This must be called before KXMLGui::SetXMLFile and friends or the actions will not be shown in the menu.
0140 //At that point in startup we don't have a MergeResultWindow object so we cannot connect the signals yet.
0141 void MergeResultWindow::initActions(KActionCollection* ac)
0142 {
0143     assert(ac != nullptr);
0144 
0145     chooseAEverywhere = GuiUtils::createAction<QAction>(i18n("Choose A Everywhere"), QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_1), ac, "merge_choose_a_everywhere");
0146     chooseBEverywhere = GuiUtils::createAction<QAction>(i18n("Choose B Everywhere"), QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_2), ac, "merge_choose_b_everywhere");
0147     chooseCEverywhere = GuiUtils::createAction<QAction>(i18n("Choose C Everywhere"), QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_3), ac, "merge_choose_c_everywhere");
0148     chooseAForUnsolvedConflicts = GuiUtils::createAction<QAction>(i18n("Choose A for All Unsolved Conflicts"), ac, "merge_choose_a_for_unsolved_conflicts");
0149     chooseBForUnsolvedConflicts = GuiUtils::createAction<QAction>(i18n("Choose B for All Unsolved Conflicts"), ac, "merge_choose_b_for_unsolved_conflicts");
0150     chooseCForUnsolvedConflicts = GuiUtils::createAction<QAction>(i18n("Choose C for All Unsolved Conflicts"), ac, "merge_choose_c_for_unsolved_conflicts");
0151     chooseAForUnsolvedWhiteSpaceConflicts = GuiUtils::createAction<QAction>(i18n("Choose A for All Unsolved Whitespace Conflicts"), ac, "merge_choose_a_for_unsolved_whitespace_conflicts");
0152     chooseBForUnsolvedWhiteSpaceConflicts = GuiUtils::createAction<QAction>(i18n("Choose B for All Unsolved Whitespace Conflicts"), ac, "merge_choose_b_for_unsolved_whitespace_conflicts");
0153     chooseCForUnsolvedWhiteSpaceConflicts = GuiUtils::createAction<QAction>(i18n("Choose C for All Unsolved Whitespace Conflicts"), ac, "merge_choose_c_for_unsolved_whitespace_conflicts");
0154 }
0155 
0156 void MergeResultWindow::connectActions() const
0157 {
0158     static bool setupComplete = false;
0159     if(setupComplete) return;
0160 
0161     setupComplete = true;
0162     chk_connect_a(chooseAEverywhere, &QAction::triggered, this, &MergeResultWindow::slotChooseAEverywhere);
0163     chk_connect_a(chooseBEverywhere, &QAction::triggered, this, &MergeResultWindow::slotChooseBEverywhere);
0164     chk_connect_a(chooseCEverywhere, &QAction::triggered, this, &MergeResultWindow::slotChooseCEverywhere);
0165 
0166     chk_connect_a(chooseAForUnsolvedConflicts, &QAction::triggered, this, &MergeResultWindow::slotChooseAForUnsolvedConflicts);
0167     chk_connect_a(chooseBForUnsolvedConflicts, &QAction::triggered, this, &MergeResultWindow::slotChooseBForUnsolvedConflicts);
0168     chk_connect_a(chooseCForUnsolvedConflicts, &QAction::triggered, this, &MergeResultWindow::slotChooseCForUnsolvedConflicts);
0169 
0170     chk_connect_a(chooseAForUnsolvedWhiteSpaceConflicts, &QAction::triggered, this, &MergeResultWindow::slotChooseAForUnsolvedWhiteSpaceConflicts);
0171     chk_connect_a(chooseBForUnsolvedWhiteSpaceConflicts, &QAction::triggered, this, &MergeResultWindow::slotChooseBForUnsolvedWhiteSpaceConflicts);
0172     chk_connect_a(chooseCForUnsolvedWhiteSpaceConflicts, &QAction::triggered, this, &MergeResultWindow::slotChooseCForUnsolvedWhiteSpaceConflicts);
0173 }
0174 
0175 void MergeResultWindow::setupConnections(const KDiff3App* app)
0176 {
0177     chk_connect_a(app, &KDiff3App::cut, this, &MergeResultWindow::slotCut);
0178     chk_connect_a(app, &KDiff3App::copy, this, &MergeResultWindow::slotCopy);
0179     chk_connect_a(app, &KDiff3App::selectAll, this, &MergeResultWindow::slotSelectAll);
0180 
0181     chk_connect_a(this, &MergeResultWindow::scrollMergeResultWindow, app, &KDiff3App::scrollMergeResultWindow);
0182     chk_connect_a(this, &MergeResultWindow::sourceMask, app, &KDiff3App::sourceMask);
0183     chk_connect_a(this, &MergeResultWindow::resizeSignal, app, &KDiff3App::setHScrollBarRange);
0184     chk_connect_a(this, &MergeResultWindow::resizeSignal, this, &MergeResultWindow::slotResize);
0185 
0186     chk_connect_a(this, &MergeResultWindow::selectionEnd, app, &KDiff3App::slotSelectionEnd);
0187     chk_connect_a(this, &MergeResultWindow::newSelection, app, &KDiff3App::slotSelectionStart);
0188     chk_connect_a(this, &MergeResultWindow::modifiedChanged, app, &KDiff3App::slotOutputModified);
0189     //TODO: Why two slots?
0190     chk_connect_a(this, &MergeResultWindow::updateAvailabilities, app, &KDiff3App::slotUpdateAvailabilities);
0191     chk_connect_a(this, &MergeResultWindow::updateAvailabilities, this, &MergeResultWindow::slotUpdateAvailabilities);
0192     chk_connect_a(app, &KDiff3App::updateAvailabilities, this, &MergeResultWindow::slotUpdateAvailabilities);
0193     chk_connect_a(this, &MergeResultWindow::showPopupMenu, app, &KDiff3App::showPopupMenu);
0194     chk_connect_a(this, &MergeResultWindow::noRelevantChangesDetected, app, &KDiff3App::slotNoRelevantChangesDetected);
0195     chk_connect_a(this, &MergeResultWindow::statusBarMessage, app, &KDiff3App::slotStatusMsg);
0196     //connect menu actions
0197     chk_connect_a(app, &KDiff3App::showWhiteSpaceToggled, this, static_cast<void (MergeResultWindow::*)(void)>(&MergeResultWindow::update));
0198     chk_connect_a(app, &KDiff3App::doRefresh, this, &MergeResultWindow::slotRefresh);
0199 
0200     chk_connect_a(app, &KDiff3App::autoSolve, this, &MergeResultWindow::slotAutoSolve);
0201     chk_connect_a(app, &KDiff3App::unsolve, this, &MergeResultWindow::slotUnsolve);
0202     chk_connect_a(app, &KDiff3App::mergeHistory, this, &MergeResultWindow::slotMergeHistory);
0203     chk_connect_a(app, &KDiff3App::regExpAutoMerge, this, &MergeResultWindow::slotRegExpAutoMerge);
0204 
0205     chk_connect_a(app, &KDiff3App::goCurrent, this, &MergeResultWindow::slotGoCurrent);
0206     chk_connect_a(app, &KDiff3App::goTop, this, &MergeResultWindow::slotGoTop);
0207     chk_connect_a(app, &KDiff3App::goBottom, this, &MergeResultWindow::slotGoBottom);
0208     chk_connect_a(app, &KDiff3App::goPrevUnsolvedConflict, this, &MergeResultWindow::slotGoPrevUnsolvedConflict);
0209     chk_connect_a(app, &KDiff3App::goNextUnsolvedConflict, this, &MergeResultWindow::slotGoNextUnsolvedConflict);
0210     chk_connect_a(app, &KDiff3App::goPrevConflict, this, &MergeResultWindow::slotGoPrevConflict);
0211     chk_connect_a(app, &KDiff3App::goNextConflict, this, &MergeResultWindow::slotGoNextConflict);
0212     chk_connect_a(app, &KDiff3App::goPrevDelta, this, &MergeResultWindow::slotGoPrevDelta);
0213     chk_connect_a(app, &KDiff3App::goNextDelta, this, &MergeResultWindow::slotGoNextDelta);
0214 
0215     chk_connect_a(app, &KDiff3App::changeOverViewMode, this, &MergeResultWindow::setOverviewMode);
0216 
0217     connections.push_back(KDiff3App::allowCut.connect(boost::bind(&MergeResultWindow::canCut, this)));
0218     connections.push_back(KDiff3App::allowCopy.connect(boost::bind(&MergeResultWindow::canCopy, this)));
0219     connections.push_back(KDiff3App::getSelection.connect(boost::bind(&MergeResultWindow::getSelection, this)));
0220 }
0221 
0222 void MergeResultWindow::slotResize()
0223 {
0224     mVScrollBar->setRange(0, std::max(0, getNofLines() - getNofVisibleLines()));
0225     mVScrollBar->setPageStep(getNofVisibleLines());
0226 }
0227 
0228 void MergeResultWindow::slotCut()
0229 {
0230     const QString curSelection = getSelection();
0231     assert(!curSelection.isEmpty() && hasFocus());
0232     deleteSelection();
0233     update();
0234 
0235     QApplication::clipboard()->setText(curSelection, QClipboard::Clipboard);
0236 }
0237 
0238 void MergeResultWindow::slotCopy()
0239 {
0240     if(!hasFocus())
0241         return;
0242 
0243     const QString curSelection = getSelection();
0244 
0245     if(!curSelection.isEmpty())
0246     {
0247         QApplication::clipboard()->setText(curSelection, QClipboard::Clipboard);
0248     }
0249 }
0250 
0251 void MergeResultWindow::slotSelectAll()
0252 {
0253     if(hasFocus())
0254     {
0255         setSelection(0, 0, getNofLines(), 0);
0256     }
0257 }
0258 
0259 void MergeResultWindow::showUnsolvedConflictsStatusMessage()
0260 {
0261     if(m_pStatusBar != nullptr)
0262     {
0263         qint32 wsc;
0264         qint32 nofUnsolved = getNumberOfUnsolvedConflicts(&wsc);
0265 
0266         m_persistentStatusMessage = i18n("Number of remaining unsolved conflicts: %1 (of which %2 are whitespace)", nofUnsolved, wsc);
0267 
0268         Q_EMIT statusBarMessage(m_persistentStatusMessage);
0269     }
0270 }
0271 
0272 void MergeResultWindow::slotRefresh()
0273 {
0274     setFont(gOptions->defaultFont());
0275     update();
0276 }
0277 
0278 void MergeResultWindow::slotUpdateAvailabilities()
0279 {
0280     const QWidget* frame = qobject_cast<QWidget*>(parent());
0281     assert(frame != nullptr);
0282     const bool bMergeEditorVisible = frame->isVisible();
0283     const bool bTripleDiff = KDiff3App::isTripleDiff();
0284 
0285     chooseAEverywhere->setEnabled(bMergeEditorVisible);
0286     chooseBEverywhere->setEnabled(bMergeEditorVisible);
0287     chooseCEverywhere->setEnabled(bMergeEditorVisible && bTripleDiff);
0288     chooseAForUnsolvedConflicts->setEnabled(bMergeEditorVisible);
0289     chooseBForUnsolvedConflicts->setEnabled(bMergeEditorVisible);
0290     chooseCForUnsolvedConflicts->setEnabled(bMergeEditorVisible && bTripleDiff);
0291     chooseAForUnsolvedWhiteSpaceConflicts->setEnabled(bMergeEditorVisible);
0292     chooseBForUnsolvedWhiteSpaceConflicts->setEnabled(bMergeEditorVisible);
0293     chooseCForUnsolvedWhiteSpaceConflicts->setEnabled(bMergeEditorVisible && bTripleDiff);
0294 }
0295 
0296 void MergeResultWindow::slotStatusMessageChanged(const QString& s)
0297 {
0298     if(s.isEmpty() && !m_persistentStatusMessage.isEmpty())
0299     {
0300         Q_EMIT statusBarMessage(m_persistentStatusMessage);
0301     }
0302 }
0303 
0304 void MergeResultWindow::reset()
0305 {
0306     mUndoRec.reset();
0307     m_mergeBlockList.clear();
0308 
0309     m_currentMergeBlockIt = m_mergeBlockList.end();
0310     m_pDiff3LineList = nullptr;
0311     m_pTotalDiffStatus = nullptr;
0312     m_pldA = nullptr;
0313     m_pldB = nullptr;
0314     m_pldC = nullptr;
0315     if(!m_persistentStatusMessage.isEmpty())
0316     {
0317         m_persistentStatusMessage = QString();
0318     }
0319 }
0320 
0321 void MergeResultWindow::merge(bool bAutoSolve, e_SrcSelector defaultSelector, bool bConflictsOnly, bool bWhiteSpaceOnly)
0322 {
0323     const bool lIsThreeWay = m_pldC != nullptr;
0324 
0325     if(!bConflictsOnly)
0326     {
0327         if(m_bModified)
0328         {
0329             KMessageBox::ButtonCode result = Compat::warningTwoActions(this,
0330                                                                        i18n("The output has been modified.\n"
0331                                                                             "If you continue your changes will be lost."),
0332                                                                        i18nc("Error dialog title", "Warning"),
0333                                                                        KStandardGuiItem::cont(),
0334                                                                        KStandardGuiItem::cancel());
0335             if(result == Compat::SecondaryAction)
0336                 return;
0337         }
0338 
0339         mUndoRec.reset();
0340         m_mergeBlockList.clear();
0341         m_currentMergeBlockIt = m_mergeBlockList.end();
0342 
0343         m_mergeBlockList.buildFromDiff3(*m_pDiff3LineList, lIsThreeWay);
0344     }
0345 
0346     bool bSolveWhiteSpaceConflicts = false;
0347     if(bAutoSolve) // when true, then the other params are not used and we can change them here. (see all invocations of merge())
0348     {
0349         if(!lIsThreeWay && gOptions->m_whiteSpace2FileMergeDefault != (qint32)e_SrcSelector::None) // Only two inputs
0350         {
0351             assert(gOptions->m_whiteSpace2FileMergeDefault <= (qint32)e_SrcSelector::Max && gOptions->m_whiteSpace2FileMergeDefault >= (qint32)e_SrcSelector::Min);
0352             defaultSelector = (e_SrcSelector)gOptions->m_whiteSpace2FileMergeDefault;
0353             bWhiteSpaceOnly = true;
0354             bSolveWhiteSpaceConflicts = true;
0355         }
0356         else if(lIsThreeWay && gOptions->m_whiteSpace3FileMergeDefault != (qint32)e_SrcSelector::None)
0357         {
0358             assert(gOptions->m_whiteSpace3FileMergeDefault <= (qint32)e_SrcSelector::Max && gOptions->m_whiteSpace2FileMergeDefault >= (qint32)e_SrcSelector::Min);
0359             defaultSelector = (e_SrcSelector)gOptions->m_whiteSpace3FileMergeDefault;
0360             bWhiteSpaceOnly = true;
0361             bSolveWhiteSpaceConflicts = true;
0362         }
0363     }
0364 
0365     if(!bAutoSolve || bSolveWhiteSpaceConflicts)
0366     {
0367         m_mergeBlockList.updateDefaults(defaultSelector, bConflictsOnly, bWhiteSpaceOnly);
0368     }
0369 
0370     for(MergeBlock& mb: m_mergeBlockList)
0371     {
0372         // Remove all lines that are empty, because no src lines are there.
0373         mb.removeEmptySource();
0374     }
0375 
0376     if(bAutoSolve && !bConflictsOnly)
0377     {
0378         if(gOptions->m_bRunHistoryAutoMergeOnMergeStart)
0379             slotMergeHistory();
0380         if(gOptions->m_bRunRegExpAutoMergeOnMergeStart)
0381             slotRegExpAutoMerge();
0382         if(m_pldC != nullptr && !doRelevantChangesExist())
0383             Q_EMIT noRelevantChangesDetected();
0384     }
0385 
0386     qint32 nrOfSolvedConflicts = 0;
0387     qint32 nrOfUnsolvedConflicts = 0;
0388     qint32 nrOfWhiteSpaceConflicts = 0;
0389 
0390     for(const MergeBlock& mb: m_mergeBlockList)
0391     {
0392         if(mb.isConflict())
0393             ++nrOfUnsolvedConflicts;
0394         else if(mb.isDelta())
0395             ++nrOfSolvedConflicts;
0396 
0397         if(mb.isWhiteSpaceConflict())
0398             ++nrOfWhiteSpaceConflicts;
0399     }
0400 
0401     m_pTotalDiffStatus->setUnsolvedConflicts(nrOfUnsolvedConflicts);
0402     m_pTotalDiffStatus->setSolvedConflicts(nrOfSolvedConflicts);
0403     m_pTotalDiffStatus->setWhitespaceConflicts(nrOfWhiteSpaceConflicts);
0404 
0405     m_cursorXPos = 0;
0406     m_cursorOldXPixelPos = 0;
0407     m_cursorYPos = 0;
0408     m_maxTextWidth = -1;
0409 
0410     //m_firstLine = 0; // Must not set line/column without scrolling there
0411     //m_horizScrollOffset = 0;
0412 
0413     setModified(false);
0414 
0415     m_currentMergeBlockIt = m_mergeBlockList.begin();
0416     slotGoTop();
0417 
0418     Q_EMIT updateAvailabilities();
0419     update();
0420 }
0421 
0422 void MergeResultWindow::setFirstLine(LineRef firstLine) //connected to qt controled signal
0423 {
0424     m_firstLine = std::max<LineRef>(0, firstLine);
0425     update();
0426 }
0427 
0428 void MergeResultWindow::setHorizScrollOffset(const qint32 horizScrollOffset)
0429 {
0430     m_horizScrollOffset = std::max(0, horizScrollOffset);
0431     update();
0432 }
0433 
0434 qint32 MergeResultWindow::getMaxTextWidth()
0435 {
0436     if(m_maxTextWidth < 0)
0437     {
0438         m_maxTextWidth = 0;
0439 
0440         for(const MergeBlock& mb: m_mergeBlockList)
0441         {
0442             for(const MergeEditLine& mel: mb.list())
0443             {
0444                 const QString s = mel.getString(m_pldA, m_pldB, m_pldC);
0445 
0446                 QTextLayout textLayout(s, font(), this);
0447                 textLayout.beginLayout();
0448                 textLayout.createLine();
0449                 textLayout.endLayout();
0450                 if(m_maxTextWidth < textLayout.maximumWidth())
0451                 {
0452                     m_maxTextWidth = qCeil(textLayout.maximumWidth());
0453                 }
0454             }
0455         }
0456         m_maxTextWidth += 5; // cursorwidth
0457     }
0458     return m_maxTextWidth;
0459 }
0460 
0461 LineType MergeResultWindow::getNofLines() const
0462 {
0463     return m_nofLines;
0464 }
0465 
0466 qint32 MergeResultWindow::getVisibleTextAreaWidth() const
0467 {
0468     return width() - getTextXOffset();
0469 }
0470 
0471 qint32 MergeResultWindow::getNofVisibleLines() const
0472 {
0473     QFontMetrics fm = fontMetrics();
0474     return (height() - 3) / fm.lineSpacing() - 2;
0475 }
0476 
0477 qint32 MergeResultWindow::getTextXOffset() const
0478 {
0479     QFontMetrics fm = fontMetrics();
0480     return 3 * fm.horizontalAdvance('0');
0481 }
0482 
0483 void MergeResultWindow::resizeEvent(QResizeEvent* e)
0484 {
0485     QWidget::resizeEvent(e);
0486     Q_EMIT resizeSignal();
0487 }
0488 
0489 e_OverviewMode MergeResultWindow::getOverviewMode() const
0490 {
0491     return mOverviewMode;
0492 }
0493 
0494 void MergeResultWindow::setOverviewMode(e_OverviewMode eOverviewMode)
0495 {
0496     mOverviewMode = eOverviewMode;
0497 }
0498 
0499 // Check whether we should ignore current delta when moving to next/previous delta
0500 bool MergeResultWindow::checkOverviewIgnore(const MergeBlockList::const_iterator i) const
0501 {
0502     if(mOverviewMode == e_OverviewMode::eOMNormal) return false;
0503     if(mOverviewMode == e_OverviewMode::eOMAvsB)
0504         return i->details() == e_MergeDetails::eCAdded || i->details() == e_MergeDetails::eCDeleted || i->details() == e_MergeDetails::eCChanged;
0505     if(mOverviewMode == e_OverviewMode::eOMAvsC)
0506         return i->details() == e_MergeDetails::eBAdded || i->details() == e_MergeDetails::eBDeleted || i->details() == e_MergeDetails::eBChanged;
0507     if(mOverviewMode == e_OverviewMode::eOMBvsC)
0508         return i->details() == e_MergeDetails::eBCAddedAndEqual || i->details() == e_MergeDetails::eBCDeleted || i->details() == e_MergeDetails::eBCChangedAndEqual;
0509     return false;
0510 }
0511 
0512 // Go to prev/next delta/conflict or first/last delta.
0513 void MergeResultWindow::go(Direction eDir, EndPoint eEndPoint)
0514 {
0515     assert(eDir == Direction::eUp || eDir == Direction::eDown);
0516     MergeBlockList::iterator i = m_currentMergeBlockIt;
0517     bool bSkipWhiteConflicts = !gOptions->m_bShowWhiteSpace;
0518     if(eEndPoint == EndPoint::eEnd)
0519     {
0520         if(eDir == Direction::eUp)
0521             i = m_mergeBlockList.begin(); // first mergeline
0522         else
0523             i = --m_mergeBlockList.end(); // last mergeline
0524 
0525         while(isItAtEnd(eDir == Direction::eUp, i) && !i->isDelta())
0526         {
0527             if(eDir == Direction::eUp)
0528                 ++i; // search downwards
0529             else
0530                 --i; // search upwards
0531         }
0532     }
0533     else if(eEndPoint == EndPoint::eDelta && isItAtEnd(eDir != Direction::eUp, i))
0534     {
0535         do
0536         {
0537             if(eDir == Direction::eUp)
0538                 --i;
0539             else
0540                 ++i;
0541         } while(isItAtEnd(eDir != Direction::eUp, i) && (!i->isDelta() || checkOverviewIgnore(i) || (bSkipWhiteConflicts && i->isWhiteSpaceConflict())));
0542     }
0543     else if(eEndPoint == EndPoint::eConflict && isItAtEnd(eDir != Direction::eUp, i))
0544     {
0545         do
0546         {
0547             if(eDir == Direction::eUp)
0548                 --i;
0549             else
0550                 ++i;
0551         } while(isItAtEnd(eDir != Direction::eUp, i) && (!i->isConflict() || (bSkipWhiteConflicts && i->isWhiteSpaceConflict())));
0552     }
0553     else if(isItAtEnd(eDir != Direction::eUp, i) && eEndPoint == EndPoint::eUnsolvedConflict)
0554     {
0555         do
0556         {
0557             if(eDir == Direction::eUp)
0558                 --i;
0559             else
0560                 ++i;
0561         } while(isItAtEnd(eDir != Direction::eUp, i) && !i->list().begin()->isConflict());
0562     }
0563 
0564     if(isVisible())
0565         setFocus();
0566 
0567     setFastSelector(i);
0568 }
0569 
0570 bool MergeResultWindow::isDeltaAboveCurrent() const
0571 {
0572     bool bSkipWhiteConflicts = !gOptions->m_bShowWhiteSpace;
0573     if(m_mergeBlockList.empty()) return false;
0574     MergeBlockList::const_iterator i = m_currentMergeBlockIt;
0575     if(i == m_mergeBlockList.begin()) return false;
0576     do
0577     {
0578         --i;
0579         if(i->isDelta() && !checkOverviewIgnore(i) && !(bSkipWhiteConflicts && i->isWhiteSpaceConflict())) return true;
0580     } while(i != m_mergeBlockList.begin());
0581 
0582     return false;
0583 }
0584 
0585 bool MergeResultWindow::isDeltaBelowCurrent() const
0586 {
0587     const bool bSkipWhiteConflicts = !gOptions->m_bShowWhiteSpace;
0588     if(m_mergeBlockList.empty()) return false;
0589 
0590     MergeBlockList::const_iterator i = m_currentMergeBlockIt;
0591     if(i != m_mergeBlockList.end())
0592     {
0593         ++i;
0594         for(; i != m_mergeBlockList.end(); ++i)
0595         {
0596             if(i->isDelta() && !checkOverviewIgnore(i) && !(bSkipWhiteConflicts && i->isWhiteSpaceConflict())) return true;
0597         }
0598     }
0599     return false;
0600 }
0601 
0602 bool MergeResultWindow::isConflictAboveCurrent() const
0603 {
0604     if(m_mergeBlockList.empty()) return false;
0605     MergeBlockList::const_iterator i = m_currentMergeBlockIt;
0606     if(i == m_mergeBlockList.cbegin()) return false;
0607 
0608     bool bSkipWhiteConflicts = !gOptions->m_bShowWhiteSpace;
0609 
0610     do
0611     {
0612         --i;
0613         if(i->isConflict() && !(bSkipWhiteConflicts && i->isWhiteSpaceConflict())) return true;
0614     } while(i != m_mergeBlockList.cbegin());
0615 
0616     return false;
0617 }
0618 
0619 bool MergeResultWindow::isConflictBelowCurrent() const
0620 {
0621     MergeBlockList::const_iterator i = m_currentMergeBlockIt;
0622     if(m_mergeBlockList.empty()) return false;
0623 
0624     bool bSkipWhiteConflicts = !gOptions->m_bShowWhiteSpace;
0625 
0626     if(i != m_mergeBlockList.cend())
0627     {
0628         ++i;
0629         for(; i != m_mergeBlockList.cend(); ++i)
0630         {
0631             if(i->isConflict() && !(bSkipWhiteConflicts && i->isWhiteSpaceConflict())) return true;
0632         }
0633     }
0634     return false;
0635 }
0636 
0637 bool MergeResultWindow::isUnsolvedConflictAtCurrent() const
0638 {
0639     if(m_mergeBlockList.empty()) return false;
0640 
0641     return m_currentMergeBlockIt->list().cbegin()->isConflict();
0642 }
0643 
0644 bool MergeResultWindow::isUnsolvedConflictAboveCurrent() const
0645 {
0646     if(m_mergeBlockList.empty()) return false;
0647     MergeBlockList::const_iterator i = m_currentMergeBlockIt;
0648     if(i == m_mergeBlockList.cbegin()) return false;
0649 
0650     do
0651     {
0652         --i;
0653         if(i->list().cbegin()->isConflict()) return true;
0654     } while(i != m_mergeBlockList.cbegin());
0655 
0656     return false;
0657 }
0658 
0659 bool MergeResultWindow::isUnsolvedConflictBelowCurrent() const
0660 {
0661     MergeBlockList::const_iterator i = m_currentMergeBlockIt;
0662     if(m_mergeBlockList.empty()) return false;
0663 
0664     if(i != m_mergeBlockList.cend())
0665     {
0666         ++i;
0667         for(; i != m_mergeBlockList.cend(); ++i)
0668         {
0669             if(i->list().cbegin()->isConflict()) return true;
0670         }
0671     }
0672     return false;
0673 }
0674 
0675 void MergeResultWindow::slotGoTop()
0676 {
0677     go(Direction::eUp, EndPoint::eEnd);
0678 }
0679 
0680 void MergeResultWindow::slotGoCurrent()
0681 {
0682     setFastSelector(m_currentMergeBlockIt);
0683 }
0684 
0685 void MergeResultWindow::slotGoBottom()
0686 {
0687     go(Direction::eDown, EndPoint::eEnd);
0688 }
0689 
0690 void MergeResultWindow::slotGoPrevDelta()
0691 {
0692     go(Direction::eUp, EndPoint::eDelta);
0693 }
0694 
0695 void MergeResultWindow::slotGoNextDelta()
0696 {
0697     go(Direction::eDown, EndPoint::eDelta);
0698 }
0699 
0700 void MergeResultWindow::slotGoPrevConflict()
0701 {
0702     go(Direction::eUp, EndPoint::eConflict);
0703 }
0704 
0705 void MergeResultWindow::slotGoNextConflict()
0706 {
0707     go(Direction::eDown, EndPoint::eConflict);
0708 }
0709 
0710 void MergeResultWindow::slotGoPrevUnsolvedConflict()
0711 {
0712     go(Direction::eUp, EndPoint::eUnsolvedConflict);
0713 }
0714 
0715 void MergeResultWindow::slotGoNextUnsolvedConflict()
0716 {
0717     go(Direction::eDown, EndPoint::eUnsolvedConflict);
0718 }
0719 
0720 /** The line is given as a index in the Diff3LineList.
0721     The function calculates the corresponding iterator. */
0722 void MergeResultWindow::slotSetFastSelectorLine(LineType line)
0723 {
0724     MergeBlockList::iterator i;
0725     for(i = m_mergeBlockList.begin(); i != m_mergeBlockList.end(); ++i)
0726     {
0727         if(line >= i->getIndex() && line < i->getIndex() + i->sourceRangeLength())
0728         {
0729             setFastSelector(i);
0730             break;
0731         }
0732     }
0733 }
0734 
0735 qint32 MergeResultWindow::getNumberOfUnsolvedConflicts(qint32* pNrOfWhiteSpaceConflicts) const
0736 {
0737     qint32 nrOfUnsolvedConflicts = 0;
0738     if(pNrOfWhiteSpaceConflicts != nullptr)
0739         *pNrOfWhiteSpaceConflicts = 0;
0740 
0741     for(const MergeBlock& mb: m_mergeBlockList)
0742     {
0743         MergeEditLineList::const_iterator melIt = mb.list().cbegin();
0744         if(melIt->isConflict())
0745         {
0746             ++nrOfUnsolvedConflicts;
0747             if(mb.isWhiteSpaceConflict() && pNrOfWhiteSpaceConflicts != nullptr)
0748                 ++*pNrOfWhiteSpaceConflicts;
0749         }
0750     }
0751 
0752     return nrOfUnsolvedConflicts;
0753 }
0754 
0755 void MergeResultWindow::showNumberOfConflicts(bool showIfNone)
0756 {
0757     if(!gOptions->m_bShowInfoDialogs)
0758         return;
0759     qint32 nrOfConflicts = 0;
0760     qint32 nrOfUnsolvedConflicts = getNumberOfUnsolvedConflicts();
0761 
0762     for(const MergeBlock& entry: m_mergeBlockList)
0763     {
0764         if(entry.isConflict() || entry.isDelta())
0765             ++nrOfConflicts;
0766     }
0767 
0768     if(!showIfNone && nrOfUnsolvedConflicts == 0)
0769         return;
0770 
0771     QString totalInfo;
0772     if(m_pTotalDiffStatus->isBinaryEqualAB() && m_pTotalDiffStatus->isBinaryEqualAC())
0773         totalInfo += i18n("All input files are binary equal.");
0774     else if(m_pTotalDiffStatus->isTextEqualAB() && m_pTotalDiffStatus->isTextEqualAC())
0775         totalInfo += i18n("All input files contain the same text.");
0776     else
0777     {
0778         if(m_pTotalDiffStatus->isBinaryEqualAB())
0779             totalInfo += i18n("Files %1 and %2 are binary equal.\n", QStringLiteral("A"), QStringLiteral("B"));
0780         else if(m_pTotalDiffStatus->isTextEqualAB())
0781             totalInfo += i18n("Files %1 and %2 have equal text.\n", QStringLiteral("A"), QStringLiteral("B"));
0782         if(m_pTotalDiffStatus->isBinaryEqualAC())
0783             totalInfo += i18n("Files %1 and %2 are binary equal.\n", QStringLiteral("A"), QStringLiteral("C"));
0784         else if(m_pTotalDiffStatus->isTextEqualAC())
0785             totalInfo += i18n("Files %1 and %2 have equal text.\n", QStringLiteral("A"), QStringLiteral("C"));
0786         if(m_pTotalDiffStatus->isBinaryEqualBC())
0787             totalInfo += i18n("Files %1 and %2 are binary equal.\n", QStringLiteral("B"), QStringLiteral("C"));
0788         else if(m_pTotalDiffStatus->isTextEqualBC())
0789             totalInfo += i18n("Files %1 and %2 have equal text.\n", QStringLiteral("B"), QStringLiteral("C"));
0790     }
0791 
0792     KMessageBox::information(this,
0793                              i18n("Total number of conflicts: %1\n"
0794                                   "Number of automatically solved conflicts: %2\n"
0795                                   "Number of unsolved conflicts: %3\n"
0796                                   "%4",
0797                                   nrOfConflicts, nrOfConflicts - nrOfUnsolvedConflicts,
0798                                   nrOfUnsolvedConflicts, totalInfo),
0799                              i18n("Conflicts"));
0800 }
0801 
0802 void MergeResultWindow::setFastSelector(MergeBlockList::iterator i)
0803 {
0804     if(i == m_mergeBlockList.end())
0805         return;
0806     m_currentMergeBlockIt = i;
0807     Q_EMIT setFastSelectorRange(i->getIndex(), i->sourceRangeLength());
0808 
0809     LineRef line1 = 0;
0810 
0811     MergeBlockList::const_iterator mbIt = m_mergeBlockList.cbegin();
0812     for(; mbIt != m_mergeBlockList.cend(); ++mbIt)
0813     {
0814         if(mbIt == m_currentMergeBlockIt)
0815             break;
0816         line1 += mbIt->lineCount();
0817     }
0818 
0819     LineType nofLines = m_currentMergeBlockIt->lineCount();
0820     LineRef newFirstLine = getBestFirstLine(line1, nofLines, m_firstLine, getNofVisibleLines());
0821     if(newFirstLine != m_firstLine)
0822     {
0823         scrollVertically(newFirstLine - m_firstLine);
0824     }
0825 
0826     if(m_selection.isEmpty())
0827     {
0828         m_cursorXPos = 0;
0829         m_cursorOldXPixelPos = 0;
0830         m_cursorYPos = line1;
0831     }
0832 
0833     update();
0834     updateSourceMask();
0835     Q_EMIT updateAvailabilities();
0836 }
0837 
0838 void MergeResultWindow::choose(const e_SrcSelector selector)
0839 {
0840     if(m_currentMergeBlockIt == m_mergeBlockList.end())
0841         return;
0842 
0843     setModified();
0844 
0845     // First find range for which this change works.
0846     MergeBlock& mb = *m_currentMergeBlockIt;
0847 
0848     MergeEditLineList::iterator melIt;
0849 
0850     // Now check if selector is active for this range already.
0851     bool bActive = false;
0852 
0853     // Remove unneeded lines in the range.
0854     for(melIt = mb.list().begin(); melIt != mb.list().end();)
0855     {
0856         const MergeEditLine& mel = *melIt;
0857         if(mel.src() == selector)
0858             bActive = true;
0859 
0860         if(mel.src() == selector || !mel.isEditableText() || mel.isModified())
0861             melIt = mb.list().erase(melIt);
0862         else
0863             ++melIt;
0864     }
0865 
0866     if(!bActive) // Selected source wasn't active.
0867     {            // Append the lines from selected source here at rangeEnd.
0868         Diff3LineList::const_iterator d3llit = mb.id3l();
0869         qint32 j;
0870 
0871         for(j = 0; j < mb.sourceRangeLength(); ++j)
0872         {
0873             MergeEditLine mel(d3llit);
0874             mel.setSource(selector, false);
0875             mb.list().push_back(mel);
0876 
0877             ++d3llit;
0878         }
0879     }
0880 
0881     if(!mb.list().empty())
0882     {
0883         // Remove all lines that are empty, because no src lines are there.
0884         for(melIt = mb.list().begin(); melIt != mb.list().end();)
0885         {
0886             const MergeEditLine& mel = *melIt;
0887             const LineRef srcLine = mel.src() == e_SrcSelector::A ? mel.id3l()->getLineA() : mel.src() == e_SrcSelector::B ? mel.id3l()->getLineB() : mel.src() == e_SrcSelector::C ? mel.id3l()->getLineC() : LineRef();
0888 
0889             if(!srcLine.isValid())
0890                 melIt = mb.list().erase(melIt);
0891             else
0892                 ++melIt;
0893         }
0894     }
0895 
0896     if(mb.list().empty())
0897     {
0898         // Insert a dummy line:
0899         MergeEditLine mel(mb.id3l());
0900 
0901         if(bActive)
0902             mel.setConflict(); // All src entries deleted => conflict
0903         else
0904             mel.setRemoved(selector); // No lines in corresponding src found.
0905 
0906         mb.list().push_back(mel);
0907     }
0908 
0909     if(m_cursorYPos >= m_nofLines)
0910     {
0911         m_cursorYPos = m_nofLines - 1;
0912         m_cursorXPos = 0;
0913     }
0914 
0915     m_maxTextWidth = -1;
0916     update();
0917     updateSourceMask();
0918     Q_EMIT updateAvailabilities();
0919     showUnsolvedConflictsStatusMessage();
0920 }
0921 
0922 // bConflictsOnly: automatically choose for conflicts only (true) or for everywhere (false)
0923 void MergeResultWindow::chooseGlobal(e_SrcSelector selector, bool bConflictsOnly, bool bWhiteSpaceOnly)
0924 {
0925     resetSelection();
0926 
0927     merge(false, selector, bConflictsOnly, bWhiteSpaceOnly);
0928     setModified(true);
0929     update();
0930     showUnsolvedConflictsStatusMessage();
0931 }
0932 
0933 void MergeResultWindow::slotAutoSolve()
0934 {
0935     resetSelection();
0936     merge(true, e_SrcSelector::Invalid);
0937     setModified(true);
0938     update();
0939     showUnsolvedConflictsStatusMessage();
0940     showNumberOfConflicts();
0941 }
0942 
0943 void MergeResultWindow::slotUnsolve()
0944 {
0945     resetSelection();
0946     merge(false, e_SrcSelector::Invalid);
0947     setModified(true);
0948     update();
0949     showUnsolvedConflictsStatusMessage();
0950 }
0951 
0952 bool findParenthesesGroups(const QString& s, QStringList& sl)
0953 {
0954     sl.clear();
0955     QtSizeType i = 0;
0956     std::list<QtSizeType> startPosStack;
0957     QtSizeType length = s.length();
0958     for(i = 0; i < length; ++i)
0959     {
0960         if(s[i] == '\\' && i + 1 < length && (s[i + 1] == '\\' || s[i + 1] == '(' || s[i + 1] == ')'))
0961         {
0962             ++i;
0963             continue;
0964         }
0965         if(s[i] == '(')
0966         {
0967             startPosStack.push_back(i);
0968         }
0969         else if(s[i] == ')')
0970         {
0971             if(startPosStack.empty())
0972                 return false; // Parentheses don't match
0973             QtSizeType startPos = startPosStack.back();
0974             startPosStack.pop_back();
0975             sl.push_back(s.mid(startPos + 1, i - startPos - 1));
0976         }
0977     }
0978     return startPosStack.empty(); // false if parentheses don't match
0979 }
0980 
0981 QString calcHistorySortKey(const QString& keyOrder, QRegularExpressionMatch& regExprMatch, const QStringList& parenthesesGroupList)
0982 {
0983     const QStringList keyOrderList = keyOrder.split(',');
0984     QString key;
0985 
0986     for(const QString& keyIt : keyOrderList)
0987     {
0988         if(keyIt.isEmpty())
0989             continue;
0990         bool bOk = false;
0991         qint32 groupIdx = keyIt.toInt(&bOk);
0992         if(!bOk || groupIdx < 0 || groupIdx > parenthesesGroupList.size())
0993             continue;
0994         QString s = regExprMatch.captured(groupIdx);
0995         if(groupIdx == 0)
0996         {
0997             key += s + ' ';
0998             continue;
0999         }
1000 
1001         QString groupRegExp = parenthesesGroupList[groupIdx - 1];
1002         if(groupRegExp.indexOf('|') < 0 || groupRegExp.indexOf('(') >= 0)
1003         {
1004             bOk = false;
1005             qint32 i = s.toInt(&bOk);
1006             if(bOk && i >= 0 && i < 10000)
1007             {
1008                 s += QString(4 - s.size(), '0'); // This should help for correct sorting of numbers.
1009             }
1010             key += s + ' ';
1011         }
1012         else
1013         {
1014             // Assume that the groupRegExp consists of something like "Jan|Feb|Mar|Apr"
1015             // s is the string that managed to match.
1016             // Now we want to know at which position it occurred. e.g. Jan=0, Feb=1, Mar=2, etc.
1017             QStringList sl = groupRegExp.split('|');
1018             QtSizeType idx = sl.indexOf(s);
1019             if(idx >= 0)
1020             {
1021                 QString sIdx;
1022                 sIdx.setNum(idx);
1023                 assert(sIdx.size() <= 2);
1024                 sIdx += QString(2 - sIdx.size(), '0'); // Up to 99 words in the groupRegExp (more than 12 aren't expected)
1025                 key += sIdx + ' ';
1026             }
1027         }
1028     }
1029     return key;
1030 }
1031 
1032 void MergeResultWindow::collectHistoryInformation(
1033     e_SrcSelector src, const HistoryRange& historyRange,
1034     HistoryMap& historyMap,
1035     std::list<HistoryMap::iterator>& hitList // list of iterators
1036 )
1037 {
1038     std::list<HistoryMap::iterator>::const_iterator itHitListFront = hitList.cbegin();
1039     Diff3LineList::const_iterator id3l = historyRange.start;
1040     QString historyLead;
1041 
1042     historyLead = Utils::calcHistoryLead(id3l->getLineData(src).getLine());
1043 
1044     QRegularExpression historyStart(gOptions->m_historyStartRegExp);
1045     if(id3l == historyRange.end)
1046         return;
1047     //TODO: Where is this assumption coming from?
1048     ++id3l; // Skip line with "$Log ... $"
1049     QRegularExpression newHistoryEntry(gOptions->m_historyEntryStartRegExp);
1050     QRegularExpressionMatch match;
1051     QStringList parenthesesGroups;
1052     findParenthesesGroups(gOptions->m_historyEntryStartRegExp, parenthesesGroups);
1053     QString key;
1054     MergeEditLineList melList;
1055     bool bPrevLineIsEmpty = true;
1056     bool bUseRegExp = !gOptions->m_historyEntryStartRegExp.isEmpty();
1057 
1058     for(; id3l != historyRange.end; ++id3l)
1059     {
1060         const LineData& pld = id3l->getLineData(src);
1061         const QString& oriLine = pld.getLine();
1062         if(historyLead.isEmpty()) historyLead = Utils::calcHistoryLead(oriLine);
1063         QString sLine = oriLine.mid(historyLead.length());
1064         match = newHistoryEntry.match(sLine);
1065         if((!bUseRegExp && !sLine.trimmed().isEmpty() && bPrevLineIsEmpty) || (bUseRegExp && match.hasMatch()))
1066         {
1067             if(!key.isEmpty() && !melList.empty())
1068             {
1069                 // Only insert new HistoryMapEntry if key not found; in either case p.first is a valid iterator to element key.
1070                 std::pair<HistoryMap::iterator, bool> p = historyMap.insert(HistoryMap::value_type(key, HistoryMapEntry()));
1071                 HistoryMapEntry& hme = p.first->second;
1072                 if(src == e_SrcSelector::A) hme.mellA = melList;
1073                 else if(src == e_SrcSelector::B) hme.mellB = melList;
1074                 else hme.mellC = melList;
1075                 if(p.second) // Not in list yet?
1076                 {
1077                     hitList.insert(itHitListFront, p.first);
1078                 }
1079             }
1080 
1081             if(!bUseRegExp)
1082                 key = sLine;
1083             else
1084                 key = calcHistorySortKey(gOptions->m_historyEntryStartSortKeyOrder, match, parenthesesGroups);
1085 
1086             melList.clear();
1087             melList.push_back(MergeEditLine(id3l, src));
1088         }
1089         else if(!historyStart.match(oriLine).hasMatch())
1090         {
1091             melList.push_back(MergeEditLine(id3l, src));
1092         }
1093 
1094         bPrevLineIsEmpty = sLine.trimmed().isEmpty();
1095     }
1096     if(!key.isEmpty())
1097     {
1098         // Only insert new HistoryMapEntry if key not found; in either case p.first is a valid iterator to element key.
1099         std::pair<HistoryMap::iterator, bool> p = historyMap.insert(HistoryMap::value_type(key, HistoryMapEntry()));
1100         HistoryMapEntry& hme = p.first->second;
1101         if(src == e_SrcSelector::A) hme.mellA = melList;
1102         if(src == e_SrcSelector::B) hme.mellB = melList;
1103         if(src == e_SrcSelector::C) hme.mellC = melList;
1104         if(p.second) // Not in list yet?
1105         {
1106             hitList.insert(itHitListFront, p.first);
1107         }
1108     }
1109     // End of the history
1110 }
1111 
1112 MergeEditLineList& MergeResultWindow::HistoryMapEntry::choice(bool bThreeInputs)
1113 {
1114     if(!bThreeInputs)
1115         return mellA.empty() ? mellB : mellA;
1116     else
1117     {
1118         if(mellA.empty())
1119             return mellC.empty() ? mellB : mellC; // A doesn't exist, return one that exists
1120         else if(!mellB.empty() && !mellC.empty())
1121         { // A, B and C exist
1122             return mellA;
1123         }
1124         else
1125             return mellB.empty() ? mellB : mellC; // A exists, return the one that doesn't exist
1126     }
1127 }
1128 
1129 bool MergeResultWindow::HistoryMapEntry::staysInPlace(bool bThreeInputs, Diff3LineList::const_iterator& iHistoryEnd)
1130 {
1131     // The entry should stay in place if the decision made by the automerger is correct.
1132     Diff3LineList::const_iterator& iHistoryLast = iHistoryEnd;
1133     --iHistoryLast;
1134     if(!bThreeInputs)
1135     {
1136         if(!mellA.empty() && !mellB.empty() && mellA.begin()->id3l() == mellB.begin()->id3l() &&
1137            mellA.back().id3l() == iHistoryLast && mellB.back().id3l() == iHistoryLast)
1138         {
1139             iHistoryEnd = mellA.begin()->id3l();
1140             return true;
1141         }
1142         else
1143         {
1144             return false;
1145         }
1146     }
1147     else
1148     {
1149         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)
1150         {
1151             iHistoryEnd = mellA.begin()->id3l();
1152             return true;
1153         }
1154         else
1155         {
1156             return false;
1157         }
1158     }
1159 }
1160 
1161 void MergeResultWindow::slotMergeHistory()
1162 {
1163     HistoryRange        historyRange;
1164 
1165     // Search for history start, history end in the diff3LineList
1166     m_pDiff3LineList->findHistoryRange(QRegularExpression(gOptions->m_historyStartRegExp), m_pldC != nullptr, historyRange);
1167     if(historyRange.start != m_pDiff3LineList->end())
1168     {
1169         mUndoRec.reset();
1170         // Now collect the historyMap information
1171         HistoryMap historyMap;
1172         std::list<HistoryMap::iterator> hitList;
1173         if(m_pldC == nullptr)
1174         {
1175             collectHistoryInformation(e_SrcSelector::A, historyRange, historyMap, hitList);
1176             collectHistoryInformation(e_SrcSelector::B, historyRange, historyMap, hitList);
1177         }
1178         else
1179         {
1180             collectHistoryInformation(e_SrcSelector::A, historyRange, historyMap, hitList);
1181             collectHistoryInformation(e_SrcSelector::B, historyRange, historyMap, hitList);
1182             collectHistoryInformation(e_SrcSelector::C, historyRange, historyMap, hitList);
1183         }
1184 
1185         Diff3LineList::const_iterator iD3LHistoryOrigEnd = historyRange.end;
1186 
1187         bool bHistoryMergeSorting = gOptions->m_bHistoryMergeSorting && !gOptions->m_historyEntryStartSortKeyOrder.isEmpty() &&
1188                                     !gOptions->m_historyEntryStartRegExp.isEmpty();
1189 
1190         if(gOptions->m_maxNofHistoryEntries == -1)
1191         {
1192             // Remove parts from the historyMap and hitList that stay in place
1193             if(bHistoryMergeSorting)
1194             {
1195                 while(!historyMap.empty())
1196                 {
1197                     HistoryMap::iterator hMapIt = historyMap.begin();
1198                     if(hMapIt->second.staysInPlace(m_pldC != nullptr, historyRange.end))
1199                         historyMap.erase(hMapIt);
1200                     else
1201                         break;
1202                 }
1203             }
1204             else
1205             {
1206                 while(!hitList.empty())
1207                 {
1208                     HistoryMap::iterator hMapIt = hitList.back();
1209                     if(hMapIt->second.staysInPlace(m_pldC != nullptr, historyRange.end))
1210                         hitList.pop_back();
1211                     else
1212                         break;
1213                 }
1214             }
1215             while(iD3LHistoryOrigEnd != historyRange.end)
1216             {
1217                 --iD3LHistoryOrigEnd;
1218                 --historyRange.endIdx;
1219             }
1220         }
1221 
1222         MergeBlockList::iterator iMBLStart = m_mergeBlockList.splitAtDiff3LineIdx(historyRange.startIdx);
1223         MergeBlockList::iterator iMBLEnd = m_mergeBlockList.splitAtDiff3LineIdx(historyRange.endIdx);
1224         // Now join all MergeBlocks in the history
1225         MergeBlockList::iterator i = iMBLStart;
1226         if(i != iMBLEnd)
1227         {
1228             ++i;
1229             while(i != iMBLEnd)
1230             {
1231                 iMBLStart->join(*i);
1232                 i = m_mergeBlockList.erase(i);
1233             }
1234         }
1235         iMBLStart->list().clear();
1236         // Now insert the complete history into the first MergeLine of the history
1237         iMBLStart->list().push_back(MergeEditLine(historyRange.start, m_pldC == nullptr ? e_SrcSelector::B : e_SrcSelector::C));
1238         const QString lead = Utils::calcHistoryLead(historyRange.start->getString(e_SrcSelector::A));
1239         MergeEditLine mel(m_pDiff3LineList->end());
1240         mel.setString(lead);
1241         iMBLStart->list().push_back(mel);
1242 
1243         qint32 historyCount = 0;
1244         if(bHistoryMergeSorting)
1245         {
1246             // Create a sorted history
1247             HistoryMap::reverse_iterator hmit;
1248             for(hmit = historyMap.rbegin(); hmit != historyMap.rend(); ++hmit)
1249             {
1250                 if(historyCount == gOptions->m_maxNofHistoryEntries)
1251                     break;
1252                 ++historyCount;
1253                 HistoryMapEntry& hme = hmit->second;
1254                 MergeEditLineList& mell = hme.choice(m_pldC != nullptr);
1255                 if(!mell.empty())
1256                     iMBLStart->list().splice(iMBLStart->list().end(), mell, mell.begin(), mell.end());
1257             }
1258         }
1259         else
1260         {
1261             // Create history in order of appearance
1262             std::list<HistoryMap::iterator>::const_iterator hlit;
1263             for(hlit = hitList.begin(); hlit != hitList.end(); ++hlit)
1264             {
1265                 if(historyCount == gOptions->m_maxNofHistoryEntries)
1266                     break;
1267                 ++historyCount;
1268                 HistoryMapEntry& hme = (*hlit)->second;
1269                 MergeEditLineList& mell = hme.choice(m_pldC != nullptr);
1270                 if(!mell.empty())
1271                     iMBLStart->list().splice(iMBLStart->list().end(), mell, mell.begin(), mell.end());
1272             }
1273             // If the end of start is empty and the first line at the end is empty remove the last line of start
1274             if(!iMBLStart->list().empty() && !iMBLEnd->list().empty())
1275             {
1276                 const QString lastLineOfStart = iMBLStart->list().back().getString(m_pldA, m_pldB, m_pldC);
1277                 const QString firstLineOfEnd = iMBLEnd->list().front().getString(m_pldA, m_pldB, m_pldC);
1278                 if(lastLineOfStart.mid(lead.length()).trimmed().isEmpty() && firstLineOfEnd.mid(lead.length()).trimmed().isEmpty())
1279                     iMBLStart->list().pop_back();
1280             }
1281         }
1282         setFastSelector(iMBLStart);
1283         update();
1284     }
1285 }
1286 
1287 void MergeResultWindow::slotRegExpAutoMerge()
1288 {
1289     if(gOptions->m_autoMergeRegExp.isEmpty())
1290         return;
1291 
1292     QRegularExpression vcsKeywords(gOptions->m_autoMergeRegExp);
1293     MergeBlockList::iterator i;
1294 
1295     mUndoRec.reset();
1296     for(i = m_mergeBlockList.begin(); i != m_mergeBlockList.end(); ++i)
1297     {
1298         if(i->isConflict())
1299         {
1300             Diff3LineList::const_iterator id3l = i->id3l();
1301             if(vcsKeywords.match(id3l->getString(e_SrcSelector::A)).hasMatch() &&
1302                vcsKeywords.match(id3l->getString(e_SrcSelector::B)).hasMatch() &&
1303                (m_pldC == nullptr || vcsKeywords.match(id3l->getString(e_SrcSelector::C)).hasMatch()))
1304             {
1305                 MergeEditLine& mel = *i->list().begin();
1306                 mel.setSource(m_pldC == nullptr ? e_SrcSelector::B : e_SrcSelector::C, false);
1307                 m_mergeBlockList.splitAtDiff3LineIdx(i->getIndex() + 1);
1308             }
1309         }
1310     }
1311     update();
1312 }
1313 
1314 // This doesn't detect user modifications and should only be called after automatic merge
1315 // This will only do something for three file merge.
1316 // Irrelevant changes are those where all contributions from B are already contained in C.
1317 // Also irrelevant are conflicts automatically solved (auto-merge regexp and history auto-merge)
1318 // Precondition: The VCS-keyword would also be C.
1319 bool MergeResultWindow::doRelevantChangesExist()
1320 {
1321     if(m_pldC == nullptr || m_mergeBlockList.size() <= 1)
1322         return true;
1323 
1324     for(const MergeBlock& mergeBlock: m_mergeBlockList)
1325     {
1326         if((mergeBlock.isConflict() && mergeBlock.list().cbegin()->src() != e_SrcSelector::C) || mergeBlock.source() == e_SrcSelector::B)
1327         {
1328             return true;
1329         }
1330     }
1331 
1332     return false;
1333 }
1334 
1335 void MergeResultWindow::slotSplitDiff(qint32 firstD3lLineIdx, qint32 lastD3lLineIdx)
1336 {
1337     mUndoRec.reset();
1338 
1339     if(lastD3lLineIdx >= 0)
1340         m_mergeBlockList.splitAtDiff3LineIdx(lastD3lLineIdx + 1);
1341     setFastSelector(m_mergeBlockList.splitAtDiff3LineIdx(firstD3lLineIdx));
1342 }
1343 
1344 void MergeResultWindow::slotJoinDiffs(qint32 firstD3lLineIdx, qint32 lastD3lLineIdx)
1345 {
1346     MergeBlockList::iterator i;
1347     MergeBlockList::iterator iMBLStart = m_mergeBlockList.end();
1348     MergeBlockList::iterator iMBLEnd = m_mergeBlockList.end();
1349 
1350     mUndoRec.reset();
1351     for(i = m_mergeBlockList.begin(); i != m_mergeBlockList.end(); ++i)
1352     {
1353         const MergeBlock& mb = *i;
1354         if(firstD3lLineIdx >= mb.getIndex() && firstD3lLineIdx < mb.getIndex() + mb.sourceRangeLength())
1355         {
1356             iMBLStart = i;
1357         }
1358         if(lastD3lLineIdx >= mb.getIndex() && lastD3lLineIdx < mb.getIndex() + mb.sourceRangeLength())
1359         {
1360             iMBLEnd = i;
1361             ++iMBLEnd;
1362             break;
1363         }
1364     }
1365 
1366     bool bJoined = false;
1367     for(i = iMBLStart; i != iMBLEnd && i != m_mergeBlockList.end();)
1368     {
1369         if(i == iMBLStart)
1370         {
1371             ++i;
1372         }
1373         else
1374         {
1375             iMBLStart->join(*i);
1376             i = m_mergeBlockList.erase(i);
1377             bJoined = true;
1378         }
1379     }
1380     if(bJoined)
1381     {
1382         iMBLStart->list().clear();
1383         // Insert a conflict line as placeholder
1384         iMBLStart->list().push_back(MergeEditLine(iMBLStart->id3l()));
1385     }
1386     setFastSelector(iMBLStart);
1387 }
1388 
1389 void MergeResultWindow::myUpdate(qint32 afterMilliSecs)
1390 {
1391     if(m_delayedDrawTimer)
1392         killTimer(m_delayedDrawTimer);
1393     m_bMyUpdate = true;
1394     m_delayedDrawTimer = startTimer(afterMilliSecs);
1395 }
1396 
1397 void MergeResultWindow::timerEvent(QTimerEvent*)
1398 {
1399     killTimer(m_delayedDrawTimer);
1400     m_delayedDrawTimer = 0;
1401 
1402     if(m_bMyUpdate)
1403     {
1404         update();
1405         m_bMyUpdate = false;
1406     }
1407 
1408     if(m_scrollDeltaX != 0 || m_scrollDeltaY != 0)
1409     {
1410         QtSizeType newPos = m_selection.getLastPos() + m_scrollDeltaX;
1411         try
1412         {
1413             LineRef newLine = m_selection.getLastLine() + m_scrollDeltaY;
1414             m_selection.end(newLine, newPos > 0 ? newPos : 0);
1415         }
1416         catch(const std::system_error&)
1417         {
1418             m_selection.end(LineRef::invalid, newPos > 0 ? newPos : 0);
1419         }
1420 
1421         Q_EMIT scrollMergeResultWindow(m_scrollDeltaX, m_scrollDeltaY);
1422         killTimer(m_delayedDrawTimer);
1423         m_delayedDrawTimer = startTimer(50);
1424     }
1425 }
1426 
1427 QVector<QTextLayout::FormatRange> MergeResultWindow::getTextLayoutForLine(LineRef line, const QString& str, QTextLayout& textLayout)
1428 {
1429     // tabs
1430     QTextOption textOption;
1431 
1432     textOption.setTabStopDistance(QFontMetricsF(font()).horizontalAdvance(' ') * gOptions->tabSize());
1433 
1434     if(gOptions->m_bShowWhiteSpaceCharacters)
1435     {
1436         textOption.setFlags(QTextOption::ShowTabsAndSpaces);
1437     }
1438     textLayout.setTextOption(textOption);
1439 
1440     if(gOptions->m_bShowWhiteSpaceCharacters)
1441     {
1442         // This additional format is only necessary for the tab arrow
1443         QVector<QTextLayout::FormatRange> formats;
1444         QTextLayout::FormatRange formatRange;
1445         formatRange.start = 0;
1446         formatRange.length = SafeInt<qint32>(str.length());
1447         formatRange.format.setFont(font());
1448         formats.append(formatRange);
1449         textLayout.setFormats(formats);
1450     }
1451     QVector<QTextLayout::FormatRange> selectionFormat;
1452     textLayout.beginLayout();
1453     if(m_selection.lineWithin(line))
1454     {
1455         QtSizeType firstPosInText = m_selection.firstPosInLine(line);
1456         QtSizeType lastPosInText = m_selection.lastPosInLine(line);
1457 
1458         QtSizeType lengthInText = std::max<QtSizeType>(0, lastPosInText - firstPosInText);
1459         assert(lengthInText <= limits<qint32>::max());
1460 
1461         QTextLayout::FormatRange selection;
1462         selection.start = SafeInt<qint32>(firstPosInText);
1463         selection.length = SafeInt<qint32>(lengthInText);
1464         selection.format.setBackground(palette().highlight());
1465         selection.format.setForeground(palette().highlightedText().color());
1466         selectionFormat.push_back(selection);
1467     }
1468     QTextLine textLine = textLayout.createLine();
1469     textLine.setPosition(QPointF(0, fontMetrics().leading()));
1470     textLayout.endLayout();
1471     qint32 cursorWidth = 5;
1472     if(gOptions->m_bRightToLeftLanguage)
1473         textLayout.setPosition(QPointF(width() - textLayout.maximumWidth() - getTextXOffset() + m_horizScrollOffset - cursorWidth, 0));
1474     else
1475         textLayout.setPosition(QPointF(getTextXOffset() - m_horizScrollOffset, 0));
1476     return selectionFormat;
1477 }
1478 
1479 void MergeResultWindow::writeLine(
1480     RLPainter& p, LineRef line, const QString& str,
1481     e_SrcSelector srcSelect, e_MergeDetails mergeDetails, RangeFlags rangeMark, bool bUserModified, bool bLineRemoved, bool bWhiteSpaceConflict)
1482 {
1483     const QFontMetrics& fm = fontMetrics();
1484     qint32 fontHeight = fm.lineSpacing();
1485     qint32 fontAscent = fm.ascent();
1486 
1487     qint32 topLineYOffset = 0;
1488     qint32 xOffset = getTextXOffset();
1489 
1490     qint32 yOffset = (line - m_firstLine) * fontHeight;
1491     if(yOffset < 0 || yOffset > height())
1492         return;
1493 
1494     yOffset += topLineYOffset;
1495 
1496     QString srcName = QChar(' ');
1497     if(bUserModified)
1498         srcName = QChar('m');
1499     else if(srcSelect == e_SrcSelector::A && mergeDetails != e_MergeDetails::eNoChange)
1500         srcName = QStringLiteral("A");
1501     else if(srcSelect == e_SrcSelector::B)
1502         srcName = QStringLiteral("B");
1503     else if(srcSelect == e_SrcSelector::C)
1504         srcName = QStringLiteral("C");
1505 
1506     if(rangeMark & RangeMark::current)
1507     {
1508         p.fillRect(xOffset, yOffset, width(), fontHeight, gOptions->getCurrentRangeBgColor());
1509     }
1510 
1511     if((srcSelect > e_SrcSelector::None || bUserModified) && !bLineRemoved)
1512     {
1513         if(!gOptions->m_bRightToLeftLanguage)
1514             p.setClipRect(QRectF(xOffset, 0, width() - xOffset, height()));
1515         else
1516             p.setClipRect(QRectF(0, 0, width() - xOffset, height()));
1517 
1518         p.setPen(gOptions->foregroundColor());
1519 
1520         QTextLayout textLayout(str, font(), this);
1521         QVector<QTextLayout::FormatRange> selectionFormat = getTextLayoutForLine(line, str, textLayout);
1522         textLayout.draw(&p, QPointF(0, yOffset), selectionFormat);
1523 
1524         if(line == m_cursorYPos)
1525         {
1526             m_cursorXPixelPos = qCeil(textLayout.lineAt(0).cursorToX((qint32)m_cursorXPos));
1527             if(gOptions->m_bRightToLeftLanguage)
1528                 m_cursorXPixelPos += qCeil(textLayout.position().x() - m_horizScrollOffset);
1529         }
1530 
1531         p.setClipping(false);
1532 
1533         p.setPen(gOptions->foregroundColor());
1534 
1535         p.drawText(1, yOffset + fontAscent, srcName, true);
1536     }
1537     else if(bLineRemoved)
1538     {
1539         p.setPen(gOptions->conflictColor());
1540         p.drawText(xOffset, yOffset + fontAscent, i18n("<No src line>"));
1541         p.drawText(1, yOffset + fontAscent, srcName);
1542         if(m_cursorYPos == line) m_cursorXPos = 0;
1543     }
1544     else if(srcSelect == e_SrcSelector::None)
1545     {
1546         p.setPen(gOptions->conflictColor());
1547         if(bWhiteSpaceConflict)
1548             p.drawText(xOffset, yOffset + fontAscent, i18n("<Merge Conflict (Whitespace only)>"));
1549         else
1550             p.drawText(xOffset, yOffset + fontAscent, i18n("<Merge Conflict>"));
1551         p.drawText(1, yOffset + fontAscent, "?");
1552         if(m_cursorYPos == line) m_cursorXPos = 0;
1553     }
1554     else
1555         assert(false);
1556 
1557     xOffset -= fm.horizontalAdvance('0');
1558     p.setPen(gOptions->foregroundColor());
1559     if(rangeMark & RangeMark::begin) // begin mark
1560     {
1561         p.drawLine(xOffset, yOffset + 1, xOffset, yOffset + fontHeight / 2);
1562         p.drawLine(xOffset, yOffset + 1, xOffset - 2, yOffset + 1);
1563     }
1564     else
1565     {
1566         p.drawLine(xOffset, yOffset, xOffset, yOffset + fontHeight / 2);
1567     }
1568 
1569     if(rangeMark & RangeMark::end) // end mark
1570     {
1571         p.drawLine(xOffset, yOffset + fontHeight / 2, xOffset, yOffset + fontHeight - 1);
1572         p.drawLine(xOffset, yOffset + fontHeight - 1, xOffset - 2, yOffset + fontHeight - 1);
1573     }
1574     else
1575     {
1576         p.drawLine(xOffset, yOffset + fontHeight / 2, xOffset, yOffset + fontHeight);
1577     }
1578 
1579     if(rangeMark & RangeMark::current)
1580     {
1581         p.fillRect(xOffset + 3, yOffset, 3, fontHeight, gOptions->foregroundColor());
1582         /*      p.setPen( blue );
1583       p.drawLine( xOffset+2, yOffset, xOffset+2, yOffset+fontHeight-1 );
1584       p.drawLine( xOffset+3, yOffset, xOffset+3, yOffset+fontHeight-1 );*/
1585     }
1586 }
1587 
1588 void MergeResultWindow::setPaintingAllowed(bool bPaintingAllowed)
1589 {
1590     setUpdatesEnabled(bPaintingAllowed);
1591     if(!bPaintingAllowed)
1592     {
1593         reset();
1594     }
1595     else
1596         update();
1597 }
1598 
1599 void MergeResultWindow::paintEvent(QPaintEvent*)
1600 {
1601     if(m_pDiff3LineList == nullptr)
1602         return;
1603 
1604     const QFontMetrics& fm = fontMetrics();
1605     const qint32 fontWidth = fm.horizontalAdvance('0');
1606 
1607     if(!m_bCursorUpdate) // Don't redraw everything for blinking cursor?
1608     {
1609         const auto dpr = devicePixelRatioF();
1610         if(size() * dpr != m_pixmap.size())
1611         {
1612             m_pixmap = QPixmap(size() * dpr);
1613             m_pixmap.setDevicePixelRatio(dpr);
1614         }
1615 
1616         RLPainter p(&m_pixmap, gOptions->m_bRightToLeftLanguage, width(), fontWidth);
1617         p.setFont(font());
1618         p.QPainter::fillRect(rect(), gOptions->backgroundColor());
1619 
1620         //qint32 visibleLines = height() / fontHeight;
1621 
1622         const LineRef lastVisibleLine = m_firstLine + getNofVisibleLines() + 5;
1623         LineRef line = 0;
1624         MergeBlockList::const_iterator mbIt = m_mergeBlockList.cbegin();
1625         for(; mbIt != m_mergeBlockList.cend(); ++mbIt)
1626         {
1627             const MergeBlock& mb = *mbIt;
1628             if(line > lastVisibleLine || line + mb.lineCount() < m_firstLine)
1629             {
1630                 line += mb.lineCount();
1631             }
1632             else
1633             {
1634                 MergeEditLineList::const_iterator melIt;
1635                 for(melIt = mb.list().cbegin(); melIt != mb.list().cend(); ++melIt)
1636                 {
1637                     if(line >= m_firstLine && line <= lastVisibleLine)
1638                     {
1639                         const MergeEditLine& mel = *melIt;
1640                         MergeEditLineList::const_iterator melIt1 = melIt;
1641                         ++melIt1;
1642 
1643                         RangeFlags rangeMark = RangeMark::none;
1644                         if(melIt == mb.list().cbegin()) rangeMark |= RangeMark::begin; // Begin range mark
1645                         if(melIt1 == mb.list().cend()) rangeMark |= RangeMark::end;    // End range mark
1646 
1647                         if(mbIt == m_currentMergeBlockIt) rangeMark |= RangeMark::current; // Mark of the current line
1648 
1649                         const QString s = mel.getString(m_pldA, m_pldB, m_pldC);
1650 
1651                         writeLine(p, line, s, mel.src(), mb.details(), rangeMark,
1652                                   mel.isModified(), mel.isRemoved(), mb.isWhiteSpaceConflict());
1653                     }
1654                     ++line;
1655                 }
1656             }
1657         }
1658 
1659         if(line != m_nofLines)
1660         {
1661             m_nofLines = line;
1662 
1663             Q_EMIT resizeSignal();
1664         }
1665 
1666         p.end();
1667     }
1668 
1669     QPainter painter(this);
1670 
1671     if(!m_bCursorUpdate)
1672         painter.drawPixmap(0, 0, m_pixmap);
1673     else
1674     {
1675         painter.drawPixmap(0, 0, m_pixmap); // Draw everything. (Internally cursor rect is clipped anyway.)
1676         m_bCursorUpdate = false;
1677     }
1678 
1679     if(m_bCursorOn && hasFocus() && m_cursorYPos >= m_firstLine)
1680     {
1681         painter.setPen(gOptions->foregroundColor());
1682 
1683         QString str = getString(m_cursorYPos);
1684         QTextLayout textLayout(str, font(), this);
1685         getTextLayoutForLine(m_cursorYPos, str, textLayout);
1686         textLayout.drawCursor(&painter, QPointF(0, (m_cursorYPos - m_firstLine) * fontMetrics().lineSpacing()), m_cursorXPos);
1687     }
1688 
1689     painter.end();
1690 }
1691 
1692 void MergeResultWindow::updateSourceMask()
1693 {
1694     qint32 srcMask = 0;
1695     qint32 enabledMask = 0;
1696     if(!hasFocus() || m_pDiff3LineList == nullptr || !updatesEnabled() || m_currentMergeBlockIt == m_mergeBlockList.end())
1697     {
1698         srcMask = 0;
1699         enabledMask = 0;
1700     }
1701     else
1702     {
1703         enabledMask = m_pldC == nullptr ? 3 : 7;
1704         MergeBlock& mb = *m_currentMergeBlockIt;
1705 
1706         srcMask = 0;
1707         bool bModified = false;
1708         MergeEditLineList::iterator melIt;
1709         for(melIt = mb.list().begin(); melIt != mb.list().end(); ++melIt)
1710         {
1711             MergeEditLine& mel = *melIt;
1712             if(mel.src() == e_SrcSelector::A) srcMask |= 1;
1713             if(mel.src() == e_SrcSelector::B) srcMask |= 2;
1714             if(mel.src() == e_SrcSelector::C) srcMask |= 4;
1715             if(mel.isModified() || !mel.isEditableText()) bModified = true;
1716         }
1717 
1718         if(mb.details() == e_MergeDetails::eNoChange)
1719         {
1720             srcMask = 0;
1721             enabledMask = bModified ? 1 : 0;
1722         }
1723     }
1724 
1725     Q_EMIT sourceMask(srcMask, enabledMask);
1726 }
1727 
1728 void MergeResultWindow::focusInEvent(QFocusEvent* e)
1729 {
1730     updateSourceMask();
1731     QWidget::focusInEvent(e);
1732 }
1733 
1734 LineRef MergeResultWindow::convertToLine(qint32 y)
1735 {
1736     const QFontMetrics& fm = fontMetrics();
1737     const qint32 fontHeight = fm.lineSpacing();
1738     constexpr qint32 topLineYOffset = 0;
1739 
1740     qint32 yOffset = topLineYOffset - m_firstLine * fontHeight;
1741     if(yOffset > y)
1742         return LineRef::invalid;
1743 
1744     const LineRef line = std::min((y - yOffset) / fontHeight, m_nofLines - 1);
1745     return line;
1746 }
1747 
1748 void MergeResultWindow::mousePressEvent(QMouseEvent* e)
1749 {
1750     m_bCursorOn = true;
1751 
1752     const qint32 xOffset = getTextXOffset();
1753 
1754     const LineRef line = std::max<LineType>(convertToLine(e->pos().y()), 0);
1755     const QString s = getString(line);
1756     QTextLayout textLayout(s, font(), this);
1757     getTextLayoutForLine(line, s, textLayout);
1758     qint32 pos = textLayout.lineAt(0).xToCursor(e->pos().x() - textLayout.position().x());
1759 
1760     const bool lLeftMouseButton = e->button() == Qt::LeftButton;
1761     const bool lMiddleMouseButton = e->button() == Qt::MiddleButton;
1762     const bool lRightMouseButton = e->button() == Qt::RightButton;
1763 
1764     if((lLeftMouseButton && (e->pos().x() < xOffset)) || lRightMouseButton) // Fast range selection
1765     {
1766         m_cursorXPos = 0;
1767         m_cursorOldXPixelPos = 0;
1768         m_cursorYPos = line;
1769         LineType l = 0;
1770         MergeBlockList::iterator i;
1771         for(i = m_mergeBlockList.begin(); i != m_mergeBlockList.end(); ++i)
1772         {
1773             if(l == line)
1774                 break;
1775 
1776             l += i->lineCount();
1777             if(l > line)
1778                 break;
1779         }
1780         m_selection.reset(); // Disable current selection
1781 
1782         m_bCursorOn = true;
1783         setFastSelector(i);
1784 
1785         if(lRightMouseButton)
1786         {
1787             Q_EMIT showPopupMenu(QCursor::pos());
1788         }
1789     }
1790     else if(lLeftMouseButton) // Normal cursor placement
1791     {
1792         pos = std::max(pos, 0);
1793         if(e->modifiers() & Qt::ShiftModifier)
1794         {
1795             if(!m_selection.isValidFirstLine())
1796                 m_selection.start(line, pos);
1797             m_selection.end(line, pos);
1798         }
1799         else
1800         {
1801             // Selection
1802             m_selection.reset();
1803             m_selection.start(line, pos);
1804             m_selection.end(line, pos);
1805         }
1806         m_cursorXPos = pos;
1807         m_cursorXPixelPos = qCeil(textLayout.lineAt(0).cursorToX(pos));
1808         if(gOptions->m_bRightToLeftLanguage)
1809             m_cursorXPixelPos += qCeil(textLayout.position().x() - m_horizScrollOffset);
1810         m_cursorOldXPixelPos = m_cursorXPixelPos;
1811         m_cursorYPos = line;
1812 
1813         update();
1814     }
1815     else if(lMiddleMouseButton) // Paste clipboard
1816     {
1817         pos = std::max(pos, 0);
1818 
1819         m_selection.reset();
1820         m_cursorXPos = pos;
1821         m_cursorOldXPixelPos = m_cursorXPixelPos;
1822         m_cursorYPos = line;
1823 
1824         pasteClipboard(true);
1825     }
1826 }
1827 
1828 void MergeResultWindow::mouseDoubleClickEvent(QMouseEvent* e)
1829 {
1830     if(e->button() == Qt::LeftButton)
1831     {
1832         const LineRef line = convertToLine(e->pos().y());
1833         const QString s = getString(line);
1834         QTextLayout textLayout(s, font(), this);
1835         getTextLayoutForLine(line, s, textLayout);
1836         qint32 pos = textLayout.lineAt(0).xToCursor(e->pos().x() - textLayout.position().x());
1837         m_cursorXPos = pos;
1838         m_cursorOldXPixelPos = m_cursorXPixelPos;
1839         m_cursorYPos = line;
1840 
1841         if(!s.isEmpty())
1842         {
1843             const bool selectionWasEmpty = m_selection.isEmpty();
1844             QtSizeType pos1, pos2;
1845 
1846             Utils::calcTokenPos(s, pos, pos1, pos2);
1847 
1848             resetSelection();
1849             m_selection.start(line, pos1);
1850             m_selection.end(line, pos2);
1851             if(!m_selection.isEmpty() && selectionWasEmpty)
1852                 Q_EMIT newSelection();
1853 
1854             update();
1855             // Q_EMIT selectionEnd() happens in the mouseReleaseEvent.
1856         }
1857     }
1858 }
1859 
1860 void MergeResultWindow::mouseReleaseEvent(QMouseEvent* e)
1861 {
1862     if(e->button() == Qt::LeftButton)
1863     {
1864         if(m_delayedDrawTimer)
1865         {
1866             killTimer(m_delayedDrawTimer);
1867             m_delayedDrawTimer = 0;
1868         }
1869 
1870         if(m_selection.isValidFirstLine())
1871         {
1872             Q_EMIT selectionEnd();
1873         }
1874 
1875         Q_EMIT updateAvailabilities();
1876     }
1877 }
1878 
1879 void MergeResultWindow::mouseMoveEvent(QMouseEvent* e)
1880 {
1881     const LineRef line = convertToLine(e->pos().y());
1882     const QString s = getString(line);
1883     QTextLayout textLayout(s, font(), this);
1884     getTextLayoutForLine(line, s, textLayout);
1885     const qint32 pos = textLayout.lineAt(0).xToCursor(e->pos().x() - textLayout.position().x());
1886     m_cursorXPos = pos;
1887     m_cursorOldXPixelPos = m_cursorXPixelPos;
1888     m_cursorYPos = line;
1889     if(m_selection.isValidFirstLine())
1890     {
1891         const bool selectionWasEmpty = m_selection.isEmpty();
1892         m_selection.end(line, pos);
1893         if(!m_selection.isEmpty() && selectionWasEmpty)
1894             Q_EMIT newSelection();
1895 
1896         myUpdate(0);
1897 
1898         // Scroll because mouse moved out of the window
1899         const QFontMetrics& fm = fontMetrics();
1900         const qint32 fontWidth = fm.horizontalAdvance('0');
1901         constexpr qint32 topLineYOffset = 0;
1902         qint32 deltaX = 0;
1903         qint32 deltaY = 0;
1904         if(!gOptions->m_bRightToLeftLanguage)
1905         {
1906             if(e->pos().x() < getTextXOffset()) deltaX = -1;
1907             if(e->pos().x() > width()) deltaX = +1;
1908         }
1909         else
1910         {
1911             if(e->pos().x() > width() - 1 - getTextXOffset()) deltaX = -1;
1912             if(e->pos().x() < fontWidth) deltaX = +1;
1913         }
1914         if(e->pos().y() < topLineYOffset) deltaY = -1;
1915         if(e->pos().y() > height()) deltaY = +1;
1916         m_scrollDeltaX = deltaX;
1917         m_scrollDeltaY = deltaY;
1918         if(deltaX != 0 || deltaY != 0)
1919         {
1920             Q_EMIT scrollMergeResultWindow(deltaX, deltaY);
1921         }
1922     }
1923 }
1924 
1925 void MergeResultWindow::slotCursorUpdate()
1926 {
1927     m_cursorTimer.stop();
1928     m_bCursorOn = !m_bCursorOn;
1929 
1930     if(isVisible())
1931     {
1932         m_bCursorUpdate = true;
1933 
1934         const QFontMetrics& fm = fontMetrics();
1935         constexpr qint32 topLineYOffset = 0;
1936         qint32 yOffset = (m_cursorYPos - m_firstLine) * fm.lineSpacing() + topLineYOffset;
1937 
1938         repaint(0, yOffset, width(), fm.lineSpacing() + 2);
1939 
1940         m_bCursorUpdate = false;
1941     }
1942 
1943     m_cursorTimer.start(500);
1944 }
1945 
1946 void MergeResultWindow::wheelEvent(QWheelEvent* pWheelEvent)
1947 {
1948     const QPoint delta = pWheelEvent->angleDelta();
1949     //Block diagonal scrolling easily generated unintentionally with track pads.
1950     if(delta.y() != 0 && abs(delta.y()) > abs(delta.x()) && mVScrollBar != nullptr)
1951     {
1952         pWheelEvent->accept();
1953         QCoreApplication::sendEvent(mVScrollBar, pWheelEvent);
1954     }
1955 }
1956 
1957 bool MergeResultWindow::event(QEvent* e)
1958 {
1959     if(e->type() == QEvent::KeyPress)
1960     {
1961         QKeyEvent* keyEvent = static_cast<QKeyEvent*>(e);
1962         if(keyEvent->key() == Qt::Key_Tab)
1963         {
1964             // special tab handling here to avoid moving focus
1965             keyPressEvent(keyEvent);
1966             return true;
1967         }
1968     }
1969     return QWidget::event(e);
1970 }
1971 
1972 void MergeResultWindow::keyPressEvent(QKeyEvent* keyEvent)
1973 {
1974     qint32 y = m_cursorYPos;
1975     MergeBlockList::iterator mbIt;
1976     MergeEditLineList::iterator melIt;
1977     if(!calcIteratorFromLineNr(y, mbIt, melIt))
1978     {
1979         // no data loaded or y out of bounds
1980         keyEvent->ignore();
1981         return;
1982     }
1983 
1984     QString str = melIt->getString(m_pldA, m_pldB, m_pldC);
1985     SafeInt<qint32> x = m_cursorXPos;
1986 
1987     QTextLayout textLayoutOrig(str, font(), this);
1988     getTextLayoutForLine(y, str, textLayoutOrig);
1989 
1990     bool bCtrl = (keyEvent->modifiers() & Qt::ControlModifier) != 0;
1991     bool bShift = (keyEvent->modifiers() & Qt::ShiftModifier) != 0;
1992 #ifdef Q_OS_WIN
1993     bool bAlt = (keyEvent->modifiers() & Qt::AltModifier) != 0;
1994     if(bCtrl && bAlt)
1995     {
1996         bCtrl = false;
1997         bAlt = false;
1998     } // AltGr-Key pressed.
1999 #endif
2000 
2001     bool bYMoveKey = false;
2002     // Special keys
2003     switch(keyEvent->key())
2004     {
2005         case Qt::Key_Pause:
2006         case Qt::Key_Print:
2007         case Qt::Key_SysReq:
2008         case Qt::Key_Escape:
2009         case Qt::Key_Backtab:
2010             break;
2011         case Qt::Key_Delete: {
2012             if(deleteSelection2(str, x, y, mbIt, melIt) || !melIt->isEditableText()) break;
2013 
2014             if(x >= str.length())
2015             {
2016                 if(y < m_nofLines - 1)
2017                 {
2018                     setModified();
2019                     MergeBlockList::iterator mbIt1;
2020                     MergeEditLineList::iterator melIt1;
2021                     if(calcIteratorFromLineNr(y + 1, mbIt1, melIt1) && melIt1->isEditableText())
2022                     {
2023                         const QString s2 = melIt1->getString(m_pldA, m_pldB, m_pldC);
2024                         melIt->setString(str + s2);
2025 
2026                         // Remove the line
2027                         if(mbIt1->lineCount() > 1)
2028                             mbIt1->list().erase(melIt1);
2029                         else
2030                             melIt1->setRemoved();
2031                     }
2032                 }
2033             }
2034             else
2035             {
2036                 QString s = str.left(x);
2037                 s += QStringView(str).mid(x + 1);
2038                 melIt->setString(s);
2039                 setModified();
2040             }
2041             break;
2042         }
2043         case Qt::Key_Backspace: {
2044             if(deleteSelection2(str, x, y, mbIt, melIt) || !melIt->isEditableText()) break;
2045 
2046             if(x == 0)
2047             {
2048                 if(y > 0)
2049                 {
2050                     setModified();
2051                     MergeBlockList::iterator mbIt1;
2052                     MergeEditLineList::iterator melIt1;
2053                     if(calcIteratorFromLineNr(y - 1, mbIt1, melIt1) && melIt1->isEditableText())
2054                     {
2055                         QString s1 = melIt1->getString(m_pldA, m_pldB, m_pldC);
2056                         melIt1->setString(s1 + str);
2057 
2058                         // Remove the previous line
2059                         if(mbIt->lineCount() > 1)
2060                             mbIt->list().erase(melIt);
2061                         else
2062                             melIt->setRemoved();
2063 
2064                         --y;
2065                         x = str.length();
2066                     }
2067                 }
2068             }
2069             else
2070             {
2071                 QString s = str.left(x - 1);
2072                 s += QStringView(str).mid(x);
2073                 --x;
2074                 melIt->setString(s);
2075                 setModified();
2076             }
2077             break;
2078         }
2079         case Qt::Key_Return:
2080         case Qt::Key_Enter: {
2081             if(!melIt->isEditableText()) break;
2082             deleteSelection2(str, x, y, mbIt, melIt);
2083             setModified();
2084             QString indentation;
2085             if(gOptions->autoIndent())
2086             { // calc last indentation
2087                 MergeBlockList::iterator mbIt1 = mbIt;
2088                 MergeEditLineList::iterator melIt1 = melIt;
2089                 for(;;)
2090                 {
2091                     const QString s = melIt1->getString(m_pldA, m_pldB, m_pldC);
2092                     if(!s.isEmpty())
2093                     {
2094                         QtSizeType i;
2095                         for(i = 0; i < s.length(); ++i)
2096                         {
2097                             if(s[i] != ' ' && s[i] != '\t') break;
2098                         }
2099                         if(i < s.length())
2100                         {
2101                             indentation = s.left(i);
2102                             break;
2103                         }
2104                     }
2105                     // Go back one line
2106                     if(melIt1 != mbIt1->list().begin())
2107                         --melIt1;
2108                     else
2109                     {
2110                         if(mbIt1 == m_mergeBlockList.begin()) break;
2111                         --mbIt1;
2112                         melIt1 = mbIt1->list().end();
2113                         --melIt1;
2114                     }
2115                 }
2116             }
2117             MergeEditLine mel(mbIt->id3l()); // Associate every mel with an id3l, even if not really valid.
2118             mel.setString(indentation + str.mid(x));
2119 
2120             if(x < str.length()) // Cut off the old line.
2121             {
2122                 // Since ps possibly points into melIt->str, first copy it into a temporary.
2123                 QString temp = str.left(x);
2124                 melIt->setString(temp);
2125             }
2126 
2127             ++melIt;
2128             mbIt->list().insert(melIt, mel);
2129             x = indentation.length();
2130             ++y;
2131             break;
2132         }
2133         case Qt::Key_Insert:
2134             m_bInsertMode = !m_bInsertMode;
2135             break;
2136         case Qt::Key_Home:
2137             x = 0;
2138             if(bCtrl)
2139             {
2140                 y = 0;
2141             }
2142             break; // cursor movement
2143         case Qt::Key_End:
2144             x = limits<qint32>::max();
2145             if(bCtrl)
2146             {
2147                 y = limits<qint32>::max();
2148             }
2149             break;
2150 
2151         case Qt::Key_Left:
2152         case Qt::Key_Right:
2153             if((keyEvent->key() == Qt::Key_Left) != gOptions->m_bRightToLeftLanguage)
2154             {
2155                 if(!bCtrl)
2156                 {
2157                     qint32 newX = textLayoutOrig.previousCursorPosition(x);
2158                     if(newX == x && y > 0)
2159                     {
2160                         --y;
2161                         x = limits<qint32>::max();
2162                     }
2163                     else
2164                     {
2165                         x = newX;
2166                     }
2167                 }
2168                 else
2169                 {
2170                     while(x > 0 && (str[(QtSizeType)x - 1] == ' ' || str[(QtSizeType)x - 1] == '\t'))
2171                     {
2172                         qint32 newX = textLayoutOrig.previousCursorPosition(x);
2173                         if(newX == x) break;
2174                         x = newX;
2175                     }
2176                     while(x > 0 && (str[(QtSizeType)x - 1] != ' ' && str[(QtSizeType)x - 1] != '\t'))
2177                     {
2178                         qint32 newX = textLayoutOrig.previousCursorPosition(x);
2179                         if(newX == x) break;
2180                         x = newX;
2181                     }
2182                 }
2183             }
2184             else
2185             {
2186                 if(!bCtrl)
2187                 {
2188                     qint32 newX = textLayoutOrig.nextCursorPosition(x);
2189                     if(newX == x && y < m_nofLines - 1)
2190                     {
2191                         ++y;
2192                         x = 0;
2193                     }
2194                     else
2195                     {
2196                         x = newX;
2197                     }
2198                 }
2199                 else
2200                 {
2201                     while(x < str.length() && (str[(QtSizeType)x] == ' ' || str[(QtSizeType)x] == '\t'))
2202                     {
2203                         qint32 newX = textLayoutOrig.nextCursorPosition(x);
2204                         if(newX == x) break;
2205                         x = newX;
2206                     }
2207                     while(x < str.length() && (str[(QtSizeType)x] != ' ' && str[(QtSizeType)x] != '\t'))
2208                     {
2209                         qint32 newX = textLayoutOrig.nextCursorPosition(x);
2210                         if(newX == x) break;
2211                         x = newX;
2212                     }
2213                 }
2214             }
2215             break;
2216 
2217         case Qt::Key_Up:
2218             if(!bCtrl)
2219             {
2220                 --y;
2221                 bYMoveKey = true;
2222             }
2223             break;
2224         case Qt::Key_Down:
2225             if(!bCtrl)
2226             {
2227                 ++y;
2228                 bYMoveKey = true;
2229             }
2230             break;
2231         case Qt::Key_PageUp:
2232             if(!bCtrl)
2233             {
2234                 y -= getNofVisibleLines();
2235                 bYMoveKey = true;
2236             }
2237             break;
2238         case Qt::Key_PageDown:
2239             if(!bCtrl)
2240             {
2241                 y += getNofVisibleLines();
2242                 bYMoveKey = true;
2243             }
2244             break;
2245         default: {
2246             QString t = keyEvent->text();
2247             if(t.isEmpty() || bCtrl || !melIt->isEditableText())
2248             {
2249                 keyEvent->ignore();
2250                 return;
2251             }
2252 
2253             deleteSelection2(str, x, y, mbIt, melIt);
2254 
2255             setModified();
2256             // Characters to insert
2257             QString s = str;
2258             if(t[0] == '\t' && gOptions->replaceTabs())
2259             {
2260                 qint32 spaces = (m_cursorXPos / gOptions->tabSize() + 1) * gOptions->tabSize() - m_cursorXPos;
2261                 t.fill(' ', spaces);
2262             }
2263             if(m_bInsertMode)
2264                 s.insert(x, t);
2265             else
2266                 s.replace(x, t.length(), t);
2267 
2268             melIt->setString(s);
2269             x += t.length();
2270             bShift = false;
2271         } // default case
2272     } // switch(e->key())
2273 
2274 
2275     y = qBound(0, y, m_nofLines - 1);
2276 
2277     str = calcIteratorFromLineNr(y, mbIt, melIt) ? melIt->getString(m_pldA, m_pldB, m_pldC) : QString();
2278 
2279     x = qBound<qint32>(0, x, SafeInt<qint32>(str.length()));
2280 
2281     qint32 newFirstLine = m_firstLine;
2282     qint32 newHorizScrollOffset = m_horizScrollOffset;
2283 
2284     if(y < m_firstLine)
2285         newFirstLine = y;
2286     else if(y > m_firstLine + getNofVisibleLines())
2287         newFirstLine = y - getNofVisibleLines();
2288 
2289     QTextLayout textLayout(str, font(), this);
2290     getTextLayoutForLine(m_cursorYPos, str, textLayout);
2291 
2292     // try to preserve cursor x pixel position when moving to another line
2293     if(bYMoveKey)
2294     {
2295         if(gOptions->m_bRightToLeftLanguage)
2296             x = textLayout.lineAt(0).xToCursor(m_cursorOldXPixelPos - (textLayout.position().x() - m_horizScrollOffset));
2297         else
2298             x = textLayout.lineAt(0).xToCursor(m_cursorOldXPixelPos);
2299     }
2300 
2301     m_cursorXPixelPos = qCeil(textLayout.lineAt(0).cursorToX((qint32)x));
2302     qint32 hF = 1; // horizontal factor
2303     if(gOptions->m_bRightToLeftLanguage)
2304     {
2305         m_cursorXPixelPos += qCeil(textLayout.position().x() - m_horizScrollOffset);
2306         hF = -1;
2307     }
2308     qint32 cursorWidth = 5;
2309     if(m_cursorXPixelPos < hF * m_horizScrollOffset)
2310         newHorizScrollOffset = hF * m_cursorXPixelPos;
2311     else if(m_cursorXPixelPos > hF * m_horizScrollOffset + getVisibleTextAreaWidth() - cursorWidth)
2312         newHorizScrollOffset = hF * (m_cursorXPixelPos - (getVisibleTextAreaWidth() - cursorWidth));
2313 
2314     qint32 newCursorX = x;
2315     if(bShift)
2316     {
2317         if(!m_selection.isValidFirstLine())
2318             m_selection.start(m_cursorYPos, m_cursorXPos);
2319 
2320         m_selection.end(y, newCursorX);
2321     }
2322     else
2323         m_selection.reset();
2324 
2325     m_cursorYPos = y;
2326     m_cursorXPos = newCursorX;
2327 
2328     // TODO if width of current line exceeds the current maximum width then force recalculating the scrollbars
2329     if(textLayout.maximumWidth() > getMaxTextWidth())
2330     {
2331         m_maxTextWidth = qCeil(textLayout.maximumWidth());
2332         Q_EMIT resizeSignal();
2333     }
2334     if(!bYMoveKey)
2335         m_cursorOldXPixelPos = m_cursorXPixelPos;
2336 
2337     m_bCursorOn = true;
2338     m_cursorTimer.start(500);
2339 
2340     Q_EMIT updateAvailabilities();
2341     update();
2342     if(newFirstLine != m_firstLine || newHorizScrollOffset != m_horizScrollOffset)
2343     {
2344         Q_EMIT scrollMergeResultWindow(newHorizScrollOffset - m_horizScrollOffset, newFirstLine - m_firstLine);
2345         return;
2346     }
2347 }
2348 
2349 /**
2350  * Determine MergeBlock and MergeEditLine from line number
2351  *
2352  * @param       line
2353  *              line number to look up
2354  * @param[out]  mbIt
2355  *              iterator to merge-line
2356  *              or m_mergeBlockList.end() if not available
2357  * @param[out]  melIt
2358  *              iterator to MergeEditLine
2359  *              or mbIt->mergeEditLineList.end() if not available
2360  *              @warning uninitialized if mbIt is not available!
2361  * @return      whether line is available;
2362  *              when true, @p mbIt and @p melIt are set to valid iterators
2363  */
2364 bool MergeResultWindow::calcIteratorFromLineNr(
2365     LineType line,
2366     MergeBlockList::iterator& mbIt,
2367     MergeEditLineList::iterator& melIt)
2368 {
2369     for(mbIt = m_mergeBlockList.begin(); mbIt != m_mergeBlockList.end(); ++mbIt)
2370     {
2371         MergeBlock& mb = *mbIt;
2372         if(line > mb.lineCount())
2373         {
2374             line -= mb.lineCount();
2375         }
2376         else
2377         {
2378             for(melIt = mb.list().begin(); melIt != mb.list().end(); ++melIt)
2379             {
2380                 --line;
2381                 if(line <= LineRef::invalid) return true;
2382             }
2383         }
2384     }
2385     return false;
2386 }
2387 
2388 QString MergeResultWindow::getSelection() const
2389 {
2390     QString selectionString;
2391 
2392     LineRef line = 0;
2393     for(const MergeBlock& mb: m_mergeBlockList)
2394     {
2395         for(const MergeEditLine& mel: mb.list())
2396         {
2397             if(m_selection.lineWithin(line))
2398             {
2399                 qint32 outPos = 0;
2400                 if(mel.isEditableText())
2401                 {
2402                     const QString str = mel.getString(m_pldA, m_pldB, m_pldC);
2403 
2404                     // Consider tabs
2405                     for(QtSizeType i = 0; i < str.length(); ++i)
2406                     {
2407                         qint32 spaces = 1;
2408                         if(str[i] == '\t')
2409                         {
2410                             spaces = tabber(outPos, gOptions->tabSize());
2411                         }
2412 
2413                         if(m_selection.within(line, outPos))
2414                         {
2415                             selectionString += str[i];
2416                         }
2417 
2418                         outPos += spaces;
2419                     }
2420                 }
2421                 else if(mel.isConflict())
2422                 {
2423                     selectionString += i18n("<Merge Conflict>");
2424                 }
2425 
2426                 if(m_selection.within(line, outPos))
2427                 {
2428 #ifdef Q_OS_WIN
2429                     selectionString += '\r';
2430 #endif
2431                     selectionString += '\n';
2432                 }
2433             }
2434 
2435             ++line;
2436         }
2437     }
2438 
2439     return selectionString;
2440 }
2441 
2442 bool MergeResultWindow::deleteSelection2(QString& s, SafeInt<qint32>& x, qint32& y,
2443                                          MergeBlockList::iterator& mbIt, MergeEditLineList::iterator& melIt)
2444 {
2445     if(!m_selection.isEmpty())
2446     {
2447         assert(m_selection.isValidFirstLine());
2448         deleteSelection();
2449 
2450         y = m_cursorYPos;
2451         x = m_cursorXPos;
2452 
2453         if(!calcIteratorFromLineNr(y, mbIt, melIt))
2454         {
2455             // deleteSelection() should never remove or empty the first line, so
2456             // resolving m_cursorYPos shall always succeed
2457             assert(false);
2458         }
2459 
2460         s = melIt->getString(m_pldA, m_pldB, m_pldC);
2461         return true;
2462     }
2463 
2464     return false;
2465 }
2466 
2467 void MergeResultWindow::deleteSelection()
2468 {
2469     if(m_selection.isEmpty())
2470     {
2471         return;
2472     }
2473     assert(m_selection.isValidFirstLine());
2474 
2475     setModified();
2476 
2477     LineRef line = 0;
2478     std::optional<MergeEditLineList::iterator> melItFirst;
2479     QString firstLineString;
2480 
2481     LineRef firstLine;
2482     LineRef lastLine;
2483 
2484     for(const MergeBlock& mb: m_mergeBlockList)
2485     {
2486         for(const MergeEditLine& mel: mb.list())
2487         {
2488             if(mel.isEditableText() && m_selection.lineWithin(line))
2489             {
2490                 if(!firstLine.isValid())
2491                     firstLine = line;
2492                 lastLine = line;
2493             }
2494 
2495             ++line;
2496         }
2497     }
2498 
2499     if(!firstLine.isValid())
2500     {
2501         return; // Nothing to delete.
2502     }
2503 
2504     MergeBlockList::iterator mbIt;
2505     line = 0;
2506     for(mbIt = m_mergeBlockList.begin(); mbIt != m_mergeBlockList.end(); ++mbIt)
2507     {
2508         MergeBlock& mb = *mbIt;
2509         MergeEditLineList::iterator melIt, melIt1;
2510         for(melIt = mb.list().begin(); melIt != mb.list().end();)
2511         {
2512             const MergeEditLine& mel = *melIt;
2513             melIt1 = melIt;
2514             ++melIt1;
2515 
2516             if(mel.isEditableText() && m_selection.lineWithin(line))
2517             {
2518                 const QString lineString = mel.getString(m_pldA, m_pldB, m_pldC);
2519                 QtSizeType firstPosInLine = m_selection.firstPosInLine(line);
2520                 QtSizeType lastPosInLine = m_selection.lastPosInLine(line);
2521 
2522                 if(line == firstLine)
2523                 {
2524                     mUndoRec = std::make_shared<UndoRecord>(m_selection, mbIt);
2525                     melItFirst = melIt;
2526                     QtSizeType pos = firstPosInLine;
2527                     firstLineString = lineString.left(pos);
2528                 }
2529                 assert(mUndoRec);
2530                 mUndoRec->push(mb);
2531 
2532                 if(line == lastLine)
2533                 {
2534                     assert(melItFirst.has_value());
2535                     // This is the last line in the selection
2536                     QtSizeType pos = lastPosInLine;
2537                     firstLineString += QStringView(lineString).mid(pos); // rest of line
2538                     melItFirst.value()->setString(firstLineString);
2539                 }
2540 
2541                 if(line != firstLine || (m_selection.endPos() - m_selection.beginPos()) == lineString.length())
2542                 {
2543                     // Remove the line
2544                     if(mb.lineCount() > 1)
2545                         mb.list().erase(melIt);
2546                     else
2547                         melIt->setRemoved();
2548                 }
2549             }
2550 
2551             ++line;
2552             melIt = melIt1;
2553         }
2554     }
2555 
2556     m_cursorYPos = m_selection.beginLine();
2557     m_cursorXPos = m_selection.beginPos();
2558     m_cursorOldXPixelPos = m_cursorXPixelPos;
2559 
2560     m_selection.reset();
2561 }
2562 
2563 void MergeResultWindow::pasteClipboard(bool bFromSelection)
2564 {
2565     mUndoRec.reset();
2566     //checking of m_selection if needed is done by deleteSelection no need for check here.
2567     deleteSelection();
2568 
2569     setModified();
2570 
2571     LineRef y = m_cursorYPos;
2572     MergeBlockList::iterator mbIt;
2573     MergeEditLineList::iterator melIt, melItAfter;
2574     if (!calcIteratorFromLineNr(y, mbIt, melIt))
2575     {
2576         return;
2577     }
2578     melItAfter = melIt;
2579     ++melItAfter;
2580     const QString str = melIt->getString(m_pldA, m_pldB, m_pldC);
2581     qint32 x = m_cursorXPos;
2582 
2583     if(!QApplication::clipboard()->supportsSelection())
2584         bFromSelection = false;
2585 
2586     const QString clipBoard = QApplication::clipboard()->text(bFromSelection ? QClipboard::Selection : QClipboard::Clipboard);
2587 
2588     QString currentLine = str.left(x);
2589     const QString endOfLine = str.mid(x);
2590     QtSizeType i;
2591     const QtSizeType len = clipBoard.length();
2592     for(i = 0; i < len; ++i)
2593     {
2594         QChar c = clipBoard[i];
2595         if(c == '\n' || (c == '\r' && clipBoard[i + 1] != '\n'))
2596         {
2597             melIt->setString(currentLine);
2598             MergeEditLine mel(mbIt->id3l()); // Associate every mel with an id3l, even if not really valid.
2599             melIt = mbIt->list().insert(melItAfter, mel);
2600             currentLine = "";
2601             x = 0;
2602             ++y;
2603         }
2604         else
2605         {
2606             if(c == '\r') continue;
2607 
2608             currentLine += c;
2609             ++x;
2610         }
2611     }
2612 
2613     currentLine += endOfLine;
2614     melIt->setString(currentLine);
2615 
2616     m_cursorYPos = y;
2617     m_cursorXPos = x;
2618     m_cursorOldXPixelPos = m_cursorXPixelPos;
2619 
2620     update();
2621 }
2622 
2623 void MergeResultWindow::resetSelection()
2624 {
2625     m_selection.reset();
2626     update();
2627 }
2628 
2629 void MergeResultWindow::setModified(bool bModified)
2630 {
2631     if(bModified != m_bModified)
2632     {
2633         m_bModified = bModified;
2634         Q_EMIT modifiedChanged(m_bModified);
2635     }
2636 }
2637 
2638 /// Saves and returns true when successful.
2639 bool MergeResultWindow::saveDocument(const QString& fileName, const char* encoding, e_LineEndStyle eLineEndStyle)
2640 {
2641     // Are still conflicts somewhere?
2642     if(getNumberOfUnsolvedConflicts() > 0)
2643     {
2644         KMessageBox::error(this,
2645                            i18n("Not all conflicts are solved yet.\n"
2646                                 "File not saved."),
2647                            i18nc("Dialog title", "Conflicts Left"));
2648         return false;
2649     }
2650 
2651     if(eLineEndStyle == eLineEndStyleConflict || eLineEndStyle == eLineEndStyleUndefined)
2652     {
2653         KMessageBox::error(this,
2654                            i18n("There is a line end style conflict. Please choose the line end style manually.\n"
2655                                 "File not saved."),
2656                            i18nc("Dialog title", "Conflicts Left"));
2657         return false;
2658     }
2659 
2660     update();
2661 
2662     FileAccess file(fileName, true /*bWantToWrite*/);
2663     if(gOptions->m_bDmCreateBakFiles && file.exists())
2664     {
2665         bool bSuccess = file.createBackup(".orig");
2666         if(!bSuccess)
2667         {
2668             KMessageBox::error(this, file.getStatusText() + i18n("\n\nCreating backup failed. File not saved."), i18n("File Save Error"));
2669             return false;
2670         }
2671     }
2672 
2673     QByteArray dataArray;
2674     EncodedDataStream textOutStream(&dataArray, QIODevice::WriteOnly);
2675     if(strcmp(encoding, "UTF-8") != 0)
2676         textOutStream.setGenerateByteOrderMark(false);
2677     else
2678         textOutStream.setGenerateByteOrderMark(true); // Only for UTF-16
2679 
2680     textOutStream.setEncoding(encoding);
2681 
2682     // Determine the line feed for this file
2683     const QString lineFeed(eLineEndStyle == eLineEndStyleDos ? QString("\r\n") : QString("\n"));
2684 
2685     bool isFirstLine = true;
2686     for(const MergeBlock& mb: m_mergeBlockList)
2687     {
2688         for(const MergeEditLine& mel: mb.list())
2689         {
2690             if(mel.isEditableText())
2691             {
2692                 const QString str = mel.getString(m_pldA, m_pldB, m_pldC);
2693 
2694                 if(!isFirstLine && !mel.isRemoved())
2695                 {
2696                     // Put line feed between lines, but not for the first line
2697                     // or between lines that have been removed (because there
2698                     // isn't a line there).
2699                     textOutStream << lineFeed;
2700                 }
2701 
2702                 if(isFirstLine)
2703                     isFirstLine = mel.isRemoved();
2704 
2705                 textOutStream << str;
2706             }
2707         }
2708     }
2709 
2710     bool bSuccess = !textOutStream.hasError();
2711     if(bSuccess)
2712         bSuccess = file.writeFile(dataArray.data(), dataArray.size());
2713 
2714     if(!bSuccess)
2715     {
2716         KMessageBox::error(this, i18n("Error while writing."), i18n("File Save Error"));
2717         return false;
2718     }
2719 
2720     setModified(false);
2721     update();
2722 
2723     return true;
2724 }
2725 
2726 QString MergeResultWindow::getString(qint32 lineIdx)
2727 {
2728     MergeBlockList::iterator mbIt;
2729     MergeEditLineList::iterator melIt;
2730     if(!calcIteratorFromLineNr(lineIdx, mbIt, melIt))
2731     {
2732         return QString();
2733     }
2734     return melIt->getString(m_pldA, m_pldB, m_pldC);
2735 }
2736 
2737 bool MergeResultWindow::findString(const QString& s, LineRef& d3vLine, QtSizeType& posInLine, bool bDirDown, bool bCaseSensitive)
2738 {
2739     qint32 it = d3vLine;
2740     qint32 endIt = bDirDown ? getNofLines() : -1;
2741     qint32 step = bDirDown ? 1 : -1;
2742     QtSizeType startPos = posInLine;
2743 
2744     for(; it != endIt; it += step)
2745     {
2746         QString line = getString(it);
2747         if(!line.isEmpty())
2748         {
2749             QtSizeType pos = line.indexOf(s, startPos, bCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
2750 
2751             if(pos != -1)
2752             {
2753                 d3vLine = it;
2754                 posInLine = pos;
2755                 return true;
2756             }
2757 
2758             startPos = 0;
2759         }
2760     }
2761     return false;
2762 }
2763 
2764 void MergeResultWindow::setSelection(LineType firstLine, QtSizeType startPos, LineType lastLine, QtSizeType endPos)
2765 {
2766     if(lastLine >= getNofLines())
2767     {
2768         lastLine = getNofLines() - 1;
2769         QString s = getString(lastLine);
2770         endPos = s.length();
2771     }
2772     m_selection.reset();
2773     m_selection.start(firstLine, startPos);
2774     m_selection.end(lastLine, endPos);
2775     update();
2776 }
2777 
2778 void MergeResultWindow::scrollVertically(qint32 deltaY)
2779 {
2780     mVScrollBar->setValue(mVScrollBar->value()  + deltaY);
2781 }
2782 
2783 WindowTitleWidget::WindowTitleWidget()
2784 {
2785     setAutoFillBackground(true);
2786 
2787     QHBoxLayout* pHLayout = new QHBoxLayout(this);
2788     pHLayout->setContentsMargins(2, 2, 2, 2);
2789     pHLayout->setSpacing(2);
2790 
2791     m_pLabel = new QLabel(i18n("Output:"));
2792     pHLayout->addWidget(m_pLabel);
2793 
2794     m_pFileNameLineEdit = new FileNameLineEdit();
2795     pHLayout->addWidget(m_pFileNameLineEdit, 6);
2796     m_pFileNameLineEdit->installEventFilter(this);//for focus tracking
2797     m_pFileNameLineEdit->setAcceptDrops(true);
2798     m_pFileNameLineEdit->setReadOnly(true);
2799 
2800     //m_pBrowseButton = new QPushButton("...");
2801     //pHLayout->addWidget( m_pBrowseButton, 0 );
2802     //chk_connect_a( m_pBrowseButton, &QPushButton::clicked), this, &MergeResultWindow::slotBrowseButtonClicked);
2803 
2804     m_pModifiedLabel = new QLabel(i18n("[Modified]"));
2805     pHLayout->addWidget(m_pModifiedLabel);
2806     m_pModifiedLabel->setMinimumSize(m_pModifiedLabel->sizeHint());
2807     m_pModifiedLabel->setText("");
2808 
2809     pHLayout->addStretch(1);
2810 
2811     m_pEncodingLabel = new QLabel(i18n("Encoding for saving:"));
2812     pHLayout->addWidget(m_pEncodingLabel);
2813 
2814     m_pEncodingSelector = new QComboBox();
2815     m_pEncodingSelector->setSizeAdjustPolicy(QComboBox::AdjustToContents);
2816     pHLayout->addWidget(m_pEncodingSelector, 2);
2817     setEncodings(nullptr, nullptr, nullptr);
2818 
2819     m_pLineEndStyleLabel = new QLabel(i18n("Line end style:"));
2820     pHLayout->addWidget(m_pLineEndStyleLabel);
2821     m_pLineEndStyleSelector = new QComboBox();
2822     m_pLineEndStyleSelector->setSizeAdjustPolicy(QComboBox::AdjustToContents);
2823     pHLayout->addWidget(m_pLineEndStyleSelector);
2824     setLineEndStyles(eLineEndStyleUndefined, eLineEndStyleUndefined, eLineEndStyleUndefined);
2825 }
2826 
2827 void WindowTitleWidget::setFileName(const QString& fileName)
2828 {
2829     m_pFileNameLineEdit->setText(QDir::toNativeSeparators(fileName));
2830 }
2831 
2832 QString WindowTitleWidget::getFileName()
2833 {
2834     return m_pFileNameLineEdit->text();
2835 }
2836 
2837 //static QString getLineEndStyleName( e_LineEndStyle eLineEndStyle )
2838 //{
2839 //   if ( eLineEndStyle == eLineEndStyleDos )
2840 //      return "DOS";
2841 //   else if ( eLineEndStyle == eLineEndStyleUnix )
2842 //      return "Unix";
2843 //   return QString();
2844 //}
2845 
2846 void WindowTitleWidget::setLineEndStyles(e_LineEndStyle eLineEndStyleA, e_LineEndStyle eLineEndStyleB, e_LineEndStyle eLineEndStyleC)
2847 {
2848     m_pLineEndStyleSelector->clear();
2849     QString dosUsers;
2850     if(eLineEndStyleA == eLineEndStyleDos)
2851         dosUsers += QStringLiteral("A");
2852     if(eLineEndStyleB == eLineEndStyleDos)
2853         dosUsers += QLatin1String(dosUsers.isEmpty() ? "" : ", ") + QStringLiteral("B");
2854     if(eLineEndStyleC == eLineEndStyleDos)
2855         dosUsers += QLatin1String(dosUsers.isEmpty() ? "" : ", ") + QStringLiteral("C");
2856     QString unxUsers;
2857     if(eLineEndStyleA == eLineEndStyleUnix)
2858         unxUsers += QStringLiteral("A");
2859     if(eLineEndStyleB == eLineEndStyleUnix)
2860         unxUsers += QLatin1String(unxUsers.isEmpty() ? "" : ", ") + QStringLiteral("B");
2861     if(eLineEndStyleC == eLineEndStyleUnix)
2862         unxUsers += QLatin1String(unxUsers.isEmpty() ? "" : ", ") + QStringLiteral("C");
2863 
2864     m_pLineEndStyleSelector->addItem(i18n("Unix") + (unxUsers.isEmpty() ? QString("") : u8" (" + unxUsers + u8")"));
2865     m_pLineEndStyleSelector->addItem(i18n("DOS") + (dosUsers.isEmpty() ? QString("") : u8" (" + dosUsers + u8")"));
2866 
2867     e_LineEndStyle autoChoice = (e_LineEndStyle)gOptions->m_lineEndStyle;
2868 
2869     if(gOptions->m_lineEndStyle == eLineEndStyleAutoDetect)
2870     {
2871         if(eLineEndStyleA != eLineEndStyleUndefined && eLineEndStyleB != eLineEndStyleUndefined && eLineEndStyleC != eLineEndStyleUndefined)
2872         {
2873             if(eLineEndStyleA == eLineEndStyleB)
2874                 autoChoice = eLineEndStyleC;
2875             else if(eLineEndStyleA == eLineEndStyleC)
2876                 autoChoice = eLineEndStyleB;
2877             else
2878                 autoChoice = eLineEndStyleConflict; //conflict (not likely while only two values exist)
2879         }
2880         else
2881         {
2882             e_LineEndStyle c1, c2;
2883             if(eLineEndStyleA == eLineEndStyleUndefined)
2884             {
2885                 c1 = eLineEndStyleB;
2886                 c2 = eLineEndStyleC;
2887             }
2888             else if(eLineEndStyleB == eLineEndStyleUndefined)
2889             {
2890                 c1 = eLineEndStyleA;
2891                 c2 = eLineEndStyleC;
2892             }
2893             else /*if( eLineEndStyleC == eLineEndStyleUndefined )*/
2894             {
2895                 c1 = eLineEndStyleA;
2896                 c2 = eLineEndStyleB;
2897             }
2898             if(c1 == c2 && c1 != eLineEndStyleUndefined)
2899                 autoChoice = c1;
2900             else
2901                 autoChoice = eLineEndStyleConflict;
2902         }
2903     }
2904 
2905     if(autoChoice == eLineEndStyleUnix)
2906         m_pLineEndStyleSelector->setCurrentIndex(0);
2907     else if(autoChoice == eLineEndStyleDos)
2908         m_pLineEndStyleSelector->setCurrentIndex(1);
2909     else if(autoChoice == eLineEndStyleConflict)
2910     {
2911         m_pLineEndStyleSelector->addItem(i18n("Conflict"));
2912         m_pLineEndStyleSelector->setCurrentIndex(2);
2913     }
2914 }
2915 
2916 e_LineEndStyle WindowTitleWidget::getLineEndStyle()
2917 {
2918 
2919     qint32 current = m_pLineEndStyleSelector->currentIndex();
2920     if(current == 0)
2921         return eLineEndStyleUnix;
2922     else if(current == 1)
2923         return eLineEndStyleDos;
2924     else
2925         return eLineEndStyleConflict;
2926 }
2927 
2928 void WindowTitleWidget::setEncodings(const char* pCodecForA, const char* pCodecForB, const char* pCodecForC)
2929 {
2930     m_pEncodingSelector->clear();
2931 
2932     //Gather unique codecs not aliases
2933     const QList<qint32> mibs = QTextCodec::availableMibs();
2934     QList<QByteArray> names;
2935     for(const qint32 mib: mibs)
2936     {
2937         names.append(QTextCodec::codecForMib(mib)->name());
2938     }
2939 
2940     if(pCodecForA != nullptr)
2941         m_pEncodingSelector->addItem(i18n("Codec from A: %1", QLatin1String(pCodecForA)), QVariant::fromValue(QByteArray(pCodecForA)));
2942     if(pCodecForB != nullptr)
2943         m_pEncodingSelector->addItem(i18n("Codec from B: %1", QLatin1String(pCodecForB)), QVariant::fromValue(QByteArray(pCodecForB)));
2944     if(pCodecForC != nullptr)
2945         m_pEncodingSelector->addItem(i18n("Codec from C: %1", QLatin1String(pCodecForC)), QVariant::fromValue(QByteArray(pCodecForC)));
2946 
2947     m_pEncodingSelector->addItem("UTF 8", QVariant::fromValue(QByteArray("UTF-8")));
2948     m_pEncodingSelector->addItem("UTF 8 (BOM)", QVariant::fromValue(QByteArray("UTF-8-BOM")));
2949 
2950     for(const QByteArray& name: names)
2951     {
2952         m_pEncodingSelector->addItem(QString::fromUtf8(name), QVariant::fromValue(name));
2953     }
2954     m_pEncodingSelector->setMinimumSize(m_pEncodingSelector->sizeHint());
2955 
2956     if(pCodecForC != nullptr && pCodecForB != nullptr && pCodecForA != nullptr)
2957     {
2958         if(pCodecForA == pCodecForC)
2959             m_pEncodingSelector->setCurrentIndex(1); // B
2960         else
2961             m_pEncodingSelector->setCurrentIndex(2); // C
2962     }
2963     else if(pCodecForA != nullptr && pCodecForB != nullptr)
2964         m_pEncodingSelector->setCurrentIndex(1); // B
2965     else
2966         m_pEncodingSelector->setCurrentIndex(0);
2967 }
2968 
2969 const char* WindowTitleWidget::getEncoding()
2970 {
2971     return (const char*)m_pEncodingSelector->itemData(m_pEncodingSelector->currentIndex()).value<void*>();
2972 }
2973 
2974 void WindowTitleWidget::setEncoding(const char* encoding)
2975 {
2976     qint32 idx = m_pEncodingSelector->findText(QLatin1String(encoding));
2977     if(idx >= 0)
2978         m_pEncodingSelector->setCurrentIndex(idx);
2979 }
2980 
2981 //void WindowTitleWidget::slotBrowseButtonClicked()
2982 //{
2983 //   QString current = m_pFileNameLineEdit->text();
2984 //
2985 //   QUrl newURL = KFileDialog::getSaveUrl( current, 0, this, i18n("Select file (not saving yet)"));
2986 //   if ( !newURL.isEmpty() )
2987 //   {
2988 //      m_pFileNameLineEdit->setText( newURL.url() );
2989 //   }
2990 //}
2991 
2992 void WindowTitleWidget::slotSetModified(bool bModified)
2993 {
2994     m_pModifiedLabel->setText(bModified ? i18n("[Modified]") : "");
2995 }
2996 
2997 bool WindowTitleWidget::eventFilter([[maybe_unused]] QObject* o, QEvent* e)
2998 {
2999     if(e->type() == QEvent::FocusIn || e->type() == QEvent::FocusOut)
3000     {
3001         QPalette p = m_pLabel->palette();
3002 
3003         QColor c1 = gOptions->foregroundColor();
3004         QColor c2 = Qt::lightGray;
3005         if(e->type() == QEvent::FocusOut)
3006             c2 = gOptions->backgroundColor();
3007 
3008         p.setColor(QPalette::Window, c2);
3009         setPalette(p);
3010 
3011         p.setColor(QPalette::WindowText, c1);
3012         m_pLabel->setPalette(p);
3013         m_pEncodingLabel->setPalette(p);
3014         m_pEncodingSelector->setPalette(p);
3015     }
3016     return false;
3017 }
3018 
3019 //#include "mergeresultwindow.moc"